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