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