Skip to content

Commit

Permalink
Add Preview Hashing sentinel value to dxil validator (#7053)
Browse files Browse the repository at this point in the history
When the dxil validator detects that the target shader model is a
preview shader model that hasn't officially been released, it should
sign the dxil container with a special sentinel hash:
02020202020202020202020202020202

Fixes #7083

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
  • Loading branch information
bob80905 and github-actions[bot] authored Jan 25, 2025
1 parent 25faa88 commit 10b3328
Show file tree
Hide file tree
Showing 9 changed files with 250 additions and 23 deletions.
13 changes: 12 additions & 1 deletion include/dxc/DXIL/DxilShaderModel.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
/* <py::lines('VALRULE-TEXT')>hctdb_instrhelp.get_highest_shader_model()</py>*/
Expand All @@ -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
/* <py::lines('VALRULE-TEXT')>hctdb_instrhelp.get_highest_released_shader_model()</py>*/
// 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; }
Expand Down Expand Up @@ -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);
Expand Down
3 changes: 3 additions & 0 deletions include/dxc/DxilContainer/DxilContainer.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions include/dxc/Support/ErrorCodes.h
Original file line number Diff line number Diff line change
Expand Up @@ -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))
27 changes: 23 additions & 4 deletions lib/DXIL/DxilShaderModel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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]) {
Expand Down Expand Up @@ -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] != '_')
Expand Down
23 changes: 22 additions & 1 deletion lib/DxilValidation/DxilContainerValidation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<const DxilProgramHeader *>(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;

Expand Down
32 changes: 29 additions & 3 deletions tools/clang/tools/dxcvalidator/dxcvalidator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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 =
Expand All @@ -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 {
Expand All @@ -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));
}
}
Expand Down
144 changes: 131 additions & 13 deletions tools/clang/unittests/HLSL/ValidationTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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<LPCSTR> pLookFors,
llvm::ArrayRef<LPCSTR> pReplacements,
IDxcBlob **pBlob, bool bRegex = false) {
CComPtr<IDxcBlob> 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<LPCSTR> pLookFors,
llvm::ArrayRef<LPCSTR> pReplacements,
IDxcBlob **pBlob, bool bRegex = false) {
for (unsigned i = 0; i < pLookFors.size(); ++i) {
LPCSTR pLookFor = pLookFors[i];
bool bOptional = false;
Expand Down Expand Up @@ -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<LPCSTR> pLookFors,
llvm::ArrayRef<LPCSTR> pReplacements,
IDxcBlob **pBlob, bool bRegex = false) {
CComPtr<IDxcBlob> 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;
}

Expand Down Expand Up @@ -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<IDxcBlob> pProgram;
CompileSource("float4 main(float a:A, float b:B) : SV_Target { return 1; }",
Expand Down Expand Up @@ -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<IDxcBlob> pProgram;
LPCSTR pSource =
R"(float4 main(float a:A, float b:B) : SV_Target { return 1; })";

CComPtr<IDxcBlobEncoding> 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<IDxcBlob> pProgram;
LPCSTR pSource =
R"(float4 main(float a:A, float b:B) : SV_Target { return 1; })";

CComPtr<IDxcBlobEncoding> 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<DxilProgramHeader *>(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<IDxcValidator> pValidator;
CComPtr<IDxcOperationResult> 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;
Expand Down
5 changes: 5 additions & 0 deletions utils/hct/hctdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Loading

0 comments on commit 10b3328

Please sign in to comment.