1 //===- DXILEmitter.cpp - DXIL operation Emitter ---------------------------===//
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 // DXILEmitter uses the descriptions of DXIL operation to construct enum and
10 // helper functions for DXIL operation.
11 //
12 //===----------------------------------------------------------------------===//
13 
14 #include "SequenceToOffsetTable.h"
15 #include "llvm/ADT/STLExtras.h"
16 #include "llvm/ADT/SmallVector.h"
17 #include "llvm/ADT/StringSet.h"
18 #include "llvm/ADT/StringSwitch.h"
19 #include "llvm/Support/DXILOperationCommon.h"
20 #include "llvm/TableGen/Record.h"
21 #include "llvm/TableGen/TableGenBackend.h"
22 
23 using namespace llvm;
24 using namespace llvm::dxil;
25 
26 namespace {
27 
28 struct DXILShaderModel {
29   int Major = 0;
30   int Minor = 0;
31 };
32 
33 struct DXILParam {
34   int Pos; // position in parameter list
35   ParameterKind Kind;
36   StringRef Name; // short, unique name
37   StringRef Doc;  // the documentation description of this parameter
38   bool IsConst;   // whether this argument requires a constant value in the IR
39   StringRef EnumName; // the name of the enum type if applicable
40   int MaxValue;       // the maximum value for this parameter if applicable
41   DXILParam(const Record *R);
42 };
43 
44 struct DXILOperationData {
45   StringRef Name; // short, unique name
46 
47   StringRef DXILOp;    // name of DXIL operation
48   int DXILOpID;        // ID of DXIL operation
49   StringRef DXILClass; // name of the opcode class
50   StringRef Category;  // classification for this instruction
51   StringRef Doc;       // the documentation description of this instruction
52 
53   SmallVector<DXILParam> Params; // the operands that this instruction takes
54   StringRef OverloadTypes;       // overload types if applicable
55   StringRef FnAttr;              // attribute shorthands: rn=does not access
56                                  // memory,ro=only reads from memory
57   StringRef Intrinsic; // The llvm intrinsic map to DXILOp. Default is "" which
58                        // means no map exist
59   bool IsDeriv = false;    // whether this is some kind of derivative
60   bool IsGradient = false; // whether this requires a gradient calculation
61   bool IsFeedback = false; // whether this is a sampler feedback op
62   bool IsWave = false;     // whether this requires in-wave, cross-lane functionality
63   bool RequiresUniformInputs = false; // whether this operation requires that
64                                       // all of its inputs are uniform across
65                                       // the wave
66   SmallVector<StringRef, 4>
67       ShaderStages; // shader stages to which this applies, empty for all.
68   DXILShaderModel ShaderModel;           // minimum shader model required
69   DXILShaderModel ShaderModelTranslated; // minimum shader model required with
70                                          // translation by linker
71   int OverloadParamIndex; // parameter index which control the overload.
72                           // When < 0, should be only 1 overload type.
73   SmallVector<StringRef, 4> counters; // counters for this inst.
DXILOperationData__anon32d4f5ee0111::DXILOperationData74   DXILOperationData(const Record *R) {
75     Name = R->getValueAsString("name");
76     DXILOp = R->getValueAsString("dxil_op");
77     DXILOpID = R->getValueAsInt("dxil_opid");
78     DXILClass = R->getValueAsDef("op_class")->getValueAsString("name");
79     Category = R->getValueAsDef("category")->getValueAsString("name");
80 
81     if (R->getValue("llvm_intrinsic")) {
82       auto *IntrinsicDef = R->getValueAsDef("llvm_intrinsic");
83       auto DefName = IntrinsicDef->getName();
84       assert(DefName.starts_with("int_") && "invalid intrinsic name");
85       // Remove the int_ from intrinsic name.
86       Intrinsic = DefName.substr(4);
87     }
88 
89     Doc = R->getValueAsString("doc");
90 
91     ListInit *ParamList = R->getValueAsListInit("ops");
92     OverloadParamIndex = -1;
93     for (unsigned I = 0; I < ParamList->size(); ++I) {
94       Record *Param = ParamList->getElementAsRecord(I);
95       Params.emplace_back(DXILParam(Param));
96       auto &CurParam = Params.back();
97       if (CurParam.Kind >= ParameterKind::OVERLOAD)
98         OverloadParamIndex = I;
99     }
100     OverloadTypes = R->getValueAsString("oload_types");
101     FnAttr = R->getValueAsString("fn_attr");
102   }
103 };
104 } // end anonymous namespace
105 
DXILParam(const Record * R)106 DXILParam::DXILParam(const Record *R) {
107   Name = R->getValueAsString("name");
108   Pos = R->getValueAsInt("pos");
109   Kind = parameterTypeNameToKind(R->getValueAsString("llvm_type"));
110   if (R->getValue("doc"))
111     Doc = R->getValueAsString("doc");
112   IsConst = R->getValueAsBit("is_const");
113   EnumName = R->getValueAsString("enum_name");
114   MaxValue = R->getValueAsInt("max_value");
115 }
116 
parameterKindToString(ParameterKind Kind)117 static std::string parameterKindToString(ParameterKind Kind) {
118   switch (Kind) {
119   case ParameterKind::INVALID:
120     return "INVALID";
121   case ParameterKind::VOID:
122     return "VOID";
123   case ParameterKind::HALF:
124     return "HALF";
125   case ParameterKind::FLOAT:
126     return "FLOAT";
127   case ParameterKind::DOUBLE:
128     return "DOUBLE";
129   case ParameterKind::I1:
130     return "I1";
131   case ParameterKind::I8:
132     return "I8";
133   case ParameterKind::I16:
134     return "I16";
135   case ParameterKind::I32:
136     return "I32";
137   case ParameterKind::I64:
138     return "I64";
139   case ParameterKind::OVERLOAD:
140     return "OVERLOAD";
141   case ParameterKind::CBUFFER_RET:
142     return "CBUFFER_RET";
143   case ParameterKind::RESOURCE_RET:
144     return "RESOURCE_RET";
145   case ParameterKind::DXIL_HANDLE:
146     return "DXIL_HANDLE";
147   }
148   llvm_unreachable("Unknown llvm::dxil::ParameterKind enum");
149 }
150 
emitDXILOpEnum(DXILOperationData & DXILOp,raw_ostream & OS)151 static void emitDXILOpEnum(DXILOperationData &DXILOp, raw_ostream &OS) {
152   // Name = ID, // Doc
153   OS << DXILOp.Name << " = " << DXILOp.DXILOpID << ", // " << DXILOp.Doc
154      << "\n";
155 }
156 
buildCategoryStr(StringSet<> & Cetegorys)157 static std::string buildCategoryStr(StringSet<> &Cetegorys) {
158   std::string Str;
159   raw_string_ostream OS(Str);
160   for (auto &It : Cetegorys) {
161     OS << " " << It.getKey();
162   }
163   return OS.str();
164 }
165 
166 // Emit enum declaration for DXIL.
emitDXILEnums(std::vector<DXILOperationData> & DXILOps,raw_ostream & OS)167 static void emitDXILEnums(std::vector<DXILOperationData> &DXILOps,
168                           raw_ostream &OS) {
169   // Sort by Category + OpName.
170   llvm::sort(DXILOps, [](DXILOperationData &A, DXILOperationData &B) {
171     // Group by Category first.
172     if (A.Category == B.Category)
173       // Inside same Category, order by OpName.
174       return A.DXILOp < B.DXILOp;
175     else
176       return A.Category < B.Category;
177   });
178 
179   OS << "// Enumeration for operations specified by DXIL\n";
180   OS << "enum class OpCode : unsigned {\n";
181 
182   StringMap<StringSet<>> ClassMap;
183   StringRef PrevCategory = "";
184   for (auto &DXILOp : DXILOps) {
185     StringRef Category = DXILOp.Category;
186     if (Category != PrevCategory) {
187       OS << "\n// " << Category << "\n";
188       PrevCategory = Category;
189     }
190     emitDXILOpEnum(DXILOp, OS);
191     auto It = ClassMap.find(DXILOp.DXILClass);
192     if (It != ClassMap.end()) {
193       It->second.insert(DXILOp.Category);
194     } else {
195       ClassMap[DXILOp.DXILClass].insert(DXILOp.Category);
196     }
197   }
198 
199   OS << "\n};\n\n";
200 
201   std::vector<std::pair<std::string, std::string>> ClassVec;
202   for (auto &It : ClassMap) {
203     ClassVec.emplace_back(
204         std::make_pair(It.getKey().str(), buildCategoryStr(It.second)));
205   }
206   // Sort by Category + ClassName.
207   llvm::sort(ClassVec, [](std::pair<std::string, std::string> &A,
208                           std::pair<std::string, std::string> &B) {
209     StringRef ClassA = A.first;
210     StringRef CategoryA = A.second;
211     StringRef ClassB = B.first;
212     StringRef CategoryB = B.second;
213     // Group by Category first.
214     if (CategoryA == CategoryB)
215       // Inside same Category, order by ClassName.
216       return ClassA < ClassB;
217     else
218       return CategoryA < CategoryB;
219   });
220 
221   OS << "// Groups for DXIL operations with equivalent function templates\n";
222   OS << "enum class OpCodeClass : unsigned {\n";
223   PrevCategory = "";
224   for (auto &It : ClassVec) {
225 
226     StringRef Category = It.second;
227     if (Category != PrevCategory) {
228       OS << "\n// " << Category << "\n";
229       PrevCategory = Category;
230     }
231     StringRef Name = It.first;
232     OS << Name << ",\n";
233   }
234   OS << "\n};\n\n";
235 }
236 
237 // Emit map from llvm intrinsic to DXIL operation.
emitDXILIntrinsicMap(std::vector<DXILOperationData> & DXILOps,raw_ostream & OS)238 static void emitDXILIntrinsicMap(std::vector<DXILOperationData> &DXILOps,
239                                  raw_ostream &OS) {
240   OS << "\n";
241   // FIXME: use array instead of SmallDenseMap.
242   OS << "static const SmallDenseMap<Intrinsic::ID, dxil::OpCode> LowerMap = "
243         "{\n";
244   for (auto &DXILOp : DXILOps) {
245     if (DXILOp.Intrinsic.empty())
246       continue;
247     // {Intrinsic::sin, dxil::OpCode::Sin},
248     OS << "  { Intrinsic::" << DXILOp.Intrinsic
249        << ", dxil::OpCode::" << DXILOp.DXILOp << "},\n";
250   }
251   OS << "};\n";
252   OS << "\n";
253 }
254 
emitDXILOperationFnAttr(StringRef FnAttr)255 static std::string emitDXILOperationFnAttr(StringRef FnAttr) {
256   return StringSwitch<std::string>(FnAttr)
257       .Case("rn", "Attribute::ReadNone")
258       .Case("ro", "Attribute::ReadOnly")
259       .Default("Attribute::None");
260 }
261 
getOverloadKind(StringRef Overload)262 static std::string getOverloadKind(StringRef Overload) {
263   return StringSwitch<std::string>(Overload)
264       .Case("half", "OverloadKind::HALF")
265       .Case("float", "OverloadKind::FLOAT")
266       .Case("double", "OverloadKind::DOUBLE")
267       .Case("i1", "OverloadKind::I1")
268       .Case("i16", "OverloadKind::I16")
269       .Case("i32", "OverloadKind::I32")
270       .Case("i64", "OverloadKind::I64")
271       .Case("udt", "OverloadKind::UserDefineType")
272       .Case("obj", "OverloadKind::ObjectType")
273       .Default("OverloadKind::VOID");
274 }
275 
getDXILOperationOverload(StringRef Overloads)276 static std::string getDXILOperationOverload(StringRef Overloads) {
277   SmallVector<StringRef> OverloadStrs;
278   Overloads.split(OverloadStrs, ';', /*MaxSplit*/ -1, /*KeepEmpty*/ false);
279   // Format is: OverloadKind::FLOAT | OverloadKind::HALF
280   assert(!OverloadStrs.empty() && "Invalid overloads");
281   auto It = OverloadStrs.begin();
282   std::string Result;
283   raw_string_ostream OS(Result);
284   OS << getOverloadKind(*It);
285   for (++It; It != OverloadStrs.end(); ++It) {
286     OS << " | " << getOverloadKind(*It);
287   }
288   return OS.str();
289 }
290 
lowerFirstLetter(StringRef Name)291 static std::string lowerFirstLetter(StringRef Name) {
292   if (Name.empty())
293     return "";
294 
295   std::string LowerName = Name.str();
296   LowerName[0] = llvm::toLower(Name[0]);
297   return LowerName;
298 }
299 
getDXILOpClassName(StringRef DXILOpClass)300 static std::string getDXILOpClassName(StringRef DXILOpClass) {
301   // Lower first letter expect for special case.
302   return StringSwitch<std::string>(DXILOpClass)
303       .Case("CBufferLoad", "cbufferLoad")
304       .Case("CBufferLoadLegacy", "cbufferLoadLegacy")
305       .Case("GSInstanceID", "gsInstanceID")
306       .Default(lowerFirstLetter(DXILOpClass));
307 }
308 
emitDXILOperationTable(std::vector<DXILOperationData> & DXILOps,raw_ostream & OS)309 static void emitDXILOperationTable(std::vector<DXILOperationData> &DXILOps,
310                                    raw_ostream &OS) {
311   // Sort by DXILOpID.
312   llvm::sort(DXILOps, [](DXILOperationData &A, DXILOperationData &B) {
313     return A.DXILOpID < B.DXILOpID;
314   });
315 
316   // Collect Names.
317   SequenceToOffsetTable<std::string> OpClassStrings;
318   SequenceToOffsetTable<std::string> OpStrings;
319   SequenceToOffsetTable<SmallVector<ParameterKind>> Parameters;
320 
321   StringMap<SmallVector<ParameterKind>> ParameterMap;
322   StringSet<> ClassSet;
323   for (auto &DXILOp : DXILOps) {
324     OpStrings.add(DXILOp.DXILOp.str());
325 
326     if (ClassSet.contains(DXILOp.DXILClass))
327       continue;
328     ClassSet.insert(DXILOp.DXILClass);
329     OpClassStrings.add(getDXILOpClassName(DXILOp.DXILClass));
330     SmallVector<ParameterKind> ParamKindVec;
331     for (auto &Param : DXILOp.Params) {
332       ParamKindVec.emplace_back(Param.Kind);
333     }
334     ParameterMap[DXILOp.DXILClass] = ParamKindVec;
335     Parameters.add(ParamKindVec);
336   }
337 
338   // Layout names.
339   OpStrings.layout();
340   OpClassStrings.layout();
341   Parameters.layout();
342 
343   // Emit the DXIL operation table.
344   //{dxil::OpCode::Sin, OpCodeNameIndex, OpCodeClass::Unary,
345   // OpCodeClassNameIndex,
346   // OverloadKind::FLOAT | OverloadKind::HALF, Attribute::AttrKind::ReadNone, 0,
347   // 3, ParameterTableOffset},
348   OS << "static const OpCodeProperty *getOpCodeProperty(dxil::OpCode DXILOp) "
349         "{\n";
350 
351   OS << "  static const OpCodeProperty OpCodeProps[] = {\n";
352   for (auto &DXILOp : DXILOps) {
353     OS << "  { dxil::OpCode::" << DXILOp.DXILOp << ", "
354        << OpStrings.get(DXILOp.DXILOp.str())
355        << ", OpCodeClass::" << DXILOp.DXILClass << ", "
356        << OpClassStrings.get(getDXILOpClassName(DXILOp.DXILClass)) << ", "
357        << getDXILOperationOverload(DXILOp.OverloadTypes) << ", "
358        << emitDXILOperationFnAttr(DXILOp.FnAttr) << ", "
359        << DXILOp.OverloadParamIndex << ", " << DXILOp.Params.size() << ", "
360        << Parameters.get(ParameterMap[DXILOp.DXILClass]) << " },\n";
361   }
362   OS << "  };\n";
363 
364   OS << "  // FIXME: change search to indexing with\n";
365   OS << "  // DXILOp once all DXIL op is added.\n";
366   OS << "  OpCodeProperty TmpProp;\n";
367   OS << "  TmpProp.OpCode = DXILOp;\n";
368   OS << "  const OpCodeProperty *Prop =\n";
369   OS << "      llvm::lower_bound(OpCodeProps, TmpProp,\n";
370   OS << "                        [](const OpCodeProperty &A, const "
371         "OpCodeProperty &B) {\n";
372   OS << "                          return A.OpCode < B.OpCode;\n";
373   OS << "                        });\n";
374   OS << "  assert(Prop && \"fail to find OpCodeProperty\");\n";
375   OS << "  return Prop;\n";
376   OS << "}\n\n";
377 
378   // Emit the string tables.
379   OS << "static const char *getOpCodeName(dxil::OpCode DXILOp) {\n\n";
380 
381   OpStrings.emitStringLiteralDef(OS,
382                                  "  static const char DXILOpCodeNameTable[]");
383 
384   OS << "  auto *Prop = getOpCodeProperty(DXILOp);\n";
385   OS << "  unsigned Index = Prop->OpCodeNameOffset;\n";
386   OS << "  return DXILOpCodeNameTable + Index;\n";
387   OS << "}\n\n";
388 
389   OS << "static const char *getOpCodeClassName(const OpCodeProperty &Prop) "
390         "{\n\n";
391 
392   OpClassStrings.emitStringLiteralDef(
393       OS, "  static const char DXILOpCodeClassNameTable[]");
394 
395   OS << "  unsigned Index = Prop.OpCodeClassNameOffset;\n";
396   OS << "  return DXILOpCodeClassNameTable + Index;\n";
397   OS << "}\n ";
398 
399   OS << "static const ParameterKind *getOpCodeParameterKind(const "
400         "OpCodeProperty &Prop) "
401         "{\n\n";
402   OS << "  static const ParameterKind DXILOpParameterKindTable[] = {\n";
403   Parameters.emit(
404       OS,
405       [](raw_ostream &ParamOS, ParameterKind Kind) {
406         ParamOS << "ParameterKind::" << parameterKindToString(Kind);
407       },
408       "ParameterKind::INVALID");
409   OS << "  };\n\n";
410   OS << "  unsigned Index = Prop.ParameterTableOffset;\n";
411   OS << "  return DXILOpParameterKindTable + Index;\n";
412   OS << "}\n ";
413 }
414 
EmitDXILOperation(RecordKeeper & Records,raw_ostream & OS)415 static void EmitDXILOperation(RecordKeeper &Records, raw_ostream &OS) {
416   std::vector<Record *> Ops = Records.getAllDerivedDefinitions("dxil_op");
417   OS << "// Generated code, do not edit.\n";
418   OS << "\n";
419 
420   std::vector<DXILOperationData> DXILOps;
421   DXILOps.reserve(Ops.size());
422   for (auto *Record : Ops) {
423     DXILOps.emplace_back(DXILOperationData(Record));
424   }
425 
426   OS << "#ifdef DXIL_OP_ENUM\n";
427   emitDXILEnums(DXILOps, OS);
428   OS << "#endif\n\n";
429 
430   OS << "#ifdef DXIL_OP_INTRINSIC_MAP\n";
431   emitDXILIntrinsicMap(DXILOps, OS);
432   OS << "#endif\n\n";
433 
434   OS << "#ifdef DXIL_OP_OPERATION_TABLE\n";
435   emitDXILOperationTable(DXILOps, OS);
436   OS << "#endif\n\n";
437 
438   OS << "\n";
439 }
440 
441 static TableGen::Emitter::Opt X("gen-dxil-operation", EmitDXILOperation,
442                                 "Generate DXIL operation information");
443