diff --git a/include/dxc/DXIL/DxilShaderModel.h b/include/dxc/DXIL/DxilShaderModel.h index c89c079bb9..c5c5d66884 100644 --- a/include/dxc/DXIL/DxilShaderModel.h +++ b/include/dxc/DXIL/DxilShaderModel.h @@ -26,7 +26,7 @@ class ShaderModel { public: using Kind = DXIL::ShaderKind; - // Major/Minor version of highest shader model + // Major/Minor version of highest recognized shader model // clang-format off // Python lines need to be not formatted. /* hctdb_instrhelp.get_highest_shader_model()*/ @@ -35,6 +35,15 @@ class ShaderModel { static const unsigned kHighestMajor = 6; static const unsigned kHighestMinor = 9; // VALRULE-TEXT:END + + // Major/Minor version of highest released shader model + /* hctdb_instrhelp.get_highest_released_shader_model()*/ + // clang-format on + // VALRULE-TEXT:BEGIN + static const unsigned kHighestReleasedMajor = 6; + static const unsigned kHighestReleasedMinor = 8; + // VALRULE-TEXT:END + static const unsigned kOfflineMinor = 0xF; bool IsPS() const { return m_Kind == Kind::Pixel; } @@ -86,6 +95,8 @@ class ShaderModel { static const ShaderModel *Get(Kind Kind, unsigned Major, unsigned Minor); static const ShaderModel *GetByName(llvm::StringRef Name); static const char *GetKindName(Kind kind); + static bool IsPreReleaseShaderModel(int Major, int Minor); + static Kind GetKindFromName(llvm::StringRef Name); static DXIL::ShaderKind KindFromFullName(llvm::StringRef Name); static const llvm::StringRef FullNameFromKind(DXIL::ShaderKind sk); static const char *GetNodeLaunchTypeName(DXIL::NodeLaunchType launchTy); diff --git a/include/dxc/DxilContainer/DxilContainer.h b/include/dxc/DxilContainer/DxilContainer.h index 80e6308430..8b7d85954b 100644 --- a/include/dxc/DxilContainer/DxilContainer.h +++ b/include/dxc/DxilContainer/DxilContainer.h @@ -36,6 +36,9 @@ struct DxilContainerHash { uint8_t Digest[DxilContainerHashSize]; }; +static const DxilContainerHash PreviewByPassHash = {2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2}; + enum class DxilShaderHashFlags : uint32_t { None = 0, // No flags defined. IncludesSource = 1, // This flag indicates that the shader hash was computed diff --git a/include/dxc/Support/ErrorCodes.h b/include/dxc/Support/ErrorCodes.h index 7a5830fe8f..5239c8118c 100644 --- a/include/dxc/Support/ErrorCodes.h +++ b/include/dxc/Support/ErrorCodes.h @@ -153,3 +153,8 @@ // 0X80AA001E - External validator (DXIL.dll) required, and missing. #define DXC_E_VALIDATOR_MISSING \ DXC_MAKE_HRESULT(DXC_SEVERITY_ERROR, FACILITY_DXC, (0x001E)) + +// 0X80AA001F - DXIL container Program Version mismatches Dxil module shader +// model +#define DXC_E_INCORRECT_PROGRAM_VERSION \ + DXC_MAKE_HRESULT(DXC_SEVERITY_ERROR, FACILITY_DXC, (0x001F)) \ No newline at end of file diff --git a/lib/DXIL/DxilShaderModel.cpp b/lib/DXIL/DxilShaderModel.cpp index 8c94b3f5a1..861e8586e0 100644 --- a/lib/DXIL/DxilShaderModel.cpp +++ b/lib/DXIL/DxilShaderModel.cpp @@ -193,11 +193,21 @@ const ShaderModel *ShaderModel::Get(Kind Kind, unsigned Major, unsigned Minor) { // VALRULE-TEXT:END } -const ShaderModel *ShaderModel::GetByName(llvm::StringRef Name) { - // [ps|vs|gs|hs|ds|cs|ms|as]_[major]_[minor] +bool ShaderModel::IsPreReleaseShaderModel(int major, int minor) { + if (DXIL::CompareVersions(major, minor, kHighestReleasedMajor, + kHighestReleasedMinor) <= 0) + return false; + + // now compare against highest recognized + if (DXIL::CompareVersions(major, minor, kHighestMajor, kHighestMinor) <= 0) + return true; + return false; +} + +ShaderModel::Kind ShaderModel::GetKindFromName(llvm::StringRef Name) { Kind kind; if (Name.empty()) { - return GetInvalid(); + return Kind::Invalid; } switch (Name[0]) { @@ -229,8 +239,17 @@ const ShaderModel *ShaderModel::GetByName(llvm::StringRef Name) { kind = Kind::Amplification; break; default: - return GetInvalid(); + return Kind::Invalid; } + return kind; +} + +const ShaderModel *ShaderModel::GetByName(llvm::StringRef Name) { + // [ps|vs|gs|hs|ds|cs|ms|as]_[major]_[minor] + Kind kind = GetKindFromName(Name); + if (kind == Kind::Invalid) + return GetInvalid(); + unsigned Idx = 3; if (kind != Kind::Library) { if (Name[1] != 's' || Name[2] != '_') diff --git a/lib/DxilValidation/DxilContainerValidation.cpp b/lib/DxilValidation/DxilContainerValidation.cpp index 2276b0d3de..890e90e354 100644 --- a/lib/DxilValidation/DxilContainerValidation.cpp +++ b/lib/DxilValidation/DxilContainerValidation.cpp @@ -1033,8 +1033,29 @@ HRESULT ValidateDxilContainerParts(llvm::Module *pModule, case DFCC_ResourceDef: case DFCC_ShaderStatistics: case DFCC_PrivateData: + break; case DFCC_DXIL: - case DFCC_ShaderDebugInfoDXIL: + case DFCC_ShaderDebugInfoDXIL: { + const DxilProgramHeader *pProgramHeader = + reinterpret_cast(GetDxilPartData(pPart)); + if (!pProgramHeader) + continue; + + int PV = pProgramHeader->ProgramVersion; + int major = (PV >> 4) & 0xF; // Extract the major version (next 4 bits) + int minor = PV & 0xF; // Extract the minor version (lowest 4 bits) + + int moduleMajor = pDxilModule->GetShaderModel()->GetMajor(); + int moduleMinor = pDxilModule->GetShaderModel()->GetMinor(); + if (moduleMajor != major || moduleMinor != minor) { + ValCtx.EmitFormatError(ValidationRule::SmProgramVersion, + {std::to_string(major), std::to_string(minor), + std::to_string(moduleMajor), + std::to_string(moduleMinor)}); + return DXC_E_INCORRECT_PROGRAM_VERSION; + } + continue; + } case DFCC_ShaderDebugName: continue; diff --git a/tools/clang/tools/dxcvalidator/dxcvalidator.cpp b/tools/clang/tools/dxcvalidator/dxcvalidator.cpp index b8b71ece62..60ad35036f 100644 --- a/tools/clang/tools/dxcvalidator/dxcvalidator.cpp +++ b/tools/clang/tools/dxcvalidator/dxcvalidator.cpp @@ -20,6 +20,7 @@ #include "dxc/dxcapi.h" #include "dxcvalidator.h" +#include "dxc/DXIL/DxilShaderModel.h" #include "dxc/DxilRootSignature/DxilRootSignature.h" #include "dxc/Support/FileIOHelper.h" #include "dxc/Support/Global.h" @@ -32,7 +33,13 @@ using namespace llvm; using namespace hlsl; -static void HashAndUpdate(DxilContainerHeader *Container) { +static void HashAndUpdate(DxilContainerHeader *Container, bool isPreRelease) { + if (isPreRelease) { + // If preview bypass is enabled, use the preview hash. + memcpy(Container->Hash.Digest, PreviewByPassHash.Digest, + sizeof(PreviewByPassHash.Digest)); + return; + } // Compute hash and update stored hash. // Hash the container from this offset to the end. static const uint32_t DXBCHashStartOffset = @@ -45,8 +52,26 @@ static void HashAndUpdate(DxilContainerHeader *Container) { static void HashAndUpdateOrCopy(uint32_t Flags, IDxcBlob *Shader, IDxcBlob **Hashed) { + bool isPreRelease = false; + const DxilContainerHeader *DxilContainer = + IsDxilContainerLike(Shader->GetBufferPointer(), Shader->GetBufferSize()); + if (!DxilContainer) + return; + + const DxilProgramHeader *ProgramHeader = + GetDxilProgramHeader(DxilContainer, DFCC_DXIL); + + // ProgramHeader may be null here, when hashing a root signature container + if (ProgramHeader) { + int PV = ProgramHeader->ProgramVersion; + int major = (PV >> 4) & 0xF; // Extract the major version (next 4 bits) + int minor = PV & 0xF; // Extract the minor version (lowest 4 bits) + isPreRelease = ShaderModel::IsPreReleaseShaderModel(major, minor); + } + if (Flags & DxcValidatorFlags_InPlaceEdit) { - HashAndUpdate((DxilContainerHeader *)Shader->GetBufferPointer()); + HashAndUpdate((DxilContainerHeader *)Shader->GetBufferPointer(), + isPreRelease); *Hashed = Shader; Shader->AddRef(); } else { @@ -55,7 +80,8 @@ static void HashAndUpdateOrCopy(uint32_t Flags, IDxcBlob *Shader, unsigned long CB; IFT(HashedBlobStream->Write(Shader->GetBufferPointer(), Shader->GetBufferSize(), &CB)); - HashAndUpdate((DxilContainerHeader *)HashedBlobStream->GetPtr()); + HashAndUpdate((DxilContainerHeader *)HashedBlobStream->GetPtr(), + isPreRelease); IFT(HashedBlobStream.QueryInterface(Hashed)); } } diff --git a/tools/clang/unittests/HLSL/ValidationTest.cpp b/tools/clang/unittests/HLSL/ValidationTest.cpp index 08f67f35d0..8008541bfa 100644 --- a/tools/clang/unittests/HLSL/ValidationTest.cpp +++ b/tools/clang/unittests/HLSL/ValidationTest.cpp @@ -31,6 +31,7 @@ #include "dxc/Support/FileIOHelper.h" #include "dxc/Support/Global.h" +#include "dxc/DXIL/DxilShaderModel.h" #include "dxc/Test/DxcTestUtils.h" #include "dxc/Test/HlslTestUtils.h" @@ -300,6 +301,8 @@ class ValidationTest : public ::testing::Test { TEST_METHOD(ValidateWithHash) TEST_METHOD(ValidateVersionNotAllowed) + TEST_METHOD(ValidatePreviewBypassHash) + TEST_METHOD(ValidateProgramVersionAgainstDxilModule) TEST_METHOD(CreateHandleNotAllowedSM66) TEST_METHOD(AtomicsConsts) @@ -537,18 +540,10 @@ class ValidationTest : public ::testing::Test { pLookFors, pReplacements, pErrorMsgs, bRegex); } - bool RewriteAssemblyToText(IDxcBlobEncoding *pSource, LPCSTR pShaderModel, - LPCWSTR *pArguments, UINT32 argCount, - const DxcDefine *pDefines, UINT32 defineCount, - llvm::ArrayRef pLookFors, - llvm::ArrayRef pReplacements, - IDxcBlob **pBlob, bool bRegex = false) { - CComPtr pProgram; - std::string disassembly; - if (!CompileSource(pSource, pShaderModel, pArguments, argCount, pDefines, - defineCount, &pProgram)) - return false; - DisassembleProgram(pProgram, &disassembly); + void PerformReplacementOnDisassembly(std::string disassembly, + llvm::ArrayRef pLookFors, + llvm::ArrayRef pReplacements, + IDxcBlob **pBlob, bool bRegex = false) { for (unsigned i = 0; i < pLookFors.size(); ++i) { LPCSTR pLookFor = pLookFors[i]; bool bOptional = false; @@ -605,6 +600,22 @@ class ValidationTest : public ::testing::Test { } } Utf8ToBlob(m_dllSupport, disassembly.c_str(), pBlob); + } + + bool RewriteAssemblyToText(IDxcBlobEncoding *pSource, LPCSTR pShaderModel, + LPCWSTR *pArguments, UINT32 argCount, + const DxcDefine *pDefines, UINT32 defineCount, + llvm::ArrayRef pLookFors, + llvm::ArrayRef pReplacements, + IDxcBlob **pBlob, bool bRegex = false) { + CComPtr pProgram; + std::string disassembly; + if (!CompileSource(pSource, pShaderModel, pArguments, argCount, pDefines, + defineCount, &pProgram)) + return false; + DisassembleProgram(pProgram, &disassembly); + PerformReplacementOnDisassembly(disassembly, pLookFors, pReplacements, + pBlob, bRegex); return true; } @@ -4114,7 +4125,7 @@ TEST_F(ValidationTest, ValidatePrintfNotAllowed) { } TEST_F(ValidationTest, ValidateWithHash) { - if (m_ver.SkipDxilVersion(1, 8)) + if (m_ver.SkipDxilVersion(1, ShaderModel::kHighestReleasedMinor)) return; CComPtr pProgram; CompileSource("float4 main(float a:A, float b:B) : SV_Target { return 1; }", @@ -4149,6 +4160,113 @@ TEST_F(ValidationTest, ValidateWithHash) { VERIFY_ARE_EQUAL(memcmp(Result, pHeader->Hash.Digest, sizeof(Result)), 0); } +TEST_F(ValidationTest, ValidatePreviewBypassHash) { + if (m_ver.SkipDxilVersion(1, ShaderModel::kHighestMinor)) + return; + // If there is no available pre-release version to test, return + if (DXIL::CompareVersions(ShaderModel::kHighestMajor, + ShaderModel::kHighestMinor, + ShaderModel::kHighestReleasedMajor, + ShaderModel::kHighestReleasedMinor) <= 0) { + return; + } + + // Now test a pre-release version. + CComPtr pProgram; + LPCSTR pSource = + R"(float4 main(float a:A, float b:B) : SV_Target { return 1; })"; + + CComPtr pSourceBlob; + Utf8ToBlob(m_dllSupport, pSource, &pSourceBlob); + + LPCSTR pShaderModel = + ShaderModel::Get(ShaderModel::Kind::Pixel, ShaderModel::kHighestMajor, + ShaderModel::kHighestMinor) + ->GetName(); + + bool result = CompileSource(pSourceBlob, pShaderModel, nullptr, 0, nullptr, 0, + &pProgram); + VERIFY_IS_TRUE(result); + + hlsl::DxilContainerHeader *pHeader = + (hlsl::DxilContainerHeader *)pProgram->GetBufferPointer(); + + // Should be equal, this proves the hash is set to the preview bypass hash + // when a prerelease version is used + VERIFY_ARE_EQUAL(memcmp(&hlsl::PreviewByPassHash, pHeader->Hash.Digest, + sizeof(hlsl::PreviewByPassHash)), + 0); +} + +TEST_F(ValidationTest, ValidateProgramVersionAgainstDxilModule) { + if (m_ver.SkipDxilVersion(1, 8)) + return; + + CComPtr pProgram; + LPCSTR pSource = + R"(float4 main(float a:A, float b:B) : SV_Target { return 1; })"; + + CComPtr pSourceBlob; + Utf8ToBlob(m_dllSupport, pSource, &pSourceBlob); + + LPCSTR pShaderModel = + ShaderModel::Get(ShaderModel::Kind::Pixel, 6, 0)->GetName(); + + bool result = CompileSource(pSourceBlob, pShaderModel, nullptr, 0, nullptr, 0, + &pProgram); + VERIFY_IS_TRUE(result); + + hlsl::DxilContainerHeader *pHeader = + (hlsl::DxilContainerHeader *)pProgram->GetBufferPointer(); + // test that when the program version differs from the dxil module shader + // model version, the validator fails + DxilPartHeader *pPart = GetDxilPartByType(pHeader, DxilFourCC::DFCC_DXIL); + + DxilProgramHeader *pMutableProgramHeader = + reinterpret_cast(GetDxilPartData(pPart)); + int oldMajor = 0; + int oldMinor = 0; + int newMajor = 0; + int newMinor = 0; + VERIFY_IS_NOT_NULL(pMutableProgramHeader); + uint32_t &PV = pMutableProgramHeader->ProgramVersion; + oldMajor = (PV >> 4) & 0xF; // Extract the major version (next 4 bits) + oldMinor = PV & 0xF; // Extract the minor version (lowest 4 bits) + + // Add one to the last bit of the program version, which is 0, because + // the program version (shader model version) is 6.0, and we want to + // test that the validation fails when the program version is changed to 6.1 + PV += 1; + + newMajor = (PV >> 4) & 0xF; // Extract the major version (next 4 bits) + newMinor = PV & 0xF; // Extract the new minor version (lowest 4 bits) + + // now test that the validation fails + CComPtr pValidator; + CComPtr pResult; + unsigned Flags = 0; + VERIFY_SUCCEEDED( + m_dllSupport.CreateInstance(CLSID_DxcValidator, &pValidator)); + + HRESULT status; + VERIFY_SUCCEEDED(pValidator->Validate(pProgram, Flags, &pResult)); + VERIFY_IS_NOT_NULL(pResult); + pResult->GetStatus(&status); + + // expect validation to fail + VERIFY_FAILED(status); + // validation succeeded prior, so by inference we know that oldMajor / + // oldMinor were the old dxil module shader model versions + char buffer[100]; + std::snprintf(buffer, sizeof(buffer), + "error: Program Version is %d.%d but Dxil Module shader model " + "version is %d.%d.\nValidation failed.\n", + newMajor, newMinor, oldMajor, oldMinor); + std::string formattedString = buffer; + + CheckOperationResultMsgs(pResult, {buffer}, false, false); +} + TEST_F(ValidationTest, ValidateVersionNotAllowed) { if (m_ver.SkipDxilVersion(1, 6)) return; diff --git a/utils/hct/hctdb.py b/utils/hct/hctdb.py index 19220d6d1a..2f632aceee 100644 --- a/utils/hct/hctdb.py +++ b/utils/hct/hctdb.py @@ -7500,6 +7500,11 @@ def build_valrules(self): "Target shader model requires specific Dxil Version", "Shader model requires Dxil Version %0.%1.", ) + self.add_valrule_msg( + "Sm.ProgramVersion", + "Program Version in Dxil Container does not match Dxil Module shader model version", + "Program Version is %0.%1 but Dxil Module shader model version is %2.%3.", + ) self.add_valrule_msg( "Sm.Opcode", "Opcode must be defined in target shader model", diff --git a/utils/hct/hctdb_instrhelp.py b/utils/hct/hctdb_instrhelp.py index 7b0de52db4..17eefd4918 100644 --- a/utils/hct/hctdb_instrhelp.py +++ b/utils/hct/hctdb_instrhelp.py @@ -4,6 +4,8 @@ import functools import collections from hctdb import * +import json +import os # get db singletons g_db_dxil = None @@ -1536,10 +1538,20 @@ def get_interpretation_table(): return run_with_stdout(lambda: gen.print_interpretation_table()) +# highest minor is different than highest released minor, +# since there can be pre-release versions that are higher +# than the last released version highest_major = 6 highest_minor = 9 highest_shader_models = {4: 1, 5: 1, 6: highest_minor} +# fetch the last released version from latest-released.json +json_path = os.path.dirname(os.path.dirname(__file__)) + "/version/latest-release.json" +with open(json_path, "r") as file: + json_data = json.load(file) + +highest_released_minor = int(json_data["version"]["minor"]) + def getShaderModels(): shader_models = [] @@ -1550,6 +1562,14 @@ def getShaderModels(): return shader_models +def get_highest_released_shader_model(): + result = """static const unsigned kHighestReleasedMajor = %d; +static const unsigned kHighestReleasedMinor = %d;""" % ( + highest_major, + highest_released_minor, + ) + return result + def get_highest_shader_model(): result = """static const unsigned kHighestMajor = %d; static const unsigned kHighestMinor = %d;""" % ( @@ -1558,7 +1578,6 @@ def get_highest_shader_model(): ) return result - def get_dxil_version_minor(): return "const unsigned kDxilMinor = %d;" % highest_minor