//===----------------------------------------------------------------------===// // // 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, " << SM6_2; if (!Version.empty()) OS << ", " << Version; OS << ")\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.Use16BitAvail = IsCond16Bit; if (IsCond16Bit) Item.Availability = Availability; else Item.Availability = SM6_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 (size_t I = 0, E = Worklist.size(); I != E; ++I) { const TypeWorkItem &Item = Worklist[I]; if (Item.NeedsIfdefGuard && !InIfdef) { OS << "#ifdef __HLSL_ENABLE_16_BIT\n"; InIfdef = true; } auto EmitAvail = [&]() { if (!Item.Availability.empty() || Item.Use16BitAvail) 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 = (I + 1 == E) || !Worklist[I + 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); }