From daec3b9fb6e2192febfbd29046e02e476a5b159f Mon Sep 17 00:00:00 2001 From: "Deric C." Date: Tue, 24 Mar 2026 13:30:13 -0700 Subject: [PATCH] [HLSL] Implement TableGen for builtin HLSL intrinsics (#187610) This PR introduces a TableGen-based code generation system for HLSL intrinsic overloads as described in proposal [[0043]](https://github.com/llvm/wg-hlsl/blob/main/proposals/0043-hlsl-intrinsic-tablegen.md) for replacing hand-written boilerplate with declarative .td definitions. Actual changes to `hlsl_intrinsics.h` and `hlsl_alias_intrinsics.h` to replace handwritten HLSL intrinsic overloads with TableGen is left to follow-up PRs. Assisted-by: GitHub Copilot (powered by Claude Opus 4.6) --- clang/include/clang/Basic/HLSLIntrinsics.td | 320 ++++++++++ clang/lib/Headers/CMakeLists.txt | 11 +- .../lib/Headers/hlsl/hlsl_alias_intrinsics.h | 3 + clang/lib/Headers/hlsl/hlsl_intrinsics.h | 3 + clang/test/TableGen/hlsl-intrinsics.td | 469 ++++++++++++++ clang/utils/TableGen/CMakeLists.txt | 1 + clang/utils/TableGen/HLSLEmitter.cpp | 590 ++++++++++++++++++ clang/utils/TableGen/TableGen.cpp | 14 + clang/utils/TableGen/TableGenBackends.h | 5 + 9 files changed, 1415 insertions(+), 1 deletion(-) create mode 100644 clang/include/clang/Basic/HLSLIntrinsics.td create mode 100644 clang/test/TableGen/hlsl-intrinsics.td create mode 100644 clang/utils/TableGen/HLSLEmitter.cpp diff --git a/clang/include/clang/Basic/HLSLIntrinsics.td b/clang/include/clang/Basic/HLSLIntrinsics.td new file mode 100644 index 000000000000..b205491e6ca7 --- /dev/null +++ b/clang/include/clang/Basic/HLSLIntrinsics.td @@ -0,0 +1,320 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file defines HLSL intrinsic functions in tablegen. The HLSLEmitter +// backend processes these definitions and generates two .inc files: +// - hlsl_alias_intrinsics_gen.inc: builtin alias declarations using +// _HLSL_BUILTIN_ALIAS, included by hlsl_alias_intrinsics.h. +// - hlsl_inline_intrinsics_gen.inc: inline function definitions (detail +// helper calls and literal bodies), included by hlsl_intrinsics.h. +// +//===----------------------------------------------------------------------===// + +//===----------------------------------------------------------------------===// +// Argument and return type base classes +//===----------------------------------------------------------------------===// + +// Base class for argument and return type positions. +class HLSLArgType; + +// Base class for return type positions. +class HLSLReturnType; + +// Void return type. +def VoidTy : HLSLReturnType; + +//===----------------------------------------------------------------------===// +// HLSL element types +//===----------------------------------------------------------------------===// + +// Represents a concrete HLSL scalar element type. +// Can be used directly as an argument or return type for a fixed scalar +// (e.g., FloatTy in Args produces a 'float' argument). +class HLSLType : HLSLArgType, HLSLReturnType { + string Name = name; + string TypeName = name; + + // When set, overloads using this type are guarded by + // #ifdef __HLSL_ENABLE_16_BIT and emitted with + // _HLSL_AVAILABILITY(shadermodel, 6.2), or the intrinsic's Availability + // if it is greater. + bit Is16Bit = 0; + + // When set, overloads using this type are emitted with + // _HLSL_16BIT_AVAILABILITY(shadermodel, 6.2) instead of _HLSL_AVAILABILITY. + // This macro expands to an availability attribute only when + // __HLSL_ENABLE_16_BIT is defined (i.e. half is a true 16-bit float); + // otherwise it expands to nothing since half is an alias for float. + // If the intrinsic's Availability is >= SM6.2, _HLSL_AVAILABILITY is used + // instead because 16-bit support is already implied. + bit IsConditionally16Bit = 0; +} + +def BoolTy : HLSLType<"bool">; +def HalfTy : HLSLType<"half"> { let IsConditionally16Bit = 1; } +def FloatTy : HLSLType<"float">; +def DoubleTy : HLSLType<"double">; +def Int16Ty : HLSLType<"int16_t"> { let Is16Bit = 1; } +def UInt16Ty : HLSLType<"uint16_t"> { let Is16Bit = 1; } +def IntTy : HLSLType<"int">; +def UIntTy : HLSLType<"uint">; +def Int64Ty : HLSLType<"int64_t">; +def UInt64Ty : HLSLType<"uint64_t">; + +//===----------------------------------------------------------------------===// +// Element type groups +//===----------------------------------------------------------------------===// + +defvar AllFloatTypes = [HalfTy, FloatTy, DoubleTy]; +defvar SignedIntTypes = [Int16Ty, IntTy, Int64Ty]; +defvar UnsignedIntTypes = [UInt16Ty, UIntTy, UInt64Ty]; +defvar AllIntTypes = [Int16Ty, UInt16Ty, IntTy, UIntTy, + Int64Ty, UInt64Ty]; +defvar SignedTypes = [Int16Ty, HalfTy, IntTy, FloatTy, + Int64Ty, DoubleTy]; +defvar AllNumericTypes = [Int16Ty, UInt16Ty, HalfTy, IntTy, UIntTy, + FloatTy, Int64Ty, UInt64Ty, DoubleTy]; +defvar AllTypesWithBool = [BoolTy, Int16Ty, UInt16Ty, HalfTy, + IntTy, UIntTy, FloatTy, Int64Ty, + UInt64Ty, DoubleTy]; +defvar NumericTypesNoDbl = [Int16Ty, UInt16Ty, HalfTy, IntTy, UIntTy, + FloatTy, Int64Ty, UInt64Ty]; + +//===----------------------------------------------------------------------===// +// Argument/return types +// +// These classes are usable in both argument and return type positions. +//===----------------------------------------------------------------------===// + +// The varying type — expanded per VaryingTypes. +// As an argument: the arg type varies with each overload. +// As a return type: returns the same type as the varying arg. +def Varying : HLSLArgType, HLSLReturnType; + +// The scalar element of the varying type. +// As an argument: always the scalar element type regardless of overload shape. +// As a return type: returns the scalar element type (e.g., float dot(float3, float3)). +def VaryingElemType : HLSLArgType, HLSLReturnType; + +// The varying shape with a fixed element type. +// As an argument: same shape as Varying but with the given element type. +// As a return type: same shape as the varying arg but with the given element type. +// For example, VaryingShape with a float3 overload produces uint3. +class VaryingShape : HLSLArgType, HLSLReturnType { + HLSLType ElementType = ty; +} + +// A concrete vector type (e.g., VectorType -> uint4). +// As an argument: the arg is always this vector type. +// As a return type: always returns this vector type, ignoring argument shape. +class VectorType : HLSLArgType, HLSLReturnType { + HLSLType ElementType = ty; + int Size = size; +} + +//===----------------------------------------------------------------------===// +// Shader model versions +//===----------------------------------------------------------------------===// + +// Represents a shader model version +class ShaderModel { + int Major = major; + int Minor = minor; +} + +// Sentinel: no shader model requirement. +def NoSM : ShaderModel<0, 0>; + +// Valid Shader Model records +foreach i = 0...9 in { + def SM6_ #i : ShaderModel<6, i>; +} + +//===----------------------------------------------------------------------===// +// Matrix dimension records +//===----------------------------------------------------------------------===// + +class MatDim { + int Rows = rows; + int Cols = cols; +} + +foreach r = 1...4 in + foreach c = 1...4 in + def Mat#r#"x"#c : MatDim; + +// All non-1x1 matrix dimensions (1x2 through 4x4). +defvar AllMatDims = [Mat1x2, Mat1x3, Mat1x4, + Mat2x1, Mat2x2, Mat2x3, Mat2x4, + Mat3x1, Mat3x2, Mat3x3, Mat3x4, + Mat4x1, Mat4x2, Mat4x3, Mat4x4]; + +//===----------------------------------------------------------------------===// +// HLSLBuiltin class +//===----------------------------------------------------------------------===// + +class HLSLBuiltin { + string Name = name; + + // When set, generates a _HLSL_BUILTIN_ALIAS(Builtin) declaration that + // aliases the named Clang builtin. Mutually exclusive with DetailFunc + // and Body. + string Builtin = builtin; + + // Doxygen documentation comment emitted before overloads in generated code. + string Doc = ""; + + // When set, generates an inline function body calling + // __detail::DetailFunc(args...) instead of _HLSL_BUILTIN_ALIAS(Builtin). + // Parameters are named p0, p1, p2, ... by default, or use ParamNames to + // specify custom names. Mutually exclusive with Body and Builtin. + string DetailFunc = ""; + + // When set, generates an inline function with this literal body text. + // Intended for single-statement functions. Multi-line functions should + // instead be defined as a helper and called with DetailFunc. + // Parameters are named p0, p1, p2, ... by default, or use ParamNames to + // specify custom names. Mutually exclusive with DetailFunc and Builtin. + code Body = ""; + + // Determines how the return type is derived for each overload. + HLSLReturnType ReturnType = VoidTy; + + // Argument list. Each entry is either: + // Varying - type varies with VaryingTypes (expanded per type) + // HLSLType - a fixed scalar type at that position (e.g., UIntTy) + // VectorType - a fixed vector type at that position + // The number of arguments is deduced from the length of this list. + // Examples: + // [Varying] -> func(T) + // [Varying, Varying, Varying] -> func(T, T, T) + // [Varying, UIntTy] -> func(T, uint) + // [UIntTy, UIntTy, IntTy] -> func(uint, uint, int) + // [] -> func() + list Args = []; + + // Custom parameter names for generated functions. + // When empty, inline functions (Body or DetailFunc) use p0, p1, p2, ... + // and alias functions omit parameter names. + list ParamNames = []; + + // When set, emits 'constexpr' instead of 'inline' for inline functions + // (i.e., functions using Body or DetailFunc). + bit IsConstexpr = 0; + + // Whether the function has the convergent attribute + bit IsConvergent = 0; + + // Argument element types — drives overload expansion. + // One overload set is generated per type (scalar + vectors + matrices). + // Only used when Args contains Varying entries. + list VaryingTypes = []; + + // Whether to generate scalar overloads for Varying typed arguments. + bit VaryingScalar = 0; + + // Vector sizes to generate for Varying typed arguments (e.g., [2,3,4]). + list VaryingVecSizes = []; + + // Matrix dimensions to generate for Varying typed arguments. + list VaryingMatDims = []; + + // Default shader model availability version for all types. + // Use NoSM for no availability requirement. + ShaderModel Availability = NoSM; +} + +//===----------------------------------------------------------------------===// +// HLSLBuiltin helper subclasses +//===----------------------------------------------------------------------===// + +// T func(T) with scalar + vec2/3/4 + matrix overloads. +class HLSLOneArgBuiltin + : HLSLBuiltin { + let Args = [Varying]; + let ReturnType = Varying; + let VaryingScalar = 1; + let VaryingVecSizes = [2, 3, 4]; + let VaryingMatDims = AllMatDims; +} + +// T func(T, T) with scalar + vec2/3/4 + matrix overloads. +class HLSLTwoArgBuiltin + : HLSLBuiltin { + let Args = [Varying, Varying]; + let ReturnType = Varying; + let VaryingScalar = 1; + let VaryingVecSizes = [2, 3, 4]; + let VaryingMatDims = AllMatDims; +} + +// T func(T, T, T) with scalar + vec2/3/4 + matrix overloads. +class HLSLThreeArgBuiltin + : HLSLBuiltin { + let Args = [Varying, Varying, Varying]; + let ReturnType = Varying; + let VaryingScalar = 1; + let VaryingVecSizes = [2, 3, 4]; + let VaryingMatDims = AllMatDims; +} + +// Detail function base: generates inline function bodies calling +// __detail::DetailFunc(args...) instead of _HLSL_BUILTIN_ALIAS. +class HLSLDetail : HLSLBuiltin { + let DetailFunc = detail; +} + +// T func(T) with scalar + vec2/3/4 + matrix overloads. +class HLSLOneArgDetail + : HLSLDetail { + let Args = [Varying]; + let ReturnType = Varying; + let VaryingScalar = 1; + let VaryingVecSizes = [2, 3, 4]; + let VaryingMatDims = AllMatDims; +} + +// T func(T, T) with scalar + vec2/3/4 + matrix overloads. +class HLSLTwoArgDetail + : HLSLDetail { + let Args = [Varying, Varying]; + let ReturnType = Varying; + let VaryingScalar = 1; + let VaryingVecSizes = [2, 3, 4]; + let VaryingMatDims = AllMatDims; +} + +// T func(T, T, T) with scalar + vec2/3/4 + matrix overloads. +class HLSLThreeArgDetail + : HLSLDetail { + let Args = [Varying, Varying, Varying]; + let ReturnType = Varying; + let VaryingScalar = 1; + let VaryingVecSizes = [2, 3, 4]; + let VaryingMatDims = AllMatDims; +} + +// Inline body variant: T func(T) with a literal inline body (no builtin alias). +// Body must be specified (e.g., let Body = "return p0;"). +class HLSLOneArgInlineBuiltin : HLSLBuiltin { + let Args = [Varying]; + let ReturnType = Varying; + let VaryingScalar = 1; + let VaryingVecSizes = [2, 3, 4]; + let VaryingMatDims = AllMatDims; +} + +//===----------------------------------------------------------------------===// +// Intrinsic definitions (sorted alphabetically by function name) +//===----------------------------------------------------------------------===// + +// TODO: Convert hand-written overloads from hlsl_intrinsics.h and +// hlsl_alias_intrinsics.h into TableGen below. +// Include "hlsl_alias_intrinsics_gen.inc" in hlsl_alias_intrinsics.h +// Include "hlsl_inline_intrinsics_gen.inc" in hlsl_intrinsics.h + diff --git a/clang/lib/Headers/CMakeLists.txt b/clang/lib/Headers/CMakeLists.txt index c6c299bb61af..c0a2028e81ad 100644 --- a/clang/lib/Headers/CMakeLists.txt +++ b/clang/lib/Headers/CMakeLists.txt @@ -504,6 +504,14 @@ if(RISCV IN_LIST LLVM_TARGETS_TO_BUILD) ) endif() +# Generate HLSL intrinsic overloads +clang_generate_header(-gen-hlsl-alias-intrinsics HLSLIntrinsics.td + hlsl/hlsl_alias_intrinsics_gen.inc) +clang_generate_header(-gen-hlsl-inline-intrinsics HLSLIntrinsics.td + hlsl/hlsl_inline_intrinsics_gen.inc) +set(hlsl_generated_files + "${CMAKE_CURRENT_BINARY_DIR}/hlsl/hlsl_alias_intrinsics_gen.inc" + "${CMAKE_CURRENT_BINARY_DIR}/hlsl/hlsl_inline_intrinsics_gen.inc") # Check if the generated headers are included in a target specific lists # Currently, all generated headers are target specific. @@ -511,6 +519,7 @@ set(all_target_specific_generated_files ${arm_common_generated_files} ${arm_only_generated_files} ${aarch64_only_generated_files} + ${hlsl_generated_files} ${riscv_generated_files}) foreach( f ${generated_files} ) if (NOT ${f} IN_LIST all_target_specific_generated_files) @@ -579,7 +588,7 @@ add_header_target("x86-resource-headers" "${x86_files}") add_header_target("gpu-resource-headers" "${gpu_files}") # Other header groupings -add_header_target("hlsl-resource-headers" ${hlsl_files}) +add_header_target("hlsl-resource-headers" "${hlsl_files};${hlsl_generated_files}") add_header_target("spirv-resource-headers" ${spirv_files}) add_header_target("opencl-resource-headers" ${opencl_files}) add_header_target("llvm-libc-resource-headers" ${llvm_libc_wrapper_files}) diff --git a/clang/lib/Headers/hlsl/hlsl_alias_intrinsics.h b/clang/lib/Headers/hlsl/hlsl_alias_intrinsics.h index ee3b8f3f790a..76fca33ae0c1 100644 --- a/clang/lib/Headers/hlsl/hlsl_alias_intrinsics.h +++ b/clang/lib/Headers/hlsl/hlsl_alias_intrinsics.h @@ -38,6 +38,9 @@ namespace hlsl { #define _HLSL_16BIT_AVAILABILITY_SHADERMODEL_DEFAULT() #endif +// Generated by clang-tblgen from HLSLIntrinsics.td (alias intrinsics). +#include "hlsl_alias_intrinsics_gen.inc" + //===----------------------------------------------------------------------===// // abs builtins //===----------------------------------------------------------------------===// diff --git a/clang/lib/Headers/hlsl/hlsl_intrinsics.h b/clang/lib/Headers/hlsl/hlsl_intrinsics.h index 25d36f548394..6a37a5006441 100644 --- a/clang/lib/Headers/hlsl/hlsl_intrinsics.h +++ b/clang/lib/Headers/hlsl/hlsl_intrinsics.h @@ -13,6 +13,9 @@ namespace hlsl { +// Generated by clang-tblgen from HLSLIntrinsics.td (detail/inline intrinsics). +#include "hlsl_inline_intrinsics_gen.inc" + //===----------------------------------------------------------------------===// // asfloat builtins //===----------------------------------------------------------------------===// diff --git a/clang/test/TableGen/hlsl-intrinsics.td b/clang/test/TableGen/hlsl-intrinsics.td new file mode 100644 index 000000000000..e7dff7be97a3 --- /dev/null +++ b/clang/test/TableGen/hlsl-intrinsics.td @@ -0,0 +1,469 @@ +// RUN: clang-tblgen -gen-hlsl-alias-intrinsics -I%p/../../include %s | FileCheck %s --check-prefix=ALIAS +// RUN: clang-tblgen -gen-hlsl-inline-intrinsics -I%p/../../include %s | FileCheck %s --check-prefix=INLINE + +// Tests for the HLSL intrinsic TableGen backend (HLSLEmitter). Each test def +// exercises a specific feature. Def names are numbered (test_NN_) to control +// TableGen's alphabetical emission order so that CHECK lines read top-to-bottom. + +include "clang/Basic/HLSLIntrinsics.td" + +//===----------------------------------------------------------------------===// +// Basic alias builtin: scalar + vector overloads, no availability. +// Exercises: Varying arg/return, VaryingScalar, VaryingVecSizes, builtin +// alias emission, multiple regular types. +//===----------------------------------------------------------------------===// + +def test_01_basic_alias : HLSLBuiltin<"myfunc", "__builtin_myfunc"> { + let Args = [Varying]; + let ReturnType = Varying; + let VaryingScalar = 1; + let VaryingVecSizes = [2, 3]; + let VaryingTypes = [IntTy, FloatTy]; +} + +// ALIAS-LABEL: // myfunc overloads +// ALIAS-NEXT: _HLSL_BUILTIN_ALIAS(__builtin_myfunc) +// ALIAS-NEXT: int myfunc(int); +// ALIAS-NEXT: _HLSL_BUILTIN_ALIAS(__builtin_myfunc) +// ALIAS-NEXT: int2 myfunc(int2); +// ALIAS-NEXT: _HLSL_BUILTIN_ALIAS(__builtin_myfunc) +// ALIAS-NEXT: int3 myfunc(int3); +// +// ALIAS-NOT: half myfunc +// ALIAS-NOT: uint myfunc +// ALIAS-NOT: double myfunc +// +// ALIAS: _HLSL_BUILTIN_ALIAS(__builtin_myfunc) +// ALIAS-NEXT: float myfunc(float); +// ALIAS-NEXT: _HLSL_BUILTIN_ALIAS(__builtin_myfunc) +// ALIAS-NEXT: float2 myfunc(float2); +// ALIAS-NEXT: _HLSL_BUILTIN_ALIAS(__builtin_myfunc) +// ALIAS-NEXT: float3 myfunc(float3); +// +// ALIAS-NOT: myfunc + +//===----------------------------------------------------------------------===// +// VaryingElemType return: return type is scalar even for vector args. +// Exercises: VaryingElemType as ReturnType, two Varying args. +//===----------------------------------------------------------------------===// + +def test_02_elem_return : HLSLBuiltin<"dotlike", "__builtin_dotlike"> { + let Args = [Varying, Varying]; + let ReturnType = VaryingElemType; + let VaryingScalar = 1; + let VaryingVecSizes = [3]; + let VaryingTypes = [FloatTy]; +} + +// ALIAS-LABEL: // dotlike overloads +// ALIAS-NEXT: _HLSL_BUILTIN_ALIAS(__builtin_dotlike) +// ALIAS-NEXT: float dotlike(float, float); +// ALIAS-NEXT: _HLSL_BUILTIN_ALIAS(__builtin_dotlike) +// ALIAS-NEXT: float dotlike(float3, float3); +// +// ALIAS-NOT: dotlike + +//===----------------------------------------------------------------------===// +// VaryingShape return: return shape matches arg shape but with a different +// element type. +// Exercises: VaryingShape as ReturnType. +//===----------------------------------------------------------------------===// + +def test_03_shape_return : HLSLBuiltin<"isspecial", "__builtin_isspecial"> { + let Args = [Varying]; + let ReturnType = VaryingShape; + let VaryingScalar = 1; + let VaryingVecSizes = [2]; + let VaryingTypes = [FloatTy]; +} + +// ALIAS-LABEL: // isspecial overloads +// ALIAS-NEXT: _HLSL_BUILTIN_ALIAS(__builtin_isspecial) +// ALIAS-NEXT: bool isspecial(float); +// ALIAS-NEXT: _HLSL_BUILTIN_ALIAS(__builtin_isspecial) +// ALIAS-NEXT: bool2 isspecial(float2); +// +// ALIAS-NOT: isspecial + +//===----------------------------------------------------------------------===// +// Fixed VectorType args and return: no Varying types at all. +// Exercises: VectorType in Args and ReturnType, single overload. +//===----------------------------------------------------------------------===// + +def test_04_fixed_vector : HLSLBuiltin<"fixedfunc", "__builtin_fixedfunc"> { + let Args = [VectorType, VectorType]; + let ReturnType = VectorType; +} + +// ALIAS-LABEL: // fixedfunc overloads +// ALIAS-NEXT: _HLSL_BUILTIN_ALIAS(__builtin_fixedfunc) +// ALIAS-NEXT: float3 fixedfunc(float3, float3); +// +// ALIAS-NOT: fixedfunc + +//===----------------------------------------------------------------------===// +// Matrix overloads. +// Exercises: VaryingMatDims emission with matrix type names. +//===----------------------------------------------------------------------===// + +def test_05_matrix : HLSLBuiltin<"matfunc", "__builtin_matfunc"> { + let Args = [Varying]; + let ReturnType = Varying; + let VaryingMatDims = [Mat2x3]; + let VaryingTypes = [FloatTy]; +} + +// ALIAS-LABEL: // matfunc overloads +// ALIAS-NEXT: _HLSL_BUILTIN_ALIAS(__builtin_matfunc) +// ALIAS-NEXT: float2x3 matfunc(float2x3); +// +// ALIAS-NOT: matfunc + +//===----------------------------------------------------------------------===// +// Half type: conditionally-16-bit availability. +// Exercises: _HLSL_16BIT_AVAILABILITY for half, no ifdef guard. +//===----------------------------------------------------------------------===// + +def test_06_half : HLSLBuiltin<"halffunc", "__builtin_halffunc"> { + let Args = [Varying]; + let ReturnType = Varying; + let VaryingScalar = 1; + let VaryingTypes = [HalfTy, FloatTy]; +} + +// ALIAS-LABEL: // halffunc overloads +// ALIAS-NEXT: _HLSL_16BIT_AVAILABILITY(shadermodel, 6.2) +// ALIAS-NEXT: _HLSL_BUILTIN_ALIAS(__builtin_halffunc) +// ALIAS-NEXT: half halffunc(half); +// +// ALIAS-NOT: int halffunc +// ALIAS-NOT: double halffunc +// +// ALIAS: _HLSL_BUILTIN_ALIAS(__builtin_halffunc) +// ALIAS-NEXT: float halffunc(float); +// +// ALIAS-NOT: halffunc + +//===----------------------------------------------------------------------===// +// 16-bit integer type: ifdef guard + availability. +// Exercises: #ifdef __HLSL_ENABLE_16_BIT guard, _HLSL_AVAILABILITY for +// Is16Bit types, guard closing before non-16-bit types. +//===----------------------------------------------------------------------===// + +def test_07_16bit : HLSLBuiltin<"i16func", "__builtin_i16func"> { + let Args = [Varying]; + let ReturnType = Varying; + let VaryingScalar = 1; + let VaryingTypes = [Int16Ty, IntTy]; +} + +// ALIAS-LABEL: // i16func overloads +// ALIAS-NEXT: #ifdef __HLSL_ENABLE_16_BIT +// ALIAS-NEXT: _HLSL_AVAILABILITY(shadermodel, 6.2) +// ALIAS-NEXT: _HLSL_BUILTIN_ALIAS(__builtin_i16func) +// ALIAS-NEXT: int16_t i16func(int16_t); +// ALIAS-NEXT: #endif +// +// ALIAS-NOT: half i16func +// ALIAS-NOT: float i16func +// ALIAS-NOT: double i16func +// +// ALIAS: _HLSL_BUILTIN_ALIAS(__builtin_i16func) +// ALIAS-NEXT: int i16func(int); +// +// ALIAS-NOT: i16func + +//===----------------------------------------------------------------------===// +// Explicit availability (>= SM6.2): half and int16_t both use +// _HLSL_AVAILABILITY with the intrinsic's version instead of the 16-bit +// specific macros, since SM >= 6.2 already implies 16-bit support. +// Exercises: Availability field, SM override for conditionally-16-bit and +// 16-bit integer types. +//===----------------------------------------------------------------------===// + +def test_08_avail_ge62 : HLSLBuiltin<"availfunc", "__builtin_availfunc"> { + let Args = [Varying]; + let ReturnType = Varying; + let VaryingScalar = 1; + let VaryingTypes = [HalfTy, Int16Ty, IntTy, FloatTy]; + let Availability = SM6_4; +} + +// ALIAS-LABEL: // availfunc overloads +// ALIAS-NEXT: _HLSL_AVAILABILITY(shadermodel, 6.4) +// ALIAS-NEXT: _HLSL_BUILTIN_ALIAS(__builtin_availfunc) +// ALIAS-NEXT: half availfunc(half); +// ALIAS: #ifdef __HLSL_ENABLE_16_BIT +// ALIAS-NEXT: _HLSL_AVAILABILITY(shadermodel, 6.4) +// ALIAS-NEXT: _HLSL_BUILTIN_ALIAS(__builtin_availfunc) +// ALIAS-NEXT: int16_t availfunc(int16_t); +// ALIAS-NEXT: #endif +// ALIAS: _HLSL_AVAILABILITY(shadermodel, 6.4) +// ALIAS-NEXT: _HLSL_BUILTIN_ALIAS(__builtin_availfunc) +// ALIAS-NEXT: int availfunc(int); +// ALIAS: _HLSL_AVAILABILITY(shadermodel, 6.4) +// ALIAS-NEXT: _HLSL_BUILTIN_ALIAS(__builtin_availfunc) +// ALIAS-NEXT: float availfunc(float); +// +// ALIAS-NOT: availfunc + +//===----------------------------------------------------------------------===// +// Explicit availability (< SM6.2) with 16-bit integer types: the ifdef guard +// and _HLSL_AVAILABILITY(shadermodel, 6.2) are used for int16_t regardless of +// the intrinsic's stated availability, because 16-bit types require SM6.2. +// Note: half (IsConditionally16Bit) cannot be used here — the emitter asserts +// because neither _HLSL_AVAILABILITY nor _HLSL_16BIT_AVAILABILITY can correctly +// express availability for half when the intrinsic's SM < 6.2. +// Exercises: Availability < SM6.2 with Is16Bit types. +//===----------------------------------------------------------------------===// + +def test_08b_avail_lt62 : HLSLBuiltin<"availlt62", "__builtin_availlt62"> { + let Args = [Varying]; + let ReturnType = Varying; + let VaryingScalar = 1; + let VaryingTypes = [Int16Ty, IntTy]; + let Availability = SM6_1; +} + +// ALIAS-LABEL: // availlt62 overloads +// ALIAS-NEXT: #ifdef __HLSL_ENABLE_16_BIT +// ALIAS-NEXT: _HLSL_AVAILABILITY(shadermodel, 6.2) +// ALIAS-NEXT: _HLSL_BUILTIN_ALIAS(__builtin_availlt62) +// ALIAS-NEXT: int16_t availlt62(int16_t); +// ALIAS-NEXT: #endif +// ALIAS: _HLSL_AVAILABILITY(shadermodel, 6.1) +// ALIAS-NEXT: _HLSL_BUILTIN_ALIAS(__builtin_availlt62) +// ALIAS-NEXT: int availlt62(int); +// +// ALIAS-NOT: availlt62 + +//===----------------------------------------------------------------------===// +// Fixed half VectorType arg: conditionally-16-bit flag from fixed arg. +// Exercises: 16-bit flag propagation through fixed VectorType args. +//===----------------------------------------------------------------------===// + +def test_09_fixed_half_vec : HLSLBuiltin<"fixedhalffunc", "__builtin_fixedhalffunc"> { + let Args = [VectorType, VectorType, FloatTy]; + let ReturnType = FloatTy; +} + +// ALIAS-LABEL: // fixedhalffunc overloads +// ALIAS-NEXT: _HLSL_16BIT_AVAILABILITY(shadermodel, 6.2) +// ALIAS-NEXT: _HLSL_BUILTIN_ALIAS(__builtin_fixedhalffunc) +// ALIAS-NEXT: float fixedhalffunc(half2, half2, float); +// +// ALIAS-NOT: fixedhalffunc + +//===----------------------------------------------------------------------===// +// No-arg void function with convergent attribute. +// Exercises: VoidTy return, empty Args, IsConvergent. +//===----------------------------------------------------------------------===// + +def test_10_void_convergent : HLSLBuiltin<"barrier", "__builtin_barrier"> { + let IsConvergent = 1; +} + +// ALIAS-LABEL: // barrier overloads +// ALIAS-NEXT: _HLSL_BUILTIN_ALIAS(__builtin_barrier) +// ALIAS-NEXT: __attribute__((convergent)) void barrier(); +// +// ALIAS-NOT: barrier + +//===----------------------------------------------------------------------===// +// Inline body: constexpr function with literal body. +// Exercises: Body field, IsConstexpr, ParamNames, inline backend only. +//===----------------------------------------------------------------------===// + +def test_11_inline_body : HLSLBuiltin<"inline_only_func"> { + let Args = [Varying]; + let ReturnType = Varying; + let VaryingScalar = 1; + let VaryingTypes = [UIntTy]; + let Body = "return V;"; + let ParamNames = ["V"]; + let IsConstexpr = 1; +} + +// INLINE-NOT: _HLSL_BUILTIN_ALIAS +// INLINE-LABEL: // inline_only_func overloads +// INLINE-NEXT: constexpr uint inline_only_func(uint V) { return V; } +// +// INLINE-NOT: inline_only_func + +//===----------------------------------------------------------------------===// +// Detail function: inline function calling __detail::helper. +// Exercises: DetailFunc, ParamNames, VaryingElemType arg, mixed Varying and +// non-Varying args in inline backend. +//===----------------------------------------------------------------------===// + +def test_12_detail_func : HLSLBuiltin<"detailfunc"> { + let DetailFunc = "detail_impl"; + let Args = [Varying, Varying, VaryingElemType]; + let ReturnType = Varying; + let VaryingScalar = 1; + let VaryingVecSizes = [2]; + let VaryingTypes = [FloatTy]; + let ParamNames = ["A", "B", "C"]; +} + +// INLINE-LABEL: // detailfunc overloads +// INLINE-NEXT: inline float detailfunc(float A, float B, float C) { +// INLINE-NEXT: return __detail::detail_impl(A, B, C); +// INLINE-NEXT: } +// INLINE-NEXT: inline float2 detailfunc(float2 A, float2 B, float C) { +// INLINE-NEXT: return __detail::detail_impl(A, B, C); +// INLINE-NEXT: } +// +// INLINE-NOT: detailfunc + +//===----------------------------------------------------------------------===// +// Doc comment emission. +// Exercises: Doc field producing /// lines with blank-line handling. +//===----------------------------------------------------------------------===// + +def test_13_doc : HLSLBuiltin<"docfunc", "__builtin_docfunc"> { + let Doc = [{ +\fn void docfunc() +\brief A documented function. +}]; +} + +// ALIAS: /// \fn void docfunc() +// ALIAS-NEXT: /// \brief A documented function. +// ALIAS-NEXT: // docfunc overloads +// ALIAS-NEXT: _HLSL_BUILTIN_ALIAS(__builtin_docfunc) +// ALIAS-NEXT: void docfunc(); +// +// ALIAS-NOT: docfunc + +//===----------------------------------------------------------------------===// +// Canonical type sort order: types emitted in priority order regardless of +// declaration order. +// Exercises: getTypeSortPriority ordering (half before int before float). +//===----------------------------------------------------------------------===// + +def test_14_sort_order : HLSLBuiltin<"sortedfunc", "__builtin_sortedfunc"> { + let Args = [Varying]; + let ReturnType = Varying; + let VaryingScalar = 1; + let VaryingTypes = [FloatTy, IntTy, HalfTy]; +} + +// ALIAS-LABEL: // sortedfunc overloads +// ALIAS-NEXT: _HLSL_16BIT_AVAILABILITY(shadermodel, 6.2) +// ALIAS-NEXT: _HLSL_BUILTIN_ALIAS(__builtin_sortedfunc) +// ALIAS-NEXT: half sortedfunc(half); +// +// ALIAS-NOT: double sortedfunc +// ALIAS-NOT: uint sortedfunc +// +// ALIAS: _HLSL_BUILTIN_ALIAS(__builtin_sortedfunc) +// ALIAS-NEXT: int sortedfunc(int); +// ALIAS: _HLSL_BUILTIN_ALIAS(__builtin_sortedfunc) +// ALIAS-NEXT: float sortedfunc(float); +// +// ALIAS-NOT: sortedfunc + +//===----------------------------------------------------------------------===// +// Vector and matrix dimension sort order: vector sizes are emitted ascending +// and matrix dimensions sorted by rows then columns, regardless of declaration +// order. +// Exercises: VaryingVecSizes sorting, VaryingMatDims sorting. +//===----------------------------------------------------------------------===// + +def test_15_shape_sort : HLSLBuiltin<"shapesorted", "__builtin_shapesorted"> { + let Args = [Varying]; + let ReturnType = Varying; + let VaryingVecSizes = [4, 2, 3]; + let VaryingMatDims = [Mat3x2, Mat2x1, Mat2x3]; + let VaryingTypes = [FloatTy]; +} + +// ALIAS-LABEL: // shapesorted overloads +// ALIAS-NEXT: _HLSL_BUILTIN_ALIAS(__builtin_shapesorted) +// ALIAS-NEXT: float2 shapesorted(float2); +// ALIAS-NEXT: _HLSL_BUILTIN_ALIAS(__builtin_shapesorted) +// ALIAS-NEXT: float3 shapesorted(float3); +// ALIAS-NEXT: _HLSL_BUILTIN_ALIAS(__builtin_shapesorted) +// ALIAS-NEXT: float4 shapesorted(float4); +// ALIAS-NEXT: _HLSL_BUILTIN_ALIAS(__builtin_shapesorted) +// ALIAS-NEXT: float2x1 shapesorted(float2x1); +// ALIAS-NEXT: _HLSL_BUILTIN_ALIAS(__builtin_shapesorted) +// ALIAS-NEXT: float2x3 shapesorted(float2x3); +// ALIAS-NEXT: _HLSL_BUILTIN_ALIAS(__builtin_shapesorted) +// ALIAS-NEXT: float3x2 shapesorted(float3x2); +// +// ALIAS-NOT: shapesorted + +//===----------------------------------------------------------------------===// +// VaryingElemType as argument: arg is always the scalar element type even when +// the overload shape is a vector. +// Exercises: VaryingElemType in Args position. +//===----------------------------------------------------------------------===// + +def test_16_elemtype_arg : HLSLBuiltin<"elemarg", "__builtin_elemarg"> { + let Args = [Varying, VaryingElemType]; + let ReturnType = Varying; + let VaryingScalar = 1; + let VaryingVecSizes = [3]; + let VaryingTypes = [FloatTy]; +} + +// ALIAS-LABEL: // elemarg overloads +// ALIAS-NEXT: _HLSL_BUILTIN_ALIAS(__builtin_elemarg) +// ALIAS-NEXT: float elemarg(float, float); +// ALIAS-NEXT: _HLSL_BUILTIN_ALIAS(__builtin_elemarg) +// ALIAS-NEXT: float3 elemarg(float3, float); +// +// ALIAS-NOT: elemarg + +//===----------------------------------------------------------------------===// +// VaryingShape as argument: arg uses the varying shape but with a fixed element +// type. +// Exercises: VaryingShape in Args position. +//===----------------------------------------------------------------------===// + +def test_17_shape_arg : HLSLBuiltin<"shapearg", "__builtin_shapearg"> { + let Args = [Varying, VaryingShape]; + let ReturnType = Varying; + let VaryingScalar = 1; + let VaryingVecSizes = [2]; + let VaryingTypes = [FloatTy]; +} + +// ALIAS-LABEL: // shapearg overloads +// ALIAS-NEXT: _HLSL_BUILTIN_ALIAS(__builtin_shapearg) +// ALIAS-NEXT: float shapearg(float, uint); +// ALIAS-NEXT: _HLSL_BUILTIN_ALIAS(__builtin_shapearg) +// ALIAS-NEXT: float2 shapearg(float2, uint2); +// +// ALIAS-NOT: shapearg + +//===----------------------------------------------------------------------===// +// Verify no alias intrinsics are generated in the inline header. +//===----------------------------------------------------------------------===// + +// INLINE-NOT: myfunc +// INLINE-NOT: dotlike +// INLINE-NOT: isspecial +// INLINE-NOT: fixedfunc +// INLINE-NOT: matfunc +// INLINE-NOT: halffunc +// INLINE-NOT: i16func +// INLINE-NOT: availfunc +// INLINE-NOT: availlt62 +// INLINE-NOT: fixedhalffunc +// INLINE-NOT: barrier +// INLINE-NOT: docfunc +// INLINE-NOT: sortedfunc +// INLINE-NOT: shapesorted +// INLINE-NOT: elemarg +// INLINE-NOT: shapearg + +//===----------------------------------------------------------------------===// +// Verify no inline intrinsics are generated in the alias header. +//===----------------------------------------------------------------------===// + +// ALIAS-NOT: inline_only_func +// ALIAS-NOT: detailfunc + diff --git a/clang/utils/TableGen/CMakeLists.txt b/clang/utils/TableGen/CMakeLists.txt index 14f13824e957..8b5e00c189cb 100644 --- a/clang/utils/TableGen/CMakeLists.txt +++ b/clang/utils/TableGen/CMakeLists.txt @@ -21,6 +21,7 @@ add_tablegen(clang-tblgen CLANG ClangSACheckersEmitter.cpp ClangSyntaxEmitter.cpp ClangTypeNodesEmitter.cpp + HLSLEmitter.cpp MveEmitter.cpp NeonEmitter.cpp RISCVVEmitter.cpp diff --git a/clang/utils/TableGen/HLSLEmitter.cpp b/clang/utils/TableGen/HLSLEmitter.cpp new file mode 100644 index 000000000000..d115c35af5f6 --- /dev/null +++ b/clang/utils/TableGen/HLSLEmitter.cpp @@ -0,0 +1,590 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This tablegen backend generates hlsl_alias_intrinsics_gen.inc (alias +// overloads) and hlsl_inline_intrinsics_gen.inc (inline/detail overloads) for +// HLSL intrinsic functions. +// +//===----------------------------------------------------------------------===// + +#include "TableGenBackends.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringExtras.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/ADT/StringSwitch.h" +#include "llvm/Support/ErrorHandling.h" +#include "llvm/Support/raw_ostream.h" +#include "llvm/TableGen/Record.h" + +using namespace llvm; + +/// Minimum shader model version that supports 16-bit types. +static constexpr StringLiteral SM6_2 = "6.2"; + +//===----------------------------------------------------------------------===// +// Type name helpers +//===----------------------------------------------------------------------===// + +static std::string getVectorTypeName(StringRef ElemType, unsigned N) { + return (ElemType + Twine(N)).str(); +} + +static std::string getMatrixTypeName(StringRef ElemType, unsigned Rows, + unsigned Cols) { + return (ElemType + Twine(Rows) + "x" + Twine(Cols)).str(); +} + +/// Get the fixed type name string for a VectorType or HLSLType record. +static std::string getFixedTypeName(const Record *R) { + if (R->isSubClassOf("VectorType")) + return getVectorTypeName( + R->getValueAsDef("ElementType")->getValueAsString("Name"), + R->getValueAsInt("Size")); + assert(R->isSubClassOf("HLSLType")); + return R->getValueAsString("Name").str(); +} + +/// For a VectorType, return its ElementType record; for an HLSLType, return +/// the record itself (it is already a scalar element type). +static const Record *getElementTypeRecord(const Record *R) { + if (R->isSubClassOf("VectorType")) + return R->getValueAsDef("ElementType"); + assert(R->isSubClassOf("HLSLType")); + return R; +} + +//===----------------------------------------------------------------------===// +// Type information +//===----------------------------------------------------------------------===// + +namespace { + +/// Classifies how a type varies across overloads. +enum TypeKindEnum { + TK_Varying = 0, ///< Type matches the full varying type (e.g. float3). + TK_ElemType = 1, ///< Type is the scalar element type (e.g. float). + TK_VaryingShape = 2, ///< Type uses the varying shape with a fixed element. + TK_FixedType = 3, ///< Type is a fixed concrete type (e.g. "half2"). + TK_Void = 4 ///< Type is void (only valid for return types). +}; + +/// Metadata describing how a type (argument or return) varies across overloads. +struct TypeInfo { + /// Classification of how this type varies across overloads. + TypeKindEnum Kind = TK_Varying; + + /// Fixed type name (e.g. "half2") for types with a concrete type that does + /// not vary across overloads. Empty for varying types. + std::string FixedType; + + /// Element type name for TK_VaryingShape types (e.g. "bool" for + /// VaryingShape). Empty for other type kinds. + StringRef ShapeElemType; + + /// Explicit parameter name (e.g. "eta"). Empty to use the default "p0", + /// "p1", ... naming. Only meaningful for argument types. + StringRef Name; + + /// Construct a TypeInfo from a TableGen record. + static TypeInfo resolve(const Record *Rec) { + TypeInfo TI; + if (Rec->getName() == "VoidTy") { + TI.Kind = TK_Void; + } else if (Rec->getName() == "Varying") { + TI.Kind = TK_Varying; + } else if (Rec->getName() == "VaryingElemType") { + TI.Kind = TK_ElemType; + } else if (Rec->isSubClassOf("VaryingShape")) { + TI.Kind = TK_VaryingShape; + TI.ShapeElemType = + Rec->getValueAsDef("ElementType")->getValueAsString("Name"); + } else if (Rec->isSubClassOf("VectorType") || + Rec->isSubClassOf("HLSLType")) { + TI.Kind = TK_FixedType; + TI.FixedType = getFixedTypeName(Rec); + } else { + llvm_unreachable("unhandled record for type resolution"); + } + return TI; + } + + /// Resolve this type to a concrete type name string. + /// \p ElemType is the scalar element type for the current overload. + /// \p FormatVarying formats a scalar element type into the shaped type name. + std::string + toTypeString(StringRef ElemType, + function_ref FormatVarying) const { + switch (Kind) { + case TK_Void: + return "void"; + case TK_Varying: + return FormatVarying(ElemType); + case TK_ElemType: + return ElemType.str(); + case TK_VaryingShape: + return FormatVarying(ShapeElemType); + case TK_FixedType: + assert(!FixedType.empty() && "TK_FixedType requires non-empty FixedType"); + return FixedType; + } + llvm_unreachable("unhandled TypeKindEnum"); + } +}; + +} // anonymous namespace + +//===----------------------------------------------------------------------===// +// Availability helpers +//===----------------------------------------------------------------------===// + +static void emitAvailability(raw_ostream &OS, StringRef Version, + bool Use16Bit = false) { + if (Use16Bit) + OS << "_HLSL_16BIT_AVAILABILITY(shadermodel, " << Version << ")\n"; + else + OS << "_HLSL_AVAILABILITY(shadermodel, " << Version << ")\n"; +} + +static std::string getVersionString(const Record *SM) { + unsigned Major = SM->getValueAsInt("Major"); + unsigned Minor = SM->getValueAsInt("Minor"); + if (Major == 0 && Minor == 0) + return ""; + return (Twine(Major) + "." + Twine(Minor)).str(); +} + +//===----------------------------------------------------------------------===// +// Type work item — describes one element type to emit overloads for +//===----------------------------------------------------------------------===// + +namespace { + +/// A single entry in the worklist of types to process for an intrinsic. +struct TypeWorkItem { + /// Element type name (e.g. "half", "float"). Empty for fixed-arg-only + /// intrinsics with no type expansion. + StringRef ElemType; + + /// Version string for the availability attribute (e.g. "6.2"). Empty if + /// no availability annotation is needed. + std::string Availability; + + /// If true, emit _HLSL_16BIT_AVAILABILITY instead of _HLSL_AVAILABILITY. + bool Use16BitAvail = false; + + /// If true, wrap overloads in #ifdef __HLSL_ENABLE_16_BIT / #endif. + bool NeedsIfdefGuard = false; +}; + +} // anonymous namespace + +/// Fixed canonical ordering for overload types. Types are grouped as: +/// 0: conditionally-16-bit (half) +/// 1-2: 16-bit integers (int16_t, uint16_t) — ifdef-guarded +/// 3+: regular types (bool, int, uint, int64_t, uint64_t, float, double) +/// Within each group, signed precedes unsigned, smaller precedes larger, +/// and integer types precede floating-point types. +static int getTypeSortPriority(const Record *ET) { + return StringSwitch(ET->getValueAsString("Name")) + .Case("half", 0) + .Case("int16_t", 1) + .Case("uint16_t", 2) + .Case("bool", 3) + .Case("int", 4) + .Case("uint", 5) + .Case("int64_t", 7) + .Case("uint64_t", 8) + .Case("float", 9) + .Case("double", 10) + .Default(11); +} + +//===----------------------------------------------------------------------===// +// Overload context — shared state across all overloads of one intrinsic +//===----------------------------------------------------------------------===// + +namespace { + +/// Shared state for emitting all overloads of a single HLSL intrinsic. +struct OverloadContext { + /// Output stream to write generated code to. + raw_ostream &OS; + + /// Builtin name for _HLSL_BUILTIN_ALIAS (e.g. "__builtin_hlsl_dot"). + /// Empty for inline/detail intrinsics. + StringRef Builtin; + + /// __detail helper function to call (e.g. "refract_impl"). + /// Empty for alias and inline-body intrinsics. + StringRef DetailFunc; + + /// Literal inline function body (e.g. "return p0;"). + /// Empty for alias and detail intrinsics. + StringRef Body; + + /// The HLSL function name to emit (e.g. "dot", "refract"). + StringRef FuncName; + + /// Metadata describing the return type and its variation behavior. + TypeInfo RetType; + + /// Per-argument metadata describing type and variation behavior. + SmallVector Args; + + /// Whether to emit the function as constexpr. + bool IsConstexpr = false; + + /// Whether to emit the __attribute__((convergent)) annotation. + bool IsConvergent = false; + + /// Whether any fixed arg has a 16-bit integer type (e.g. int16_t). + bool Uses16BitType = false; + + /// Whether any fixed arg has a conditionally-16-bit type (half). + bool UsesConditionally16BitType = false; + + explicit OverloadContext(raw_ostream &OS) : OS(OS) {} +}; + +} // anonymous namespace + +/// Emit a complete function declaration or definition with pre-resolved types. +static void emitDeclaration(const OverloadContext &Ctx, StringRef RetType, + ArrayRef ArgTypes) { + raw_ostream &OS = Ctx.OS; + bool IsDetail = !Ctx.DetailFunc.empty(); + bool IsInline = !Ctx.Body.empty(); + bool HasBody = IsDetail || IsInline; + + bool EmitNames = HasBody || llvm::any_of(Ctx.Args, [](const TypeInfo &A) { + return !A.Name.empty(); + }); + + auto GetParamName = [&](unsigned I) -> std::string { + if (!Ctx.Args[I].Name.empty()) + return Ctx.Args[I].Name.str(); + return ("p" + Twine(I)).str(); + }; + + if (!HasBody) + OS << "_HLSL_BUILTIN_ALIAS(" << Ctx.Builtin << ")\n"; + if (Ctx.IsConvergent) + OS << "__attribute__((convergent)) "; + if (HasBody) + OS << (Ctx.IsConstexpr ? "constexpr " : "inline "); + OS << RetType << " " << Ctx.FuncName << "("; + + { + ListSeparator LS; + for (unsigned I = 0, N = ArgTypes.size(); I < N; ++I) { + OS << LS << ArgTypes[I]; + if (EmitNames) + OS << " " << GetParamName(I); + } + } + + if (IsDetail) { + OS << ") {\n return __detail::" << Ctx.DetailFunc << "("; + ListSeparator LS; + for (unsigned I = 0, N = ArgTypes.size(); I < N; ++I) + OS << LS << GetParamName(I); + OS << ");\n}\n"; + } else if (IsInline) { + OS << ") { " << Ctx.Body << " }\n"; + } else { + OS << ");\n"; + } +} + +/// Emit a single overload declaration by resolving all types through +/// \p FormatVarying, which maps element types to their shaped form. +static void emitOverload(const OverloadContext &Ctx, StringRef ElemType, + function_ref FormatVarying) { + std::string RetType = Ctx.RetType.toTypeString(ElemType, FormatVarying); + SmallVector ArgTypes; + for (const TypeInfo &TI : Ctx.Args) + ArgTypes.push_back(TI.toTypeString(ElemType, FormatVarying)); + emitDeclaration(Ctx, RetType, ArgTypes); +} + +/// Emit a scalar overload for the given element type. +static void emitScalarOverload(const OverloadContext &Ctx, StringRef ElemType) { + emitOverload(Ctx, ElemType, [](StringRef ET) { return ET.str(); }); +} + +/// Emit a vector overload for the given element type and vector size. +static void emitVectorOverload(const OverloadContext &Ctx, StringRef ElemType, + unsigned VecSize) { + emitOverload(Ctx, ElemType, [VecSize](StringRef ET) { + return getVectorTypeName(ET, VecSize); + }); +} + +/// Emit a matrix overload for the given element type and matrix dimensions. +static void emitMatrixOverload(const OverloadContext &Ctx, StringRef ElemType, + unsigned Rows, unsigned Cols) { + emitOverload(Ctx, ElemType, [Rows, Cols](StringRef ET) { + return getMatrixTypeName(ET, Rows, Cols); + }); +} + +//===----------------------------------------------------------------------===// +// Main emission logic +//===----------------------------------------------------------------------===// + +/// Build an OverloadContext from an HLSLBuiltin record. +static void buildOverloadContext(const Record *R, OverloadContext &Ctx) { + Ctx.Builtin = R->getValueAsString("Builtin"); + Ctx.DetailFunc = R->getValueAsString("DetailFunc"); + Ctx.Body = R->getValueAsString("Body"); + Ctx.FuncName = R->getValueAsString("Name"); + Ctx.IsConstexpr = R->getValueAsBit("IsConstexpr"); + Ctx.IsConvergent = R->getValueAsBit("IsConvergent"); + + // Note use of 16-bit fixed types in the overload context. + auto Update16BitFlags = [&Ctx](const Record *Rec) { + const Record *ElemTy = getElementTypeRecord(Rec); + Ctx.Uses16BitType |= ElemTy->getValueAsBit("Is16Bit"); + Ctx.UsesConditionally16BitType |= + ElemTy->getValueAsBit("IsConditionally16Bit"); + }; + + // Resolve return and argument types. + const Record *RetRec = R->getValueAsDef("ReturnType"); + Ctx.RetType = TypeInfo::resolve(RetRec); + if (Ctx.RetType.Kind == TK_FixedType) + Update16BitFlags(RetRec); + + std::vector ArgRecords = R->getValueAsListOfDefs("Args"); + std::vector ParamNames = R->getValueAsListOfStrings("ParamNames"); + + for (const auto &[I, Arg] : llvm::enumerate(ArgRecords)) { + TypeInfo TI = TypeInfo::resolve(Arg); + if (I < ParamNames.size()) + TI.Name = ParamNames[I]; + if (TI.Kind == TK_FixedType) + Update16BitFlags(Arg); + Ctx.Args.push_back(TI); + } +} + +/// Build the worklist of element types to emit overloads for, sorted in +/// canonical order (see getTypeSortPriority). +static void buildWorklist(const Record *R, + SmallVectorImpl &Worklist, + const OverloadContext &Ctx) { + const Record *AvailRec = R->getValueAsDef("Availability"); + std::string Availability = getVersionString(AvailRec); + bool AvailabilityIsAtLeastSM6_2 = AvailRec->getValueAsInt("Major") > 6 || + (AvailRec->getValueAsInt("Major") == 6 && + AvailRec->getValueAsInt("Minor") >= 2); + + std::vector VaryingTypeRecords = + R->getValueAsListOfDefs("VaryingTypes"); + + // Populate the availability and guard fields of a TypeWorkItem based on + // whether the type is 16-bit, conditionally 16-bit, or a regular type. + auto SetAvailability = [&](TypeWorkItem &Item, bool Is16Bit, + bool IsCond16Bit) { + Item.NeedsIfdefGuard = Is16Bit; + if (Is16Bit || IsCond16Bit) { + if (AvailabilityIsAtLeastSM6_2) { + Item.Availability = Availability; + } else { + Item.Availability = SM6_2; + Item.Use16BitAvail = IsCond16Bit; + + // Note: If Availability = x where x < 6.2 and a half type is used, + // neither _HLSL_AVAILABILITY(shadermodel, x) nor + // _HLSL_16BIT_AVAILABILITY(shadermodel, 6.2) are correct: + // + // _HLSL_AVAILABILITY(shadermodel, x) will set the availbility for the + // half overload to x even when 16-bit types are enabled, but x < 6.2 + // and 6.2 is required for 16-bit half. + // + // _HLSL_16BIT_AVAILABILITY(shadermodel, 6.2) will set the + // availability for the half overload to 6.2 when 16-bit types are + // enabled, but there will be no availability set when 16-bit types + // are not enabled. + // + // A possible solution to this is to make _HLSL_16BIT_AVAILABILITY + // accept 3 args: (shadermodel, X, Y) where X is the availability for + // the 16-bit half type overload (which will typically be 6.2), and Y is + // the availability for the non-16-bit half overload. However, this + // situation does not currently arise, so we just assert below that this + // case will never occur. + assert( + !(IsCond16Bit && !Availability.empty()) && + "Can not handle availability for an intrinsic using half types and" + " which has an explicit shader model requirement older than 6.2"); + } + } else { + Item.Availability = Availability; + } + }; + + // If no Varying types are specified, just add a single work item. + // This is for HLSLBuiltin records that don't use Varying types. + if (VaryingTypeRecords.empty()) { + TypeWorkItem Item; + SetAvailability(Item, Ctx.Uses16BitType, Ctx.UsesConditionally16BitType); + Worklist.push_back(Item); + return; + } + + // Sort Varying types so that overloads are always emitted in canonical order. + llvm::sort(VaryingTypeRecords, [](const Record *A, const Record *B) { + return getTypeSortPriority(A) < getTypeSortPriority(B); + }); + + // Add a work item for each Varying element type. + for (const Record *ElemTy : VaryingTypeRecords) { + TypeWorkItem Item; + Item.ElemType = ElemTy->getValueAsString("Name"); + bool Is16Bit = Ctx.Uses16BitType || ElemTy->getValueAsBit("Is16Bit"); + bool IsCond16Bit = Ctx.UsesConditionally16BitType || + ElemTy->getValueAsBit("IsConditionally16Bit"); + SetAvailability(Item, Is16Bit, IsCond16Bit); + Worklist.push_back(Item); + } +} + +/// Emit a Doxygen documentation comment from the Doc field. +static void emitDocComment(raw_ostream &OS, const Record *R) { + StringRef Doc = R->getValueAsString("Doc"); + if (Doc.empty()) + return; + Doc = Doc.trim(); + SmallVector DocLines; + Doc.split(DocLines, '\n'); + for (StringRef Line : DocLines) { + if (Line.empty()) + OS << "///\n"; + else + OS << "/// " << Line << "\n"; + } +} + +/// Process the worklist: emit all shape variants for each type with +/// availability annotations and #ifdef guards. +static void emitWorklistOverloads(raw_ostream &OS, const OverloadContext &Ctx, + ArrayRef Worklist, + bool EmitScalarOverload, + ArrayRef VectorSizes, + ArrayRef MatrixDimensions) { + bool InIfdef = false; + for (const TypeWorkItem &Item : Worklist) { + if (Item.NeedsIfdefGuard && !InIfdef) { + OS << "#ifdef __HLSL_ENABLE_16_BIT\n"; + InIfdef = true; + } + + auto EmitAvail = [&]() { + if (!Item.Availability.empty()) + emitAvailability(OS, Item.Availability, Item.Use16BitAvail); + }; + + if (EmitScalarOverload) { + EmitAvail(); + emitScalarOverload(Ctx, Item.ElemType); + } + for (int64_t N : VectorSizes) { + EmitAvail(); + emitVectorOverload(Ctx, Item.ElemType, N); + } + for (const Record *MD : MatrixDimensions) { + EmitAvail(); + emitMatrixOverload(Ctx, Item.ElemType, MD->getValueAsInt("Rows"), + MD->getValueAsInt("Cols")); + } + + if (InIfdef) { + bool NextIsUnguarded = + (&Item == &Worklist.back()) || !(&Item + 1)->NeedsIfdefGuard; + if (NextIsUnguarded) { + OS << "#endif\n"; + InIfdef = false; + } + } + + OS << "\n"; + } +} + +/// Emit all overloads for a single HLSLBuiltin record. +static void emitBuiltinOverloads(raw_ostream &OS, const Record *R) { + OverloadContext Ctx(OS); + buildOverloadContext(R, Ctx); + + SmallVector Worklist; + buildWorklist(R, Worklist, Ctx); + + emitDocComment(OS, R); + OS << "// " << Ctx.FuncName << " overloads\n"; + + // Emit a scalar overload if a scalar Varying overload was requested. + // If no Varying types are used at all, emit a scalar overload to handle + // emitting a single overload for fixed-typed args or arg-less functions. + bool EmitScalarOverload = R->getValueAsBit("VaryingScalar") || + R->getValueAsListOfDefs("VaryingTypes").empty(); + + std::vector VectorSizes = R->getValueAsListOfInts("VaryingVecSizes"); + std::vector MatrixDimensions = + R->getValueAsListOfDefs("VaryingMatDims"); + + // Sort vector sizes and matrix dimensions for consistent output order. + llvm::sort(VectorSizes); + llvm::sort(MatrixDimensions, [](const Record *A, const Record *B) { + int RowA = A->getValueAsInt("Rows"), RowB = B->getValueAsInt("Rows"); + if (RowA != RowB) + return RowA < RowB; + return A->getValueAsInt("Cols") < B->getValueAsInt("Cols"); + }); + + emitWorklistOverloads(OS, Ctx, Worklist, EmitScalarOverload, VectorSizes, + MatrixDimensions); +} + +/// Emit alias overloads for a single HLSLBuiltin record. +/// Skips records that have inline bodies (DetailFunc or Body). +static void emitAliasBuiltin(raw_ostream &OS, const Record *R) { + if (!R->getValueAsString("DetailFunc").empty() || + !R->getValueAsString("Body").empty()) + return; + emitBuiltinOverloads(OS, R); +} + +/// Emit inline overloads for a single HLSLBuiltin record. +/// Skips records that are pure alias declarations. +static void emitInlineBuiltin(raw_ostream &OS, const Record *R) { + if (R->getValueAsString("DetailFunc").empty() && + R->getValueAsString("Body").empty()) + return; + emitBuiltinOverloads(OS, R); +} + +void clang::EmitHLSLAliasIntrinsics(const RecordKeeper &Records, + raw_ostream &OS) { + OS << "// This file is auto-generated by clang-tblgen from " + "HLSLIntrinsics.td.\n"; + OS << "// Do not edit this file directly.\n\n"; + + for (const Record *R : Records.getAllDerivedDefinitions("HLSLBuiltin")) + emitAliasBuiltin(OS, R); +} + +void clang::EmitHLSLInlineIntrinsics(const RecordKeeper &Records, + raw_ostream &OS) { + OS << "// This file is auto-generated by clang-tblgen from " + "HLSLIntrinsics.td.\n"; + OS << "// Do not edit this file directly.\n\n"; + + for (const Record *R : Records.getAllDerivedDefinitions("HLSLBuiltin")) + emitInlineBuiltin(OS, R); +} diff --git a/clang/utils/TableGen/TableGen.cpp b/clang/utils/TableGen/TableGen.cpp index bd8573dcb940..0ce9d8306ae1 100644 --- a/clang/utils/TableGen/TableGen.cpp +++ b/clang/utils/TableGen/TableGen.cpp @@ -118,6 +118,8 @@ enum ActionType { GenRISCVAndesVectorBuiltins, GenRISCVAndesVectorBuiltinCG, GenRISCVAndesVectorBuiltinSema, + GenHLSLAliasIntrinsics, + GenHLSLInlineIntrinsics, GenAttrDocs, GenBuiltinDocs, GenDiagDocs, @@ -346,6 +348,12 @@ cl::opt Action( clEnumValN(GenRISCVAndesVectorBuiltinSema, "gen-riscv-andes-vector-builtin-sema", "Generate riscv_andes_vector_builtin_sema.inc for clang"), + clEnumValN(GenHLSLAliasIntrinsics, "gen-hlsl-alias-intrinsics", + "Generate HLSL alias intrinsic overloads for " + "hlsl_alias_intrinsics.h"), + clEnumValN(GenHLSLInlineIntrinsics, "gen-hlsl-inline-intrinsics", + "Generate HLSL inline intrinsic overloads for " + "hlsl_intrinsics.h"), clEnumValN(GenAttrDocs, "gen-attr-docs", "Generate attribute documentation"), clEnumValN(GenBuiltinDocs, "gen-builtin-docs", @@ -654,6 +662,12 @@ bool ClangTableGenMain(raw_ostream &OS, const RecordKeeper &Records) { case GenRISCVAndesVectorBuiltinSema: EmitRVVBuiltinSema(Records, OS); break; + case GenHLSLAliasIntrinsics: + EmitHLSLAliasIntrinsics(Records, OS); + break; + case GenHLSLInlineIntrinsics: + EmitHLSLInlineIntrinsics(Records, OS); + break; case GenAttrDocs: EmitClangAttrDocs(Records, OS); break; diff --git a/clang/utils/TableGen/TableGenBackends.h b/clang/utils/TableGen/TableGenBackends.h index 98dc9f461191..f9bd7ccf9d0d 100644 --- a/clang/utils/TableGen/TableGenBackends.h +++ b/clang/utils/TableGen/TableGenBackends.h @@ -191,6 +191,11 @@ void EmitCdeBuiltinCG(const llvm::RecordKeeper &Records, llvm::raw_ostream &OS); void EmitCdeBuiltinAliases(const llvm::RecordKeeper &Records, llvm::raw_ostream &OS); +void EmitHLSLAliasIntrinsics(const llvm::RecordKeeper &Records, + llvm::raw_ostream &OS); +void EmitHLSLInlineIntrinsics(const llvm::RecordKeeper &Records, + llvm::raw_ostream &OS); + void EmitClangAttrDocs(const llvm::RecordKeeper &Records, llvm::raw_ostream &OS); void EmitClangDiagDocs(const llvm::RecordKeeper &Records,