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(FieldName: "VT" ); |
75 | switch (getValueType(Rec: 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(FieldName: "isHalfOrFloat" ) || R->getValueAsInt(FieldName: "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(FieldName: "OpCode" ); |
114 | |
115 | Doc = R->getValueAsString(FieldName: "Doc" ); |
116 | |
117 | auto TypeRecs = R->getValueAsListOfDefs(FieldName: "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(FieldName: "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(reason: "Specification of multiple differing overload " |
148 | "parameter types not yet supported" , |
149 | gen_crash_diag: false); |
150 | } |
151 | } else { |
152 | OverloadParamIndices.push_back(Elt: 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(FieldName: "Number" ); |
162 | // Resolve and insert the type to that at OLParamIndex |
163 | OpTypes.emplace_back(Args&: TypeRecs[OLParamIndex]); |
164 | } else { |
165 | // A non-anonymous type. Just record it in OpTypes |
166 | OpTypes.emplace_back(Args&: 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(reason: "Multiple overload type specification not supported" , |
175 | gen_crash_diag: false); |
176 | OverloadParamIndex = OverloadParamIndices[0]; |
177 | } |
178 | // Get the operation class |
179 | OpClass = R->getValueAsDef(FieldName: "OpClass" )->getName(); |
180 | |
181 | if (R->getValue(Name: "LLVMIntrinsic" )) { |
182 | auto *IntrinsicDef = R->getValueAsDef(FieldName: "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(Start: 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(FieldName: "IntrProperties" ); |
192 | auto IntrPropListSize = IntrPropList->size(); |
193 | for (unsigned i = 0; i < IntrPropListSize; i++) { |
194 | OpAttributes.emplace_back(Args: 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(FieldName: "VT" ); |
243 | switch (getValueType(Rec: 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(FieldName: "isHalfOrFloat" )) { |
270 | return "OverloadKind::HALF | OverloadKind::FLOAT" ; |
271 | } else if (R->getValueAsInt(FieldName: "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(C&: Ops, Comp: [](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(V: 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(C&: Ops, Comp: [](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(Seq: Op.OpName); |
372 | |
373 | if (ClassSet.contains(key: Op.OpClass)) |
374 | continue; |
375 | ClassSet.insert(key: Op.OpClass); |
376 | OpClassStrings.add(Seq: 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(Args: getParameterKind(R: Op.OpTypes[i])); |
381 | } |
382 | ParameterMap[Op.OpClass] = ParamKindVec; |
383 | Parameters.add(Seq: 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(Seq: Op.OpName) |
412 | << ", OpCodeClass::" << Op.OpClass << ", " |
413 | << OpClassStrings.get(Seq: Op.OpClass.data()) << ", " |
414 | << getOverloadKindStr(R: Op.OpTypes[OLParamIdx]) << ", " |
415 | << emitDXILOperationAttr(Attrs: Op.OpAttributes) << ", " |
416 | << Op.OverloadParamIndex << ", " << Op.OpTypes.size() - 1 << ", " |
417 | << Parameters.get(Seq: 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 | Decl: " 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, Decl: " 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 | Print: [](raw_ostream &ParamOS, ParameterKind Kind) { |
463 | ParamOS << "ParameterKind::" << getParameterKindStr(Kind); |
464 | }, |
465 | Term: "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(ClassName: "DXILOpMapping" ); |
481 | std::vector<DXILOperationDesc> DXILOps; |
482 | for (auto *Record : OpIntrMaps) { |
483 | DXILOps.emplace_back(args: DXILOperationDesc(Record)); |
484 | } |
485 | OS << "#ifdef DXIL_OP_ENUM\n" ; |
486 | emitDXILEnums(Ops&: DXILOps, OS); |
487 | OS << "#endif\n\n" ; |
488 | OS << "#ifdef DXIL_OP_INTRINSIC_MAP\n" ; |
489 | emitDXILIntrinsicMap(Ops&: DXILOps, OS); |
490 | OS << "#endif\n\n" ; |
491 | OS << "#ifdef DXIL_OP_OPERATION_TABLE\n" ; |
492 | emitDXILOperationTable(Ops&: 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 | |