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
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 5 additions & 2 deletions llvm/include/llvm/Analysis/DXILResource.h
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,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:
Expand Down Expand Up @@ -394,8 +397,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 {
Expand Down
61 changes: 53 additions & 8 deletions llvm/lib/Target/DirectX/DXILPostOptimizationValidation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -50,15 +51,57 @@ static void reportInvalidDirection(Module &M, DXILResourceMap &DRM) {
}
}

} // namespace
static void reportOverlappingError(Module &M, ResourceInfo R1,
ResourceInfo R2) {
SmallString<128> 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 << " in 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 (const 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 *CurrentRI = &*I;
const ResourceInfo *RI = CurrentRI;
while (RI != ResList.end() &&
PrevRI->getBinding().overlapsWith(RI->getBinding())) {
reportOverlappingError(M, *PrevRI, *RI);
RI++;
}
PrevRI = CurrentRI;
}
}
}

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();
}

Expand All @@ -68,10 +111,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 {
Expand All @@ -82,7 +124,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>();
}
Expand All @@ -92,6 +136,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,
Expand Down
19 changes: 19 additions & 0 deletions llvm/test/CodeGen/DirectX/Binding/binding-overlap-1.ll
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
; 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.

; A overlaps with B
; RWBuffer<float> A[10] : register(u0);
; RWBuffer<float> B[10] : register(u5);

; CHECK: error: resource A at register 0 overlaps with resource B at register 5 in 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:
%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
}
19 changes: 19 additions & 0 deletions llvm/test/CodeGen/DirectX/Binding/binding-overlap-2.ll
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
; 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

; R overlaps exactly with S
; RWBuffer<float> R : register(u5, space10);
; RWBuffer<float> S : register(u5, space10);

; CHECK: error: resource R at register 5 overlaps with resource S at register 5 in 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:
%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
}
46 changes: 46 additions & 0 deletions llvm/test/CodeGen/DirectX/Binding/binding-overlap-3.ll
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
; 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).

; C overlaps with A
; C overlaps with B
; StructuredBuffer<float> A : register(t5);
; StructuredBuffer<float> B : register(t9);
; StructuredBuffer<float> C[10] : register(t0);
; RWBuffer<float> S[10] : register(u0);

; CHECK: error: resource C at register 0 overlaps with resource A at register 5 in space 0
; CHECK: error: resource C at register 0 overlaps with resource B at register 9 in space 0

target triple = "dxil-pc-shadermodel6.3-library"

@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:
%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
}
41 changes: 41 additions & 0 deletions llvm/test/CodeGen/DirectX/Binding/binding-overlap-4.ll
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
; 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.

; A overlaps with B
; A overlaps with C
; B overlaps with C
; StructuredBuffer<float> A[5] : register(t1); // 1-5
; StructuredBuffer<float> B[2] : register(t2); // 2-3
; StructuredBuffer<float> C[3] : register(t3); // 3-5

; CHECK: error: resource A at register 1 overlaps with resource B at register 2 in space 0
; CHECK: error: resource A at register 1 overlaps with resource C at register 3 in space 0
; CHECK: error: resource B at register 2 overlaps with resource C at register 3 in space 0

target triple = "dxil-pc-shadermodel6.3-library"

@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

; 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

define void @test_overlapping() "hlsl.export" {
entry:
%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)
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 2, i32 2, 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 3, i32 3, i32 4, i1 false, ptr @C.str)
store target("dx.RawBuffer", float, 0, 0) %h3, ptr @Three, align 4

ret void
}
39 changes: 39 additions & 0 deletions llvm/test/CodeGen/DirectX/Binding/binding-overlap-5.ll
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
; 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.

; A overlaps with B
; B overlaps with C
; StructuredBuffer<float> A[5] : register(t1, space11); // 1-5
; StructuredBuffer<float> B[6] : register(t2, space11); // 2-7
; StructuredBuffer<float> C[3] : register(t6, space11); // 6-8

; CHECK: error: resource A at register 1 overlaps with resource B at register 2 in space 11
; CHECK: error: resource B at register 2 overlaps with resource C at register 6 in space 11

target triple = "dxil-pc-shadermodel6.3-library"

@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

; 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

define void @test_overlapping() "hlsl.export" {
entry:
%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)
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 11, i32 2, i32 6, 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 11, i32 6, i32 3, i32 4, i1 false, ptr @C.str)
store target("dx.RawBuffer", float, 0, 0) %h3, ptr @Three, align 4

ret void
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand Down