Skip to content

[HLSL] Diagnose overlapping resource bindings #140982

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

Merged
merged 2 commits into from
May 30, 2025

Conversation

hekota
Copy link
Member

@hekota hekota commented May 22, 2025

Adds reporting of overlapping binding errors to DXILPostOptimizationValidation pass. Only runs when DXILResourceBindingAnalysis detects that there is a resource binding that overlaps while it is building up a map of available register spaces.

Fixes #110723

@llvmbot
Copy link
Member

llvmbot commented May 22, 2025

@llvm/pr-subscribers-llvm-analysis

@llvm/pr-subscribers-backend-directx

Author: Helena Kotas (hekota)

Changes

Adds reporting of overlapping binding errors to DXILPostOptimizationValidation pass. Only runs when DXILResourceBindingAnalysis detects there is an overlapping binding.

Fixes #110723

Depends on #140635, #140645, #140981


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

6 Files Affected:

  • (modified) llvm/include/llvm/Analysis/DXILResource.h (+5-2)
  • (modified) llvm/lib/Target/DirectX/DXILPostOptimizationValidation.cpp (+51-8)
  • (added) llvm/test/CodeGen/DirectX/Binding/binding-overlap-1.ll (+17)
  • (added) llvm/test/CodeGen/DirectX/Binding/binding-overlap-2.ll (+17)
  • (added) llvm/test/CodeGen/DirectX/Binding/binding-overlap-3.ll (+44)
  • (modified) llvm/test/CodeGen/DirectX/ShaderFlags/typed-uav-load-additional-formats.ll (+2-2)
diff --git a/llvm/include/llvm/Analysis/DXILResource.h b/llvm/include/llvm/Analysis/DXILResource.h
index a274e2294561e..f6e924041da11 100644
--- a/llvm/include/llvm/Analysis/DXILResource.h
+++ b/llvm/include/llvm/Analysis/DXILResource.h
@@ -355,6 +355,9 @@ class ResourceInfo {
       return std::tie(RecordID, Space, LowerBound, Size) <
              std::tie(RHS.RecordID, RHS.Space, RHS.LowerBound, RHS.Size);
     }
+    bool overlapsWith(const ResourceBinding &RHS) const {
+      return Space == RHS.Space && LowerBound + Size - 1 >= RHS.LowerBound;
+    }
   };
 
 private:
@@ -393,8 +396,8 @@ class ResourceInfo {
   getAnnotateProps(Module &M, dxil::ResourceTypeInfo &RTI) const;
 
   bool operator==(const ResourceInfo &RHS) const {
-    return std::tie(Binding, HandleTy, Symbol) ==
-           std::tie(RHS.Binding, RHS.HandleTy, RHS.Symbol);
+    return std::tie(Binding, HandleTy, Symbol, Name) ==
+           std::tie(RHS.Binding, RHS.HandleTy, RHS.Symbol, RHS.Name);
   }
   bool operator!=(const ResourceInfo &RHS) const { return !(*this == RHS); }
   bool operator<(const ResourceInfo &RHS) const {
diff --git a/llvm/lib/Target/DirectX/DXILPostOptimizationValidation.cpp b/llvm/lib/Target/DirectX/DXILPostOptimizationValidation.cpp
index 1dc0c2fb13c11..c0bb6a61e8a24 100644
--- a/llvm/lib/Target/DirectX/DXILPostOptimizationValidation.cpp
+++ b/llvm/lib/Target/DirectX/DXILPostOptimizationValidation.cpp
@@ -9,6 +9,7 @@
 #include "DXILPostOptimizationValidation.h"
 #include "DXILShaderFlags.h"
 #include "DirectX.h"
+#include "llvm/ADT/SmallString.h"
 #include "llvm/Analysis/DXILMetadataAnalysis.h"
 #include "llvm/Analysis/DXILResource.h"
 #include "llvm/IR/DiagnosticInfo.h"
@@ -50,15 +51,55 @@ static void reportInvalidDirection(Module &M, DXILResourceMap &DRM) {
   }
 }
 
-} // namespace
+static void reportOverlappingError(Module &M, ResourceInfo R1,
+                                   ResourceInfo R2) {
+  SmallString<64> Message;
+  raw_svector_ostream OS(Message);
+  OS << "resource " << R1.getName() << " at register "
+     << R1.getBinding().LowerBound << " overlaps with resource " << R2.getName()
+     << " at register " << R2.getBinding().LowerBound << ", space "
+     << R2.getBinding().Space;
+  M.getContext().diagnose(DiagnosticInfoGeneric(Message));
+}
 
-PreservedAnalyses
-DXILPostOptimizationValidation::run(Module &M, ModuleAnalysisManager &MAM) {
-  DXILResourceMap &DRM = MAM.getResult<DXILResourceAnalysis>(M);
+static void reportOverlappingBinding(Module &M, DXILResourceMap &DRM) {
+  if (DRM.empty())
+    return;
 
+  for (auto ResList :
+       {DRM.srvs(), DRM.uavs(), DRM.cbuffers(), DRM.samplers()}) {
+    if (ResList.empty())
+      continue;
+    const ResourceInfo *PrevRI = &*ResList.begin();
+    for (auto *I = ResList.begin() + 1; I != ResList.end(); ++I) {
+      const ResourceInfo *RI = &*I;
+      if (PrevRI->getBinding().overlapsWith(RI->getBinding())) {
+        reportOverlappingError(M, *PrevRI, *RI);
+        continue;
+      }
+      PrevRI = RI;
+    }
+  }
+}
+
+static void reportErrors(Module &M, DXILResourceMap &DRM,
+                         DXILResourceBindingInfo &DRBI) {
   if (DRM.hasInvalidCounterDirection())
     reportInvalidDirection(M, DRM);
 
+  if (DRBI.hasOverlappingBinding())
+    reportOverlappingBinding(M, DRM);
+
+  assert(!DRBI.hasImplicitBinding() && "implicit bindings should be handled in "
+                                       "DXILResourceImplicitBinding pass");
+}
+} // namespace
+
+PreservedAnalyses
+DXILPostOptimizationValidation::run(Module &M, ModuleAnalysisManager &MAM) {
+  DXILResourceMap &DRM = MAM.getResult<DXILResourceAnalysis>(M);
+  DXILResourceBindingInfo &DRBI = MAM.getResult<DXILResourceBindingAnalysis>(M);
+  reportErrors(M, DRM, DRBI);
   return PreservedAnalyses::all();
 }
 
@@ -68,10 +109,9 @@ class DXILPostOptimizationValidationLegacy : public ModulePass {
   bool runOnModule(Module &M) override {
     DXILResourceMap &DRM =
         getAnalysis<DXILResourceWrapperPass>().getResourceMap();
-
-    if (DRM.hasInvalidCounterDirection())
-      reportInvalidDirection(M, DRM);
-
+    DXILResourceBindingInfo &DRBI =
+        getAnalysis<DXILResourceBindingWrapperPass>().getBindingInfo();
+    reportErrors(M, DRM, DRBI);
     return false;
   }
   StringRef getPassName() const override {
@@ -82,7 +122,9 @@ class DXILPostOptimizationValidationLegacy : public ModulePass {
   static char ID; // Pass identification.
   void getAnalysisUsage(llvm::AnalysisUsage &AU) const override {
     AU.addRequired<DXILResourceWrapperPass>();
+    AU.addRequired<DXILResourceBindingWrapperPass>();
     AU.addPreserved<DXILResourceWrapperPass>();
+    AU.addPreserved<DXILResourceBindingWrapperPass>();
     AU.addPreserved<DXILMetadataAnalysisWrapperPass>();
     AU.addPreserved<ShaderFlagsAnalysisWrapper>();
   }
@@ -92,6 +134,7 @@ char DXILPostOptimizationValidationLegacy::ID = 0;
 
 INITIALIZE_PASS_BEGIN(DXILPostOptimizationValidationLegacy, DEBUG_TYPE,
                       "DXIL Post Optimization Validation", false, false)
+INITIALIZE_PASS_DEPENDENCY(DXILResourceBindingWrapperPass)
 INITIALIZE_PASS_DEPENDENCY(DXILResourceTypeWrapperPass)
 INITIALIZE_PASS_DEPENDENCY(DXILResourceWrapperPass)
 INITIALIZE_PASS_END(DXILPostOptimizationValidationLegacy, DEBUG_TYPE,
diff --git a/llvm/test/CodeGen/DirectX/Binding/binding-overlap-1.ll b/llvm/test/CodeGen/DirectX/Binding/binding-overlap-1.ll
new file mode 100644
index 0000000000000..2f8ad52e72e3b
--- /dev/null
+++ b/llvm/test/CodeGen/DirectX/Binding/binding-overlap-1.ll
@@ -0,0 +1,17 @@
+; RUN: not opt -S -passes='dxil-post-optimization-validation' -mtriple=dxil-pc-shadermodel6.3-library %s 2>&1 | FileCheck %s
+
+; Check overlap error for two resource arrays.
+
+; CHECK: error: resource A at register 0 overlaps with resource B at register 5, space 0
+
+@A.str = private unnamed_addr constant [2 x i8] c"A\00", align 1
+@B.str = private unnamed_addr constant [2 x i8] c"B\00", align 1
+
+define void @test_overlapping() {
+entry:
+; RWBuffer<float> A[10] : register(u0);
+; RWBuffer<float> B[10] : register(u5);
+  %h1 = call target("dx.TypedBuffer", float, 1, 0, 0) @llvm.dx.resource.handlefrombinding(i32 0, i32 0, i32 10, i32 4, i1 false, ptr @A.str)
+  %h2 = call target("dx.TypedBuffer", float, 1, 0, 0) @llvm.dx.resource.handlefrombinding(i32 0, i32 5, i32 10, i32 4, i1 false, ptr @B.str)
+  ret void
+}
diff --git a/llvm/test/CodeGen/DirectX/Binding/binding-overlap-2.ll b/llvm/test/CodeGen/DirectX/Binding/binding-overlap-2.ll
new file mode 100644
index 0000000000000..043f8c87dcc90
--- /dev/null
+++ b/llvm/test/CodeGen/DirectX/Binding/binding-overlap-2.ll
@@ -0,0 +1,17 @@
+; RUN: not opt -S -passes='dxil-post-optimization-validation' -mtriple=dxil-pc-shadermodel6.3-library %s 2>&1 | FileCheck %s
+
+; Check overlap error for two resources with identical binding
+
+; CHECK: error: resource R at register 5 overlaps with resource S at register 5, space 10
+
+@R.str = private unnamed_addr constant [2 x i8] c"R\00", align 1
+@S.str = private unnamed_addr constant [2 x i8] c"S\00", align 1
+
+define void @test_overlapping() {
+entry:
+; RWBuffer<float> R : register(u5, space10);
+; RWBuffer<float> S : register(u5, space10);
+  %h1 = call target("dx.TypedBuffer", float, 1, 0, 0) @llvm.dx.resource.handlefrombinding(i32 10, i32 5, i32 1, i32 0, i1 false, ptr @R.str)
+  %h2 = call target("dx.TypedBuffer", float, 1, 0, 0) @llvm.dx.resource.handlefrombinding(i32 10, i32 5, i32 1, i32 0, i1 false, ptr @S.str)
+  ret void
+}
diff --git a/llvm/test/CodeGen/DirectX/Binding/binding-overlap-3.ll b/llvm/test/CodeGen/DirectX/Binding/binding-overlap-3.ll
new file mode 100644
index 0000000000000..f69f82f206106
--- /dev/null
+++ b/llvm/test/CodeGen/DirectX/Binding/binding-overlap-3.ll
@@ -0,0 +1,44 @@
+; Use llc for this test so that we don't abort after the first error.
+; RUN: not llc %s -o /dev/null 2>&1 | FileCheck %s
+
+; Check multiple overlap errors.
+; Also check different resource class with same binding values is ok (no error expected).
+
+target triple = "dxil-pc-shadermodel6.3-library"
+
+; CHECK: error: resource C at register 0 overlaps with resource A at register 5, space 0
+; CHECK: error: resource C at register 0 overlaps with resource B at register 9, space 0
+
+@A.str = private unnamed_addr constant [2 x i8] c"A\00", align 1
+@B.str = private unnamed_addr constant [2 x i8] c"B\00", align 1
+@C.str = private unnamed_addr constant [2 x i8] c"C\00", align 1
+@S.str = private unnamed_addr constant [2 x i8] c"S\00", align 1
+
+; Fake globals to store handles in; this is to make sure the handlefrombinding calls
+; are not optimized away by llc.
+@One = internal global { target("dx.RawBuffer", float, 0, 0) } poison, align 4
+@Two = internal global { target("dx.RawBuffer", float, 0, 0) } poison, align 4
+@Three = internal global { target("dx.RawBuffer", float, 0, 0) } poison, align 4
+@Four = internal global { target("dx.TypedBuffer", float, 1, 0, 0) } poison, align 4
+
+define void @test_overlapping() "hlsl.export" {
+entry:
+; StructuredBuffer<float> A : register(t5);
+; StructuredBuffer<float> B : register(t9);
+; StructuredBuffer<float> C[10] : register(t0);
+; RWBuffer<float> S[10] : register(u0);
+
+  %h1 = call target("dx.RawBuffer", float, 0, 0) @llvm.dx.resource.handlefrombinding(i32 0, i32 5, i32 1, i32 0, i1 false, ptr @A.str)
+  store target("dx.RawBuffer", float, 0, 0) %h1, ptr @One, align 4
+  
+  %h2 = call target("dx.RawBuffer", float, 0, 0) @llvm.dx.resource.handlefrombinding(i32 0, i32 9, i32 1, i32 0, i1 false, ptr @B.str)
+  store target("dx.RawBuffer", float, 0, 0) %h2, ptr @Two, align 4
+ 
+  %h3 = call target("dx.RawBuffer", float, 0, 0) @llvm.dx.resource.handlefrombinding(i32 0, i32 0, i32 10, i32 4, i1 false, ptr @C.str)
+  store target("dx.RawBuffer", float, 0, 0) %h3, ptr @Three, align 4
+ 
+  %h4 = call target("dx.TypedBuffer", float, 1, 0, 0) @llvm.dx.resource.handlefrombinding(i32 0, i32 0, i32 1, i32 0, i1 false, ptr @S.str)
+  store target("dx.TypedBuffer", float, 1, 0, 0) %h4, ptr @Four, align 4
+  
+  ret void
+}
diff --git a/llvm/test/CodeGen/DirectX/ShaderFlags/typed-uav-load-additional-formats.ll b/llvm/test/CodeGen/DirectX/ShaderFlags/typed-uav-load-additional-formats.ll
index 2c18d0b24326a..b0a5d5de77c29 100644
--- a/llvm/test/CodeGen/DirectX/ShaderFlags/typed-uav-load-additional-formats.ll
+++ b/llvm/test/CodeGen/DirectX/ShaderFlags/typed-uav-load-additional-formats.ll
@@ -26,7 +26,7 @@ define <4 x float> @multicomponent() #0 {
 ; CHECK: Function onecomponent : 0x00000000
 define float @onecomponent() #0 {
   %res = call target("dx.TypedBuffer", float, 1, 0, 0)
-      @llvm.dx.resource.handlefrombinding(i32 0, i32 0, i32 1, i32 0, i1 false, ptr null)
+      @llvm.dx.resource.handlefrombinding(i32 0, i32 1, i32 1, i32 0, i1 false, ptr null)
   %load = call {float, i1} @llvm.dx.resource.load.typedbuffer(
       target("dx.TypedBuffer", float, 1, 0, 0) %res, i32 0)
   %val = extractvalue {float, i1} %load, 0
@@ -36,7 +36,7 @@ define float @onecomponent() #0 {
 ; CHECK: Function noload : 0x00000000
 define void @noload(<4 x float> %val) #0 {
   %res = call target("dx.TypedBuffer", <4 x float>, 1, 0, 0)
-      @llvm.dx.resource.handlefrombinding(i32 0, i32 0, i32 1, i32 0, i1 false, ptr null)
+      @llvm.dx.resource.handlefrombinding(i32 0, i32 2, i32 1, i32 0, i1 false, ptr null)
   call void @llvm.dx.resource.store.typedbuffer(
       target("dx.TypedBuffer", <4 x float>, 1, 0, 0) %res, i32 0,
       <4 x float> %val)

@hekota hekota changed the base branch from users/hekota/pr140635+pr140645+pr140981 to main May 29, 2025 17:20
@hekota hekota force-pushed the report-overlapping-binding branch from dc8a3fa to 6252a27 Compare May 29, 2025 17:21
@hekota hekota merged commit 8eadbea into llvm:main May 30, 2025
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[HLSL] Diagnose overlapping resource bindings
6 participants