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))
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
22 changes: 21 additions & 1 deletion lib/DxilValidation/DxilContainerValidation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1033,8 +1033,28 @@ 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) {
bob80905 marked this conversation as resolved.
Show resolved Hide resolved
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) {
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
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
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)

// 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
Loading