xref: /freebsd/contrib/llvm-project/llvm/utils/TableGen/DXILEmitter.cpp (revision 56b17de1e8360fe131d425de20b5e75ff3ea897c)
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 "Basic/SequenceToOffsetTable.h"
15 #include "Common/CodeGenTarget.h"
16 #include "llvm/ADT/STLExtras.h"
17 #include "llvm/ADT/SmallSet.h"
18 #include "llvm/ADT/SmallVector.h"
19 #include "llvm/ADT/StringSet.h"
20 #include "llvm/ADT/StringSwitch.h"
21 #include "llvm/CodeGenTypes/MachineValueType.h"
22 #include "llvm/Support/DXILABI.h"
23 #include "llvm/TableGen/Record.h"
24 #include "llvm/TableGen/TableGenBackend.h"
25 #include <string>
26 
27 using namespace llvm;
28 using namespace llvm::dxil;
29 
30 namespace {
31 
32 struct DXILShaderModel {
33   int Major = 0;
34   int Minor = 0;
35 };
36 
37 struct DXILOperationDesc {
38   std::string OpName; // name of DXIL operation
39   int OpCode;         // ID of DXIL operation
40   StringRef OpClass;  // name of the opcode class
41   StringRef Doc;      // the documentation description of this instruction
42   SmallVector<Record *> OpTypes; // Vector of operand type records -
43                                  // return type is at index 0
44   SmallVector<std::string>
45       OpAttributes;     // operation attribute represented as strings
46   StringRef Intrinsic;  // The llvm intrinsic map to OpName. Default is "" which
47                         // means no map exists
48   bool IsDeriv = false; // whether this is some kind of derivative
49   bool IsGradient = false; // whether this requires a gradient calculation
50   bool IsFeedback = false; // whether this is a sampler feedback op
51   bool IsWave =
52       false; // whether this requires in-wave, cross-lane functionality
53   bool RequiresUniformInputs = false; // whether this operation requires that
54                                       // all of its inputs are uniform across
55                                       // the wave
56   SmallVector<StringRef, 4>
57       ShaderStages; // shader stages to which this applies, empty for all.
58   DXILShaderModel ShaderModel;           // minimum shader model required
59   DXILShaderModel ShaderModelTranslated; // minimum shader model required with
60                                          // translation by linker
61   int OverloadParamIndex;             // Index of parameter with overload type.
62                                       //   -1 : no overload types
63   SmallVector<StringRef, 4> counters; // counters for this inst.
64   DXILOperationDesc(const Record *);
65 };
66 } // end anonymous namespace
67 
68 /// Return dxil::ParameterKind corresponding to input LLVMType record
69 ///
70 /// \param R TableGen def record of class LLVMType
71 /// \return ParameterKind As defined in llvm/Support/DXILABI.h
72 
73 static ParameterKind getParameterKind(const Record *R) {
74   auto VTRec = R->getValueAsDef("VT");
75   switch (getValueType(VTRec)) {
76   case MVT::isVoid:
77     return ParameterKind::Void;
78   case MVT::f16:
79     return ParameterKind::Half;
80   case MVT::f32:
81     return ParameterKind::Float;
82   case MVT::f64:
83     return ParameterKind::Double;
84   case MVT::i1:
85     return ParameterKind::I1;
86   case MVT::i8:
87     return ParameterKind::I8;
88   case MVT::i16:
89     return ParameterKind::I16;
90   case MVT::i32:
91     return ParameterKind::I32;
92   case MVT::fAny:
93   case MVT::iAny:
94     return ParameterKind::Overload;
95   case MVT::Other:
96     // Handle DXIL-specific overload types
97     if (R->getValueAsInt("isHalfOrFloat") || R->getValueAsInt("isI16OrI32")) {
98       return ParameterKind::Overload;
99     }
100     [[fallthrough]];
101   default:
102     llvm_unreachable("Support for specified DXIL Type not yet implemented");
103   }
104 }
105 
106 /// Construct an object using the DXIL Operation records specified
107 /// in DXIL.td. This serves as the single source of reference of
108 /// the information extracted from the specified Record R, for
109 /// C++ code generated by this TableGen backend.
110 //  \param R Object representing TableGen record of a DXIL Operation
111 DXILOperationDesc::DXILOperationDesc(const Record *R) {
112   OpName = R->getNameInitAsString();
113   OpCode = R->getValueAsInt("OpCode");
114 
115   Doc = R->getValueAsString("Doc");
116 
117   auto TypeRecs = R->getValueAsListOfDefs("OpTypes");
118   unsigned TypeRecsSize = TypeRecs.size();
119   // Populate OpTypes with return type and parameter types
120 
121   // Parameter indices of overloaded parameters.
122   // This vector contains overload parameters in the order used to
123   // resolve an LLVMMatchType in accordance with  convention outlined in
124   // the comment before the definition of class LLVMMatchType in
125   // llvm/IR/Intrinsics.td
126   SmallVector<int> OverloadParamIndices;
127   for (unsigned i = 0; i < TypeRecsSize; i++) {
128     auto TR = TypeRecs[i];
129     // Track operation parameter indices of any overload types
130     auto isAny = TR->getValueAsInt("isAny");
131     if (isAny == 1) {
132       // TODO: At present it is expected that all overload types in a DXIL Op
133       // are of the same type. Hence, OverloadParamIndices will have only one
134       // element. This implies we do not need a vector. However, until more
135       // (all?) DXIL Ops are added in DXIL.td, a vector is being used to flag
136       // cases this assumption would not hold.
137       if (!OverloadParamIndices.empty()) {
138         bool knownType = true;
139         // Ensure that the same overload type registered earlier is being used
140         for (auto Idx : OverloadParamIndices) {
141           if (TR != TypeRecs[Idx]) {
142             knownType = false;
143             break;
144           }
145         }
146         if (!knownType) {
147           report_fatal_error("Specification of multiple differing overload "
148                              "parameter types not yet supported",
149                              false);
150         }
151       } else {
152         OverloadParamIndices.push_back(i);
153       }
154     }
155     // Populate OpTypes array according to the type specification
156     if (TR->isAnonymous()) {
157       // Check prior overload types exist
158       assert(!OverloadParamIndices.empty() &&
159              "No prior overloaded parameter found to match.");
160       // Get the parameter index of anonymous type, TR, references
161       auto OLParamIndex = TR->getValueAsInt("Number");
162       // Resolve and insert the type to that at OLParamIndex
163       OpTypes.emplace_back(TypeRecs[OLParamIndex]);
164     } else {
165       // A non-anonymous type. Just record it in OpTypes
166       OpTypes.emplace_back(TR);
167     }
168   }
169 
170   // Set the index of the overload parameter, if any.
171   OverloadParamIndex = -1; // default; indicating none
172   if (!OverloadParamIndices.empty()) {
173     if (OverloadParamIndices.size() > 1)
174       report_fatal_error("Multiple overload type specification not supported",
175                          false);
176     OverloadParamIndex = OverloadParamIndices[0];
177   }
178   // Get the operation class
179   OpClass = R->getValueAsDef("OpClass")->getName();
180 
181   if (R->getValue("LLVMIntrinsic")) {
182     auto *IntrinsicDef = R->getValueAsDef("LLVMIntrinsic");
183     auto DefName = IntrinsicDef->getName();
184     assert(DefName.starts_with("int_") && "invalid intrinsic name");
185     // Remove the int_ from intrinsic name.
186     Intrinsic = DefName.substr(4);
187     // TODO: For now, assume that attributes of DXIL Operation are the same as
188     // that of the intrinsic. Deviations are expected to be encoded in TableGen
189     // record specification and handled accordingly here. Support to be added
190     // as needed.
191     auto IntrPropList = IntrinsicDef->getValueAsListInit("IntrProperties");
192     auto IntrPropListSize = IntrPropList->size();
193     for (unsigned i = 0; i < IntrPropListSize; i++) {
194       OpAttributes.emplace_back(IntrPropList->getElement(i)->getAsString());
195     }
196   }
197 }
198 
199 /// Return a string representation of ParameterKind enum
200 /// \param Kind Parameter Kind enum value
201 /// \return std::string string representation of input Kind
202 static std::string getParameterKindStr(ParameterKind Kind) {
203   switch (Kind) {
204   case ParameterKind::Invalid:
205     return "Invalid";
206   case ParameterKind::Void:
207     return "Void";
208   case ParameterKind::Half:
209     return "Half";
210   case ParameterKind::Float:
211     return "Float";
212   case ParameterKind::Double:
213     return "Double";
214   case ParameterKind::I1:
215     return "I1";
216   case ParameterKind::I8:
217     return "I8";
218   case ParameterKind::I16:
219     return "I16";
220   case ParameterKind::I32:
221     return "I32";
222   case ParameterKind::I64:
223     return "I64";
224   case ParameterKind::Overload:
225     return "Overload";
226   case ParameterKind::CBufferRet:
227     return "CBufferRet";
228   case ParameterKind::ResourceRet:
229     return "ResourceRet";
230   case ParameterKind::DXILHandle:
231     return "DXILHandle";
232   }
233   llvm_unreachable("Unknown llvm::dxil::ParameterKind enum");
234 }
235 
236 /// Return a string representation of OverloadKind enum that maps to
237 /// input LLVMType record
238 /// \param R TableGen def record of class LLVMType
239 /// \return std::string string representation of OverloadKind
240 
241 static std::string getOverloadKindStr(const Record *R) {
242   auto VTRec = R->getValueAsDef("VT");
243   switch (getValueType(VTRec)) {
244   case MVT::isVoid:
245     return "OverloadKind::VOID";
246   case MVT::f16:
247     return "OverloadKind::HALF";
248   case MVT::f32:
249     return "OverloadKind::FLOAT";
250   case MVT::f64:
251     return "OverloadKind::DOUBLE";
252   case MVT::i1:
253     return "OverloadKind::I1";
254   case MVT::i8:
255     return "OverloadKind::I8";
256   case MVT::i16:
257     return "OverloadKind::I16";
258   case MVT::i32:
259     return "OverloadKind::I32";
260   case MVT::i64:
261     return "OverloadKind::I64";
262   case MVT::iAny:
263     return "OverloadKind::I16 | OverloadKind::I32 | OverloadKind::I64";
264   case MVT::fAny:
265     return "OverloadKind::HALF | OverloadKind::FLOAT | OverloadKind::DOUBLE";
266   case MVT::Other:
267     // Handle DXIL-specific overload types
268     {
269       if (R->getValueAsInt("isHalfOrFloat")) {
270         return "OverloadKind::HALF | OverloadKind::FLOAT";
271       } else if (R->getValueAsInt("isI16OrI32")) {
272         return "OverloadKind::I16 | OverloadKind::I32";
273       }
274     }
275     [[fallthrough]];
276   default:
277     llvm_unreachable(
278         "Support for specified parameter OverloadKind not yet implemented");
279   }
280 }
281 
282 /// Emit Enums of DXIL Ops
283 /// \param A vector of DXIL Ops
284 /// \param Output stream
285 static void emitDXILEnums(std::vector<DXILOperationDesc> &Ops,
286                           raw_ostream &OS) {
287   // Sort by OpCode
288   llvm::sort(Ops, [](DXILOperationDesc &A, DXILOperationDesc &B) {
289     return A.OpCode < B.OpCode;
290   });
291 
292   OS << "// Enumeration for operations specified by DXIL\n";
293   OS << "enum class OpCode : unsigned {\n";
294 
295   for (auto &Op : Ops) {
296     // Name = ID, // Doc
297     OS << Op.OpName << " = " << Op.OpCode << ", // " << Op.Doc << "\n";
298   }
299 
300   OS << "\n};\n\n";
301 
302   OS << "// Groups for DXIL operations with equivalent function templates\n";
303   OS << "enum class OpCodeClass : unsigned {\n";
304   // Build an OpClass set to print
305   SmallSet<StringRef, 2> OpClassSet;
306   for (auto &Op : Ops) {
307     OpClassSet.insert(Op.OpClass);
308   }
309   for (auto &C : OpClassSet) {
310     OS << C << ",\n";
311   }
312   OS << "\n};\n\n";
313 }
314 
315 /// Emit map of DXIL operation to LLVM or DirectX intrinsic
316 /// \param A vector of DXIL Ops
317 /// \param Output stream
318 static void emitDXILIntrinsicMap(std::vector<DXILOperationDesc> &Ops,
319                                  raw_ostream &OS) {
320   OS << "\n";
321   // FIXME: use array instead of SmallDenseMap.
322   OS << "static const SmallDenseMap<Intrinsic::ID, dxil::OpCode> LowerMap = "
323         "{\n";
324   for (auto &Op : Ops) {
325     if (Op.Intrinsic.empty())
326       continue;
327     // {Intrinsic::sin, dxil::OpCode::Sin},
328     OS << "  { Intrinsic::" << Op.Intrinsic << ", dxil::OpCode::" << Op.OpName
329        << "},\n";
330   }
331   OS << "};\n";
332   OS << "\n";
333 }
334 
335 /// Convert operation attribute string to Attribute enum
336 ///
337 /// \param Attr string reference
338 /// \return std::string Attribute enum string
339 
340 static std::string emitDXILOperationAttr(SmallVector<std::string> Attrs) {
341   for (auto Attr : Attrs) {
342     // TODO: For now just recognize IntrNoMem and IntrReadMem as valid and
343     //  ignore others.
344     if (Attr == "IntrNoMem") {
345       return "Attribute::ReadNone";
346     } else if (Attr == "IntrReadMem") {
347       return "Attribute::ReadOnly";
348     }
349   }
350   return "Attribute::None";
351 }
352 
353 /// Emit DXIL operation table
354 /// \param A vector of DXIL Ops
355 /// \param Output stream
356 static void emitDXILOperationTable(std::vector<DXILOperationDesc> &Ops,
357                                    raw_ostream &OS) {
358   // Sort by OpCode.
359   llvm::sort(Ops, [](DXILOperationDesc &A, DXILOperationDesc &B) {
360     return A.OpCode < B.OpCode;
361   });
362 
363   // Collect Names.
364   SequenceToOffsetTable<std::string> OpClassStrings;
365   SequenceToOffsetTable<std::string> OpStrings;
366   SequenceToOffsetTable<SmallVector<ParameterKind>> Parameters;
367 
368   StringMap<SmallVector<ParameterKind>> ParameterMap;
369   StringSet<> ClassSet;
370   for (auto &Op : Ops) {
371     OpStrings.add(Op.OpName);
372 
373     if (ClassSet.contains(Op.OpClass))
374       continue;
375     ClassSet.insert(Op.OpClass);
376     OpClassStrings.add(Op.OpClass.data());
377     SmallVector<ParameterKind> ParamKindVec;
378     // ParamKindVec is a vector of parameters. Skip return type at index 0
379     for (unsigned i = 1; i < Op.OpTypes.size(); i++) {
380       ParamKindVec.emplace_back(getParameterKind(Op.OpTypes[i]));
381     }
382     ParameterMap[Op.OpClass] = ParamKindVec;
383     Parameters.add(ParamKindVec);
384   }
385 
386   // Layout names.
387   OpStrings.layout();
388   OpClassStrings.layout();
389   Parameters.layout();
390 
391   // Emit the DXIL operation table.
392   //{dxil::OpCode::Sin, OpCodeNameIndex, OpCodeClass::unary,
393   // OpCodeClassNameIndex,
394   // OverloadKind::FLOAT | OverloadKind::HALF, Attribute::AttrKind::ReadNone, 0,
395   // 3, ParameterTableOffset},
396   OS << "static const OpCodeProperty *getOpCodeProperty(dxil::OpCode Op) "
397         "{\n";
398 
399   OS << "  static const OpCodeProperty OpCodeProps[] = {\n";
400   for (auto &Op : Ops) {
401     // Consider Op.OverloadParamIndex as the overload parameter index, by
402     // default
403     auto OLParamIdx = Op.OverloadParamIndex;
404     // If no overload parameter index is set, treat first parameter type as
405     // overload type - unless the Op has no parameters, in which case treat the
406     // return type - as overload parameter to emit the appropriate overload kind
407     // enum.
408     if (OLParamIdx < 0) {
409       OLParamIdx = (Op.OpTypes.size() > 1) ? 1 : 0;
410     }
411     OS << "  { dxil::OpCode::" << Op.OpName << ", " << OpStrings.get(Op.OpName)
412        << ", OpCodeClass::" << Op.OpClass << ", "
413        << OpClassStrings.get(Op.OpClass.data()) << ", "
414        << getOverloadKindStr(Op.OpTypes[OLParamIdx]) << ", "
415        << emitDXILOperationAttr(Op.OpAttributes) << ", "
416        << Op.OverloadParamIndex << ", " << Op.OpTypes.size() - 1 << ", "
417        << Parameters.get(ParameterMap[Op.OpClass]) << " },\n";
418   }
419   OS << "  };\n";
420 
421   OS << "  // FIXME: change search to indexing with\n";
422   OS << "  // Op once all DXIL operations are added.\n";
423   OS << "  OpCodeProperty TmpProp;\n";
424   OS << "  TmpProp.OpCode = Op;\n";
425   OS << "  const OpCodeProperty *Prop =\n";
426   OS << "      llvm::lower_bound(OpCodeProps, TmpProp,\n";
427   OS << "                        [](const OpCodeProperty &A, const "
428         "OpCodeProperty &B) {\n";
429   OS << "                          return A.OpCode < B.OpCode;\n";
430   OS << "                        });\n";
431   OS << "  assert(Prop && \"failed to find OpCodeProperty\");\n";
432   OS << "  return Prop;\n";
433   OS << "}\n\n";
434 
435   // Emit the string tables.
436   OS << "static const char *getOpCodeName(dxil::OpCode Op) {\n\n";
437 
438   OpStrings.emitStringLiteralDef(OS,
439                                  "  static const char DXILOpCodeNameTable[]");
440 
441   OS << "  auto *Prop = getOpCodeProperty(Op);\n";
442   OS << "  unsigned Index = Prop->OpCodeNameOffset;\n";
443   OS << "  return DXILOpCodeNameTable + Index;\n";
444   OS << "}\n\n";
445 
446   OS << "static const char *getOpCodeClassName(const OpCodeProperty &Prop) "
447         "{\n\n";
448 
449   OpClassStrings.emitStringLiteralDef(
450       OS, "  static const char DXILOpCodeClassNameTable[]");
451 
452   OS << "  unsigned Index = Prop.OpCodeClassNameOffset;\n";
453   OS << "  return DXILOpCodeClassNameTable + Index;\n";
454   OS << "}\n ";
455 
456   OS << "static const ParameterKind *getOpCodeParameterKind(const "
457         "OpCodeProperty &Prop) "
458         "{\n\n";
459   OS << "  static const ParameterKind DXILOpParameterKindTable[] = {\n";
460   Parameters.emit(
461       OS,
462       [](raw_ostream &ParamOS, ParameterKind Kind) {
463         ParamOS << "ParameterKind::" << getParameterKindStr(Kind);
464       },
465       "ParameterKind::Invalid");
466   OS << "  };\n\n";
467   OS << "  unsigned Index = Prop.ParameterTableOffset;\n";
468   OS << "  return DXILOpParameterKindTable + Index;\n";
469   OS << "}\n ";
470 }
471 
472 /// Entry function call that invokes the functionality of this TableGen backend
473 /// \param Records TableGen records of DXIL Operations defined in DXIL.td
474 /// \param OS output stream
475 static void EmitDXILOperation(RecordKeeper &Records, raw_ostream &OS) {
476   OS << "// Generated code, do not edit.\n";
477   OS << "\n";
478   // Get all DXIL Ops to intrinsic mapping records
479   std::vector<Record *> OpIntrMaps =
480       Records.getAllDerivedDefinitions("DXILOpMapping");
481   std::vector<DXILOperationDesc> DXILOps;
482   for (auto *Record : OpIntrMaps) {
483     DXILOps.emplace_back(DXILOperationDesc(Record));
484   }
485   OS << "#ifdef DXIL_OP_ENUM\n";
486   emitDXILEnums(DXILOps, OS);
487   OS << "#endif\n\n";
488   OS << "#ifdef DXIL_OP_INTRINSIC_MAP\n";
489   emitDXILIntrinsicMap(DXILOps, OS);
490   OS << "#endif\n\n";
491   OS << "#ifdef DXIL_OP_OPERATION_TABLE\n";
492   emitDXILOperationTable(DXILOps, OS);
493   OS << "#endif\n\n";
494 }
495 
496 static TableGen::Emitter::Opt X("gen-dxil-operation", EmitDXILOperation,
497                                 "Generate DXIL operation information");
498