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
getParameterKind(const Record * R)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
DXILOperationDesc(const Record * R)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
getParameterKindStr(ParameterKind 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
getOverloadKindStr(const Record * R)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
emitDXILEnums(std::vector<DXILOperationDesc> & Ops,raw_ostream & OS)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
emitDXILIntrinsicMap(std::vector<DXILOperationDesc> & Ops,raw_ostream & OS)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
emitDXILOperationAttr(SmallVector<std::string> Attrs)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
emitDXILOperationTable(std::vector<DXILOperationDesc> & Ops,raw_ostream & OS)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
EmitDXILOperation(RecordKeeper & Records,raw_ostream & OS)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