//===- DXILEmitter.cpp - DXIL operation Emitter ---------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// // // DXILEmitter uses the descriptions of DXIL operation to construct enum and // helper functions for DXIL operation. // //===----------------------------------------------------------------------===// #include "SequenceToOffsetTable.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringSet.h" #include "llvm/ADT/StringSwitch.h" #include "llvm/Support/DXILOperationCommon.h" #include "llvm/TableGen/Record.h" #include "llvm/TableGen/TableGenBackend.h" using namespace llvm; using namespace llvm::dxil; namespace { struct DXILShaderModel { int Major = 0; int Minor = 0; }; struct DXILParam { int Pos; // position in parameter list ParameterKind Kind; StringRef Name; // short, unique name StringRef Doc; // the documentation description of this parameter bool IsConst; // whether this argument requires a constant value in the IR StringRef EnumName; // the name of the enum type if applicable int MaxValue; // the maximum value for this parameter if applicable DXILParam(const Record *R); }; struct DXILOperationData { StringRef Name; // short, unique name StringRef DXILOp; // name of DXIL operation int DXILOpID; // ID of DXIL operation StringRef DXILClass; // name of the opcode class StringRef Category; // classification for this instruction StringRef Doc; // the documentation description of this instruction SmallVector Params; // the operands that this instruction takes StringRef OverloadTypes; // overload types if applicable StringRef FnAttr; // attribute shorthands: rn=does not access // memory,ro=only reads from memory StringRef Intrinsic; // The llvm intrinsic map to DXILOp. Default is "" which // means no map exist bool IsDeriv = false; // whether this is some kind of derivative bool IsGradient = false; // whether this requires a gradient calculation bool IsFeedback = false; // whether this is a sampler feedback op bool IsWave = false; // whether this requires in-wave, cross-lane functionality bool RequiresUniformInputs = false; // whether this operation requires that // all of its inputs are uniform across // the wave SmallVector ShaderStages; // shader stages to which this applies, empty for all. DXILShaderModel ShaderModel; // minimum shader model required DXILShaderModel ShaderModelTranslated; // minimum shader model required with // translation by linker int OverloadParamIndex; // parameter index which control the overload. // When < 0, should be only 1 overload type. SmallVector counters; // counters for this inst. DXILOperationData(const Record *R) { Name = R->getValueAsString("name"); DXILOp = R->getValueAsString("dxil_op"); DXILOpID = R->getValueAsInt("dxil_opid"); DXILClass = R->getValueAsDef("op_class")->getValueAsString("name"); Category = R->getValueAsDef("category")->getValueAsString("name"); if (R->getValue("llvm_intrinsic")) { auto *IntrinsicDef = R->getValueAsDef("llvm_intrinsic"); auto DefName = IntrinsicDef->getName(); assert(DefName.starts_with("int_") && "invalid intrinsic name"); // Remove the int_ from intrinsic name. Intrinsic = DefName.substr(4); } Doc = R->getValueAsString("doc"); ListInit *ParamList = R->getValueAsListInit("ops"); OverloadParamIndex = -1; for (unsigned I = 0; I < ParamList->size(); ++I) { Record *Param = ParamList->getElementAsRecord(I); Params.emplace_back(DXILParam(Param)); auto &CurParam = Params.back(); if (CurParam.Kind >= ParameterKind::OVERLOAD) OverloadParamIndex = I; } OverloadTypes = R->getValueAsString("oload_types"); FnAttr = R->getValueAsString("fn_attr"); } }; } // end anonymous namespace DXILParam::DXILParam(const Record *R) { Name = R->getValueAsString("name"); Pos = R->getValueAsInt("pos"); Kind = parameterTypeNameToKind(R->getValueAsString("llvm_type")); if (R->getValue("doc")) Doc = R->getValueAsString("doc"); IsConst = R->getValueAsBit("is_const"); EnumName = R->getValueAsString("enum_name"); MaxValue = R->getValueAsInt("max_value"); } static std::string parameterKindToString(ParameterKind Kind) { switch (Kind) { case ParameterKind::INVALID: return "INVALID"; case ParameterKind::VOID: return "VOID"; case ParameterKind::HALF: return "HALF"; case ParameterKind::FLOAT: return "FLOAT"; case ParameterKind::DOUBLE: return "DOUBLE"; case ParameterKind::I1: return "I1"; case ParameterKind::I8: return "I8"; case ParameterKind::I16: return "I16"; case ParameterKind::I32: return "I32"; case ParameterKind::I64: return "I64"; case ParameterKind::OVERLOAD: return "OVERLOAD"; case ParameterKind::CBUFFER_RET: return "CBUFFER_RET"; case ParameterKind::RESOURCE_RET: return "RESOURCE_RET"; case ParameterKind::DXIL_HANDLE: return "DXIL_HANDLE"; } llvm_unreachable("Unknown llvm::dxil::ParameterKind enum"); } static void emitDXILOpEnum(DXILOperationData &DXILOp, raw_ostream &OS) { // Name = ID, // Doc OS << DXILOp.Name << " = " << DXILOp.DXILOpID << ", // " << DXILOp.Doc << "\n"; } static std::string buildCategoryStr(StringSet<> &Cetegorys) { std::string Str; raw_string_ostream OS(Str); for (auto &It : Cetegorys) { OS << " " << It.getKey(); } return OS.str(); } // Emit enum declaration for DXIL. static void emitDXILEnums(std::vector &DXILOps, raw_ostream &OS) { // Sort by Category + OpName. llvm::sort(DXILOps, [](DXILOperationData &A, DXILOperationData &B) { // Group by Category first. if (A.Category == B.Category) // Inside same Category, order by OpName. return A.DXILOp < B.DXILOp; else return A.Category < B.Category; }); OS << "// Enumeration for operations specified by DXIL\n"; OS << "enum class OpCode : unsigned {\n"; StringMap> ClassMap; StringRef PrevCategory = ""; for (auto &DXILOp : DXILOps) { StringRef Category = DXILOp.Category; if (Category != PrevCategory) { OS << "\n// " << Category << "\n"; PrevCategory = Category; } emitDXILOpEnum(DXILOp, OS); auto It = ClassMap.find(DXILOp.DXILClass); if (It != ClassMap.end()) { It->second.insert(DXILOp.Category); } else { ClassMap[DXILOp.DXILClass].insert(DXILOp.Category); } } OS << "\n};\n\n"; std::vector> ClassVec; for (auto &It : ClassMap) { ClassVec.emplace_back( std::make_pair(It.getKey().str(), buildCategoryStr(It.second))); } // Sort by Category + ClassName. llvm::sort(ClassVec, [](std::pair &A, std::pair &B) { StringRef ClassA = A.first; StringRef CategoryA = A.second; StringRef ClassB = B.first; StringRef CategoryB = B.second; // Group by Category first. if (CategoryA == CategoryB) // Inside same Category, order by ClassName. return ClassA < ClassB; else return CategoryA < CategoryB; }); OS << "// Groups for DXIL operations with equivalent function templates\n"; OS << "enum class OpCodeClass : unsigned {\n"; PrevCategory = ""; for (auto &It : ClassVec) { StringRef Category = It.second; if (Category != PrevCategory) { OS << "\n// " << Category << "\n"; PrevCategory = Category; } StringRef Name = It.first; OS << Name << ",\n"; } OS << "\n};\n\n"; } // Emit map from llvm intrinsic to DXIL operation. static void emitDXILIntrinsicMap(std::vector &DXILOps, raw_ostream &OS) { OS << "\n"; // FIXME: use array instead of SmallDenseMap. OS << "static const SmallDenseMap LowerMap = " "{\n"; for (auto &DXILOp : DXILOps) { if (DXILOp.Intrinsic.empty()) continue; // {Intrinsic::sin, dxil::OpCode::Sin}, OS << " { Intrinsic::" << DXILOp.Intrinsic << ", dxil::OpCode::" << DXILOp.DXILOp << "},\n"; } OS << "};\n"; OS << "\n"; } static std::string emitDXILOperationFnAttr(StringRef FnAttr) { return StringSwitch(FnAttr) .Case("rn", "Attribute::ReadNone") .Case("ro", "Attribute::ReadOnly") .Default("Attribute::None"); } static std::string getOverloadKind(StringRef Overload) { return StringSwitch(Overload) .Case("half", "OverloadKind::HALF") .Case("float", "OverloadKind::FLOAT") .Case("double", "OverloadKind::DOUBLE") .Case("i1", "OverloadKind::I1") .Case("i16", "OverloadKind::I16") .Case("i32", "OverloadKind::I32") .Case("i64", "OverloadKind::I64") .Case("udt", "OverloadKind::UserDefineType") .Case("obj", "OverloadKind::ObjectType") .Default("OverloadKind::VOID"); } static std::string getDXILOperationOverload(StringRef Overloads) { SmallVector OverloadStrs; Overloads.split(OverloadStrs, ';', /*MaxSplit*/ -1, /*KeepEmpty*/ false); // Format is: OverloadKind::FLOAT | OverloadKind::HALF assert(!OverloadStrs.empty() && "Invalid overloads"); auto It = OverloadStrs.begin(); std::string Result; raw_string_ostream OS(Result); OS << getOverloadKind(*It); for (++It; It != OverloadStrs.end(); ++It) { OS << " | " << getOverloadKind(*It); } return OS.str(); } static std::string lowerFirstLetter(StringRef Name) { if (Name.empty()) return ""; std::string LowerName = Name.str(); LowerName[0] = llvm::toLower(Name[0]); return LowerName; } static std::string getDXILOpClassName(StringRef DXILOpClass) { // Lower first letter expect for special case. return StringSwitch(DXILOpClass) .Case("CBufferLoad", "cbufferLoad") .Case("CBufferLoadLegacy", "cbufferLoadLegacy") .Case("GSInstanceID", "gsInstanceID") .Default(lowerFirstLetter(DXILOpClass)); } static void emitDXILOperationTable(std::vector &DXILOps, raw_ostream &OS) { // Sort by DXILOpID. llvm::sort(DXILOps, [](DXILOperationData &A, DXILOperationData &B) { return A.DXILOpID < B.DXILOpID; }); // Collect Names. SequenceToOffsetTable OpClassStrings; SequenceToOffsetTable OpStrings; SequenceToOffsetTable> Parameters; StringMap> ParameterMap; StringSet<> ClassSet; for (auto &DXILOp : DXILOps) { OpStrings.add(DXILOp.DXILOp.str()); if (ClassSet.contains(DXILOp.DXILClass)) continue; ClassSet.insert(DXILOp.DXILClass); OpClassStrings.add(getDXILOpClassName(DXILOp.DXILClass)); SmallVector ParamKindVec; for (auto &Param : DXILOp.Params) { ParamKindVec.emplace_back(Param.Kind); } ParameterMap[DXILOp.DXILClass] = ParamKindVec; Parameters.add(ParamKindVec); } // Layout names. OpStrings.layout(); OpClassStrings.layout(); Parameters.layout(); // Emit the DXIL operation table. //{dxil::OpCode::Sin, OpCodeNameIndex, OpCodeClass::Unary, // OpCodeClassNameIndex, // OverloadKind::FLOAT | OverloadKind::HALF, Attribute::AttrKind::ReadNone, 0, // 3, ParameterTableOffset}, OS << "static const OpCodeProperty *getOpCodeProperty(dxil::OpCode DXILOp) " "{\n"; OS << " static const OpCodeProperty OpCodeProps[] = {\n"; for (auto &DXILOp : DXILOps) { OS << " { dxil::OpCode::" << DXILOp.DXILOp << ", " << OpStrings.get(DXILOp.DXILOp.str()) << ", OpCodeClass::" << DXILOp.DXILClass << ", " << OpClassStrings.get(getDXILOpClassName(DXILOp.DXILClass)) << ", " << getDXILOperationOverload(DXILOp.OverloadTypes) << ", " << emitDXILOperationFnAttr(DXILOp.FnAttr) << ", " << DXILOp.OverloadParamIndex << ", " << DXILOp.Params.size() << ", " << Parameters.get(ParameterMap[DXILOp.DXILClass]) << " },\n"; } OS << " };\n"; OS << " // FIXME: change search to indexing with\n"; OS << " // DXILOp once all DXIL op is added.\n"; OS << " OpCodeProperty TmpProp;\n"; OS << " TmpProp.OpCode = DXILOp;\n"; OS << " const OpCodeProperty *Prop =\n"; OS << " llvm::lower_bound(OpCodeProps, TmpProp,\n"; OS << " [](const OpCodeProperty &A, const " "OpCodeProperty &B) {\n"; OS << " return A.OpCode < B.OpCode;\n"; OS << " });\n"; OS << " assert(Prop && \"fail to find OpCodeProperty\");\n"; OS << " return Prop;\n"; OS << "}\n\n"; // Emit the string tables. OS << "static const char *getOpCodeName(dxil::OpCode DXILOp) {\n\n"; OpStrings.emitStringLiteralDef(OS, " static const char DXILOpCodeNameTable[]"); OS << " auto *Prop = getOpCodeProperty(DXILOp);\n"; OS << " unsigned Index = Prop->OpCodeNameOffset;\n"; OS << " return DXILOpCodeNameTable + Index;\n"; OS << "}\n\n"; OS << "static const char *getOpCodeClassName(const OpCodeProperty &Prop) " "{\n\n"; OpClassStrings.emitStringLiteralDef( OS, " static const char DXILOpCodeClassNameTable[]"); OS << " unsigned Index = Prop.OpCodeClassNameOffset;\n"; OS << " return DXILOpCodeClassNameTable + Index;\n"; OS << "}\n "; OS << "static const ParameterKind *getOpCodeParameterKind(const " "OpCodeProperty &Prop) " "{\n\n"; OS << " static const ParameterKind DXILOpParameterKindTable[] = {\n"; Parameters.emit( OS, [](raw_ostream &ParamOS, ParameterKind Kind) { ParamOS << "ParameterKind::" << parameterKindToString(Kind); }, "ParameterKind::INVALID"); OS << " };\n\n"; OS << " unsigned Index = Prop.ParameterTableOffset;\n"; OS << " return DXILOpParameterKindTable + Index;\n"; OS << "}\n "; } static void EmitDXILOperation(RecordKeeper &Records, raw_ostream &OS) { std::vector Ops = Records.getAllDerivedDefinitions("dxil_op"); OS << "// Generated code, do not edit.\n"; OS << "\n"; std::vector DXILOps; DXILOps.reserve(Ops.size()); for (auto *Record : Ops) { DXILOps.emplace_back(DXILOperationData(Record)); } OS << "#ifdef DXIL_OP_ENUM\n"; emitDXILEnums(DXILOps, OS); OS << "#endif\n\n"; OS << "#ifdef DXIL_OP_INTRINSIC_MAP\n"; emitDXILIntrinsicMap(DXILOps, OS); OS << "#endif\n\n"; OS << "#ifdef DXIL_OP_OPERATION_TABLE\n"; emitDXILOperationTable(DXILOps, OS); OS << "#endif\n\n"; OS << "\n"; } static TableGen::Emitter::Opt X("gen-dxil-operation", EmitDXILOperation, "Generate DXIL operation information");