Skip to content

Commit 8eadbea

Browse files
authored
[HLSL] Diagnose overlapping resource bindings (#140982)
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
1 parent 986f519 commit 8eadbea

File tree

8 files changed

+224
-12
lines changed

8 files changed

+224
-12
lines changed

llvm/include/llvm/Analysis/DXILResource.h

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,9 @@ class ResourceInfo {
358358
return std::tie(RecordID, Space, LowerBound, Size) <
359359
std::tie(RHS.RecordID, RHS.Space, RHS.LowerBound, RHS.Size);
360360
}
361+
bool overlapsWith(const ResourceBinding &RHS) const {
362+
return Space == RHS.Space && LowerBound + Size - 1 >= RHS.LowerBound;
363+
}
361364
};
362365

363366
private:
@@ -394,8 +397,8 @@ class ResourceInfo {
394397
getAnnotateProps(Module &M, dxil::ResourceTypeInfo &RTI) const;
395398

396399
bool operator==(const ResourceInfo &RHS) const {
397-
return std::tie(Binding, HandleTy, Symbol) ==
398-
std::tie(RHS.Binding, RHS.HandleTy, RHS.Symbol);
400+
return std::tie(Binding, HandleTy, Symbol, Name) ==
401+
std::tie(RHS.Binding, RHS.HandleTy, RHS.Symbol, RHS.Name);
399402
}
400403
bool operator!=(const ResourceInfo &RHS) const { return !(*this == RHS); }
401404
bool operator<(const ResourceInfo &RHS) const {

llvm/lib/Target/DirectX/DXILPostOptimizationValidation.cpp

Lines changed: 53 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include "DXILPostOptimizationValidation.h"
1010
#include "DXILShaderFlags.h"
1111
#include "DirectX.h"
12+
#include "llvm/ADT/SmallString.h"
1213
#include "llvm/Analysis/DXILMetadataAnalysis.h"
1314
#include "llvm/Analysis/DXILResource.h"
1415
#include "llvm/IR/DiagnosticInfo.h"
@@ -50,15 +51,57 @@ static void reportInvalidDirection(Module &M, DXILResourceMap &DRM) {
5051
}
5152
}
5253

53-
} // namespace
54+
static void reportOverlappingError(Module &M, ResourceInfo R1,
55+
ResourceInfo R2) {
56+
SmallString<128> Message;
57+
raw_svector_ostream OS(Message);
58+
OS << "resource " << R1.getName() << " at register "
59+
<< R1.getBinding().LowerBound << " overlaps with resource " << R2.getName()
60+
<< " at register " << R2.getBinding().LowerBound << " in space "
61+
<< R2.getBinding().Space;
62+
M.getContext().diagnose(DiagnosticInfoGeneric(Message));
63+
}
5464

55-
PreservedAnalyses
56-
DXILPostOptimizationValidation::run(Module &M, ModuleAnalysisManager &MAM) {
57-
DXILResourceMap &DRM = MAM.getResult<DXILResourceAnalysis>(M);
65+
static void reportOverlappingBinding(Module &M, DXILResourceMap &DRM) {
66+
if (DRM.empty())
67+
return;
68+
69+
for (const auto &ResList :
70+
{DRM.srvs(), DRM.uavs(), DRM.cbuffers(), DRM.samplers()}) {
71+
if (ResList.empty())
72+
continue;
73+
const ResourceInfo *PrevRI = &*ResList.begin();
74+
for (auto *I = ResList.begin() + 1; I != ResList.end(); ++I) {
75+
const ResourceInfo *CurrentRI = &*I;
76+
const ResourceInfo *RI = CurrentRI;
77+
while (RI != ResList.end() &&
78+
PrevRI->getBinding().overlapsWith(RI->getBinding())) {
79+
reportOverlappingError(M, *PrevRI, *RI);
80+
RI++;
81+
}
82+
PrevRI = CurrentRI;
83+
}
84+
}
85+
}
5886

87+
static void reportErrors(Module &M, DXILResourceMap &DRM,
88+
DXILResourceBindingInfo &DRBI) {
5989
if (DRM.hasInvalidCounterDirection())
6090
reportInvalidDirection(M, DRM);
6191

92+
if (DRBI.hasOverlappingBinding())
93+
reportOverlappingBinding(M, DRM);
94+
95+
assert(!DRBI.hasImplicitBinding() && "implicit bindings should be handled in "
96+
"DXILResourceImplicitBinding pass");
97+
}
98+
} // namespace
99+
100+
PreservedAnalyses
101+
DXILPostOptimizationValidation::run(Module &M, ModuleAnalysisManager &MAM) {
102+
DXILResourceMap &DRM = MAM.getResult<DXILResourceAnalysis>(M);
103+
DXILResourceBindingInfo &DRBI = MAM.getResult<DXILResourceBindingAnalysis>(M);
104+
reportErrors(M, DRM, DRBI);
62105
return PreservedAnalyses::all();
63106
}
64107

@@ -68,10 +111,9 @@ class DXILPostOptimizationValidationLegacy : public ModulePass {
68111
bool runOnModule(Module &M) override {
69112
DXILResourceMap &DRM =
70113
getAnalysis<DXILResourceWrapperPass>().getResourceMap();
71-
72-
if (DRM.hasInvalidCounterDirection())
73-
reportInvalidDirection(M, DRM);
74-
114+
DXILResourceBindingInfo &DRBI =
115+
getAnalysis<DXILResourceBindingWrapperPass>().getBindingInfo();
116+
reportErrors(M, DRM, DRBI);
75117
return false;
76118
}
77119
StringRef getPassName() const override {
@@ -82,7 +124,9 @@ class DXILPostOptimizationValidationLegacy : public ModulePass {
82124
static char ID; // Pass identification.
83125
void getAnalysisUsage(llvm::AnalysisUsage &AU) const override {
84126
AU.addRequired<DXILResourceWrapperPass>();
127+
AU.addRequired<DXILResourceBindingWrapperPass>();
85128
AU.addPreserved<DXILResourceWrapperPass>();
129+
AU.addPreserved<DXILResourceBindingWrapperPass>();
86130
AU.addPreserved<DXILMetadataAnalysisWrapperPass>();
87131
AU.addPreserved<ShaderFlagsAnalysisWrapper>();
88132
}
@@ -92,6 +136,7 @@ char DXILPostOptimizationValidationLegacy::ID = 0;
92136

93137
INITIALIZE_PASS_BEGIN(DXILPostOptimizationValidationLegacy, DEBUG_TYPE,
94138
"DXIL Post Optimization Validation", false, false)
139+
INITIALIZE_PASS_DEPENDENCY(DXILResourceBindingWrapperPass)
95140
INITIALIZE_PASS_DEPENDENCY(DXILResourceTypeWrapperPass)
96141
INITIALIZE_PASS_DEPENDENCY(DXILResourceWrapperPass)
97142
INITIALIZE_PASS_END(DXILPostOptimizationValidationLegacy, DEBUG_TYPE,
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
; RUN: not opt -S -passes='dxil-post-optimization-validation' -mtriple=dxil-pc-shadermodel6.3-library %s 2>&1 | FileCheck %s
2+
3+
; Check overlap error for two resource arrays.
4+
5+
; A overlaps with B
6+
; RWBuffer<float> A[10] : register(u0);
7+
; RWBuffer<float> B[10] : register(u5);
8+
9+
; CHECK: error: resource A at register 0 overlaps with resource B at register 5 in space 0
10+
11+
@A.str = private unnamed_addr constant [2 x i8] c"A\00", align 1
12+
@B.str = private unnamed_addr constant [2 x i8] c"B\00", align 1
13+
14+
define void @test_overlapping() {
15+
entry:
16+
%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)
17+
%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)
18+
ret void
19+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
; RUN: not opt -S -passes='dxil-post-optimization-validation' -mtriple=dxil-pc-shadermodel6.3-library %s 2>&1 | FileCheck %s
2+
3+
; Check overlap error for two resources with identical binding
4+
5+
; R overlaps exactly with S
6+
; RWBuffer<float> R : register(u5, space10);
7+
; RWBuffer<float> S : register(u5, space10);
8+
9+
; CHECK: error: resource R at register 5 overlaps with resource S at register 5 in space 10
10+
11+
@R.str = private unnamed_addr constant [2 x i8] c"R\00", align 1
12+
@S.str = private unnamed_addr constant [2 x i8] c"S\00", align 1
13+
14+
define void @test_overlapping() {
15+
entry:
16+
%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)
17+
%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)
18+
ret void
19+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
; Use llc for this test so that we don't abort after the first error.
2+
; RUN: not llc %s -o /dev/null 2>&1 | FileCheck %s
3+
4+
; Check multiple overlap errors.
5+
; Also check different resource class with same binding values is ok (no error expected).
6+
7+
; C overlaps with A
8+
; C overlaps with B
9+
; StructuredBuffer<float> A : register(t5);
10+
; StructuredBuffer<float> B : register(t9);
11+
; StructuredBuffer<float> C[10] : register(t0);
12+
; RWBuffer<float> S[10] : register(u0);
13+
14+
; CHECK: error: resource C at register 0 overlaps with resource A at register 5 in space 0
15+
; CHECK: error: resource C at register 0 overlaps with resource B at register 9 in space 0
16+
17+
target triple = "dxil-pc-shadermodel6.3-library"
18+
19+
@A.str = private unnamed_addr constant [2 x i8] c"A\00", align 1
20+
@B.str = private unnamed_addr constant [2 x i8] c"B\00", align 1
21+
@C.str = private unnamed_addr constant [2 x i8] c"C\00", align 1
22+
@S.str = private unnamed_addr constant [2 x i8] c"S\00", align 1
23+
24+
; Fake globals to store handles in; this is to make sure the handlefrombinding calls
25+
; are not optimized away by llc.
26+
@One = internal global { target("dx.RawBuffer", float, 0, 0) } poison, align 4
27+
@Two = internal global { target("dx.RawBuffer", float, 0, 0) } poison, align 4
28+
@Three = internal global { target("dx.RawBuffer", float, 0, 0) } poison, align 4
29+
@Four = internal global { target("dx.TypedBuffer", float, 1, 0, 0) } poison, align 4
30+
31+
define void @test_overlapping() "hlsl.export" {
32+
entry:
33+
%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)
34+
store target("dx.RawBuffer", float, 0, 0) %h1, ptr @One, align 4
35+
36+
%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)
37+
store target("dx.RawBuffer", float, 0, 0) %h2, ptr @Two, align 4
38+
39+
%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)
40+
store target("dx.RawBuffer", float, 0, 0) %h3, ptr @Three, align 4
41+
42+
%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)
43+
store target("dx.TypedBuffer", float, 1, 0, 0) %h4, ptr @Four, align 4
44+
45+
ret void
46+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
; Use llc for this test so that we don't abort after the first error.
2+
; RUN: not llc %s -o /dev/null 2>&1 | FileCheck %s
3+
4+
; Check multiple overlap errors.
5+
6+
; A overlaps with B
7+
; A overlaps with C
8+
; B overlaps with C
9+
; StructuredBuffer<float> A[5] : register(t1); // 1-5
10+
; StructuredBuffer<float> B[2] : register(t2); // 2-3
11+
; StructuredBuffer<float> C[3] : register(t3); // 3-5
12+
13+
; CHECK: error: resource A at register 1 overlaps with resource B at register 2 in space 0
14+
; CHECK: error: resource A at register 1 overlaps with resource C at register 3 in space 0
15+
; CHECK: error: resource B at register 2 overlaps with resource C at register 3 in space 0
16+
17+
target triple = "dxil-pc-shadermodel6.3-library"
18+
19+
@A.str = private unnamed_addr constant [2 x i8] c"A\00", align 1
20+
@B.str = private unnamed_addr constant [2 x i8] c"B\00", align 1
21+
@C.str = private unnamed_addr constant [2 x i8] c"C\00", align 1
22+
23+
; Fake globals to store handles in; this is to make sure the handlefrombinding calls
24+
; are not optimized away by llc.
25+
@One = internal global { target("dx.RawBuffer", float, 0, 0) } poison, align 4
26+
@Two = internal global { target("dx.RawBuffer", float, 0, 0) } poison, align 4
27+
@Three = internal global { target("dx.RawBuffer", float, 0, 0) } poison, align 4
28+
29+
define void @test_overlapping() "hlsl.export" {
30+
entry:
31+
%h1 = call target("dx.RawBuffer", float, 0, 0) @llvm.dx.resource.handlefrombinding(i32 0, i32 1, i32 5, i32 0, i1 false, ptr @A.str)
32+
store target("dx.RawBuffer", float, 0, 0) %h1, ptr @One, align 4
33+
34+
%h2 = call target("dx.RawBuffer", float, 0, 0) @llvm.dx.resource.handlefrombinding(i32 0, i32 2, i32 2, i32 0, i1 false, ptr @B.str)
35+
store target("dx.RawBuffer", float, 0, 0) %h2, ptr @Two, align 4
36+
37+
%h3 = call target("dx.RawBuffer", float, 0, 0) @llvm.dx.resource.handlefrombinding(i32 0, i32 3, i32 3, i32 4, i1 false, ptr @C.str)
38+
store target("dx.RawBuffer", float, 0, 0) %h3, ptr @Three, align 4
39+
40+
ret void
41+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
; Use llc for this test so that we don't abort after the first error.
2+
; RUN: not llc %s -o /dev/null 2>&1 | FileCheck %s
3+
4+
; Check multiple overlap errors.
5+
6+
; A overlaps with B
7+
; B overlaps with C
8+
; StructuredBuffer<float> A[5] : register(t1, space11); // 1-5
9+
; StructuredBuffer<float> B[6] : register(t2, space11); // 2-7
10+
; StructuredBuffer<float> C[3] : register(t6, space11); // 6-8
11+
12+
; CHECK: error: resource A at register 1 overlaps with resource B at register 2 in space 11
13+
; CHECK: error: resource B at register 2 overlaps with resource C at register 6 in space 11
14+
15+
target triple = "dxil-pc-shadermodel6.3-library"
16+
17+
@A.str = private unnamed_addr constant [2 x i8] c"A\00", align 1
18+
@B.str = private unnamed_addr constant [2 x i8] c"B\00", align 1
19+
@C.str = private unnamed_addr constant [2 x i8] c"C\00", align 1
20+
21+
; Fake globals to store handles in; this is to make sure the handlefrombinding calls
22+
; are not optimized away by llc.
23+
@One = internal global { target("dx.RawBuffer", float, 0, 0) } poison, align 4
24+
@Two = internal global { target("dx.RawBuffer", float, 0, 0) } poison, align 4
25+
@Three = internal global { target("dx.RawBuffer", float, 0, 0) } poison, align 4
26+
27+
define void @test_overlapping() "hlsl.export" {
28+
entry:
29+
%h1 = call target("dx.RawBuffer", float, 0, 0) @llvm.dx.resource.handlefrombinding(i32 11, i32 1, i32 5, i32 0, i1 false, ptr @A.str)
30+
store target("dx.RawBuffer", float, 0, 0) %h1, ptr @One, align 4
31+
32+
%h2 = call target("dx.RawBuffer", float, 0, 0) @llvm.dx.resource.handlefrombinding(i32 11, i32 2, i32 6, i32 0, i1 false, ptr @B.str)
33+
store target("dx.RawBuffer", float, 0, 0) %h2, ptr @Two, align 4
34+
35+
%h3 = call target("dx.RawBuffer", float, 0, 0) @llvm.dx.resource.handlefrombinding(i32 11, i32 6, i32 3, i32 4, i1 false, ptr @C.str)
36+
store target("dx.RawBuffer", float, 0, 0) %h3, ptr @Three, align 4
37+
38+
ret void
39+
}

llvm/test/CodeGen/DirectX/ShaderFlags/typed-uav-load-additional-formats.ll

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ define <4 x float> @multicomponent() #0 {
2626
; CHECK: Function onecomponent : 0x00000000
2727
define float @onecomponent() #0 {
2828
%res = call target("dx.TypedBuffer", float, 1, 0, 0)
29-
@llvm.dx.resource.handlefrombinding(i32 0, i32 0, i32 1, i32 0, i1 false, ptr null)
29+
@llvm.dx.resource.handlefrombinding(i32 0, i32 1, i32 1, i32 0, i1 false, ptr null)
3030
%load = call {float, i1} @llvm.dx.resource.load.typedbuffer(
3131
target("dx.TypedBuffer", float, 1, 0, 0) %res, i32 0)
3232
%val = extractvalue {float, i1} %load, 0
@@ -36,7 +36,7 @@ define float @onecomponent() #0 {
3636
; CHECK: Function noload : 0x00000000
3737
define void @noload(<4 x float> %val) #0 {
3838
%res = call target("dx.TypedBuffer", <4 x float>, 1, 0, 0)
39-
@llvm.dx.resource.handlefrombinding(i32 0, i32 0, i32 1, i32 0, i1 false, ptr null)
39+
@llvm.dx.resource.handlefrombinding(i32 0, i32 2, i32 1, i32 0, i1 false, ptr null)
4040
call void @llvm.dx.resource.store.typedbuffer(
4141
target("dx.TypedBuffer", <4 x float>, 1, 0, 0) %res, i32 0,
4242
<4 x float> %val)

0 commit comments

Comments
 (0)