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 "SequenceToOffsetTable.h" 15 #include "llvm/ADT/STLExtras.h" 16 #include "llvm/ADT/SmallVector.h" 17 #include "llvm/ADT/StringSet.h" 18 #include "llvm/ADT/StringSwitch.h" 19 #include "llvm/Support/DXILOperationCommon.h" 20 #include "llvm/TableGen/Record.h" 21 #include "llvm/TableGen/TableGenBackend.h" 22 23 using namespace llvm; 24 using namespace llvm::dxil; 25 26 namespace { 27 28 struct DXILShaderModel { 29 int Major = 0; 30 int Minor = 0; 31 }; 32 33 struct DXILParam { 34 int Pos; // position in parameter list 35 ParameterKind Kind; 36 StringRef Name; // short, unique name 37 StringRef Doc; // the documentation description of this parameter 38 bool IsConst; // whether this argument requires a constant value in the IR 39 StringRef EnumName; // the name of the enum type if applicable 40 int MaxValue; // the maximum value for this parameter if applicable 41 DXILParam(const Record *R); 42 }; 43 44 struct DXILOperationData { 45 StringRef Name; // short, unique name 46 47 StringRef DXILOp; // name of DXIL operation 48 int DXILOpID; // ID of DXIL operation 49 StringRef DXILClass; // name of the opcode class 50 StringRef Category; // classification for this instruction 51 StringRef Doc; // the documentation description of this instruction 52 53 SmallVector<DXILParam> Params; // the operands that this instruction takes 54 StringRef OverloadTypes; // overload types if applicable 55 StringRef FnAttr; // attribute shorthands: rn=does not access 56 // memory,ro=only reads from memory 57 StringRef Intrinsic; // The llvm intrinsic map to DXILOp. Default is "" which 58 // means no map exist 59 bool IsDeriv = false; // whether this is some kind of derivative 60 bool IsGradient = false; // whether this requires a gradient calculation 61 bool IsFeedback = false; // whether this is a sampler feedback op 62 bool IsWave = false; // whether this requires in-wave, cross-lane functionality 63 bool RequiresUniformInputs = false; // whether this operation requires that 64 // all of its inputs are uniform across 65 // the wave 66 SmallVector<StringRef, 4> 67 ShaderStages; // shader stages to which this applies, empty for all. 68 DXILShaderModel ShaderModel; // minimum shader model required 69 DXILShaderModel ShaderModelTranslated; // minimum shader model required with 70 // translation by linker 71 int OverloadParamIndex; // parameter index which control the overload. 72 // When < 0, should be only 1 overload type. 73 SmallVector<StringRef, 4> counters; // counters for this inst. 74 DXILOperationData(const Record *R) { 75 Name = R->getValueAsString("name"); 76 DXILOp = R->getValueAsString("dxil_op"); 77 DXILOpID = R->getValueAsInt("dxil_opid"); 78 DXILClass = R->getValueAsDef("op_class")->getValueAsString("name"); 79 Category = R->getValueAsDef("category")->getValueAsString("name"); 80 81 if (R->getValue("llvm_intrinsic")) { 82 auto *IntrinsicDef = R->getValueAsDef("llvm_intrinsic"); 83 auto DefName = IntrinsicDef->getName(); 84 assert(DefName.starts_with("int_") && "invalid intrinsic name"); 85 // Remove the int_ from intrinsic name. 86 Intrinsic = DefName.substr(4); 87 } 88 89 Doc = R->getValueAsString("doc"); 90 91 ListInit *ParamList = R->getValueAsListInit("ops"); 92 OverloadParamIndex = -1; 93 for (unsigned I = 0; I < ParamList->size(); ++I) { 94 Record *Param = ParamList->getElementAsRecord(I); 95 Params.emplace_back(DXILParam(Param)); 96 auto &CurParam = Params.back(); 97 if (CurParam.Kind >= ParameterKind::OVERLOAD) 98 OverloadParamIndex = I; 99 } 100 OverloadTypes = R->getValueAsString("oload_types"); 101 FnAttr = R->getValueAsString("fn_attr"); 102 } 103 }; 104 } // end anonymous namespace 105 106 DXILParam::DXILParam(const Record *R) { 107 Name = R->getValueAsString("name"); 108 Pos = R->getValueAsInt("pos"); 109 Kind = parameterTypeNameToKind(R->getValueAsString("llvm_type")); 110 if (R->getValue("doc")) 111 Doc = R->getValueAsString("doc"); 112 IsConst = R->getValueAsBit("is_const"); 113 EnumName = R->getValueAsString("enum_name"); 114 MaxValue = R->getValueAsInt("max_value"); 115 } 116 117 static std::string parameterKindToString(ParameterKind Kind) { 118 switch (Kind) { 119 case ParameterKind::INVALID: 120 return "INVALID"; 121 case ParameterKind::VOID: 122 return "VOID"; 123 case ParameterKind::HALF: 124 return "HALF"; 125 case ParameterKind::FLOAT: 126 return "FLOAT"; 127 case ParameterKind::DOUBLE: 128 return "DOUBLE"; 129 case ParameterKind::I1: 130 return "I1"; 131 case ParameterKind::I8: 132 return "I8"; 133 case ParameterKind::I16: 134 return "I16"; 135 case ParameterKind::I32: 136 return "I32"; 137 case ParameterKind::I64: 138 return "I64"; 139 case ParameterKind::OVERLOAD: 140 return "OVERLOAD"; 141 case ParameterKind::CBUFFER_RET: 142 return "CBUFFER_RET"; 143 case ParameterKind::RESOURCE_RET: 144 return "RESOURCE_RET"; 145 case ParameterKind::DXIL_HANDLE: 146 return "DXIL_HANDLE"; 147 } 148 llvm_unreachable("Unknown llvm::dxil::ParameterKind enum"); 149 } 150 151 static void emitDXILOpEnum(DXILOperationData &DXILOp, raw_ostream &OS) { 152 // Name = ID, // Doc 153 OS << DXILOp.Name << " = " << DXILOp.DXILOpID << ", // " << DXILOp.Doc 154 << "\n"; 155 } 156 157 static std::string buildCategoryStr(StringSet<> &Cetegorys) { 158 std::string Str; 159 raw_string_ostream OS(Str); 160 for (auto &It : Cetegorys) { 161 OS << " " << It.getKey(); 162 } 163 return OS.str(); 164 } 165 166 // Emit enum declaration for DXIL. 167 static void emitDXILEnums(std::vector<DXILOperationData> &DXILOps, 168 raw_ostream &OS) { 169 // Sort by Category + OpName. 170 llvm::sort(DXILOps, [](DXILOperationData &A, DXILOperationData &B) { 171 // Group by Category first. 172 if (A.Category == B.Category) 173 // Inside same Category, order by OpName. 174 return A.DXILOp < B.DXILOp; 175 else 176 return A.Category < B.Category; 177 }); 178 179 OS << "// Enumeration for operations specified by DXIL\n"; 180 OS << "enum class OpCode : unsigned {\n"; 181 182 StringMap<StringSet<>> ClassMap; 183 StringRef PrevCategory = ""; 184 for (auto &DXILOp : DXILOps) { 185 StringRef Category = DXILOp.Category; 186 if (Category != PrevCategory) { 187 OS << "\n// " << Category << "\n"; 188 PrevCategory = Category; 189 } 190 emitDXILOpEnum(DXILOp, OS); 191 auto It = ClassMap.find(DXILOp.DXILClass); 192 if (It != ClassMap.end()) { 193 It->second.insert(DXILOp.Category); 194 } else { 195 ClassMap[DXILOp.DXILClass].insert(DXILOp.Category); 196 } 197 } 198 199 OS << "\n};\n\n"; 200 201 std::vector<std::pair<std::string, std::string>> ClassVec; 202 for (auto &It : ClassMap) { 203 ClassVec.emplace_back( 204 std::make_pair(It.getKey().str(), buildCategoryStr(It.second))); 205 } 206 // Sort by Category + ClassName. 207 llvm::sort(ClassVec, [](std::pair<std::string, std::string> &A, 208 std::pair<std::string, std::string> &B) { 209 StringRef ClassA = A.first; 210 StringRef CategoryA = A.second; 211 StringRef ClassB = B.first; 212 StringRef CategoryB = B.second; 213 // Group by Category first. 214 if (CategoryA == CategoryB) 215 // Inside same Category, order by ClassName. 216 return ClassA < ClassB; 217 else 218 return CategoryA < CategoryB; 219 }); 220 221 OS << "// Groups for DXIL operations with equivalent function templates\n"; 222 OS << "enum class OpCodeClass : unsigned {\n"; 223 PrevCategory = ""; 224 for (auto &It : ClassVec) { 225 226 StringRef Category = It.second; 227 if (Category != PrevCategory) { 228 OS << "\n// " << Category << "\n"; 229 PrevCategory = Category; 230 } 231 StringRef Name = It.first; 232 OS << Name << ",\n"; 233 } 234 OS << "\n};\n\n"; 235 } 236 237 // Emit map from llvm intrinsic to DXIL operation. 238 static void emitDXILIntrinsicMap(std::vector<DXILOperationData> &DXILOps, 239 raw_ostream &OS) { 240 OS << "\n"; 241 // FIXME: use array instead of SmallDenseMap. 242 OS << "static const SmallDenseMap<Intrinsic::ID, dxil::OpCode> LowerMap = " 243 "{\n"; 244 for (auto &DXILOp : DXILOps) { 245 if (DXILOp.Intrinsic.empty()) 246 continue; 247 // {Intrinsic::sin, dxil::OpCode::Sin}, 248 OS << " { Intrinsic::" << DXILOp.Intrinsic 249 << ", dxil::OpCode::" << DXILOp.DXILOp << "},\n"; 250 } 251 OS << "};\n"; 252 OS << "\n"; 253 } 254 255 static std::string emitDXILOperationFnAttr(StringRef FnAttr) { 256 return StringSwitch<std::string>(FnAttr) 257 .Case("rn", "Attribute::ReadNone") 258 .Case("ro", "Attribute::ReadOnly") 259 .Default("Attribute::None"); 260 } 261 262 static std::string getOverloadKind(StringRef Overload) { 263 return StringSwitch<std::string>(Overload) 264 .Case("half", "OverloadKind::HALF") 265 .Case("float", "OverloadKind::FLOAT") 266 .Case("double", "OverloadKind::DOUBLE") 267 .Case("i1", "OverloadKind::I1") 268 .Case("i16", "OverloadKind::I16") 269 .Case("i32", "OverloadKind::I32") 270 .Case("i64", "OverloadKind::I64") 271 .Case("udt", "OverloadKind::UserDefineType") 272 .Case("obj", "OverloadKind::ObjectType") 273 .Default("OverloadKind::VOID"); 274 } 275 276 static std::string getDXILOperationOverload(StringRef Overloads) { 277 SmallVector<StringRef> OverloadStrs; 278 Overloads.split(OverloadStrs, ';', /*MaxSplit*/ -1, /*KeepEmpty*/ false); 279 // Format is: OverloadKind::FLOAT | OverloadKind::HALF 280 assert(!OverloadStrs.empty() && "Invalid overloads"); 281 auto It = OverloadStrs.begin(); 282 std::string Result; 283 raw_string_ostream OS(Result); 284 OS << getOverloadKind(*It); 285 for (++It; It != OverloadStrs.end(); ++It) { 286 OS << " | " << getOverloadKind(*It); 287 } 288 return OS.str(); 289 } 290 291 static std::string lowerFirstLetter(StringRef Name) { 292 if (Name.empty()) 293 return ""; 294 295 std::string LowerName = Name.str(); 296 LowerName[0] = llvm::toLower(Name[0]); 297 return LowerName; 298 } 299 300 static std::string getDXILOpClassName(StringRef DXILOpClass) { 301 // Lower first letter expect for special case. 302 return StringSwitch<std::string>(DXILOpClass) 303 .Case("CBufferLoad", "cbufferLoad") 304 .Case("CBufferLoadLegacy", "cbufferLoadLegacy") 305 .Case("GSInstanceID", "gsInstanceID") 306 .Default(lowerFirstLetter(DXILOpClass)); 307 } 308 309 static void emitDXILOperationTable(std::vector<DXILOperationData> &DXILOps, 310 raw_ostream &OS) { 311 // Sort by DXILOpID. 312 llvm::sort(DXILOps, [](DXILOperationData &A, DXILOperationData &B) { 313 return A.DXILOpID < B.DXILOpID; 314 }); 315 316 // Collect Names. 317 SequenceToOffsetTable<std::string> OpClassStrings; 318 SequenceToOffsetTable<std::string> OpStrings; 319 SequenceToOffsetTable<SmallVector<ParameterKind>> Parameters; 320 321 StringMap<SmallVector<ParameterKind>> ParameterMap; 322 StringSet<> ClassSet; 323 for (auto &DXILOp : DXILOps) { 324 OpStrings.add(DXILOp.DXILOp.str()); 325 326 if (ClassSet.contains(DXILOp.DXILClass)) 327 continue; 328 ClassSet.insert(DXILOp.DXILClass); 329 OpClassStrings.add(getDXILOpClassName(DXILOp.DXILClass)); 330 SmallVector<ParameterKind> ParamKindVec; 331 for (auto &Param : DXILOp.Params) { 332 ParamKindVec.emplace_back(Param.Kind); 333 } 334 ParameterMap[DXILOp.DXILClass] = ParamKindVec; 335 Parameters.add(ParamKindVec); 336 } 337 338 // Layout names. 339 OpStrings.layout(); 340 OpClassStrings.layout(); 341 Parameters.layout(); 342 343 // Emit the DXIL operation table. 344 //{dxil::OpCode::Sin, OpCodeNameIndex, OpCodeClass::Unary, 345 // OpCodeClassNameIndex, 346 // OverloadKind::FLOAT | OverloadKind::HALF, Attribute::AttrKind::ReadNone, 0, 347 // 3, ParameterTableOffset}, 348 OS << "static const OpCodeProperty *getOpCodeProperty(dxil::OpCode DXILOp) " 349 "{\n"; 350 351 OS << " static const OpCodeProperty OpCodeProps[] = {\n"; 352 for (auto &DXILOp : DXILOps) { 353 OS << " { dxil::OpCode::" << DXILOp.DXILOp << ", " 354 << OpStrings.get(DXILOp.DXILOp.str()) 355 << ", OpCodeClass::" << DXILOp.DXILClass << ", " 356 << OpClassStrings.get(getDXILOpClassName(DXILOp.DXILClass)) << ", " 357 << getDXILOperationOverload(DXILOp.OverloadTypes) << ", " 358 << emitDXILOperationFnAttr(DXILOp.FnAttr) << ", " 359 << DXILOp.OverloadParamIndex << ", " << DXILOp.Params.size() << ", " 360 << Parameters.get(ParameterMap[DXILOp.DXILClass]) << " },\n"; 361 } 362 OS << " };\n"; 363 364 OS << " // FIXME: change search to indexing with\n"; 365 OS << " // DXILOp once all DXIL op is added.\n"; 366 OS << " OpCodeProperty TmpProp;\n"; 367 OS << " TmpProp.OpCode = DXILOp;\n"; 368 OS << " const OpCodeProperty *Prop =\n"; 369 OS << " llvm::lower_bound(OpCodeProps, TmpProp,\n"; 370 OS << " [](const OpCodeProperty &A, const " 371 "OpCodeProperty &B) {\n"; 372 OS << " return A.OpCode < B.OpCode;\n"; 373 OS << " });\n"; 374 OS << " assert(Prop && \"fail to find OpCodeProperty\");\n"; 375 OS << " return Prop;\n"; 376 OS << "}\n\n"; 377 378 // Emit the string tables. 379 OS << "static const char *getOpCodeName(dxil::OpCode DXILOp) {\n\n"; 380 381 OpStrings.emitStringLiteralDef(OS, 382 " static const char DXILOpCodeNameTable[]"); 383 384 OS << " auto *Prop = getOpCodeProperty(DXILOp);\n"; 385 OS << " unsigned Index = Prop->OpCodeNameOffset;\n"; 386 OS << " return DXILOpCodeNameTable + Index;\n"; 387 OS << "}\n\n"; 388 389 OS << "static const char *getOpCodeClassName(const OpCodeProperty &Prop) " 390 "{\n\n"; 391 392 OpClassStrings.emitStringLiteralDef( 393 OS, " static const char DXILOpCodeClassNameTable[]"); 394 395 OS << " unsigned Index = Prop.OpCodeClassNameOffset;\n"; 396 OS << " return DXILOpCodeClassNameTable + Index;\n"; 397 OS << "}\n "; 398 399 OS << "static const ParameterKind *getOpCodeParameterKind(const " 400 "OpCodeProperty &Prop) " 401 "{\n\n"; 402 OS << " static const ParameterKind DXILOpParameterKindTable[] = {\n"; 403 Parameters.emit( 404 OS, 405 [](raw_ostream &ParamOS, ParameterKind Kind) { 406 ParamOS << "ParameterKind::" << parameterKindToString(Kind); 407 }, 408 "ParameterKind::INVALID"); 409 OS << " };\n\n"; 410 OS << " unsigned Index = Prop.ParameterTableOffset;\n"; 411 OS << " return DXILOpParameterKindTable + Index;\n"; 412 OS << "}\n "; 413 } 414 415 static void EmitDXILOperation(RecordKeeper &Records, raw_ostream &OS) { 416 std::vector<Record *> Ops = Records.getAllDerivedDefinitions("dxil_op"); 417 OS << "// Generated code, do not edit.\n"; 418 OS << "\n"; 419 420 std::vector<DXILOperationData> DXILOps; 421 DXILOps.reserve(Ops.size()); 422 for (auto *Record : Ops) { 423 DXILOps.emplace_back(DXILOperationData(Record)); 424 } 425 426 OS << "#ifdef DXIL_OP_ENUM\n"; 427 emitDXILEnums(DXILOps, OS); 428 OS << "#endif\n\n"; 429 430 OS << "#ifdef DXIL_OP_INTRINSIC_MAP\n"; 431 emitDXILIntrinsicMap(DXILOps, OS); 432 OS << "#endif\n\n"; 433 434 OS << "#ifdef DXIL_OP_OPERATION_TABLE\n"; 435 emitDXILOperationTable(DXILOps, OS); 436 OS << "#endif\n\n"; 437 438 OS << "\n"; 439 } 440 441 static TableGen::Emitter::Opt X("gen-dxil-operation", EmitDXILOperation, 442 "Generate DXIL operation information"); 443