1//===----------------------------------------------------------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8//
9// This tablegen backend generates hlsl_alias_intrinsics_gen.inc (alias
10// overloads) and hlsl_inline_intrinsics_gen.inc (inline/detail overloads) for
11// HLSL intrinsic functions.
12//
13//===----------------------------------------------------------------------===//
14
15#include "TableGenBackends.h"
16#include "llvm/ADT/STLExtras.h"
17#include "llvm/ADT/SmallVector.h"
18#include "llvm/ADT/StringExtras.h"
19#include "llvm/ADT/StringRef.h"
20#include "llvm/ADT/StringSwitch.h"
21#include "llvm/Support/ErrorHandling.h"
22#include "llvm/Support/raw_ostream.h"
23#include "llvm/TableGen/Record.h"
24
25using namespace llvm;
26
27/// Minimum shader model version that supports 16-bit types.
28static constexpr StringLiteral SM6_2 = "6.2";
29
30//===----------------------------------------------------------------------===//
31// Type name helpers
32//===----------------------------------------------------------------------===//
33
34static std::string getVectorTypeName(StringRef ElemType, unsigned N) {
35 return (ElemType + Twine(N)).str();
36}
37
38static std::string getMatrixTypeName(StringRef ElemType, unsigned Rows,
39 unsigned Cols) {
40 return (ElemType + Twine(Rows) + "x" + Twine(Cols)).str();
41}
42
43/// Get the fixed type name string for a VectorType or HLSLType record.
44static std::string getFixedTypeName(const Record *R) {
45 if (R->isSubClassOf(Name: "VectorType"))
46 return getVectorTypeName(
47 ElemType: R->getValueAsDef(FieldName: "ElementType")->getValueAsString(FieldName: "Name"),
48 N: R->getValueAsInt(FieldName: "Size"));
49 assert(R->isSubClassOf("HLSLType"));
50 return R->getValueAsString(FieldName: "Name").str();
51}
52
53/// For a VectorType, return its ElementType record; for an HLSLType, return
54/// the record itself (it is already a scalar element type).
55static const Record *getElementTypeRecord(const Record *R) {
56 if (R->isSubClassOf(Name: "VectorType"))
57 return R->getValueAsDef(FieldName: "ElementType");
58 assert(R->isSubClassOf("HLSLType"));
59 return R;
60}
61
62//===----------------------------------------------------------------------===//
63// Type information
64//===----------------------------------------------------------------------===//
65
66namespace {
67
68/// Classifies how a type varies across overloads.
69enum TypeKindEnum {
70 TK_Varying = 0, ///< Type matches the full varying type (e.g. float3).
71 TK_ElemType = 1, ///< Type is the scalar element type (e.g. float).
72 TK_VaryingShape = 2, ///< Type uses the varying shape with a fixed element.
73 TK_FixedType = 3, ///< Type is a fixed concrete type (e.g. "half2").
74 TK_Void = 4 ///< Type is void (only valid for return types).
75};
76
77/// Metadata describing how a type (argument or return) varies across overloads.
78struct TypeInfo {
79 /// Classification of how this type varies across overloads.
80 TypeKindEnum Kind = TK_Varying;
81
82 /// Fixed type name (e.g. "half2") for types with a concrete type that does
83 /// not vary across overloads. Empty for varying types.
84 std::string FixedType;
85
86 /// Element type name for TK_VaryingShape types (e.g. "bool" for
87 /// VaryingShape<BoolTy>). Empty for other type kinds.
88 StringRef ShapeElemType;
89
90 /// Explicit parameter name (e.g. "eta"). Empty to use the default "p0",
91 /// "p1", ... naming. Only meaningful for argument types.
92 StringRef Name;
93
94 /// Construct a TypeInfo from a TableGen record.
95 static TypeInfo resolve(const Record *Rec) {
96 TypeInfo TI;
97 if (Rec->getName() == "VoidTy") {
98 TI.Kind = TK_Void;
99 } else if (Rec->getName() == "Varying") {
100 TI.Kind = TK_Varying;
101 } else if (Rec->getName() == "VaryingElemType") {
102 TI.Kind = TK_ElemType;
103 } else if (Rec->isSubClassOf(Name: "VaryingShape")) {
104 TI.Kind = TK_VaryingShape;
105 TI.ShapeElemType =
106 Rec->getValueAsDef(FieldName: "ElementType")->getValueAsString(FieldName: "Name");
107 } else if (Rec->isSubClassOf(Name: "VectorType") ||
108 Rec->isSubClassOf(Name: "HLSLType")) {
109 TI.Kind = TK_FixedType;
110 TI.FixedType = getFixedTypeName(R: Rec);
111 } else {
112 llvm_unreachable("unhandled record for type resolution");
113 }
114 return TI;
115 }
116
117 /// Resolve this type to a concrete type name string.
118 /// \p ElemType is the scalar element type for the current overload.
119 /// \p FormatVarying formats a scalar element type into the shaped type name.
120 std::string
121 toTypeString(StringRef ElemType,
122 function_ref<std::string(StringRef)> FormatVarying) const {
123 switch (Kind) {
124 case TK_Void:
125 return "void";
126 case TK_Varying:
127 return FormatVarying(ElemType);
128 case TK_ElemType:
129 return ElemType.str();
130 case TK_VaryingShape:
131 return FormatVarying(ShapeElemType);
132 case TK_FixedType:
133 assert(!FixedType.empty() && "TK_FixedType requires non-empty FixedType");
134 return FixedType;
135 }
136 llvm_unreachable("unhandled TypeKindEnum");
137 }
138};
139
140} // anonymous namespace
141
142//===----------------------------------------------------------------------===//
143// Availability helpers
144//===----------------------------------------------------------------------===//
145
146static void emitAvailability(raw_ostream &OS, StringRef Version,
147 bool Use16Bit = false) {
148 if (Use16Bit) {
149 OS << "_HLSL_16BIT_AVAILABILITY(shadermodel, " << SM6_2;
150 if (!Version.empty())
151 OS << ", " << Version;
152 OS << ")\n";
153 } else
154 OS << "_HLSL_AVAILABILITY(shadermodel, " << Version << ")\n";
155}
156
157static std::string getVersionString(const Record *SM) {
158 unsigned Major = SM->getValueAsInt(FieldName: "Major");
159 unsigned Minor = SM->getValueAsInt(FieldName: "Minor");
160 if (Major == 0 && Minor == 0)
161 return "";
162 return (Twine(Major) + "." + Twine(Minor)).str();
163}
164
165//===----------------------------------------------------------------------===//
166// Type work item — describes one element type to emit overloads for
167//===----------------------------------------------------------------------===//
168
169namespace {
170
171/// A single entry in the worklist of types to process for an intrinsic.
172struct TypeWorkItem {
173 /// Element type name (e.g. "half", "float"). Empty for fixed-arg-only
174 /// intrinsics with no type expansion.
175 StringRef ElemType;
176
177 /// Version string for the availability attribute (e.g. "6.2"). Empty if
178 /// no availability annotation is needed.
179 std::string Availability;
180
181 /// If true, emit _HLSL_16BIT_AVAILABILITY instead of _HLSL_AVAILABILITY.
182 bool Use16BitAvail = false;
183
184 /// If true, wrap overloads in #ifdef __HLSL_ENABLE_16_BIT / #endif.
185 bool NeedsIfdefGuard = false;
186};
187
188} // anonymous namespace
189
190/// Fixed canonical ordering for overload types. Types are grouped as:
191/// 0: conditionally-16-bit (half)
192/// 1-2: 16-bit integers (int16_t, uint16_t) — ifdef-guarded
193/// 3+: regular types (bool, int, uint, int64_t, uint64_t, float, double)
194/// Within each group, signed precedes unsigned, smaller precedes larger,
195/// and integer types precede floating-point types.
196static int getTypeSortPriority(const Record *ET) {
197 return StringSwitch<int>(ET->getValueAsString(FieldName: "Name"))
198 .Case(S: "half", Value: 0)
199 .Case(S: "int16_t", Value: 1)
200 .Case(S: "uint16_t", Value: 2)
201 .Case(S: "bool", Value: 3)
202 .Case(S: "int", Value: 4)
203 .Case(S: "uint", Value: 5)
204 .Case(S: "int64_t", Value: 7)
205 .Case(S: "uint64_t", Value: 8)
206 .Case(S: "float", Value: 9)
207 .Case(S: "double", Value: 10)
208 .Default(Value: 11);
209}
210
211//===----------------------------------------------------------------------===//
212// Overload context — shared state across all overloads of one intrinsic
213//===----------------------------------------------------------------------===//
214
215namespace {
216
217/// Shared state for emitting all overloads of a single HLSL intrinsic.
218struct OverloadContext {
219 /// Output stream to write generated code to.
220 raw_ostream &OS;
221
222 /// Builtin name for _HLSL_BUILTIN_ALIAS (e.g. "__builtin_hlsl_dot").
223 /// Empty for inline/detail intrinsics.
224 StringRef Builtin;
225
226 /// __detail helper function to call (e.g. "refract_impl").
227 /// Empty for alias and inline-body intrinsics.
228 StringRef DetailFunc;
229
230 /// Literal inline function body (e.g. "return p0;").
231 /// Empty for alias and detail intrinsics.
232 StringRef Body;
233
234 /// The HLSL function name to emit (e.g. "dot", "refract").
235 StringRef FuncName;
236
237 /// Metadata describing the return type and its variation behavior.
238 TypeInfo RetType;
239
240 /// Per-argument metadata describing type and variation behavior.
241 SmallVector<TypeInfo, 4> Args;
242
243 /// Whether to emit the function as constexpr.
244 bool IsConstexpr = false;
245
246 /// Whether to emit the __attribute__((convergent)) annotation.
247 bool IsConvergent = false;
248
249 /// Whether any fixed arg has a 16-bit integer type (e.g. int16_t).
250 bool Uses16BitType = false;
251
252 /// Whether any fixed arg has a conditionally-16-bit type (half).
253 bool UsesConditionally16BitType = false;
254
255 explicit OverloadContext(raw_ostream &OS) : OS(OS) {}
256};
257
258} // anonymous namespace
259
260/// Emit a complete function declaration or definition with pre-resolved types.
261static void emitDeclaration(const OverloadContext &Ctx, StringRef RetType,
262 ArrayRef<std::string> ArgTypes) {
263 raw_ostream &OS = Ctx.OS;
264 bool IsDetail = !Ctx.DetailFunc.empty();
265 bool IsInline = !Ctx.Body.empty();
266 bool HasBody = IsDetail || IsInline;
267
268 bool EmitNames = HasBody || llvm::any_of(Range: Ctx.Args, P: [](const TypeInfo &A) {
269 return !A.Name.empty();
270 });
271
272 auto GetParamName = [&](unsigned I) -> std::string {
273 if (!Ctx.Args[I].Name.empty())
274 return Ctx.Args[I].Name.str();
275 return ("p" + Twine(I)).str();
276 };
277
278 if (!HasBody)
279 OS << "_HLSL_BUILTIN_ALIAS(" << Ctx.Builtin << ")\n";
280 if (Ctx.IsConvergent)
281 OS << "__attribute__((convergent)) ";
282 if (HasBody)
283 OS << (Ctx.IsConstexpr ? "constexpr " : "inline ");
284 OS << RetType << " " << Ctx.FuncName << "(";
285
286 {
287 ListSeparator LS;
288 for (unsigned I = 0, N = ArgTypes.size(); I < N; ++I) {
289 OS << LS << ArgTypes[I];
290 if (EmitNames)
291 OS << " " << GetParamName(I);
292 }
293 }
294
295 if (IsDetail) {
296 OS << ") {\n return __detail::" << Ctx.DetailFunc << "(";
297 ListSeparator LS;
298 for (unsigned I = 0, N = ArgTypes.size(); I < N; ++I)
299 OS << LS << GetParamName(I);
300 OS << ");\n}\n";
301 } else if (IsInline) {
302 OS << ") { " << Ctx.Body << " }\n";
303 } else {
304 OS << ");\n";
305 }
306}
307
308/// Emit a single overload declaration by resolving all types through
309/// \p FormatVarying, which maps element types to their shaped form.
310static void emitOverload(const OverloadContext &Ctx, StringRef ElemType,
311 function_ref<std::string(StringRef)> FormatVarying) {
312 std::string RetType = Ctx.RetType.toTypeString(ElemType, FormatVarying);
313 SmallVector<std::string> ArgTypes;
314 for (const TypeInfo &TI : Ctx.Args)
315 ArgTypes.push_back(Elt: TI.toTypeString(ElemType, FormatVarying));
316 emitDeclaration(Ctx, RetType, ArgTypes);
317}
318
319/// Emit a scalar overload for the given element type.
320static void emitScalarOverload(const OverloadContext &Ctx, StringRef ElemType) {
321 emitOverload(Ctx, ElemType, FormatVarying: [](StringRef ET) { return ET.str(); });
322}
323
324/// Emit a vector overload for the given element type and vector size.
325static void emitVectorOverload(const OverloadContext &Ctx, StringRef ElemType,
326 unsigned VecSize) {
327 emitOverload(Ctx, ElemType, FormatVarying: [VecSize](StringRef ET) {
328 return getVectorTypeName(ElemType: ET, N: VecSize);
329 });
330}
331
332/// Emit a matrix overload for the given element type and matrix dimensions.
333static void emitMatrixOverload(const OverloadContext &Ctx, StringRef ElemType,
334 unsigned Rows, unsigned Cols) {
335 emitOverload(Ctx, ElemType, FormatVarying: [Rows, Cols](StringRef ET) {
336 return getMatrixTypeName(ElemType: ET, Rows, Cols);
337 });
338}
339
340//===----------------------------------------------------------------------===//
341// Main emission logic
342//===----------------------------------------------------------------------===//
343
344/// Build an OverloadContext from an HLSLBuiltin record.
345static void buildOverloadContext(const Record *R, OverloadContext &Ctx) {
346 Ctx.Builtin = R->getValueAsString(FieldName: "Builtin");
347 Ctx.DetailFunc = R->getValueAsString(FieldName: "DetailFunc");
348 Ctx.Body = R->getValueAsString(FieldName: "Body");
349 Ctx.FuncName = R->getValueAsString(FieldName: "Name");
350 Ctx.IsConstexpr = R->getValueAsBit(FieldName: "IsConstexpr");
351 Ctx.IsConvergent = R->getValueAsBit(FieldName: "IsConvergent");
352
353 // Note use of 16-bit fixed types in the overload context.
354 auto Update16BitFlags = [&Ctx](const Record *Rec) {
355 const Record *ElemTy = getElementTypeRecord(R: Rec);
356 Ctx.Uses16BitType |= ElemTy->getValueAsBit(FieldName: "Is16Bit");
357 Ctx.UsesConditionally16BitType |=
358 ElemTy->getValueAsBit(FieldName: "IsConditionally16Bit");
359 };
360
361 // Resolve return and argument types.
362 const Record *RetRec = R->getValueAsDef(FieldName: "ReturnType");
363 Ctx.RetType = TypeInfo::resolve(Rec: RetRec);
364 if (Ctx.RetType.Kind == TK_FixedType)
365 Update16BitFlags(RetRec);
366
367 std::vector<const Record *> ArgRecords = R->getValueAsListOfDefs(FieldName: "Args");
368 std::vector<StringRef> ParamNames = R->getValueAsListOfStrings(FieldName: "ParamNames");
369
370 for (const auto &[I, Arg] : llvm::enumerate(First&: ArgRecords)) {
371 TypeInfo TI = TypeInfo::resolve(Rec: Arg);
372 if (I < ParamNames.size())
373 TI.Name = ParamNames[I];
374 if (TI.Kind == TK_FixedType)
375 Update16BitFlags(Arg);
376 Ctx.Args.push_back(Elt: TI);
377 }
378}
379
380/// Build the worklist of element types to emit overloads for, sorted in
381/// canonical order (see getTypeSortPriority).
382static void buildWorklist(const Record *R,
383 SmallVectorImpl<TypeWorkItem> &Worklist,
384 const OverloadContext &Ctx) {
385 const Record *AvailRec = R->getValueAsDef(FieldName: "Availability");
386 std::string Availability = getVersionString(SM: AvailRec);
387 bool AvailabilityIsAtLeastSM6_2 = AvailRec->getValueAsInt(FieldName: "Major") > 6 ||
388 (AvailRec->getValueAsInt(FieldName: "Major") == 6 &&
389 AvailRec->getValueAsInt(FieldName: "Minor") >= 2);
390
391 std::vector<const Record *> VaryingTypeRecords =
392 R->getValueAsListOfDefs(FieldName: "VaryingTypes");
393
394 // Populate the availability and guard fields of a TypeWorkItem based on
395 // whether the type is 16-bit, conditionally 16-bit, or a regular type.
396 auto SetAvailability = [&](TypeWorkItem &Item, bool Is16Bit,
397 bool IsCond16Bit) {
398 Item.NeedsIfdefGuard = Is16Bit;
399 if (Is16Bit || IsCond16Bit) {
400 if (AvailabilityIsAtLeastSM6_2) {
401 Item.Availability = Availability;
402 } else {
403 Item.Use16BitAvail = IsCond16Bit;
404 if (IsCond16Bit)
405 Item.Availability = Availability;
406 else
407 Item.Availability = SM6_2;
408 }
409 } else {
410 Item.Availability = Availability;
411 }
412 };
413
414 // If no Varying types are specified, just add a single work item.
415 // This is for HLSLBuiltin records that don't use Varying types.
416 if (VaryingTypeRecords.empty()) {
417 TypeWorkItem Item;
418 SetAvailability(Item, Ctx.Uses16BitType, Ctx.UsesConditionally16BitType);
419 Worklist.push_back(Elt: Item);
420 return;
421 }
422
423 // Sort Varying types so that overloads are always emitted in canonical order.
424 llvm::sort(C&: VaryingTypeRecords, Comp: [](const Record *A, const Record *B) {
425 return getTypeSortPriority(ET: A) < getTypeSortPriority(ET: B);
426 });
427
428 // Add a work item for each Varying element type.
429 for (const Record *ElemTy : VaryingTypeRecords) {
430 TypeWorkItem Item;
431 Item.ElemType = ElemTy->getValueAsString(FieldName: "Name");
432 bool Is16Bit = Ctx.Uses16BitType || ElemTy->getValueAsBit(FieldName: "Is16Bit");
433 bool IsCond16Bit = Ctx.UsesConditionally16BitType ||
434 ElemTy->getValueAsBit(FieldName: "IsConditionally16Bit");
435 SetAvailability(Item, Is16Bit, IsCond16Bit);
436 Worklist.push_back(Elt: Item);
437 }
438}
439
440/// Emit a Doxygen documentation comment from the Doc field.
441static void emitDocComment(raw_ostream &OS, const Record *R) {
442 StringRef Doc = R->getValueAsString(FieldName: "Doc");
443 if (Doc.empty())
444 return;
445 Doc = Doc.trim();
446 SmallVector<StringRef> DocLines;
447 Doc.split(A&: DocLines, Separator: '\n');
448 for (StringRef Line : DocLines) {
449 if (Line.empty())
450 OS << "///\n";
451 else
452 OS << "/// " << Line << "\n";
453 }
454}
455
456/// Process the worklist: emit all shape variants for each type with
457/// availability annotations and #ifdef guards.
458static void emitWorklistOverloads(raw_ostream &OS, const OverloadContext &Ctx,
459 ArrayRef<TypeWorkItem> Worklist,
460 bool EmitScalarOverload,
461 ArrayRef<int64_t> VectorSizes,
462 ArrayRef<const Record *> MatrixDimensions) {
463 bool InIfdef = false;
464 for (size_t I = 0, E = Worklist.size(); I != E; ++I) {
465 const TypeWorkItem &Item = Worklist[I];
466 if (Item.NeedsIfdefGuard && !InIfdef) {
467 OS << "#ifdef __HLSL_ENABLE_16_BIT\n";
468 InIfdef = true;
469 }
470
471 auto EmitAvail = [&]() {
472 if (!Item.Availability.empty() || Item.Use16BitAvail)
473 emitAvailability(OS, Version: Item.Availability, Use16Bit: Item.Use16BitAvail);
474 };
475
476 if (EmitScalarOverload) {
477 EmitAvail();
478 emitScalarOverload(Ctx, ElemType: Item.ElemType);
479 }
480 for (int64_t N : VectorSizes) {
481 EmitAvail();
482 emitVectorOverload(Ctx, ElemType: Item.ElemType, VecSize: N);
483 }
484 for (const Record *MD : MatrixDimensions) {
485 EmitAvail();
486 emitMatrixOverload(Ctx, ElemType: Item.ElemType, Rows: MD->getValueAsInt(FieldName: "Rows"),
487 Cols: MD->getValueAsInt(FieldName: "Cols"));
488 }
489
490 if (InIfdef) {
491 bool NextIsUnguarded = (I + 1 == E) || !Worklist[I + 1].NeedsIfdefGuard;
492 if (NextIsUnguarded) {
493 OS << "#endif\n";
494 InIfdef = false;
495 }
496 }
497
498 OS << "\n";
499 }
500}
501
502/// Emit all overloads for a single HLSLBuiltin record.
503static void emitBuiltinOverloads(raw_ostream &OS, const Record *R) {
504 OverloadContext Ctx(OS);
505 buildOverloadContext(R, Ctx);
506
507 SmallVector<TypeWorkItem> Worklist;
508 buildWorklist(R, Worklist, Ctx);
509
510 emitDocComment(OS, R);
511 OS << "// " << Ctx.FuncName << " overloads\n";
512
513 // Emit a scalar overload if a scalar Varying overload was requested.
514 // If no Varying types are used at all, emit a scalar overload to handle
515 // emitting a single overload for fixed-typed args or arg-less functions.
516 bool EmitScalarOverload = R->getValueAsBit(FieldName: "VaryingScalar") ||
517 R->getValueAsListOfDefs(FieldName: "VaryingTypes").empty();
518
519 std::vector<int64_t> VectorSizes = R->getValueAsListOfInts(FieldName: "VaryingVecSizes");
520 std::vector<const Record *> MatrixDimensions =
521 R->getValueAsListOfDefs(FieldName: "VaryingMatDims");
522
523 // Sort vector sizes and matrix dimensions for consistent output order.
524 llvm::sort(C&: VectorSizes);
525 llvm::sort(C&: MatrixDimensions, Comp: [](const Record *A, const Record *B) {
526 int RowA = A->getValueAsInt(FieldName: "Rows"), RowB = B->getValueAsInt(FieldName: "Rows");
527 if (RowA != RowB)
528 return RowA < RowB;
529 return A->getValueAsInt(FieldName: "Cols") < B->getValueAsInt(FieldName: "Cols");
530 });
531
532 emitWorklistOverloads(OS, Ctx, Worklist, EmitScalarOverload, VectorSizes,
533 MatrixDimensions);
534}
535
536/// Emit alias overloads for a single HLSLBuiltin record.
537/// Skips records that have inline bodies (DetailFunc or Body).
538static void emitAliasBuiltin(raw_ostream &OS, const Record *R) {
539 if (!R->getValueAsString(FieldName: "DetailFunc").empty() ||
540 !R->getValueAsString(FieldName: "Body").empty())
541 return;
542 emitBuiltinOverloads(OS, R);
543}
544
545/// Emit inline overloads for a single HLSLBuiltin record.
546/// Skips records that are pure alias declarations.
547static void emitInlineBuiltin(raw_ostream &OS, const Record *R) {
548 if (R->getValueAsString(FieldName: "DetailFunc").empty() &&
549 R->getValueAsString(FieldName: "Body").empty())
550 return;
551 emitBuiltinOverloads(OS, R);
552}
553
554void clang::EmitHLSLAliasIntrinsics(const RecordKeeper &Records,
555 raw_ostream &OS) {
556 OS << "// This file is auto-generated by clang-tblgen from "
557 "HLSLIntrinsics.td.\n";
558 OS << "// Do not edit this file directly.\n\n";
559
560 for (const Record *R : Records.getAllDerivedDefinitions(ClassName: "HLSLBuiltin"))
561 emitAliasBuiltin(OS, R);
562}
563
564void clang::EmitHLSLInlineIntrinsics(const RecordKeeper &Records,
565 raw_ostream &OS) {
566 OS << "// This file is auto-generated by clang-tblgen from "
567 "HLSLIntrinsics.td.\n";
568 OS << "// Do not edit this file directly.\n\n";
569
570 for (const Record *R : Records.getAllDerivedDefinitions(ClassName: "HLSLBuiltin"))
571 emitInlineBuiltin(OS, R);
572}
573