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
27using namespace llvm;
28using namespace llvm::dxil;
29
30namespace {
31
32struct DXILShaderModel {
33 int Major = 0;
34 int Minor = 0;
35};
36
37struct 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
73static 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
111DXILOperationDesc::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
202static 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
241static 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
285static 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
318static 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
340static 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
356static 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
475static 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
496static TableGen::Emitter::Opt X("gen-dxil-operation", EmitDXILOperation,
497 "Generate DXIL operation information");
498