Skip to content

[DirectX] Validate registers are bound to root signature #146785

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

Open
wants to merge 50 commits into
base: users/joaosaffran/152229
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 49 commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
0e8828c
refactoring
Jun 26, 2025
2edd215
refactoring
Jun 26, 2025
242545e
clean up
Jul 2, 2025
3f8dec4
format
Jul 2, 2025
3b1ce3b
formating
Jul 2, 2025
f5720af
fix import issues
Jul 4, 2025
ea54904
formating
Jul 4, 2025
a49aa19
refactoring
Jun 26, 2025
d90676f
init refactoring
Jun 26, 2025
a04eb9f
adding validation
Jul 2, 2025
5994b8f
clean
Jul 2, 2025
e8b14bf
implementing
Jul 4, 2025
8f40e83
finish implementing && fix tests
Jul 4, 2025
28350b2
fix issue
Jul 5, 2025
4fd2e0b
sync parent
Jul 5, 2025
e25ee87
sync parent
Jul 5, 2025
881dd36
address comments
Jul 7, 2025
8779ee9
Merge branch 'refactoring/updating-return-root-sig-analysis' into val…
Jul 7, 2025
c16f15b
fix test
Jul 8, 2025
c7d5be7
format
Jul 8, 2025
cc5afae
address changes
Jul 8, 2025
571a0ef
fix tests
Jul 8, 2025
974d4bc
Merge branch 'refactoring/updating-return-root-sig-analysis' into val…
Jul 8, 2025
e0bc862
add preserved
Jul 8, 2025
b5a0b32
addressing comments
Jul 10, 2025
00a74af
Merge branch 'main' into validation/check-descriptors-are-bound
Jul 10, 2025
5ccb842
updating
Jul 11, 2025
5423aba
format
Jul 14, 2025
a7637a7
adding tests
Jul 14, 2025
da42c0c
clean up
Jul 14, 2025
edb015d
address comments
Jul 15, 2025
9f3888e
adding root constants
Jul 15, 2025
578a03b
clean
Jul 15, 2025
b4a0e16
moving code arround
Jul 17, 2025
ef14638
clean
Jul 17, 2025
662c3a8
addressing comments
Jul 21, 2025
260633c
address comments
Jul 25, 2025
d42f156
Merge branch 'main' into validation/check-descriptors-are-bound
Jul 31, 2025
9ee3a4b
Merge branch 'main' into validation/check-descriptors-are-bound
Jul 31, 2025
6db6224
update code
Aug 1, 2025
04658b8
cleanup
Aug 1, 2025
adf3feb
address comments from inbelic
Aug 5, 2025
fc338b5
Merge branch 'validation/overlapping-ranges' into validation/check-de…
Aug 6, 2025
f5b5b3e
clean
Aug 6, 2025
03d571a
clean?
Aug 6, 2025
ef51048
format
Aug 6, 2025
21675e6
formating
Aug 6, 2025
47662f0
Merge branch 'validation/overlapping-ranges' into validation/check-de…
Aug 8, 2025
6da5fb0
format
Aug 8, 2025
0c72dcf
addressing inbelic comments
Aug 12, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions llvm/include/llvm/Frontend/HLSL/HLSLBinding.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ class BindingInfo {
}
// Size == -1 means unbounded array
LLVM_ABI std::optional<uint32_t> findAvailableBinding(int32_t Size);
LLVM_ABI bool isBound(BindingRange B);
};

struct BindingSpaces {
Expand Down Expand Up @@ -95,6 +96,8 @@ class BindingInfo {
LLVM_ABI std::optional<uint32_t>
findAvailableBinding(dxil::ResourceClass RC, uint32_t Space, int32_t Size);

LLVM_ABI bool isBound(dxil::ResourceClass RC, uint32_t Space, BindingRange B);

friend class BindingInfoBuilder;
};

Expand Down
16 changes: 16 additions & 0 deletions llvm/lib/Frontend/HLSL/HLSLBinding.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,22 @@ BindingInfo::RegisterSpace::findAvailableBinding(int32_t Size) {
return std::nullopt;
}

bool BindingInfo::RegisterSpace::isBound(BindingRange B) {
for (BindingRange &R : FreeRanges) {
if (B.LowerBound >= R.LowerBound && B.LowerBound < R.UpperBound &&
B.UpperBound > R.LowerBound && B.UpperBound <= R.UpperBound)
return false;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can take advantage of FreeRanges being sorted and use llvm::lower_bound for this check

return true;
}

bool BindingInfo::isBound(dxil::ResourceClass RC, uint32_t Space,
BindingRange B) {
BindingSpaces &BS = getBindingSpaces(RC);
RegisterSpace &RS = BS.getOrInsertSpace(Space);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it worth it to add a getSpace that returns an optional if it doesn't exist? It would mean we don't create a bunch of extra RegisteSpaces that need to be traversed on each subsequent getBindingSpaces

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bogner designed this class.
@bogner: thoughts?

return RS.isBound(B);
}

BindingInfo BindingInfoBuilder::calculateBindingInfo(
llvm::function_ref<void(const BindingInfoBuilder &Builder,
const Binding &Overlapping)>
Expand Down
42 changes: 37 additions & 5 deletions llvm/lib/Target/DirectX/DXILPostOptimizationValidation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,18 @@ static void reportOverlappingRegisters(
M.getContext().diagnose(DiagnosticInfoGeneric(Message));
}

static void
reportRegNotBound(Module &M, ResourceClass Class,
llvm::dxil::ResourceInfo::ResourceBinding Unbound) {
SmallString<128> Message;
raw_svector_ostream OS(Message);
OS << "register " << getResourceClassName(Class)
<< " (space=" << Unbound.Space << ", register=" << Unbound.LowerBound
<< ")"
<< " does not have a binding in the Root Signature";
M.getContext().diagnose(DiagnosticInfoGeneric(Message));
}

static dxbc::ShaderVisibility
tripleToVisibility(llvm::Triple::EnvironmentType ET) {
switch (ET) {
Expand All @@ -154,7 +166,8 @@ tripleToVisibility(llvm::Triple::EnvironmentType ET) {

static void validateRootSignature(Module &M,
const mcdxbc::RootSignatureDesc &RSD,
dxil::ModuleMetadataInfo &MMI) {
dxil::ModuleMetadataInfo &MMI,
DXILResourceMap &DRM) {

hlsl::BindingInfoBuilder Builder;
dxbc::ShaderVisibility Visibility = tripleToVisibility(MMI.ShaderProfile);
Expand Down Expand Up @@ -213,14 +226,33 @@ static void validateRootSignature(Module &M,
Builder.trackBinding(dxil::ResourceClass::Sampler, S.RegisterSpace,
S.ShaderRegister, S.ShaderRegister,
&IDs.emplace_back());

bool HasOverlap = false;
hlsl::BindingInfo Info = Builder.calculateBindingInfo(
[&M](const llvm::hlsl::BindingInfoBuilder &Builder,
const llvm::hlsl::BindingInfoBuilder::Binding &ReportedBinding) {
[&M, &HasOverlap](
const llvm::hlsl::BindingInfoBuilder &Builder,
const llvm::hlsl::BindingInfoBuilder::Binding &ReportedBinding) {
HasOverlap = true;
const llvm::hlsl::BindingInfoBuilder::Binding &Overlaping =
Builder.findOverlapping(ReportedBinding);
reportOverlappingRegisters(M, ReportedBinding, Overlaping);
});
// Next checks require that the root signature definition is valid.
if (!HasOverlap) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it? We could still report any unbound resources here in the same compile cycle right? Or will the Info not be fully constructed on an error

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure if more error messages here is always beneficial. Since the ranges are already incorrect, the user will need to modify those. So yeah, even I could check that the ranges and report the overlapping ones, I am not sure how correct will that diagnostic be, since the data I am checking is already wrong.

But this is not a blocker, I can produce diagnosis here. I can remove if the team prefers that way.

for (const auto &ResList :
{std::make_pair(ResourceClass::SRV, DRM.srvs()),
std::make_pair(ResourceClass::UAV, DRM.uavs()),
std::make_pair(ResourceClass::CBuffer, DRM.cbuffers()),
std::make_pair(ResourceClass::Sampler, DRM.samplers())}) {
for (auto Res : ResList.second) {
llvm::dxil::ResourceInfo::ResourceBinding ResBinding = Res.getBinding();
llvm::hlsl::BindingInfo::BindingRange ResRange(
ResBinding.LowerBound, ResBinding.LowerBound + ResBinding.Size);

if (!Info.isBound(ResList.first, ResBinding.Space, ResRange))
reportRegNotBound(M, ResList.first, ResBinding);
}
}
}
}

static mcdxbc::RootSignatureDesc *
Expand All @@ -245,7 +277,7 @@ static void reportErrors(Module &M, DXILResourceMap &DRM,
"DXILResourceImplicitBinding pass");

if (mcdxbc::RootSignatureDesc *RSD = getRootSignature(RSBI, MMI))
validateRootSignature(M, *RSD, MMI);
validateRootSignature(M, *RSD, MMI, DRM);
}

PreservedAnalyses
Expand Down
34 changes: 34 additions & 0 deletions llvm/test/CodeGen/DirectX/rootsignature-validation-fail-cbuffer.ll
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
; RUN: not opt -S -passes='dxil-post-optimization-validation' -mtriple=dxil-pc-shadermodel6.6-compute %s 2>&1 | FileCheck %s

; CHECK: error: register CBuffer (space=665, register=3) does not have a binding in the Root Signature

; Root Signature(
; CBV(b3, space=666, visibility=SHADER_VISIBILITY_ALL)
; DescriptorTable(SRV(t0, space=0, numDescriptors=1), visibility=SHADER_VISIBILITY_ALL)
; DescriptorTable(Sampler(s0, numDescriptors=2), visibility=SHADER_VISIBILITY_VERTEX)
; DescriptorTable(UAV(u0, numDescriptors=unbounded), visibility=SHADER_VISIBILITY_ALL)

%__cblayout_CB = type <{ float }>

@CB.str = private unnamed_addr constant [3 x i8] c"CB\00", align 1

define void @CSMain() "hlsl.shader"="compute" {
entry:
; cbuffer CB : register(b3, space665) {
; float a;
; }
%CB = tail call target("dx.CBuffer", target("dx.Layout", %__cblayout_CB, 4, 0)) @llvm.dx.resource.handlefrombinding(i32 665, i32 3, i32 1, i32 0, i1 false, ptr nonnull @CB.str)
ret void
}

!dx.rootsignatures = !{!0}

!0 = !{ptr @CSMain, !1, i32 2}
!1 = !{!2, !3, !5, !7}
!2 = !{!"RootCBV", i32 0, i32 3, i32 666, i32 4}
!3 = !{!"DescriptorTable", i32 1, !4}
!4 = !{!"SRV", i32 1, i32 0, i32 0, i32 -1, i32 4}
!5 = !{!"DescriptorTable", i32 0, !6}
!6 = !{!"Sampler", i32 2, i32 0, i32 0, i32 -1, i32 0}
!7 = !{!"DescriptorTable", i32 0, !8}
!8 = !{!"UAV", i32 -1, i32 0, i32 0, i32 -1, i32 2}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
; RUN: not opt -S -passes='dxil-post-optimization-validation' -mtriple=dxil-pc-shadermodel6.6-compute %s 2>&1 | FileCheck %s
; CHECK: error: register CBuffer (space=666, register=2) does not have a binding in the Root Signature
; Root Signature(RootConstants(num32BitConstants=4, b2))

%__cblayout_CB = type <{ float }>

@CB.str = private unnamed_addr constant [3 x i8] c"CB\00", align 1

define void @CSMain() "hlsl.shader"="compute" {
entry:
; cbuffer CB : register(b2, space666) {
; float a;
; }
%CB = tail call target("dx.CBuffer", target("dx.Layout", %__cblayout_CB, 4, 0)) @llvm.dx.resource.handlefrombinding(i32 666, i32 2, i32 1, i32 0, i1 false, ptr nonnull @CB.str)
ret void
}

!dx.rootsignatures = !{!0}

!0 = !{ptr @CSMain, !1, i32 2}
!1 = !{!2}
!2 = !{!"RootConstants", i32 0, i32 2, i32 0, i32 4}
31 changes: 31 additions & 0 deletions llvm/test/CodeGen/DirectX/rootsignature-validation-fail-sampler.ll
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
; RUN: not opt -S -passes='dxil-post-optimization-validation' -mtriple=dxil-pc-shadermodel6.6-compute %s 2>&1 | FileCheck %s

; CHECK: error: register Sampler (space=2, register=3) does not have a binding in the Root Signature

; Root Signature(
; CBV(b3, space=666, visibility=SHADER_VISIBILITY_ALL)
; DescriptorTable(SRV(t0, space=0, numDescriptors=1), visibility=SHADER_VISIBILITY_VERTEX)
; DescriptorTable(Sampler(s0, numDescriptors=2), visibility=SHADER_VISIBILITY_ALL)
; DescriptorTable(UAV(u0, numDescriptors=unbounded), visibility=SHADER_VISIBILITY_ALL)

@Smp.str = private unnamed_addr constant [4 x i8] c"Smp\00", align 1

define void @CSMain() "hlsl.shader"="compute" {
entry:
; SamplerState S1 : register(s3, space2);
%Sampler = call target("dx.Sampler", 0) @llvm.dx.resource.handlefrombinding(i32 2, i32 3, i32 1, i32 0, i1 false, ptr nonnull @Smp.str)

ret void
}

!dx.rootsignatures = !{!0}

!0 = !{ptr @CSMain, !1, i32 2}
!1 = !{!2, !3, !5, !7}
!2 = !{!"RootCBV", i32 0, i32 3, i32 666, i32 4}
!3 = !{!"DescriptorTable", i32 1, !4}
!4 = !{!"SRV", i32 1, i32 0, i32 0, i32 -1, i32 4}
!5 = !{!"DescriptorTable", i32 0, !6}
!6 = !{!"Sampler", i32 2, i32 0, i32 0, i32 -1, i32 0}
!7 = !{!"DescriptorTable", i32 0, !8}
!8 = !{!"UAV", i32 -1, i32 0, i32 0, i32 -1, i32 2}
30 changes: 30 additions & 0 deletions llvm/test/CodeGen/DirectX/rootsignature-validation-fail-srv.ll
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
; RUN: not opt -S -passes='dxil-post-optimization-validation' -mtriple=dxil-pc-shadermodel6.6-compute %s 2>&1 | FileCheck %s

; CHECK: error: register SRV (space=0, register=0) does not have a binding in the Root Signature

; Root Signature(
; CBV(b3, space=666, visibility=SHADER_VISIBILITY_ALL)
; DescriptorTable(SRV(t0, space=0, numDescriptors=1), visibility=SHADER_VISIBILITY_VERTEX)
; DescriptorTable(Sampler(s0, numDescriptors=2), visibility=SHADER_VISIBILITY_ALL)
; DescriptorTable(UAV(u0, numDescriptors=unbounded), visibility=SHADER_VISIBILITY_ALL)

@SB.str = private unnamed_addr constant [3 x i8] c"SB\00", align 1

define void @CSMain() "hlsl.shader"="compute" {
entry:
; StructuredBuffer<int> In : register(t0, space0);
%SB = tail call target("dx.RawBuffer", i32, 0, 0) @llvm.dx.resource.handlefrombinding.tdx.RawBuffer_i32_0_0t(i32 0, i32 0, i32 1, i32 0, i1 false, ptr nonnull @SB.str)
ret void
}

!dx.rootsignatures = !{!0}

!0 = !{ptr @CSMain, !1, i32 2}
!1 = !{!2, !3, !5, !7}
!2 = !{!"RootCBV", i32 0, i32 3, i32 666, i32 4}
!3 = !{!"DescriptorTable", i32 1, !4}
!4 = !{!"SRV", i32 1, i32 0, i32 0, i32 -1, i32 4}
!5 = !{!"DescriptorTable", i32 0, !6}
!6 = !{!"Sampler", i32 2, i32 0, i32 0, i32 -1, i32 0}
!7 = !{!"DescriptorTable", i32 0, !8}
!8 = !{!"UAV", i32 -1, i32 0, i32 0, i32 -1, i32 2}
30 changes: 30 additions & 0 deletions llvm/test/CodeGen/DirectX/rootsignature-validation-fail-uav.ll
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
; RUN: not opt -S -passes='dxil-post-optimization-validation' -mtriple=dxil-pc-shadermodel6.6-compute %s 2>&1 | FileCheck %s

; CHECK: error: register UAV (space=0, register=4294967294) does not have a binding in the Root Signature

; Root Signature(
; CBV(b3, space=666, visibility=SHADER_VISIBILITY_ALL)
; DescriptorTable(SRV(t0, space=0, numDescriptors=1), visibility=SHADER_VISIBILITY_VERTEX)
; DescriptorTable(Sampler(s0, numDescriptors=2), visibility=SHADER_VISIBILITY_ALL)
; DescriptorTable(UAV(u0, numDescriptors=unbounded), visibility=SHADER_VISIBILITY_ALL)

@RWB.str = private unnamed_addr constant [4 x i8] c"RWB\00", align 1

define void @CSMain() "hlsl.shader"="compute" {
entry:
; RWBuffer<float> UAV : register(4294967294);
%RWB = tail call target("dx.TypedBuffer", float, 1, 0, 0) @llvm.dx.resource.handlefrombinding.tdx.TypedBuffer_f32_1_0_0t(i32 0, i32 4294967294, i32 1, i32 0, i1 false, ptr nonnull @RWB.str)
ret void
}

!dx.rootsignatures = !{!0}

!0 = !{ptr @CSMain, !1, i32 2}
!1 = !{!2, !3, !5, !7}
!2 = !{!"RootCBV", i32 0, i32 3, i32 666, i32 4}
!3 = !{!"DescriptorTable", i32 1, !4}
!4 = !{!"SRV", i32 1, i32 0, i32 0, i32 -1, i32 4}
!5 = !{!"DescriptorTable", i32 0, !6}
!6 = !{!"Sampler", i32 2, i32 0, i32 0, i32 -1, i32 0}
!7 = !{!"DescriptorTable", i32 0, !8}
!8 = !{!"UAV", i32 10, i32 0, i32 0, i32 -1, i32 2}