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