Skip to content

[HLSL] Add resource constructor with implicit binding for global resources #138976

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 23 commits into from
May 15, 2025
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
d5685ec
[HLSL] Implement DXILResourceBindingAnalysis
hekota Apr 24, 2025
d8a4a3f
Rename DXILResourceBindingsInfo to DXILResourceBindingInfo
hekota Apr 25, 2025
d79900c
Merge branch 'main' of https://github.com/llvm/llvm-project into reso…
hekota Apr 25, 2025
c718763
Add wrapper pass
hekota Apr 26, 2025
9a70f06
update signature
hekota Apr 28, 2025
bca1bf3
code review feedback - rename flags, remove unnecessary functions, do…
hekota Apr 30, 2025
f58e2d5
[HLSL] Implementation of DXILResourceImplicitBinding pass
hekota Apr 30, 2025
b65d3bc
fix bug & update tests
hekota May 1, 2025
075e27e
Merge branch 'main' of https://github.com/llvm/llvm-project into reso…
hekota May 2, 2025
2f0aba1
Remove extra new line
hekota May 5, 2025
79c2e7b
Merge branch 'main' of https://github.com/llvm/llvm-project into impl…
hekota May 6, 2025
e9747e5
Merge branch 'main' of https://github.com/llvm/llvm-project into reso…
hekota May 6, 2025
da838a9
Merge branch 'users/hekota/pr137258-resource-binding-analysis' of htt…
hekota May 6, 2025
dfce6ce
[HLSL] Add resource constructor with implicit binding for global reso…
hekota May 7, 2025
8a27708
code review feedback - use std::optional, renames
hekota May 9, 2025
bc93994
Merge branch 'main' of https://github.com/llvm/llvm-project into impl…
hekota May 9, 2025
8142713
Merge branch 'users/hekota/pr138043-implicit-binding-pass' of https:/…
hekota May 9, 2025
4a3e4c5
revert changes to StructuredBuffers-methods-ps.hlsl
hekota May 9, 2025
fb3d516
Merge branch 'main' of https://github.com/llvm/llvm-project into impl…
hekota May 13, 2025
2ff496b
code review feedback - update comment, rename param
hekota May 14, 2025
e4369aa
Merge branch 'main' of https://github.com/llvm/llvm-project into impl…
hekota May 14, 2025
cecd1e4
simplify constructor tests
hekota May 14, 2025
cef2c3e
rename vars
hekota May 14, 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
2 changes: 1 addition & 1 deletion clang/lib/Sema/SemaHLSL.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3290,7 +3290,7 @@ bool SemaHLSL::initGlobalResourceDecl(VarDecl *VD) {
uint32_t SpaceNo = 0;
HLSLResourceBindingAttr *RBA = VD->getAttr<HLSLResourceBindingAttr>();
if (RBA) {
if (!RBA->isImplicit())
if (RBA->hasRegisterSlot())
RegisterSlot = RBA->getSlotNumber();
SpaceNo = RBA->getSpaceNumber();
}
Expand Down
10 changes: 5 additions & 5 deletions llvm/include/llvm/Analysis/DXILResource.h
Original file line number Diff line number Diff line change
Expand Up @@ -633,13 +633,13 @@ class DXILResourceBindingInfo {
FreeRanges.emplace_back(0, UINT32_MAX);
}
// Size == -1 means unbounded array
bool findAvailableBinding(int32_t Size, uint32_t *RegSlot);
std::optional<uint32_t> findAvailableBinding(int32_t Size);
};

struct BindingSpaces {
dxil::ResourceClass ResClass;
dxil::ResourceClass RC;
llvm::SmallVector<RegisterSpace> Spaces;
BindingSpaces(dxil::ResourceClass ResClass) : ResClass(ResClass) {}
BindingSpaces(dxil::ResourceClass RC) : RC(RC) {}
RegisterSpace &getOrInsertSpace(uint32_t Space);
};

Expand Down Expand Up @@ -678,8 +678,8 @@ class DXILResourceBindingInfo {
}

// Size == -1 means unbounded array
bool findAvailableBinding(dxil::ResourceClass RC, uint32_t Space,
int32_t Size, uint32_t *RegSlot);
std::optional<uint32_t> findAvailableBinding(dxil::ResourceClass RC,
uint32_t Space, int32_t Size);

friend class DXILResourceBindingAnalysis;
friend class DXILResourceBindingWrapperPass;
Expand Down
72 changes: 38 additions & 34 deletions llvm/lib/Analysis/DXILResource.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -885,12 +885,14 @@ SmallVector<dxil::ResourceInfo *> DXILResourceMap::findByUse(const Value *Key) {

void DXILResourceBindingInfo::populate(Module &M, DXILResourceTypeMap &DRTM) {
struct Binding {
ResourceClass ResClass;
ResourceClass RC;
uint32_t Space;
uint32_t LowerBound;
uint32_t UpperBound;
Binding(ResourceClass RC, uint32_t Sp, uint32_t LB, uint32_t UB)
: ResClass(RC), Space(Sp), LowerBound(LB), UpperBound(UB) {}
Binding(ResourceClass RC, uint32_t Space, uint32_t LowerBound,
uint32_t UpperBound)
: RC(RC), Space(Space), LowerBound(LowerBound), UpperBound(UpperBound) {
}
};
SmallVector<Binding> Bindings;

Expand Down Expand Up @@ -935,15 +937,17 @@ void DXILResourceBindingInfo::populate(Module &M, DXILResourceTypeMap &DRTM) {

// sort all the collected bindings
llvm::stable_sort(Bindings, [](auto &LHS, auto &RHS) {
return std::tie(LHS.ResClass, LHS.Space, LHS.LowerBound) <
std::tie(RHS.ResClass, RHS.Space, RHS.LowerBound);
return std::tie(LHS.RC, LHS.Space, LHS.LowerBound) <
std::tie(RHS.RC, RHS.Space, RHS.LowerBound);
});

// remove duplicates
llvm::unique(Bindings, [](auto &LHS, auto &RHS) {
return std::tie(LHS.ResClass, LHS.Space, LHS.LowerBound, LHS.UpperBound) ==
std::tie(RHS.ResClass, RHS.Space, RHS.LowerBound, RHS.UpperBound);
Binding *NewEnd = llvm::unique(Bindings, [](auto &LHS, auto &RHS) {
return std::tie(LHS.RC, LHS.Space, LHS.LowerBound, LHS.UpperBound) ==
std::tie(RHS.RC, RHS.Space, RHS.LowerBound, RHS.UpperBound);
});
if (NewEnd != Bindings.end())
Bindings.erase(NewEnd);

// Go over the sorted bindings and build up lists of free register ranges
// for each binding type and used spaces. Bindings are sorted by resource
Expand All @@ -952,9 +956,9 @@ void DXILResourceBindingInfo::populate(Module &M, DXILResourceTypeMap &DRTM) {
for (unsigned I = 0, E = Bindings.size(); I != E; ++I) {
Binding &B = Bindings[I];

if (BS->ResClass != B.ResClass)
if (BS->RC != B.RC)
// move to the next resource class spaces
BS = &getBindingSpaces(B.ResClass);
BS = &getBindingSpaces(B.RC);

RegisterSpace *S = BS->Spaces.empty() ? &BS->Spaces.emplace_back(B.Space)
: &BS->Spaces.back();
Expand Down Expand Up @@ -995,12 +999,12 @@ void DXILResourceBindingInfo::populate(Module &M, DXILResourceTypeMap &DRTM) {
}

// returns false if binding could not be found in given space
bool DXILResourceBindingInfo::findAvailableBinding(dxil::ResourceClass RC,
uint32_t Space, int32_t Size,
uint32_t *RegSlot) {
std::optional<uint32_t>
DXILResourceBindingInfo::findAvailableBinding(dxil::ResourceClass RC,
uint32_t Space, int32_t Size) {
BindingSpaces &BS = getBindingSpaces(RC);
RegisterSpace &RS = BS.getOrInsertSpace(Space);
return RS.findAvailableBinding(Size, RegSlot);
return RS.findAvailableBinding(Size);
}

DXILResourceBindingInfo::RegisterSpace &
Expand All @@ -1015,38 +1019,38 @@ DXILResourceBindingInfo::BindingSpaces::getOrInsertSpace(uint32_t Space) {
return Spaces.emplace_back(Space);
}

bool DXILResourceBindingInfo::RegisterSpace::findAvailableBinding(
int32_t Size, uint32_t *RegSlot) {
std::optional<uint32_t>
DXILResourceBindingInfo::RegisterSpace::findAvailableBinding(int32_t Size) {
assert((Size == -1 || Size > 0) && "invalid size");

std::optional<uint32_t> RegSlot;
if (FreeRanges.empty())
return false;
return RegSlot;

// unbounded array
if (Size == -1) {
BindingRange &Last = FreeRanges.back();
if (Last.UpperBound != UINT32_MAX)
// this space is already occupied by an unbounded array
return false;
*RegSlot = Last.LowerBound;
RegSlot = Last.LowerBound;
FreeRanges.pop_back();
return true;
}

// single resource or fixed-size array
for (BindingRange &R : FreeRanges) {
// compare the size as uint64_t to prevent overflow for range (0,
// UINT32_MAX)
if ((uint64_t)R.UpperBound - R.LowerBound + 1 < (uint64_t)Size)
continue;
*RegSlot = R.LowerBound;
// This might create a range where (LowerBound == UpperBound + 1), but
// that's ok.
R.LowerBound += Size;
return true;
} else {
// single resource or fixed-size array
for (BindingRange &R : FreeRanges) {
// compare the size as uint64_t to prevent overflow for range (0,
// UINT32_MAX)
if ((uint64_t)R.UpperBound - R.LowerBound + 1 < (uint64_t)Size)
continue;
RegSlot = R.LowerBound;
// This might create a range where (LowerBound == UpperBound + 1). When
// that happens, the next time this function is called the range will
// skipped over by the check above (at this point Size is always > 0).
R.LowerBound += Size;
break;
}
}

return false;
return RegSlot;
}

//===----------------------------------------------------------------------===//
Expand Down
17 changes: 8 additions & 9 deletions llvm/lib/Target/DirectX/DXILResourceImplicitBinding.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,13 @@ static void diagnoseImplicitBindingNotFound(CallInst *ImplBindingCall) {

static bool assignBindings(Module &M, DXILResourceBindingInfo &DRBI,
DXILResourceTypeMap &DRTM) {
struct ImplBindingCall {
struct ImplicitBindingCall {
int OrderID;
CallInst *Call;
ImplBindingCall(int OrderID, CallInst *Call)
ImplicitBindingCall(int OrderID, CallInst *Call)
: OrderID(OrderID), Call(Call) {}
};
SmallVector<ImplBindingCall> Calls;
SmallVector<ImplicitBindingCall> Calls;
SmallVector<Function *> FunctionsToMaybeRemove;

// collect all of the llvm.dx.resource.handlefromImplicitbinding calls
Expand Down Expand Up @@ -78,7 +78,7 @@ static bool assignBindings(Module &M, DXILResourceBindingInfo &DRBI,
bool AllBindingsAssigned = true;
bool Changed = false;

for (auto &IB : Calls) {
for (ImplicitBindingCall &IB : Calls) {
IRBuilder<> Builder(IB.Call);

if (IB.OrderID != LastOrderID) {
Expand All @@ -91,15 +91,14 @@ static bool assignBindings(Module &M, DXILResourceBindingInfo &DRBI,
int32_t Size =
cast<ConstantInt>(IB.Call->getArgOperand(2))->getZExtValue();

uint32_t RegSlot;
RegSlotOp = nullptr;
if (!DRBI.findAvailableBinding(RTI.getResourceClass(), Space, Size,
&RegSlot)) {
std::optional<uint32_t> RegSlot =
DRBI.findAvailableBinding(RTI.getResourceClass(), Space, Size);
if (!RegSlot) {
diagnoseImplicitBindingNotFound(IB.Call);
AllBindingsAssigned = false;
continue;
}
RegSlotOp = ConstantInt::get(Builder.getInt32Ty(), RegSlot);
RegSlotOp = ConstantInt::get(Builder.getInt32Ty(), RegSlot.value());
}

if (!RegSlotOp)
Expand Down
62 changes: 54 additions & 8 deletions llvm/unittests/Target/DirectX/ResourceBindingAnalysisTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ define void @main() {
// check that UAV has exactly one gap
DXILResourceBindingInfo::BindingSpaces &UAVSpaces =
DRBI.getBindingSpaces(ResourceClass::UAV);
EXPECT_EQ(UAVSpaces.ResClass, ResourceClass::UAV);
EXPECT_EQ(UAVSpaces.RC, ResourceClass::UAV);
EXPECT_EQ(UAVSpaces.Spaces.size(), 1u);
checkExpectedSpaceAndFreeRanges(UAVSpaces.Spaces[0], 0,
{0, 4, 6, UINT32_MAX});
Expand All @@ -89,7 +89,7 @@ define void @main() {
for (auto RC :
{ResourceClass::SRV, ResourceClass::CBuffer, ResourceClass::Sampler}) {
DXILResourceBindingInfo::BindingSpaces &Spaces = DRBI.getBindingSpaces(RC);
EXPECT_EQ(Spaces.ResClass, RC);
EXPECT_EQ(Spaces.RC, RC);
EXPECT_EQ(Spaces.Spaces.size(), 0u);
}
}
Expand All @@ -101,6 +101,8 @@ TEST_F(ResourceBindingAnalysisTest, TestManyBindings) {
// RWBuffer<float> C : register(u5);
// StructuredBuffer<int> D[5] : register(t0);
// RWBuffer<float> E[2] : register(u2);
// SamplerState S1 : register(s5, space2);
// SamplerState S2 : register(s4, space2);
StringRef Assembly = R"(
%__cblayout_CB = type <{ i32 }>
define void @main() {
Expand All @@ -111,6 +113,10 @@ define void @main() {
%handleC = call target("dx.TypedBuffer", float, 1, 0, 0) @llvm.dx.resource.handlefrombinding(i32 0, i32 5, i32 1, i32 0, i1 false)
%handleD = call target("dx.RawBuffer", i32, 0, 0) @llvm.dx.resource.handlefrombinding(i32 0, i32 0, i32 5, i32 4, i1 false)
%handleE = call target("dx.TypedBuffer", float, 1, 0, 0) @llvm.dx.resource.handlefrombinding(i32 0, i32 2, i32 2, i32 0, i1 false)
%handleS1 = call target("dx.Sampler", 0) @llvm.dx.resource.handlefrombinding(i32 2, i32 5, i32 1, i32 0, i1 false)
%handleS2 = call target("dx.Sampler", 0) @llvm.dx.resource.handlefrombinding(i32 2, i32 4, i32 1, i32 0, i1 false)
; duplicate binding for the same resource
%handleD2 = call target("dx.RawBuffer", i32, 0, 0) @llvm.dx.resource.handlefrombinding(i32 0, i32 0, i32 5, i32 4, i1 false)
ret void
}
)";
Expand All @@ -125,13 +131,15 @@ define void @main() {

DXILResourceBindingInfo::BindingSpaces &SRVSpaces =
DRBI.getBindingSpaces(ResourceClass::SRV);
EXPECT_EQ(SRVSpaces.ResClass, ResourceClass::SRV);
EXPECT_EQ(SRVSpaces.RC, ResourceClass::SRV);
EXPECT_EQ(SRVSpaces.Spaces.size(), 1u);
// verify that consecutive bindings are merged
// (SRVSpaces has only one free space range {6, UINT32_MAX}).
checkExpectedSpaceAndFreeRanges(SRVSpaces.Spaces[0], 0, {6, UINT32_MAX});

DXILResourceBindingInfo::BindingSpaces &UAVSpaces =
DRBI.getBindingSpaces(ResourceClass::UAV);
EXPECT_EQ(UAVSpaces.ResClass, ResourceClass::UAV);
EXPECT_EQ(UAVSpaces.RC, ResourceClass::UAV);
EXPECT_EQ(UAVSpaces.Spaces.size(), 2u);
checkExpectedSpaceAndFreeRanges(UAVSpaces.Spaces[0], 0,
{0, 1, 4, 4, 6, UINT32_MAX});
Expand All @@ -140,10 +148,17 @@ define void @main() {

DXILResourceBindingInfo::BindingSpaces &CBufferSpaces =
DRBI.getBindingSpaces(ResourceClass::CBuffer);
EXPECT_EQ(CBufferSpaces.ResClass, ResourceClass::CBuffer);
EXPECT_EQ(CBufferSpaces.RC, ResourceClass::CBuffer);
EXPECT_EQ(CBufferSpaces.Spaces.size(), 1u);
checkExpectedSpaceAndFreeRanges(CBufferSpaces.Spaces[0], 0,
{0, 2, 4, UINT32_MAX});

DXILResourceBindingInfo::BindingSpaces &SamplerSpaces =
DRBI.getBindingSpaces(ResourceClass::Sampler);
EXPECT_EQ(SamplerSpaces.RC, ResourceClass::Sampler);
EXPECT_EQ(SamplerSpaces.Spaces.size(), 1u);
checkExpectedSpaceAndFreeRanges(SamplerSpaces.Spaces[0], 2,
{0, 3, 6, UINT32_MAX});
}

TEST_F(ResourceBindingAnalysisTest, TestUnboundedAndOverlap) {
Expand Down Expand Up @@ -173,12 +188,43 @@ define void @main() {

DXILResourceBindingInfo::BindingSpaces &SRVSpaces =
DRBI.getBindingSpaces(ResourceClass::SRV);
EXPECT_EQ(SRVSpaces.ResClass, ResourceClass::SRV);
EXPECT_EQ(SRVSpaces.RC, ResourceClass::SRV);
EXPECT_EQ(SRVSpaces.Spaces.size(), 2u);
checkExpectedSpaceAndFreeRanges(SRVSpaces.Spaces[0], 0, {3, 4});
checkExpectedSpaceAndFreeRanges(SRVSpaces.Spaces[1], 2, {});
}

TEST_F(ResourceBindingAnalysisTest, TestExactOverlap) {
// StructuredBuffer<float> A : register(t5);
// StructuredBuffer<float> B : register(t5);
StringRef Assembly = R"(
%__cblayout_CB = type <{ i32 }>
define void @main() {
entry:
%handleA = call target("dx.RawBuffer", float, 0, 0) @llvm.dx.resource.handlefrombinding(i32 0, i32 5, i32 1, i32 0, i1 false)
%handleB = call target("dx.RawBuffer", float, 0, 0) @llvm.dx.resource.handlefrombinding(i32 0, i32 5, i32 1, i32 0, i1 false)
ret void
}
)";

auto M = parseAsm(Assembly);

DXILResourceBindingInfo &DRBI =
MAM->getResult<DXILResourceBindingAnalysis>(*M);

EXPECT_EQ(false, DRBI.hasImplicitBinding());
// FIXME (XFAIL): detecting overlap of two resource with identical binding
// is not yet supported (llvm/llvm-project#110723).
EXPECT_EQ(false, DRBI.hasOverlappingBinding());

DXILResourceBindingInfo::BindingSpaces &SRVSpaces =
DRBI.getBindingSpaces(ResourceClass::SRV);
EXPECT_EQ(SRVSpaces.RC, ResourceClass::SRV);
EXPECT_EQ(SRVSpaces.Spaces.size(), 1u);
checkExpectedSpaceAndFreeRanges(SRVSpaces.Spaces[0], 0,
{0, 4, 6, UINT32_MAX});
}

TEST_F(ResourceBindingAnalysisTest, TestEndOfRange) {
// RWBuffer<float> A : register(u4294967295); /* UINT32_MAX */
// RWBuffer<float> B[10] : register(u4294967286, space1);
Expand Down Expand Up @@ -206,7 +252,7 @@ define void @main() {

DXILResourceBindingInfo::BindingSpaces &UAVSpaces =
DRBI.getBindingSpaces(ResourceClass::UAV);
EXPECT_EQ(UAVSpaces.ResClass, ResourceClass::UAV);
EXPECT_EQ(UAVSpaces.RC, ResourceClass::UAV);
EXPECT_EQ(UAVSpaces.Spaces.size(), 3u);
checkExpectedSpaceAndFreeRanges(UAVSpaces.Spaces[0], 0, {0, UINT32_MAX - 1});
checkExpectedSpaceAndFreeRanges(UAVSpaces.Spaces[1], 1, {0, UINT32_MAX - 10});
Expand Down Expand Up @@ -236,7 +282,7 @@ define void @main() {

DXILResourceBindingInfo::BindingSpaces &UAVSpaces =
DRBI.getBindingSpaces(ResourceClass::UAV);
EXPECT_EQ(UAVSpaces.ResClass, ResourceClass::UAV);
EXPECT_EQ(UAVSpaces.RC, ResourceClass::UAV);
EXPECT_EQ(UAVSpaces.Spaces.size(), 1u);
checkExpectedSpaceAndFreeRanges(UAVSpaces.Spaces[0], 100,
{0, 4, 6, UINT32_MAX});
Expand Down
Loading