Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Preview Hashing sentinel value to dxil validator #7053

Merged
merged 15 commits into from
Jan 25, 2025
Merged
12 changes: 11 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 released shader model
tex3d marked this conversation as resolved.
Show resolved Hide resolved
// 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,14 @@ class ShaderModel {
static const unsigned kHighestMajor = 6;
static const unsigned kHighestMinor = 9;
// VALRULE-TEXT:END

/* <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 +94,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))
19 changes: 13 additions & 6 deletions lib/DXIL/DxilMetadataHelper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -284,13 +284,20 @@ void DxilMDHelper::LoadDxilShaderModel(const ShaderModel *&pSM) {
"_" + std::to_string(Major) + "_" +
(Minor == ShaderModel::kOfflineMinor ? "x" : std::to_string(Minor));
pSM = ShaderModel::GetByName(ShaderModelName.c_str());
if (!pSM->IsValidForDxil()) {
char ErrorMsgTxt[40];
StringCchPrintfA(ErrorMsgTxt, _countof(ErrorMsgTxt),
"Unknown shader model '%s'", ShaderModelName.c_str());
string ErrorMsg(ErrorMsgTxt);
throw hlsl::Exception(DXC_E_INCORRECT_DXIL_METADATA, ErrorMsg);
// check to see if the provided shader model is the pre-release version
if (ShaderModel::IsPreReleaseShaderModel(Major, Minor)) {
tex3d marked this conversation as resolved.
Show resolved Hide resolved
ShaderModel::Kind kind = ShaderModel::GetKindFromName(ShaderModelName);
pSM = ShaderModel::Get(kind, Major, Minor);
} else {
if (!pSM->IsValidForDxil()) {
char ErrorMsgTxt[40];
StringCchPrintfA(ErrorMsgTxt, _countof(ErrorMsgTxt),
"Unknown shader model '%s'", ShaderModelName.c_str());
string ErrorMsg(ErrorMsgTxt);
throw hlsl::Exception(DXC_E_INCORRECT_DXIL_METADATA, ErrorMsg);
}
}

SetShaderModel(pSM);
}

Expand Down
29 changes: 25 additions & 4 deletions lib/DXIL/DxilShaderModel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -193,11 +193,23 @@ 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;
}
bob80905 marked this conversation as resolved.
Show resolved Hide resolved

// now compare against highest recognized
if (DXIL::CompareVersions(major, minor, kHighestMajor, kHighestMinor) <= 0) {
return true;
}
bob80905 marked this conversation as resolved.
Show resolved Hide resolved
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 +241,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
36 changes: 36 additions & 0 deletions lib/DxilValidation/DxilContainerValidation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1298,6 +1298,39 @@ HRESULT ValidateLoadModuleFromContainerLazy(
/*bLazyLoad*/ true);
}

HRESULT ValidateDxilContainerAgainstModule(const void *pContainer,
uint32_t ContainerSize,
llvm::Module *pModule,
llvm::Module *pDebugModule,
llvm::raw_ostream &DiagStream) {
const DxilPartHeader *pPart = nullptr;
IFR(FindDxilPart(pContainer, ContainerSize, DFCC_DXIL, &pPart));
// Verify that the ProgramHeader in the container contains a program version
// that matches the DxilModule's shader model version
const DxilProgramHeader *pProgramHeader =
reinterpret_cast<const DxilProgramHeader *>(GetDxilPartData(pPart));
if (pProgramHeader) {
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)
DxilModule *pDxilModule = DxilModule::TryGetDxilModule(pModule);
if (!pDxilModule) {
return DXC_E_IR_VERIFICATION_FAILED;
}
ValidationContext ValCtx(*pModule, pDebugModule, *pDxilModule);
int moduleMajor = ValCtx.DxilMod.GetShaderModel()->GetMajor();
int moduleMinor = ValCtx.DxilMod.GetShaderModel()->GetMinor();
if (moduleMajor != major || moduleMinor != minor) {
tex3d marked this conversation as resolved.
Show resolved Hide resolved
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;
}
}
return S_OK;
}

HRESULT ValidateDxilContainer(const void *pContainer, uint32_t ContainerSize,
llvm::Module *pDebugModule,
llvm::raw_ostream &DiagStream) {
Expand All @@ -1323,6 +1356,9 @@ HRESULT ValidateDxilContainer(const void *pContainer, uint32_t ContainerSize,
// Validate DXIL Module
IFR(ValidateDxilModule(pModule.get(), pDebugModule));

IFR(ValidateDxilContainerAgainstModule(
pContainer, ContainerSize, pModule.get(), pDebugModule, DiagStream));

if (DiagContext.HasErrors() || DiagContext.HasWarnings()) {
return DXC_E_IR_VERIFICATION_FAILED;
}
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 hasing a root signature container
tex3d marked this conversation as resolved.
Show resolved Hide resolved
if (ProgramHeader) {
tex3d marked this conversation as resolved.
Show resolved Hide resolved
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);
tex3d marked this conversation as resolved.
Show resolved Hide resolved
}

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
120 changes: 107 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,7 @@ class ValidationTest : public ::testing::Test {

TEST_METHOD(ValidateWithHash)
TEST_METHOD(ValidateVersionNotAllowed)
TEST_METHOD(ValidatePreviewBypassHash)
TEST_METHOD(CreateHandleNotAllowedSM66)

TEST_METHOD(AtomicsConsts)
Expand Down Expand Up @@ -537,18 +539,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 +599,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 +4124,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 +4159,90 @@ 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 that when the program version differs from the dxil module shader
// model version, the validator fails
tex3d marked this conversation as resolved.
Show resolved Hide resolved
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)

// flip the bit of the shader model version minor
PV ^= 1;
tex3d marked this conversation as resolved.
Show resolved Hide resolved

newMajor = (PV >> 4) & 0xF; // Extract the major version (next 4 bits)
newMinor = PV & 0xF; // Extract the 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
Loading
Loading