diff --git a/docs/DXIL.rst b/docs/DXIL.rst index 31878959eb..96f4f11288 100644 --- a/docs/DXIL.rst +++ b/docs/DXIL.rst @@ -2111,6 +2111,8 @@ Opcodes are defined on a dense range and will be provided as enum in a header fi .. hctdb_instrhelp.get_opcodes_rst() .. OPCODES-RST:BEGIN +Opcode Table CoreOps, id=0: Core DXIL operations + === ===================================================== ======================================================================================================================================================================================================================= ID Name Description === ===================================================== ======================================================================================================================================================================================================================= @@ -3055,6 +3057,18 @@ Given width, offset: ushr dest, src2, offset } + + + +Opcode Table ExperimentalOps, id=32768: Experimental DXIL operations + +========== =============== ================ +ID Name Description +========== =============== ================ +2147483648 ExperimentalNop nop does nothing +========== =============== ================ + + .. OPCODES-RST:END @@ -3134,10 +3148,11 @@ INSTR.CREATEHANDLEIMMRANGEID Local resource mus INSTR.DXILSTRUCTUSER Dxil struct types should only be used by ExtractValue. INSTR.DXILSTRUCTUSEROUTOFBOUND Index out of bound when extract value from dxil struct types. INSTR.EVALINTERPOLATIONMODE Interpolation mode on %0 used with eval_* instruction must be linear, linear_centroid, linear_noperspective, linear_noperspective_centroid, linear_sample or linear_noperspective_sample. +INSTR.EXPDXILOPCODEREQUIRESEXPSM Use of experimental DXILOpCode requires an experimental shader model. INSTR.EXTRACTVALUE ExtractValue should only be used on dxil struct types and cmpxchg. INSTR.FAILTORESLOVETGSMPOINTER TGSM pointers must originate from an unambiguous TGSM global variable. INSTR.HANDLENOTFROMCREATEHANDLE Resource handle should returned by createHandle. -INSTR.ILLEGALDXILOPCODE DXILOpCode must be [0..%0]. %1 specified. +INSTR.ILLEGALDXILOPCODE DXILOpCode must be valid or a supported experimental opcode. INSTR.ILLEGALDXILOPFUNCTION '%0' is not a DXILOpFuncition for DXILOpcode '%1'. INSTR.IMMBIASFORSAMPLEB bias amount for sample_b must be in the range [%0,%1], but %2 was specified as an immediate. INSTR.INBOUNDSACCESS Access to out-of-bounds memory is disallowed. diff --git a/include/dxc/DXIL/DxilConstants.h b/include/dxc/DXIL/DxilConstants.h index d66f14a1e9..faae653256 100644 --- a/include/dxc/DXIL/DxilConstants.h +++ b/include/dxc/DXIL/DxilConstants.h @@ -490,10 +490,44 @@ inline bool IsFeedbackTexture(DXIL::ResourceKind ResourceKind) { ResourceKind == DXIL::ResourceKind::FeedbackTexture2DArray; } +// clang-format off +// Python lines need to be not formatted. +/* hctdb_instrhelp.get_enum_decl("OpCodeTableID")*/ +// clang-format on +// OPCODETABLE-ENUM:BEGIN +// Enumeration for DXIL opcode tables +enum class OpCodeTableID : unsigned { + CoreOps = 0, // Core DXIL operations + ExperimentalOps = 32768, // Experimental DXIL operations + + NumOpCodeTables = 2, // exclusive last value of enumeration +}; +// OPCODETABLE-ENUM:END + +// clang-format off +// Python lines need to be not formatted. +/* hctdb_instrhelp.get_extended_table_opcode_enum_decls()*/ +// clang-format on +// EXTOPCODES-ENUM:BEGIN +namespace ExperimentalOps { +static const OpCodeTableID TableID = OpCodeTableID::ExperimentalOps; +// Enumeration for ExperimentalOps DXIL operations +enum class OpCode : unsigned { + ExperimentalNop = 0, // nop does nothing + + NumOpCodes = 1, // exclusive last value of enumeration +}; +} // namespace ExperimentalOps +// EXTOPCODES-ENUM:END + +#define EXP_OPCODE(feature, opcode) \ + opcode = \ + (((unsigned)feature::TableID << 16) | (unsigned)feature::OpCode::opcode) + // TODO: change opcodes. /* hctdb_instrhelp.get_enum_decl("OpCode")*/ // OPCODE-ENUM:BEGIN -// Enumeration for operations specified by DXIL +// Enumeration for CoreOps DXIL operations enum class OpCode : unsigned { // Reserved0 = 226, // reserved @@ -1089,9 +1123,24 @@ enum class OpCode : unsigned { NumOpCodes_Dxil_1_8 = 258, NumOpCodes_Dxil_1_9 = 312, - NumOpCodes = 312 // exclusive last value of enumeration + NumOpCodes = 312, // exclusive last value of enumeration + Invalid = 0xFFFFFFFF, // stable invalid OpCode value + + // OpCodes for extended tables follow. + + // OpCodeTableID = 32768 + // ExperimentalOps + EXP_OPCODE(ExperimentalOps, ExperimentalNop), // nop does nothing + }; // OPCODE-ENUM:END +#undef EXP_OPCODE + +// Create Core namespace for consistency with other opcode groups +namespace CoreOps { +static const OpCodeTableID TableID = OpCodeTableID::CoreOps; +using OpCode = hlsl::DXIL::OpCode; +} // namespace CoreOps // clang-format off // Python lines need to be not formatted. @@ -1244,6 +1293,9 @@ enum class OpCodeClass : unsigned { StorePrimitiveOutput, StoreVertexOutput, + // No-op + Nop, + // Other CycleCounterLegacy, @@ -1414,18 +1466,7 @@ enum class OpCodeClass : unsigned { NodeOutputIsValid, OutputComplete, - NumOpClasses_Dxil_1_0 = 93, - NumOpClasses_Dxil_1_1 = 95, - NumOpClasses_Dxil_1_2 = 97, - NumOpClasses_Dxil_1_3 = 118, - NumOpClasses_Dxil_1_4 = 120, - NumOpClasses_Dxil_1_5 = 143, - NumOpClasses_Dxil_1_6 = 149, - NumOpClasses_Dxil_1_7 = 153, - NumOpClasses_Dxil_1_8 = 174, - NumOpClasses_Dxil_1_9 = 196, - - NumOpClasses = 196 // exclusive last value of enumeration + NumOpClasses = 197, // exclusive last value of enumeration }; // OPCODECLASS-ENUM:END diff --git a/include/dxc/DXIL/DxilInstructions.h b/include/dxc/DXIL/DxilInstructions.h index 726d9615ac..52a3efaaac 100644 --- a/include/dxc/DXIL/DxilInstructions.h +++ b/include/dxc/DXIL/DxilInstructions.h @@ -10231,5 +10231,25 @@ struct DxilInst_FDot { llvm::Value *get_b() const { return Instr->getOperand(2); } void set_b(llvm::Value *val) { Instr->setOperand(2, val); } }; + +/// This instruction nop does nothing +struct DxilInst_ExperimentalNop { + llvm::Instruction *Instr; + // Construction and identification + DxilInst_ExperimentalNop(llvm::Instruction *pInstr) : Instr(pInstr) {} + operator bool() const { + return hlsl::OP::IsDxilOpFuncCallInst(Instr, + hlsl::OP::OpCode::ExperimentalNop); + } + // Validation support + bool isAllowed() const { return true; } + bool isArgumentListValid() const { + if (1 != llvm::dyn_cast(Instr)->getNumArgOperands()) + return false; + return true; + } + // Metadata + bool requiresUniformInputs() const { return false; } +}; // INSTR-HELPER:END } // namespace hlsl diff --git a/include/dxc/DXIL/DxilOperations.h b/include/dxc/DXIL/DxilOperations.h index c8b6762b3f..6bc672699b 100644 --- a/include/dxc/DXIL/DxilOperations.h +++ b/include/dxc/DXIL/DxilOperations.h @@ -37,6 +37,7 @@ class OP { public: using OpCode = DXIL::OpCode; using OpCodeClass = DXIL::OpCodeClass; + using OpCodeTableID = DXIL::OpCodeTableID; public: OP() = delete; @@ -131,6 +132,7 @@ class OP { llvm::Constant *GetFloatConst(float v); llvm::Constant *GetDoubleConst(double v); + static OP::OpCode getOpCode(unsigned OpCode); static OP::OpCode getOpCode(const llvm::Instruction *I); static llvm::Type *GetOverloadType(OpCode OpCode, llvm::Function *F); static OpCode GetDxilOpFuncCallInst(const llvm::Instruction *I); @@ -226,8 +228,7 @@ class OP { std::unordered_map m_FunctionToOpClass; void UpdateCache(OpCodeClass opClass, llvm::Type *Ty, llvm::Function *F); -private: - // Static properties. +public: struct OverloadMask { // mask of type slot bits as (1 << TypeSlot) uint16_t SlotMask; @@ -255,7 +256,31 @@ class OP { // AllowedOverloads[n][TS_Vector] is true. OverloadMask AllowedVectorElements[DXIL::kDxilMaxOloadDims]; }; - static const OpCodeProperty m_OpCodeProps[(unsigned)OpCode::NumOpCodes]; + struct OpCodeTable { + OpCodeTableID ID; + const OpCodeProperty *Table; + unsigned Count; + }; + + // Look up table using high 16-bits as table ID, low 16-bits as OpCode. + // Return true if valid. + // unsigned versions are for use with whatever value was in a DXIL Op + // instruction. + // OpIndex is the low 16-bits, for index lookup within the table. + static bool DecodeOpCode(unsigned EncodedOpCode, OpCodeTableID &TableID, + unsigned &OpIndex, + unsigned *OptTableIndex = nullptr); + static bool DecodeOpCode(OpCode EncodedOpCode, OpCodeTableID &TableID, + unsigned &OpIndex, + unsigned *OptTableIndex = nullptr); + static bool IsValidOpCode(unsigned EncodedOpCode); + static bool IsValidOpCode(OpCode EncodedOpCode); + +private: + // Static properties. + static OpCodeTable g_OpCodeTables[(unsigned)OpCodeTableID::NumOpCodeTables]; + static const OpCodeProperty &GetOpCodeProps(unsigned opCode); + static const OpCodeProperty &GetOpCodeProps(OpCode opCode); static const char *m_OverloadTypeName[TS_BasicCount]; static const char *m_NamePrefix; diff --git a/include/dxc/DXIL/DxilShaderModel.h b/include/dxc/DXIL/DxilShaderModel.h index c61903ea8f..2d10ec63f4 100644 --- a/include/dxc/DXIL/DxilShaderModel.h +++ b/include/dxc/DXIL/DxilShaderModel.h @@ -66,6 +66,9 @@ class ShaderModel { bool IsSMAtLeast(unsigned Major, unsigned Minor) const { return m_Major > Major || (m_Major == Major && m_Minor >= Minor); } + bool IsPreReleaseShaderModel() const { + return IsPreReleaseShaderModel(m_Major, m_Minor); + } bool IsSM50Plus() const { return IsSMAtLeast(5, 0); } bool IsSM51Plus() const { return IsSMAtLeast(5, 1); } bool AllowDerivatives(DXIL::ShaderKind sk) const; diff --git a/lib/DXIL/DxilOperations.cpp b/lib/DXIL/DxilOperations.cpp index 9902ef8d14..0a542bfb25 100644 --- a/lib/DXIL/DxilOperations.cpp +++ b/lib/DXIL/DxilOperations.cpp @@ -39,7 +39,7 @@ import hctdb_instrhelp */ /* hctdb_instrhelp.get_oloads_props()*/ // OPCODE-OLOADS:BEGIN -const OP::OpCodeProperty OP::m_OpCodeProps[(unsigned)OP::OpCode::NumOpCodes] = { +static const OP::OpCodeProperty CoreOps_OpCodeProps[] = { // Temporary, indexable, input, output registers {OC::TempRegLoad, "TempRegLoad", @@ -2715,6 +2715,32 @@ const OP::OpCodeProperty OP::m_OpCodeProps[(unsigned)OP::OpCode::NumOpCodes] = { {{0x400}}, {{0x3}}}, // Overloads: > 16); + unsigned TableIndex = GetOpCodeTableIndex(TID); + if (TableIndex >= (unsigned)OP::OpCodeTableID::NumOpCodeTables) + return false; + unsigned Op = (EncodedOpCode & 0xFFFF); + if (Op >= OP::g_OpCodeTables[TableIndex].Count) + return false; + TableID = (OP::OpCodeTableID)TID; + OpIndex = Op; + if (OptTableIndex) + *OptTableIndex = TableIndex; + return true; +} +bool OP::DecodeOpCode(OpCode EncodedOpCode, OP::OpCodeTableID &TableID, + unsigned &OpIndex, unsigned *OptTableIndex) { + return DecodeOpCode((unsigned)EncodedOpCode, TableID, OpIndex, OptTableIndex); +} +bool OP::IsValidOpCode(unsigned EncodedOpCode) { + if (EncodedOpCode == (unsigned)OP::OpCode::Invalid) + return false; + OP::OpCodeTableID TID; + unsigned OpIndex; + return DecodeOpCode(EncodedOpCode, TID, OpIndex); +} +bool OP::IsValidOpCode(OP::OpCode EncodedOpCode) { + return IsValidOpCode((unsigned)EncodedOpCode); +} +const OP::OpCodeProperty &OP::GetOpCodeProps(unsigned OriginalOpCode) { + OP::OpCodeTableID TID = OP::OpCodeTableID::CoreOps; + unsigned Op = 0; + unsigned TableIndex = 0; + bool Success = DecodeOpCode(OriginalOpCode, TID, Op, &TableIndex); + DXASSERT_LOCALVAR(Success, Success, "otherwise invalid OpCode"); + const OP::OpCodeTable &Table = OP::g_OpCodeTables[TableIndex]; + return Table.Table[Op]; +} +const OP::OpCodeProperty &OP::GetOpCodeProps(OP::OpCode OriginalOpCode) { + return GetOpCodeProps((unsigned)OriginalOpCode); +} + unsigned OP::GetTypeSlot(Type *pType) { Type::TypeID T = pType->getTypeID(); switch (T) { @@ -2842,7 +2926,7 @@ StringRef OP::ConstructOverloadName(Type *Ty, DXIL::OpCode opCode, } const char *OP::GetOpCodeName(OpCode opCode) { - return m_OpCodeProps[(unsigned)opCode].pOpCodeName; + return GetOpCodeProps(opCode).pOpCodeName; } const char *OP::GetAtomicOpName(DXIL::AtomicBinOpCode OpCode) { @@ -2854,24 +2938,23 @@ const char *OP::GetAtomicOpName(DXIL::AtomicBinOpCode OpCode) { } OP::OpCodeClass OP::GetOpCodeClass(OpCode opCode) { - return m_OpCodeProps[(unsigned)opCode].opCodeClass; + return GetOpCodeProps(opCode).opCodeClass; } const char *OP::GetOpCodeClassName(OpCode opCode) { - return m_OpCodeProps[(unsigned)opCode].pOpCodeClassName; + return GetOpCodeProps(opCode).pOpCodeClassName; } llvm::Attribute::AttrKind OP::GetMemAccessAttr(OpCode opCode) { - return m_OpCodeProps[(unsigned)opCode].FuncAttr; + return GetOpCodeProps(opCode).FuncAttr; } bool OP::IsOverloadLegal(OpCode opCode, Type *pType) { - if (static_cast(opCode) >= - static_cast(OpCode::NumOpCodes)) - return false; if (!pType) return false; - auto &OpProps = m_OpCodeProps[static_cast(opCode)]; + if (!IsValidOpCode(opCode)) + return false; + auto &OpProps = GetOpCodeProps(opCode); if (OpProps.NumOverloadDims == 0) return pType->isVoidTy(); @@ -2904,9 +2987,23 @@ bool OP::IsOverloadLegal(OpCode opCode, Type *pType) { } bool OP::CheckOpCodeTable() { - for (unsigned i = 0; i < (unsigned)OpCode::NumOpCodes; i++) { - if ((unsigned)m_OpCodeProps[i].opCode != i) - return false; + for (unsigned TableIndex = 0; + TableIndex < (unsigned)OP::OpCodeTableID::NumOpCodeTables; + TableIndex++) { + const OP::OpCodeTable &Table = OP::g_OpCodeTables[TableIndex]; + for (unsigned OpIndex = 0; OpIndex < Table.Count; OpIndex++) { + const OP::OpCodeProperty &Prop = Table.Table[OpIndex]; + OP::OpCodeTableID DecodedTID; + unsigned DecodedOpIndex; + unsigned DecodedTableIndex; + bool Success = OP::DecodeOpCode(Prop.opCode, DecodedTID, DecodedOpIndex, + &DecodedTableIndex); + if (!Success) + return false; + if (DecodedTID != Table.ID || DecodedOpIndex != OpIndex || + DecodedTableIndex != TableIndex) + return false; + } } return true; @@ -2937,14 +3034,19 @@ bool OP::IsDxilOpFuncCallInst(const llvm::Instruction *I, OpCode opcode) { return (unsigned)getOpCode(I) == (unsigned)opcode; } +OP::OpCode OP::getOpCode(unsigned OpCode) { + if (!IsValidOpCode(OpCode)) + return OP::OpCode::Invalid; + return static_cast(OpCode); +} OP::OpCode OP::getOpCode(const llvm::Instruction *I) { auto *OpConst = llvm::dyn_cast(I->getOperand(0)); if (!OpConst) - return OpCode::NumOpCodes; + return OpCode::Invalid; uint64_t OpCodeVal = OpConst->getZExtValue(); - if (OpCodeVal >= static_cast(OP::OpCode::NumOpCodes)) - return OP::OpCode::NumOpCodes; - return static_cast(OpCodeVal); + if (OpCodeVal >= static_cast(OP::OpCode::Invalid)) + return OP::OpCode::Invalid; + return getOpCode(static_cast(OpCodeVal)); } OP::OpCode OP::GetDxilOpFuncCallInst(const llvm::Instruction *I) { @@ -3014,9 +3116,9 @@ bool OP::IsDxilOpBarrier(OpCode C) { } bool OP::IsDxilOpExtendedOverload(OpCode C) { - if (C >= OpCode::NumOpCodes) + if (!IsValidOpCode(C)) return false; - return m_OpCodeProps[static_cast(C)].NumOverloadDims > 1; + return GetOpCodeProps(C).NumOverloadDims > 1; } static unsigned MaskMemoryTypeFlagsIfAllowed(unsigned memoryTypeFlags, @@ -3536,8 +3638,8 @@ void OP::GetMinShaderModelAndMask(OpCode C, bool bWithTranslation, return; } // Instructions: MatVecMul=305, MatVecMulAdd=306, OuterProductAccumulate=307, - // VectorAccumulate=308 - if ((305 <= op && op <= 308)) { + // VectorAccumulate=308, ExperimentalNop=2147483648 + if ((305 <= op && op <= 308) || op == 2147483648) { major = 6; minor = 10; return; @@ -3645,8 +3747,6 @@ OP::OP(LLVMContext &Ctx, Module *pModule) memset(m_pResRetType, 0, sizeof(m_pResRetType)); memset(m_pCBufferRetType, 0, sizeof(m_pCBufferRetType)); memset(m_OpCodeClassCache, 0, sizeof(m_OpCodeClassCache)); - static_assert(_countof(OP::m_OpCodeProps) == (size_t)OP::OpCode::NumOpCodes, - "forgot to update OP::m_OpCodeProps"); m_pHandleType = GetOrCreateStructType(m_Ctx, Type::getInt8PtrTy(m_Ctx), "dx.types.Handle", pModule); @@ -3750,10 +3850,10 @@ void OP::UpdateCache(OpCodeClass opClass, Type *Ty, llvm::Function *F) { } bool OP::MayHaveNonCanonicalOverload(OpCode OC) { - if (OC >= OpCode::NumOpCodes) + if (!IsValidOpCode(OC)) return false; const unsigned CheckMask = (1 << TS_UDT) | (1 << TS_Object); - auto &OpProps = m_OpCodeProps[static_cast(OC)]; + auto &OpProps = GetOpCodeProps(OC); for (unsigned I = 0; I < OpProps.NumOverloadDims; ++I) if ((CheckMask & OpProps.AllowedOverloads[I].SlotMask) != 0) return true; @@ -3761,10 +3861,9 @@ bool OP::MayHaveNonCanonicalOverload(OpCode OC) { } Function *OP::GetOpFunc(OpCode OC, ArrayRef OverloadTypes) { - if (OC >= OpCode::NumOpCodes) + if (!IsValidOpCode(OC)) return nullptr; - if (OverloadTypes.size() != - m_OpCodeProps[static_cast(OC)].NumOverloadDims) { + if (OverloadTypes.size() != GetOpCodeProps(OC).NumOverloadDims) { llvm_unreachable("incorrect overload dimensions"); return nullptr; } @@ -3777,12 +3876,12 @@ Function *OP::GetOpFunc(OpCode OC, ArrayRef OverloadTypes) { } Function *OP::GetOpFunc(OpCode opCode, Type *pOverloadType) { - if (opCode >= OpCode::NumOpCodes) + if (!IsValidOpCode(opCode)) return nullptr; if (!pOverloadType) return nullptr; - auto &OpProps = m_OpCodeProps[static_cast(opCode)]; + auto &OpProps = GetOpCodeProps(opCode); if (IsDxilOpExtendedOverload(opCode)) { // Make sure pOverloadType is well formed for an extended overload. StructType *ST = dyn_cast(pOverloadType); @@ -6044,6 +6143,12 @@ Function *OP::GetOpFunc(OpCode opCode, Type *pOverloadType) { A(pETy); A(pETy); break; + + // No-op + case OpCode::ExperimentalNop: + A(pV); + A(pI32); + break; // OPCODE-OLOAD-FUNCS:END default: DXASSERT(false, "otherwise unhandled case"); @@ -6086,8 +6191,7 @@ Function *OP::GetOpFunc(OpCode opCode, Type *pOverloadType) { const SmallMapVector & OP::GetOpFuncList(OpCode opCode) const { - return m_OpCodeClassCache[(unsigned)m_OpCodeProps[(unsigned)opCode] - .opCodeClass] + return m_OpCodeClassCache[(unsigned)GetOpCodeProps(opCode).opCodeClass] .pOverloads; } @@ -6335,6 +6439,7 @@ llvm::Type *OP::GetOverloadType(OpCode opCode, llvm::Function *F) { case OpCode::ReservedC7: case OpCode::ReservedC8: case OpCode::ReservedC9: + case OpCode::ExperimentalNop: return Type::getVoidTy(Ctx); case OpCode::CheckAccessFullyMapped: case OpCode::SampleIndex: diff --git a/lib/DxilValidation/DxilValidation.cpp b/lib/DxilValidation/DxilValidation.cpp index 2ea6701581..8c962aca23 100644 --- a/lib/DxilValidation/DxilValidation.cpp +++ b/lib/DxilValidation/DxilValidation.cpp @@ -2520,7 +2520,11 @@ static void ValidateExternalFunction(Function *F, ValidationContext &ValCtx) { } unsigned Opcode = ConstOpcode->getLimitedValue(); - if (Opcode >= (unsigned)DXIL::OpCode::NumOpCodes) { + OP::OpCodeTableID TableID; + unsigned OpIndex; + if (!OP::DecodeOpCode(Opcode, TableID, OpIndex) || + (TableID != OP::OpCodeTableID::CoreOps && + !pSM->IsPreReleaseShaderModel())) { // invalid Opcode; function body will validate this error. continue; } @@ -3205,6 +3209,8 @@ static void ValidateFunctionBody(Function *F, ValidationContext &ValCtx) { ValCtx.DxilMod.GetGlobalFlags() & DXIL::kEnableMinPrecision; bool SupportsLifetimeIntrinsics = ValCtx.DxilMod.GetShaderModel()->IsSM66Plus(); + bool ExperimentalShaderModel = + ValCtx.DxilMod.GetShaderModel()->IsPreReleaseShaderModel(); SmallVector GradientOps; SmallVector Barriers; CallInst *SetMeshOutputCounts = nullptr; @@ -3262,13 +3268,21 @@ static void ValidateFunctionBody(Function *F, ValidationContext &ValCtx) { } unsigned Opcode = OpcodeConst->getLimitedValue(); - if (Opcode >= static_cast(DXIL::OpCode::NumOpCodes)) { + OP::OpCodeTableID TableID; + unsigned OpIndex; + if (!OP::DecodeOpCode(Opcode, TableID, OpIndex)) { ValCtx.EmitInstrFormatError( &I, ValidationRule::InstrIllegalDXILOpCode, {std::to_string((unsigned)DXIL::OpCode::NumOpCodes), std::to_string(Opcode)}); continue; } + if (TableID != OP::OpCodeTableID::CoreOps && + !ExperimentalShaderModel) { + ValCtx.EmitInstrError( + &I, ValidationRule::InstrExpDXILOpCodeRequiresExpSM); + continue; + } DXIL::OpCode DxilOpcode = (DXIL::OpCode)Opcode; bool IllegalOpFunc = true; diff --git a/tools/clang/test/DXC/experimental-dxil-6-10-op.ll b/tools/clang/test/DXC/experimental-dxil-6-10-op.ll new file mode 100644 index 0000000000..4868648d2c --- /dev/null +++ b/tools/clang/test/DXC/experimental-dxil-6-10-op.ll @@ -0,0 +1,44 @@ +; REQUIRES: dxil-1-10 +; RUN: %dxa %s -o %t.dxil | FileCheck %s -check-prefix=DXA +; RUN: %dxc -dumpbin %t.dxil | FileCheck %s -check-prefix=DXIL +; RUN: %dxv %t.dxil -o %t.hash.dxil 2>&1 | FileCheck %s -check-prefix=VAL +; RUN: %dxa %t.hash.dxil -dumphash | FileCheck %s -check-prefix=HASH + +; DXA: Assembly succeeded. + +; DXIL: call void @dx.op.nop(i32 -2147483648) ; ExperimentalNop(index) +; DXIL: declare void @dx.op.nop(i32) #[[ATTR:[0-9]+]] +; DXIL: attributes #[[ATTR]] = { nounwind readnone } + +; VAL: Validation succeeded. + +; Make sure it's the PREVIEW hash. +; HASH: Validation hash: 0x02020202020202020202020202020202 + +target datalayout = "e-m:e-p:32:32-i1:32-i8:8-i16:16-i32:32-i64:64-f16:16-f32:32-f64:64-n8:16:32:64" +target triple = "dxil-ms-dx" + +define void @main() { + call void @dx.op.nop(i32 -2147483648) + ret void +} + +; Function Attrs: nounwind readnone +declare void @dx.op.nop(i32) #0 + +attributes #0 = { nounwind readnone } + +!llvm.ident = !{!0} +!dx.version = !{!1} +!dx.valver = !{!1} +!dx.shaderModel = !{!2} +!dx.resources = !{!3} +!dx.entryPoints = !{!4} + +!0 = !{!"custom IR"} +!1 = !{i32 1, i32 10} +!2 = !{!"cs", i32 6, i32 10} +!3 = !{null, null, null, null} +!4 = !{void ()* @main, !"main", null, !3, !5} +!5 = !{i32 0, i64 0, i32 4, !6} +!6 = !{i32 4, i32 1, i32 1} diff --git a/tools/clang/test/LitDXILValidation/illegalDXILOp.ll b/tools/clang/test/LitDXILValidation/illegalDXILOp.ll index 1ff27428ef..3997a1cecc 100644 --- a/tools/clang/test/LitDXILValidation/illegalDXILOp.ll +++ b/tools/clang/test/LitDXILValidation/illegalDXILOp.ll @@ -39,10 +39,16 @@ define void @main() { %6 = call %dx.types.Handle @dx.op.annotateHandle2(i32 216, %dx.types.Handle %2, %dx.types.ResourceProperties { i32 32782, i32 0 }) ; AnnotateHandle(res,props) resource: SamplerComparisonState -; CHECK: error: DXILOpCode must be [0..{{[0-9]+}}]. 1999981 specified. -; CHECK: note: at '%7 = call float @dx.op.calculateLOD.f32(i32 1999981, %dx.types.Handle %5, %dx.types.Handle %6, float %3, float %4, float undef, i1 true)' in block '#0' of function 'main'. +; CHECK: error: DXILOpCode must be [0..{{[0-9]+}}] or a supported experimental opcode. 12345 specified. +; CHECK: note: at '%7 = call float @dx.op.calculateLOD.f32(i32 12345, %dx.types.Handle %5, %dx.types.Handle %6, float %3, float %4, float undef, i1 true)' in block '#0' of function 'main'. - %7 = call float @dx.op.calculateLOD.f32(i32 1999981, %dx.types.Handle %5, %dx.types.Handle %6, float %3, float %4, float undef, i1 true) ; CalculateLOD(handle,sampler,coord0,coord1,coord2,clamped) + %7 = call float @dx.op.calculateLOD.f32(i32 12345, %dx.types.Handle %5, %dx.types.Handle %6, float %3, float %4, float undef, i1 true) ; CalculateLOD(handle,sampler,coord0,coord1,coord2,clamped) + +; Try Opcode with invalid table ID (1) +; CHECK: error: DXILOpCode must be [0..{{[0-9]+}}] or a supported experimental opcode. 65536 specified. +; CHECK: note: at '%invalid_optable = call float @dx.op.calculateLOD.f32(i32 65536, %dx.types.Handle %5, %dx.types.Handle %6, float %3, float %4, float undef, i1 true)' in block '#0' of function 'main'. + + %invalid_optable = call float @dx.op.calculateLOD.f32(i32 65536, %dx.types.Handle %5, %dx.types.Handle %6, float %3, float %4, float undef, i1 true) ; CalculateLOD(handle,sampler,coord0,coord1,coord2,clamped) %I = call i32 @dx.op.loadInput.i32(i32 4, i32 0, i32 0, i8 0, i32 undef) ; LoadInput(inputSigId,rowIndex,colIndex,gsVertexAxis) diff --git a/tools/clang/test/LitDXILValidation/invalid-experimental-dxil-6-10-op-on-6-8.ll b/tools/clang/test/LitDXILValidation/invalid-experimental-dxil-6-10-op-on-6-8.ll new file mode 100644 index 0000000000..438cb03c2c --- /dev/null +++ b/tools/clang/test/LitDXILValidation/invalid-experimental-dxil-6-10-op-on-6-8.ll @@ -0,0 +1,35 @@ +; REQUIRES: dxil-1-10 +; RUN: not %dxv %s 2>&1 | FileCheck %s +target datalayout = "e-m:e-p:32:32-i1:32-i8:8-i16:16-i32:32-i64:64-f16:16-f32:32-f64:64-n8:16:32:64" +target triple = "dxil-ms-dx" + +; CHECK: Function: main: error: Use of experimental DXILOpCode requires an experimental shader model. +; CHECK-NEXT: note: at 'call void @dx.op.nop(i32 -2147483648)' in block '#0' of function 'main'. +; CHECK-NEXT: Function: main: error: Entry function performs some operation that is incompatible with the shader stage or other entry properties. See other errors for details. +; CHECK-NEXT: Function: main: error: Function uses features incompatible with the shader model. +; CHECK-NEXT: Validation failed. + +define void @main() { + call void @dx.op.nop(i32 -2147483648) + ret void +} + +; Function Attrs: nounwind readnone +declare void @dx.op.nop(i32) #0 + +attributes #0 = { nounwind readnone } + +!llvm.ident = !{!0} +!dx.version = !{!1} +!dx.valver = !{!1} +!dx.shaderModel = !{!2} +!dx.resources = !{!3} +!dx.entryPoints = !{!6} + +!0 = !{!"custom IR"} +!1 = !{i32 1, i32 8} +!2 = !{!"cs", i32 6, i32 8} +!3 = !{null, null, null, null} +!6 = !{void ()* @main, !"main", null, !3, !7} +!7 = !{i32 0, i64 0, i32 4, !8} +!8 = !{i32 4, i32 1, i32 1} diff --git a/tools/clang/test/LitDXILValidation/invalid-experimental-dxil-6-10-op-on-6-9.ll b/tools/clang/test/LitDXILValidation/invalid-experimental-dxil-6-10-op-on-6-9.ll new file mode 100644 index 0000000000..93434cc584 --- /dev/null +++ b/tools/clang/test/LitDXILValidation/invalid-experimental-dxil-6-10-op-on-6-9.ll @@ -0,0 +1,35 @@ +; REQUIRES: dxil-1-10 +; RUN: not %dxv %s 2>&1 | FileCheck %s +target datalayout = "e-m:e-p:32:32-i1:32-i8:8-i16:16-i32:32-i64:64-f16:16-f32:32-f64:64-n8:16:32:64" +target triple = "dxil-ms-dx" + +; CHECK: Function: main: error: Opcode ExperimentalNop not valid in shader model cs_6_9. +; CHECK-NEXT: note: at 'call void @dx.op.nop(i32 -2147483648)' in block '#0' of function 'main'. +; CHECK-NEXT: Function: main: error: Entry function performs some operation that is incompatible with the shader stage or other entry properties. See other errors for details. +; CHECK-NEXT: Function: main: error: Function uses features incompatible with the shader model. +; CHECK-NEXT: Validation failed. + +define void @main() { + call void @dx.op.nop(i32 -2147483648) + ret void +} + +; Function Attrs: nounwind readnone +declare void @dx.op.nop(i32) #0 + +attributes #0 = { nounwind readnone } + +!llvm.ident = !{!0} +!dx.version = !{!1} +!dx.valver = !{!1} +!dx.shaderModel = !{!2} +!dx.resources = !{!3} +!dx.entryPoints = !{!6} + +!0 = !{!"custom IR"} +!1 = !{i32 1, i32 9} +!2 = !{!"cs", i32 6, i32 9} +!3 = !{null, null, null, null} +!6 = !{void ()* @main, !"main", null, !3, !7} +!7 = !{i32 0, i64 0, i32 4, !8} +!8 = !{i32 4, i32 1, i32 1} diff --git a/tools/clang/tools/dxcompiler/dxcdisassembler.cpp b/tools/clang/tools/dxcompiler/dxcdisassembler.cpp index 16d8b1dadd..1ad8474e50 100644 --- a/tools/clang/tools/dxcompiler/dxcdisassembler.cpp +++ b/tools/clang/tools/dxcompiler/dxcdisassembler.cpp @@ -1312,7 +1312,10 @@ class DxcAssemblyAnnotationWriter : public llvm::AssemblyAnnotationWriter { } unsigned opcodeVal = CInt->getZExtValue(); - if (opcodeVal >= (unsigned)DXIL::OpCode::NumOpCodes) { + OP::OpCodeTableID TableID; + unsigned OpIndex; + unsigned TableIndex; + if (!hlsl::OP::DecodeOpCode(opcodeVal, TableID, OpIndex, &TableIndex)) { OS << " ; invalid DXIL opcode #" << opcodeVal; return; } @@ -1321,7 +1324,7 @@ class DxcAssemblyAnnotationWriter : public llvm::AssemblyAnnotationWriter { // name/binding DXIL::OpCode opcode = (DXIL::OpCode)opcodeVal; OS << " ; " << hlsl::OP::GetOpCodeName(opcode) - << OpCodeSignatures[opcodeVal]; + << OpCodeSignatures[TableIndex][OpIndex]; // Add extra decoding for certain ops switch (opcode) { diff --git a/utils/hct/hctdb.py b/utils/hct/hctdb.py index 3639f49c76..2f4c24a877 100644 --- a/utils/hct/hctdb.py +++ b/utils/hct/hctdb.py @@ -82,6 +82,9 @@ def __init__(self, name, doc, valNameDocTuples=()): db_dxil_enum_value(n, v, d) for v, n, d in valNameDocTuples ] # Note transmutation self.is_internal = False # whether this is never serialized + self.last_value_name = None # optional last value name for dense enums + self.dxil_version_info = {} # version info for this enum + self.postfix_lines = [] # optional postfix to include inside enum declaration def value_names(self): return [i.name for i in self.values] @@ -94,6 +97,7 @@ def __init__(self, name, **kwargs): self.name = name # short, unique name self.llvm_id = 0 # ID of LLVM instruction self.llvm_name = "" # name of LLVM instruction type + self.dxil_table = "CoreOps" # name of the DXIL operation table self.is_dxil_op = False # whether this is a call into a built-in DXIL function self.dxil_op = "" # name of DXIL operation @@ -117,19 +121,29 @@ def __init__(self, name, **kwargs): self.shader_model = 6, 0 # minimum shader model required self.inst_helper_prefix = None self.fully_qualified_name_prefix = "hlsl::OP::OpCode" + self.shader_model_translated = () # minimum shader model required with translation by linker + self.props = {} # extra properties + self.num_oloads = 0 # number of overloads for this instruction + for k, v in list(kwargs.items()): setattr(self, k, v) + self.is_dxil_op = self.dxil_op != "" # whether this is a DXIL operation self.is_reserved = self.dxil_class == "Reserved" - self.shader_model_translated = () # minimum shader model required with translation by linker - self.props = {} # extra properties - self.num_oloads = 0 # number of overloads for this instruction if self.is_dxil_op: self.process_oload_types() def __str__(self): return self.name + def dxil_op_index(self): + "Get the index of this DXIL op in its table." + return self.dxil_opid & 0xFFFF + + def table_id(self): + "Get the table ID of this DXIL op." + return (self.dxil_opid >> 16) & 0xFFFF + def fully_qualified_name(self): return "{}::{}".format(self.fully_qualified_name_prefix, self.name) @@ -333,26 +347,72 @@ def __str__(self): return self.name +# DXIL operations are grouped into tables to support experimental and extended +# features. +class db_dxil_op_table(object): + "Table definition for a set of DXIL operations" + + def __init__(self, id, name, doc): + assert id & ~0xFFFF == 0, "DXIL op table ID must fit in high 16 bits" + self.id = id + self.name = name + self.doc = doc + self.instr = [] # DXIL instructions + self.op_count = 0 + self.op_enum = db_dxil_enum( + "OpCode", f"Enumeration for {self.name} DXIL operations" + ) + self.op_enum.last_value_name = "NumOpCodes" + + def next_id(self): + new_id = self.op_count + self.op_count += 1 + return (self.id << 16) | new_id + + def get_count(self): + return self.op_count + + def set_op_count_for_version(self, major, minor): + op_count = self.get_count() + self.op_enum.dxil_version_info[(major, minor)] = op_count + return op_count + + class db_dxil(object): "A database of DXIL instruction data" def __init__(self): - self.instr = [] # DXIL instructions + self.instr = [] # all LLVM instructions and DXIL operations self.enums = [] # enumeration types self.val_rules = [] # validation rules self.metadata = [] # named metadata (db_dxil_metadata) self.passes = [] # inventory of available passes (db_dxil_pass) self.name_idx = {} # DXIL instructions by name self.enum_idx = {} # enumerations by name - self.dxil_version_info = {} # list of counters for instructions and dxil ops, # starting with extra ones specified here self.counters = extra_counters - self.next_dxil_op_id = 0 # next available DXIL op ID + self.dxil_op_tables = [ + db_dxil_op_table(0, "CoreOps", "Core DXIL operations"), + db_dxil_op_table(0x8000, "ExperimentalOps", "Experimental DXIL operations"), + ] + self.dxil_op_tables_by_name = dict([(t.name, t) for t in self.dxil_op_tables]) + # Table id should be strictly increasing. + last_table_id = None + for t in self.dxil_op_tables: + if last_table_id is not None: + assert ( + t.id > last_table_id + ), "DXIL op table IDs must be strictly increasing" + last_table_id = t.id + # Set cur_table. + self.set_dxil_op_table() self.populate_llvm_instructions() self.call_instr = self.get_instr_by_llvm_name("CallInst") self.populate_dxil_operations() + self.populate_experimental_ops() + self.finalize_dxil_operations() self.build_indices() self.populate_extended_docs() self.populate_categories_and_models() @@ -365,15 +425,17 @@ def __init__(self): self.build_indices() self.populate_counters() + def set_dxil_op_table(self, table_name="CoreOps"): + "Set the current DXIL operation table, defaulting to CoreOps." + self.cur_table = self.dxil_op_tables_by_name[table_name] + + def get_dxil_op_table(self, table_name="CoreOps"): + "Get the specified DXIL operation table." + return self.dxil_op_tables_by_name[table_name] + def __str__(self): return "\n".join(str(i) for i in self.instr) - def next_id(self): - "Returns the next available DXIL op ID and increments the counter" - val = self.next_dxil_op_id - self.next_dxil_op_id += 1 - return val - def add_enum_type(self, name, doc, valNameDocTuples): "Adds a new enumeration type with name/value/doc tuples" self.enums.append(db_dxil_enum(name, doc, valNameDocTuples)) @@ -389,28 +451,56 @@ def build_indices(self): def build_opcode_enum(self): # Build enumeration from instructions - OpCodeEnum = db_dxil_enum( - "OpCode", "Enumeration for operations specified by DXIL" - ) + OpCodeEnum = self.get_dxil_op_table().op_enum class_dict = {} class_dict["LlvmInst"] = "LLVM Instructions" - for i in self.instr: + for i in self.get_dxil_op_table().instr: if i.is_dxil_op: v = db_dxil_enum_value(i.dxil_op, i.dxil_opid, i.doc) v.category = i.category class_dict[i.dxil_class] = i.category OpCodeEnum.values.append(v) + postfix = OpCodeEnum.postfix_lines or [] + # Add OpCode::Invalid + postfix.append("Invalid = 0xFFFFFFFF, // stable invalid OpCode value") + # Generate extended opcode enums into OpCodeEnum.postfix_lines: + OpCodeTableID = db_dxil_enum( + "OpCodeTableID", + "Enumeration for DXIL opcode tables", + [(0, "CoreOps", "Core DXIL operations")], + ) + OpCodeTableID.last_value_name = "NumOpCodeTables" + postfix.append("") + postfix.append("// OpCodes for extended tables follow.\n") + for table in self.dxil_op_tables[1:]: # Skip CoreOps table + OpCodeTableID.values.append( + db_dxil_enum_value(table.name, table.id, table.doc) + ) + if not table.get_count(): + continue + postfix.append(f"// OpCodeTableID = {table.id}") + postfix.append(f"// {table.name}") + for i in table.instr: + class_dict[i.dxil_class] = i.category + postfix.append(f"EXP_OPCODE({table.name}, {i.dxil_op}), // {i.doc}") + table.op_enum.values.append( + db_dxil_enum_value(i.dxil_op, i.dxil_op_index(), i.doc) + ) + postfix.append("") + OpCodeEnum.postfix_lines = postfix self.enums.append(OpCodeEnum) OpCodeClass = db_dxil_enum( "OpCodeClass", "Groups for DXIL operations with equivalent function templates", ) OpCodeClass.is_internal = True + OpCodeClass.last_value_name = "NumOpClasses" for k, v in iter(class_dict.items()): ev = db_dxil_enum_value(k, 0, None) ev.category = v OpCodeClass.values.append(ev) self.enums.append(OpCodeClass) + self.enums.append(OpCodeTableID) def mark_disallowed_operations(self): # Disallow indirect branching, unreachable instructions and support for exception unwinding. @@ -436,10 +526,7 @@ def verify_dense(self, it, pred, name_proj): val = i_val def set_op_count_for_version(self, major, minor): - info = self.dxil_version_info.setdefault((major, minor), dict()) - info["NumOpCodes"] = self.next_dxil_op_id - info["NumOpClasses"] = len(set([op.dxil_class for op in self.instr])) - return self.next_dxil_op_id + return self.cur_table.set_op_count_for_version(major, minor) def populate_categories_and_models(self): "Populate the category and shader_stages member of instructions." @@ -886,6 +973,16 @@ def populate_categories_and_models(self): ).split(","): self.name_idx[i].category = "Linear Algebra Operations" self.name_idx[i].shader_model = 6, 10 + # End of core DXIL ops + self.populate_categories_and_models_ExperimentalOps() + + def populate_categories_and_models_ExperimentalOps(self): + # Note: Experimental ops must be set to a shader model higher than the + # most recent release until infrastructure is in place to opt-in to + # experimental ops and the validator can force use of the PREVIEW hash. + for i in "ExperimentalNop".split(","): + self.name_idx[i].category = "No-op" + self.name_idx[i].shader_model = 6, 10 def populate_llvm_instructions(self): # Add instructions that map to LLVM instructions. @@ -5889,6 +5986,27 @@ def UFI(name, **mappings): % op_count ) + def populate_experimental_ops(self): + "Populate experimental DXIL operations." + self.set_dxil_op_table("ExperimentalOps") + self.populate_ExperimentalOps_ops() + self.set_dxil_op_table() + + def populate_ExperimentalOps_ops(self): + "Populate experimental DXIL operations for ExperimentalOps." + # Add Nop to test experimental table infrastructure. + self.add_dxil_op( + "ExperimentalNop", + "Nop", + "nop does nothing", + "v", + "rn", + [ + db_dxil_param(0, "v", "", "no result"), + ], + ) + + def finalize_dxil_operations(self): # Set interesting properties. self.build_indices() for ( @@ -5905,11 +6023,15 @@ def UFI(name, **mappings): # TODO - some arguments are required to be immediate constants in DXIL, eg resource kinds; add this information # consider - report instructions that are overloaded on a single type, then turn them into non-overloaded version of that type - self.verify_dense( - self.get_dxil_insts(), lambda x: x.dxil_opid, lambda x: x.name - ) - for i in self.instr: - self.verify_dense(i.ops, lambda x: x.pos, lambda x: i.name) + for table in self.dxil_op_tables: + insts = [i for i in table.instr if i.is_dxil_op] + self.verify_dense(insts, lambda x: x.dxil_opid, lambda x: x.name) + for i in insts: + assert i.table_id() == table.id, ( + "dxil op %s has table id %d inconsistent with containing table id %d" + % (i.name, i.table_id(), table.id) + ) + self.verify_dense(i.ops, lambda x: x.pos, lambda x: i.name) # Verify that all operations in each class have the same signature. import itertools @@ -7556,8 +7678,17 @@ def build_valrules(self): "Instr.ImmBiasForSampleB", "bias amount for sample_b must be in the range [%0,%1], but %2 was specified as an immediate.", ) + self.add_valrule_msg( + "Instr.IllegalDXILOpCode", + "DXILOpCode must be valid or a supported experimental opcode.", + "DXILOpCode must be [0..%0] or a supported experimental opcode. %1 specified.", + ) + # In the future, if experimental opcodes are allowed in non-experimental + # shader models, the following rule will need to change to one requiring + # a flag to support experimental opcodes. self.add_valrule( - "Instr.IllegalDXILOpCode", "DXILOpCode must be [0..%0]. %1 specified." + "Instr.ExpDXILOpCodeRequiresExpSM", + "Use of experimental DXILOpCode requires an experimental shader model.", ) self.add_valrule( "Instr.IllegalDXILOpFunction", @@ -8498,7 +8629,7 @@ def populate_counters(self): self.dxil_op_counters = set() for i in self.instr: counters = getattr(i, "props", {}).get("counters", ()) - if i.dxil_opid: + if i.is_dxil_op: self.dxil_op_counters.update(counters) else: self.llvm_op_counters.update(counters) @@ -8517,9 +8648,17 @@ def add_valrule_msg(self, name, desc, err_msg): db_dxil_valrule(name, len(self.val_rules), err_msg=err_msg, doc=desc) ) + def add_inst(self, i): + assert i.table_id() == self.cur_table.id, "Instruction table mismatch" + self.cur_table.instr.append(i) + self.instr.append(i) + def add_llvm_instr( self, kind, llvm_id, name, llvm_name, doc, oload_types, op_params, **props ): + assert ( + self.cur_table.name == "CoreOps" + ), "LLVM instructions can only be added to the CoreOps table" i = db_dxil_inst( name, llvm_id=llvm_id, @@ -8529,7 +8668,7 @@ def add_llvm_instr( oload_types=oload_types, ) i.props = props - self.instr.append(i) + self.add_inst(i) def add_dxil_op( self, name, code_class, doc, oload_types, fn_attr, op_params, **props @@ -8541,7 +8680,8 @@ def add_dxil_op( llvm_id=self.call_instr.llvm_id, llvm_name=self.call_instr.llvm_name, dxil_op=name, - dxil_opid=self.next_id(), + dxil_opid=self.cur_table.next_id(), + dxil_table=self.cur_table.name, doc=doc, ops=op_params, dxil_class=code_class, @@ -8549,7 +8689,7 @@ def add_dxil_op( fn_attr=fn_attr, ) i.props = props - self.instr.append(i) + self.add_inst(i) def add_dxil_op_reserved(self, name): # The return value is parameter 0, insert the opcode as 1. @@ -8559,14 +8699,15 @@ def add_dxil_op_reserved(self, name): llvm_id=self.call_instr.llvm_id, llvm_name=self.call_instr.llvm_name, dxil_op=name, - dxil_opid=self.next_id(), + dxil_opid=self.cur_table.next_id(), + dxil_table=self.cur_table.name, doc="reserved", ops=op_params, dxil_class="Reserved", oload_types="v", fn_attr="", ) - self.instr.append(i) + self.add_inst(i) def reserve_dxil_op_range(self, group_name, count, start_reserved_id=0): "Reserve a range of dxil opcodes for future use; returns next id" diff --git a/utils/hct/hctdb_instrhelp.py b/utils/hct/hctdb_instrhelp.py index 9debd6e07f..e587bec906 100644 --- a/utils/hct/hctdb_instrhelp.py +++ b/utils/hct/hctdb_instrhelp.py @@ -423,8 +423,6 @@ class db_enumhelp_gen: def __init__(self, db): self.db = db - # Some enums should get a last enum marker. - self.lastEnumNames = {"OpCode": "NumOpCodes", "OpCodeClass": "NumOpClasses"} def print_enum(self, e, **kwargs): print("// %s" % e.doc) @@ -451,12 +449,11 @@ def print_enum(self, e, **kwargs): if v.doc: line_format += " // {doc}" print(line_format.format(name=v.name, value=v.value, doc=v.doc)) - if e.name in self.lastEnumNames: - lastName = self.lastEnumNames[e.name] + if e.last_value_name: + lastName = e.last_value_name versioned = [ - "%s_Dxil_%d_%d = %d," % (lastName, major, minor, info[lastName]) - for (major, minor), info in sorted(self.db.dxil_version_info.items()) - if lastName in info + "%s_Dxil_%d_%d = %d," % (lastName, major, minor, count) + for (major, minor), count in sorted(e.dxil_version_info.items()) ] if versioned: print("") @@ -468,8 +465,11 @@ def print_enum(self, e, **kwargs): + lastName + " = " + str(len(sorted_values)) - + " // exclusive last value of enumeration" + + ", // exclusive last value of enumeration" ) + if e.postfix_lines: + for line in e.postfix_lines: + print(" " + line) print("};") def print_rdat_enum(self, e, **kwargs): @@ -484,6 +484,13 @@ def print_rdat_enum(self, e, **kwargs): line_format += " // {doc}" print(line_format.format(name=v.name, value=v.value, doc=v.doc)) + def print_extended_table_opcode_enums(self): + for table in self.db.dxil_op_tables[1:]: # Skip Core table + print(f"namespace {table.name} {{") + print(f"static const OpCodeTableID TableID = OpCodeTableID::{table.name};") + self.print_enum(table.op_enum) + print(f"}} // namespace {table.name}") + def print_content(self): for e in sorted(self.db.enums, key=lambda e: e.name): self.print_enum(e) @@ -494,13 +501,7 @@ class db_oload_gen: def __init__(self, db): self.db = db - instrs = [i for i in self.db.instr if i.is_dxil_op] - self.instrs = sorted(instrs, key=lambda i: i.dxil_opid) - - # Allow these to be overridden by external scripts. - self.OP = "OP" - self.OC = "OC" - self.OCC = "OCC" + self.instrs = [i for i in self.db.instr if i.is_dxil_op] def print_content(self): self.print_opfunc_props() @@ -508,11 +509,27 @@ def print_content(self): self.print_opfunc_table() def print_opfunc_props(self): + # Print all the tables for OP::m_OpCodeProps + for table in self.db.dxil_op_tables: + self.print_opfunc_props_for_table(table) + print() + # Print the overall table of tables + print("// Table of DXIL OpCode Property tables") print( - "const {OP}::OpCodeProperty {OP}::m_OpCodeProps[(unsigned){OP}::OpCode::NumOpCodes] = {{".format( - OP=self.OP - ) + f"OP::OpCodeTable OP::g_OpCodeTables[(unsigned)" + + f"OP::OpCodeTableID::NumOpCodeTables] = {{" ) + for table in self.db.dxil_op_tables: + print( + f" {{ OP::OpCodeTableID::{table.name}, " + + f"{table.name}_OpCodeProps, " + + f"(unsigned)DXIL::{table.name}::OpCode::NumOpCodes }}," + ) + print("};") + + def print_opfunc_props_for_table(self, table): + print(f"static const OP::OpCodeProperty {table.name}_OpCodeProps[] = {{") + instrs = [i for i in table.instr if i.is_dxil_op] last_category = None lower_exceptions = { @@ -539,7 +556,7 @@ def print_opfunc_props(self): oloads_fn = lambda oloads: ( "{" + ",".join(["{0x%x}" % m for m in oloads]) + "}" ) - for i in self.instrs: + for i in instrs: if last_category != i.category: if last_category != None: print("") @@ -559,7 +576,7 @@ def print_opfunc_props(self): vector_masks.append(0) print( ( - " {{ {OC}::{name:24} {quotName:27} {OCC}::{className:25} " + " {{ OC::{name:24} {quotName:27} OCC::{className:25} " + "{classNameQuot:28} {attr:20}, {num_oloads}, " + "{scalar_masks:16}, {vector_masks:16} }}, " + "// Overloads: {oloads}" @@ -573,11 +590,14 @@ def print_opfunc_props(self): scalar_masks=oloads_fn(scalar_masks), vector_masks=oloads_fn(vector_masks), oloads=i.oload_types, - OC=self.OC, - OCC=self.OCC, ) ) print("};") + print( + f"static_assert(_countof({table.name}_OpCodeProps) == " + + f"(size_t)DXIL::{table.name}::OpCode::NumOpCodes, " + + f'"mismatch in opcode count for {table.name} OpCodeProps");' + ) def print_opfunc_table(self): # Print the table for OP::GetOpFunc @@ -1378,10 +1398,18 @@ def get_is_pass_option_name(): def get_opcodes_rst(): - "Create an rst table of opcodes" + "Create an rst table for each opcode table" db = get_db_dxil() - instrs = [i for i in db.instr if i.is_allowed and i.is_dxil_op] - instrs = sorted(instrs, key=lambda v: v.dxil_opid) + result = "" + for table in db.dxil_op_tables: + result += f"\n\nOpcode Table {table.name}, id={table.id}: {table.doc}" + result += get_opcodes_rst_for_table(table) + return result + + +def get_opcodes_rst_for_table(table): + "Create an rst table of opcodes for given opcode table" + instrs = [i for i in table.instr if i.is_allowed and i.is_dxil_op] rows = [] rows.append(["ID", "Name", "Description"]) for i in instrs: @@ -1413,13 +1441,25 @@ def get_valrules_rst(): def get_opsigs(): + db = get_db_dxil() + result = "" + for table in db.dxil_op_tables: + result += f"\n\n// Opcode Signatures for Table {table.name}, id={table.id}\n" + result += get_opsigs_for_table(table) + result += "static const char **OpCodeSignatures[] = {\n" + for table in db.dxil_op_tables: + result += " OpCodeSignatures_%s,\n" % table.name + result += "};\n" + return result + + +def get_opsigs_for_table(table): # Create a list of DXIL operation signatures, sorted by ID. db = get_db_dxil() instrs = [i for i in db.instr if i.is_dxil_op] - instrs = sorted(instrs, key=lambda v: v.dxil_opid) # db_dxil already asserts that the numbering is dense. # Create the code to write out. - code = "static const char *OpCodeSignatures[] = {\n" + code = f"static const char *OpCodeSignatures_{table.name}[] = {{\n" for inst_idx, i in enumerate(instrs): code += ' "(' for operand in i.ops: @@ -1644,6 +1684,12 @@ def get_interpretation_table(): return run_with_stdout(lambda: gen.print_interpretation_table()) +def get_extended_table_opcode_enum_decls(): + db = get_db_dxil() + gen = db_enumhelp_gen(db) + return run_with_stdout(lambda: gen.print_extended_table_opcode_enums()) + + # highest minor is different than highest released minor, # since there can be pre-release versions that are higher # than the last released version