Skip to content

[DirectX] Adding missing descriptor table validations #153276

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 16 commits into
base: users/joaosaffran/147573
Choose a base branch
from

Conversation

joaosaffran
Copy link
Contributor

This patch adds 2 small validation to DirectX backend. First, it checks if registers in descriptor tables are not overflowing, meaning they don't try to bind registers over the maximum allowed value; second, it checks if samplers are being mixed with other resource types.
Closes: #153057, #153058

@llvmbot
Copy link
Member

llvmbot commented Aug 12, 2025

@llvm/pr-subscribers-hlsl

@llvm/pr-subscribers-backend-directx

Author: None (joaosaffran)

Changes

This patch adds 2 small validation to DirectX backend. First, it checks if registers in descriptor tables are not overflowing, meaning they don't try to bind registers over the maximum allowed value; second, it checks if samplers are being mixed with other resource types.
Closes: #153057, #153058


Full diff: https://github.com/llvm/llvm-project/pull/153276.diff

4 Files Affected:

  • (modified) llvm/lib/Target/DirectX/DXILPostOptimizationValidation.cpp (+96-7)
  • (added) llvm/test/CodeGen/DirectX/rootsignature-validation-fail-appending-overflow.ll (+18)
  • (added) llvm/test/CodeGen/DirectX/rootsignature-validation-fail-register-overflow.ll (+17)
  • (added) llvm/test/CodeGen/DirectX/rootsignature-validation-fail-sampler-mix.ll (+18)
diff --git a/llvm/lib/Target/DirectX/DXILPostOptimizationValidation.cpp b/llvm/lib/Target/DirectX/DXILPostOptimizationValidation.cpp
index abcefe287d9bc..7067d6a2960e8 100644
--- a/llvm/lib/Target/DirectX/DXILPostOptimizationValidation.cpp
+++ b/llvm/lib/Target/DirectX/DXILPostOptimizationValidation.cpp
@@ -155,6 +155,31 @@ reportRegNotBound(Module &M, ResourceClass Class,
   M.getContext().diagnose(DiagnosticInfoGeneric(Message));
 }
 
+static void
+reportDescriptorTableMixingTypes(Module &M, uint32_t Location,
+                                 dxbc::DescriptorRangeType RangeType) {
+  SmallString<128> Message;
+  raw_svector_ostream OS(Message);
+  OS << "Samplers cannot be mixed with other "
+     << "resource types in a descriptor table, "
+     << getResourceClassName(toResourceClass(RangeType))
+     << "(location=" << Location << ")";
+
+  M.getContext().diagnose(DiagnosticInfoGeneric(Message));
+}
+
+static void reportOverlowingRange(Module &M, const dxbc::RTS0::v2::DescriptorRange &Range) {
+  SmallString<128> Message;
+  raw_svector_ostream OS(Message);
+  OS << "Cannot append range with implicit lower " 
+      << "bound after an unbounded range "
+      << getResourceClassName(toResourceClass(static_cast<dxbc::DescriptorRangeType>(Range.RangeType)))
+      << "(register=" << Range.BaseShaderRegister << ", space=" << 
+      Range.RegisterSpace
+      << ") exceeds maximum allowed value.";
+  M.getContext().diagnose(DiagnosticInfoGeneric(Message));
+}
+
 static void reportInvalidHandleTy(
     Module &M, const llvm::ArrayRef<dxil::ResourceInfo::ResourceBinding> &RDs,
     const iterator_range<SmallVectorImpl<dxil::ResourceInfo>::iterator>
@@ -236,10 +261,73 @@ getRootDescriptorsBindingInfo(const mcdxbc::RootSignatureDesc &RSD,
   return RDs;
 }
 
-static void validateRootSignature(Module &M,
-                                  const mcdxbc::RootSignatureDesc &RSD,
-                                  dxil::ModuleMetadataInfo &MMI,
-                                  DXILResourceMap &DRM) {
+
+
+static void validateDescriptorTables(Module &M,
+                                     const mcdxbc::RootSignatureDesc &RSD,
+                                     dxil::ModuleMetadataInfo &MMI,
+                                     DXILResourceMap &DRM) {
+  for (const mcdxbc::RootParameterInfo &ParamInfo : RSD.ParametersContainer) {
+    if (static_cast<dxbc::RootParameterType>(ParamInfo.Header.ParameterType) !=
+        dxbc::RootParameterType::DescriptorTable)
+      continue;
+
+    mcdxbc::DescriptorTable Table =
+        RSD.ParametersContainer.getDescriptorTable(ParamInfo.Location);
+
+    bool HasSampler = false;
+    bool HasOtherRangeType = false;
+    dxbc::DescriptorRangeType OtherRangeType;
+    uint32_t OtherRangeTypeLocation = 0;
+
+    uint64_t AppendingOffset = 0;
+
+
+    for (const dxbc::RTS0::v2::DescriptorRange &Range : Table.Ranges) {
+      dxbc::DescriptorRangeType RangeType =
+          static_cast<dxbc::DescriptorRangeType>(Range.RangeType);
+      
+      uint64_t Offset = AppendingOffset;
+      if(Range.OffsetInDescriptorsFromTableStart != ~0U)
+        Offset = Range.OffsetInDescriptorsFromTableStart;
+      
+      if(Offset > ~0U)
+        reportOverlowingRange(M, Range);
+      if(Range.NumDescriptors == ~0U) {
+        AppendingOffset = (uint64_t)~0U + (uint64_t)1ULL;
+      } else { 
+        uint64_t UpperBound = (uint64_t)Range.BaseShaderRegister + (uint64_t)Range.NumDescriptors - (uint64_t)1U;
+        if(UpperBound > ~0U)
+          reportOverlowingRange(M, Range);
+
+        uint64_t AppendingUpperBound = (uint64_t)Offset + (uint64_t)Range.NumDescriptors - (uint64_t)1U;
+        if(AppendingUpperBound > ~0U)
+          reportOverlowingRange(M, Range);
+        AppendingOffset = Offset + Range.NumDescriptors;
+      }
+      
+      if (RangeType == dxbc::DescriptorRangeType::Sampler) {
+        HasSampler = true;
+      } else {
+        HasOtherRangeType = true;
+        OtherRangeType = RangeType;
+        OtherRangeTypeLocation = ParamInfo.Location;
+      }
+    }
+
+    // Samplers cannot be mixed with other resources in a descriptor table.
+    if (HasSampler && HasOtherRangeType) {
+      reportDescriptorTableMixingTypes(M, OtherRangeTypeLocation,
+                                       OtherRangeType);
+      continue;
+    }
+  }
+}
+
+static void validateRootSignatureBindings(Module &M,
+                                          const mcdxbc::RootSignatureDesc &RSD,
+                                          dxil::ModuleMetadataInfo &MMI,
+                                          DXILResourceMap &DRM) {
 
   hlsl::BindingInfoBuilder Builder;
   dxbc::ShaderVisibility Visibility = tripleToVisibility(MMI.ShaderProfile);
@@ -309,7 +397,6 @@ static void validateRootSignature(Module &M,
         reportOverlappingRegisters(M, ReportedBinding, Overlaping);
       });
   // Next checks require that the root signature definition is valid.
-  // Next checks require that the root signature definition is valid.
   if (!HasOverlap) {
     SmallVector<ResourceInfo::ResourceBinding> RDs =
         getRootDescriptorsBindingInfo(RSD, Visibility);
@@ -351,8 +438,10 @@ static void reportErrors(Module &M, DXILResourceMap &DRM,
   assert(!DRBI.hasImplicitBinding() && "implicit bindings should be handled in "
                                        "DXILResourceImplicitBinding pass");
 
-  if (mcdxbc::RootSignatureDesc *RSD = getRootSignature(RSBI, MMI))
-    validateRootSignature(M, *RSD, MMI, DRM);
+  if (mcdxbc::RootSignatureDesc *RSD = getRootSignature(RSBI, MMI)) {
+    validateRootSignatureBindings(M, *RSD, MMI, DRM);
+    validateDescriptorTables(M, *RSD, MMI, DRM);
+  }
 }
 
 PreservedAnalyses
diff --git a/llvm/test/CodeGen/DirectX/rootsignature-validation-fail-appending-overflow.ll b/llvm/test/CodeGen/DirectX/rootsignature-validation-fail-appending-overflow.ll
new file mode 100644
index 0000000000000..865312a43b5a8
--- /dev/null
+++ b/llvm/test/CodeGen/DirectX/rootsignature-validation-fail-appending-overflow.ll
@@ -0,0 +1,18 @@
+; RUN: not opt -S -passes='dxil-post-optimization-validation' -mtriple=dxil-pc-shadermodel6.6-compute %s 2>&1 | FileCheck %s
+; CHECK: error: Cannot append range with implicit lower bound after an unbounded range UAV(register=3, space=4) exceeds maximum allowed value.
+
+@TB.str = private unnamed_addr constant [3 x i8] c"TB\00", align 1
+
+define void @CSMain() "hlsl.shader"="compute" {
+entry:
+  ret void
+}
+
+!dx.rootsignatures = !{!0}
+
+!0 = !{ptr @CSMain, !1, i32 2}
+!1 = !{!3}
+!3 = !{!"DescriptorTable", i32 0, !4, !5}
+!4 = !{!"UAV", i32 -1, i32 1, i32 2, i32 -1, i32 0}
+!5 = !{!"UAV", i32 1, i32 3, i32 4, i32 -1, i32 0}
+
diff --git a/llvm/test/CodeGen/DirectX/rootsignature-validation-fail-register-overflow.ll b/llvm/test/CodeGen/DirectX/rootsignature-validation-fail-register-overflow.ll
new file mode 100644
index 0000000000000..ef887baf80728
--- /dev/null
+++ b/llvm/test/CodeGen/DirectX/rootsignature-validation-fail-register-overflow.ll
@@ -0,0 +1,17 @@
+; RUN: not opt -S -passes='dxil-post-optimization-validation' -mtriple=dxil-pc-shadermodel6.6-compute %s 2>&1 | FileCheck %s
+; CHECK: error: Cannot append range with implicit lower bound after an unbounded range UAV(register=4294967295, space=0) exceeds maximum allowed value. 
+
+@TB.str = private unnamed_addr constant [3 x i8] c"TB\00", align 1
+
+define void @CSMain() "hlsl.shader"="compute" {
+entry:
+  ret void
+}
+
+!dx.rootsignatures = !{!0}
+
+!0 = !{ptr @CSMain, !1, i32 2}
+!1 = !{!3}
+!3 = !{!"DescriptorTable", i32 0, !4}
+!4 = !{!"UAV", i32 100, i32 4294967295, i32 0, i32 -1, i32 0}
+
diff --git a/llvm/test/CodeGen/DirectX/rootsignature-validation-fail-sampler-mix.ll b/llvm/test/CodeGen/DirectX/rootsignature-validation-fail-sampler-mix.ll
new file mode 100644
index 0000000000000..d4caeb2675d82
--- /dev/null
+++ b/llvm/test/CodeGen/DirectX/rootsignature-validation-fail-sampler-mix.ll
@@ -0,0 +1,18 @@
+; RUN: not opt -S -passes='dxil-post-optimization-validation' -mtriple=dxil-pc-shadermodel6.6-compute %s 2>&1 | FileCheck %s
+; CHECK: error: Samplers cannot be mixed with other resource types in a descriptor table, UAV(location=0)
+
+@TB.str = private unnamed_addr constant [3 x i8] c"TB\00", align 1
+
+define void @CSMain() "hlsl.shader"="compute" {
+entry:
+  ret void
+}
+
+!dx.rootsignatures = !{!0}
+
+!0 = !{ptr @CSMain, !1, i32 2}
+!1 = !{!3}
+!3 = !{!"DescriptorTable", i32 0, !4, !5}
+!4 = !{!"UAV", i32 1, i32 0, i32 0, i32 -1, i32 0}
+!5 = !{!"Sampler", i32 2, i32 0, i32 0, i32 -1, i32 0}
+

Copy link

⚠️ We detected that you are using a GitHub private e-mail address to contribute to the repo.
Please turn off Keep my email addresses private setting in your account.
See LLVM Developer Policy and LLVM Discourse for more information.

Copy link

github-actions bot commented Aug 12, 2025

✅ With the latest revision this PR passed the C/C++ code formatter.

@@ -0,0 +1,17 @@
; RUN: not opt -S -passes='dxil-post-optimization-validation' -mtriple=dxil-pc-shadermodel6.6-compute %s 2>&1 | FileCheck %s
; CHECK: error: Cannot append range with implicit lower bound after an unbounded range UAV(register=4294967295, space=0) exceeds maximum allowed value.
Copy link
Contributor

Choose a reason for hiding this comment

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

This error message is incorrect, this case does have an explicit binding

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That is the same error message as DXC. It has nothing to do with implicit binding. It means that you cannot bind if the lower bound is, implicit, exceeding the maximum allowed value. This happens when you append a range after an unbounded range.

Thant being said, I change the error message to fix this confusion.

Comment on lines 266 to 267
static void validateDescriptorTables(Module &M,
const mcdxbc::RootSignatureDesc &RSD) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
static void validateDescriptorTables(Module &M,
const mcdxbc::RootSignatureDesc &RSD) {
static void validateDescriptorTable(Module &M,
const mcdxbc::DescriptorTable &Table) {

Does it make more sense to call validateDescriptorTable when we encounter a descriptor table while iterating in validateRootSignature?

Comment on lines 317 to 322
// Samplers cannot be mixed with other resources in a descriptor table.
if (HasSampler && HasOtherRangeType) {
reportDescriptorTableMixingTypes(M, OtherRangeTypeLocation,
OtherRangeType);
continue;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
// Samplers cannot be mixed with other resources in a descriptor table.
if (HasSampler && HasOtherRangeType) {
reportDescriptorTableMixingTypes(M, OtherRangeTypeLocation,
OtherRangeType);
continue;
}
// Samplers cannot be mixed with other resources in a descriptor table.
if (HasSampler && HasOtherRangeType)
reportDescriptorTableMixingTypes(M, OtherRangeTypeLocation,
OtherRangeType);

dxbc::DescriptorRangeType RangeType =
static_cast<dxbc::DescriptorRangeType>(Range.RangeType);

uint64_t Offset = AppendingOffset;
Copy link
Contributor

@inbelic inbelic Aug 12, 2025

Choose a reason for hiding this comment

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

I am having a really hard time trying to figure what it happening here to validate it is correct. Can you add some comments to describe how this checks for an overflow

@llvmbot llvmbot added the HLSL HLSL Language Support label Aug 12, 2025
@inbelic
Copy link
Contributor

inbelic commented Aug 18, 2025

Given we want to use these for the frontend as well. Would it be possible to do these validations with an HLSLBinding instead? Both the front and backend will create the bindings I believe and could be a common interface?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
backend:DirectX HLSL HLSL Language Support
Projects
None yet
3 participants