From d5685ecc132979e88424d06e81fbefe8d1f6450d Mon Sep 17 00:00:00 2001 From: Helena Kotas Date: Thu, 24 Apr 2025 13:27:52 -0700 Subject: [PATCH 1/7] [HLSL] Implement DXILResourceBindingAnalysis DXILResourceBindingAnalysis analyses all explicit resource bindings in the module and puts together lists of used virtual register spaces and available virtual register slot ranges for each binding type. It also stores additional information found during the analysis such as whether the module uses implicit bindings or if any of the bindings overlap. This information will be used in DXILResourceImplicitBindings pass to assign register slots to resources with implicit bindings, and in a post-optimization validation pass that will raise diagnostic about overlapping bindings. Part 1/2 of #136786 --- llvm/include/llvm/Analysis/DXILResource.h | 105 +++++++ llvm/include/llvm/IR/IntrinsicsDirectX.td | 9 + llvm/lib/Analysis/DXILResource.cpp | 125 ++++++++ llvm/lib/Passes/PassRegistry.def | 1 + llvm/unittests/Target/DirectX/CMakeLists.txt | 1 + .../DirectX/ResourceBindingAnalysisTests.cpp | 268 ++++++++++++++++++ 6 files changed, 509 insertions(+) create mode 100644 llvm/unittests/Target/DirectX/ResourceBindingAnalysisTests.cpp diff --git a/llvm/include/llvm/Analysis/DXILResource.h b/llvm/include/llvm/Analysis/DXILResource.h index 1d871a448c16c..5019fc38665ff 100644 --- a/llvm/include/llvm/Analysis/DXILResource.h +++ b/llvm/include/llvm/Analysis/DXILResource.h @@ -10,6 +10,7 @@ #define LLVM_ANALYSIS_DXILRESOURCE_H #include "llvm/ADT/MapVector.h" +#include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringRef.h" #include "llvm/IR/DerivedTypes.h" #include "llvm/IR/GlobalVariable.h" @@ -17,6 +18,8 @@ #include "llvm/Pass.h" #include "llvm/Support/Alignment.h" #include "llvm/Support/DXILABI.h" +#include +#include namespace llvm { class CallInst; @@ -586,6 +589,108 @@ class DXILResourceWrapperPass : public ModulePass { ModulePass *createDXILResourceWrapperPassPass(); +//===----------------------------------------------------------------------===// + +// DXILResourceBindingsInfo stores the results of DXILResourceBindingAnalysis +// which analyses all llvm.dx.resource.handlefrombinding calls in the module +// and puts together lists of used virtual register spaces and available +// virtual register slot ranges for each binding type. +// It also stores additional information found during the analysis such as +// whether the module uses implicit bindings or if any of the bindings overlap. +// +// This information will be used in DXILResourceImplicitBindings pass to assign +// register slots to resources with implicit bindings, and in a +// post-optimization validation pass that will raise diagnostic about +// overlapping bindings. +// +// For example for these resource bindings: +// +// RWBuffer A[10] : register(u3); +// RWBuffer B[] : register(u5, space2) +// +// The analysis result for UAV binding type will look like this: +// +// UAVSpaces { +// ResClass = ResourceClass::UAV, +// Spaces = { +// { Space = 0, FreeRanges = {{ 0, 2 }, { 13, UINT32_MAX }} }, +// { Space = 2, FreeRanges = {{ 0, 4 }} } +// } +// } +// +class DXILResourceBindingsInfo { +public: + struct BindingRange { + uint32_t LowerBound; + uint32_t UpperBound; + BindingRange(uint32_t LB, uint32_t UB) : LowerBound(LB), UpperBound(UB) {} + }; + + struct RegisterSpace { + uint32_t Space; + SmallVector FreeRanges; + RegisterSpace(uint32_t Space) : Space(Space) { + FreeRanges.emplace_back(0, UINT32_MAX); + } + }; + + struct BindingSpaces { + dxil::ResourceClass ResClass; + llvm::SmallVector Spaces; + BindingSpaces(dxil::ResourceClass ResClass) : ResClass(ResClass) { + // initialize space0 + Spaces.emplace_back(0); + } + }; + +private: + BindingSpaces SRVSpaces, UAVSpaces, CBufferSpaces, SamplerSpaces; + bool ImplicitBinding; + bool OverlappingBinding; + + // Populate the resource binding info given explicit resource binding calls + // in the module. + void populate(Module &M, DXILResourceTypeMap &DRTM); + +public: + DXILResourceBindingsInfo() + : SRVSpaces(dxil::ResourceClass::SRV), + UAVSpaces(dxil::ResourceClass::UAV), + CBufferSpaces(dxil::ResourceClass::CBuffer), + SamplerSpaces(dxil::ResourceClass::Sampler), ImplicitBinding(false), + OverlappingBinding(false) {} + + bool containsImplicitBinding() const { return ImplicitBinding; } + bool containsOverlappingBinding() const { return OverlappingBinding; } + + BindingSpaces &getBindingSpaces(dxil::ResourceClass RC) { + switch (RC) { + case dxil::ResourceClass::SRV: + return SRVSpaces; + case dxil::ResourceClass::UAV: + return UAVSpaces; + case dxil::ResourceClass::CBuffer: + return CBufferSpaces; + case dxil::ResourceClass::Sampler: + return SamplerSpaces; + } + } + + friend class DXILResourceBindingAnalysis; +}; + +class DXILResourceBindingAnalysis + : public AnalysisInfoMixin { + friend AnalysisInfoMixin; + + static AnalysisKey Key; + +public: + using Result = DXILResourceBindingsInfo; + + DXILResourceBindingsInfo run(Module &M, ModuleAnalysisManager &AM); +}; + } // namespace llvm #endif // LLVM_ANALYSIS_DXILRESOURCE_H diff --git a/llvm/include/llvm/IR/IntrinsicsDirectX.td b/llvm/include/llvm/IR/IntrinsicsDirectX.td index b1a27311e2a9c..444600980fc1e 100644 --- a/llvm/include/llvm/IR/IntrinsicsDirectX.td +++ b/llvm/include/llvm/IR/IntrinsicsDirectX.td @@ -27,6 +27,15 @@ def int_dx_resource_handlefrombinding [llvm_i32_ty, llvm_i32_ty, llvm_i32_ty, llvm_i32_ty, llvm_i1_ty], [IntrNoMem]>; +// Create resource handle with implicit binding in given register space. +// Returns a `target("dx.")` type appropriate for the kind of resource and +// the range size and index of the binding. +def int_dx_resource_handlefromimplicitbinding + : DefaultAttrsIntrinsic< + [llvm_any_ty], + [llvm_i32_ty, llvm_i32_ty, llvm_i32_ty, llvm_i32_ty], + [IntrNoMem]>; + def int_dx_resource_getpointer : DefaultAttrsIntrinsic<[llvm_anyptr_ty], [llvm_any_ty, llvm_i32_ty], [IntrNoMem]>; diff --git a/llvm/lib/Analysis/DXILResource.cpp b/llvm/lib/Analysis/DXILResource.cpp index cc1d931c9e077..1ca73ce81789a 100644 --- a/llvm/lib/Analysis/DXILResource.cpp +++ b/llvm/lib/Analysis/DXILResource.cpp @@ -8,7 +8,9 @@ #include "llvm/Analysis/DXILResource.h" #include "llvm/ADT/APInt.h" +#include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SmallString.h" +#include "llvm/ADT/SmallVector.h" #include "llvm/IR/Constants.h" #include "llvm/IR/DerivedTypes.h" #include "llvm/IR/DiagnosticInfo.h" @@ -19,6 +21,8 @@ #include "llvm/IR/Module.h" #include "llvm/InitializePasses.h" #include "llvm/Support/FormatVariadic.h" +#include +#include #define DEBUG_TYPE "dxil-resource" @@ -879,8 +883,121 @@ SmallVector DXILResourceMap::findByUse(const Value *Key) { //===----------------------------------------------------------------------===// +void DXILResourceBindingsInfo::populate(Module &M, DXILResourceTypeMap &DRTM) { + struct Binding { + ResourceClass ResClass; + 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) {} + }; + SmallVector Bindings; + + // collect all of the llvm.dx.resource.handlefrombinding calls; + // make a note if there is llvm.dx.resource.handlefromimplicitbinding + for (Function &F : M.functions()) { + if (!F.isDeclaration()) + continue; + + switch (F.getIntrinsicID()) { + default: + continue; + case Intrinsic::dx_resource_handlefrombinding: { + auto *HandleTy = cast(F.getReturnType()); + ResourceTypeInfo &RTI = DRTM[HandleTy]; + + for (User *U : F.users()) + if (CallInst *CI = dyn_cast(U)) { + uint32_t Space = + cast(CI->getArgOperand(0))->getZExtValue(); + uint32_t LowerBound = + cast(CI->getArgOperand(1))->getZExtValue(); + int32_t Size = + cast(CI->getArgOperand(2))->getZExtValue(); + + // negative size means unbounded resource array; + // upper bound register overflow should be detected in Sema + assert((Size < 0 || (unsigned)LowerBound + Size - 1 <= UINT32_MAX) && + "upper bound register overflow"); + uint32_t UpperBound = Size < 0 ? UINT32_MAX : LowerBound + Size - 1; + Bindings.emplace_back(RTI.getResourceClass(), Space, LowerBound, + UpperBound); + } + break; + } + case Intrinsic::dx_resource_handlefromimplicitbinding: { + if (!F.user_empty()) + ImplicitBinding = true; + break; + } + } + } + + // 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); + }); + + // 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); + }); + + // 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 + // class, space, and lower bound register slot. + BindingSpaces *BS = &SRVSpaces; + for (unsigned I = 0, E = Bindings.size(); I != E; ++I) { + Binding &B = Bindings[I]; + + if (BS->ResClass != B.ResClass) + // move to the next resource class spaces + BS = &getBindingSpaces(B.ResClass); + + RegisterSpace *S = &BS->Spaces.back(); + assert(S->Space <= B.Space && "bindings not sorted correctly?"); + if (B.Space != S->Space) + // add new space + S = &BS->Spaces.emplace_back(B.Space); + + // the space is full - set flag to report overlapping binding later + if (S->FreeRanges.empty()) { + OverlappingBinding = true; + continue; + } + + // adjust the last free range lower bound, split it in two, or remove it + BindingRange &LastFreeRange = S->FreeRanges.back(); + assert(LastFreeRange.UpperBound == UINT32_MAX); + if (LastFreeRange.LowerBound == B.LowerBound) { + if (B.UpperBound < UINT32_MAX) + LastFreeRange.LowerBound = B.UpperBound + 1; + else + S->FreeRanges.pop_back(); + + } else if (LastFreeRange.LowerBound < B.LowerBound) { + LastFreeRange.UpperBound = B.LowerBound - 1; + if (B.UpperBound < UINT32_MAX) + S->FreeRanges.emplace_back(B.UpperBound + 1, UINT32_MAX); + } else { + OverlappingBinding = true; + if (B.UpperBound < UINT32_MAX) + LastFreeRange.LowerBound = + std::max(LastFreeRange.LowerBound, B.UpperBound + 1); + else + S->FreeRanges.pop_back(); + } + } +} + +//===----------------------------------------------------------------------===// + AnalysisKey DXILResourceTypeAnalysis::Key; AnalysisKey DXILResourceAnalysis::Key; +AnalysisKey DXILResourceBindingAnalysis::Key; DXILResourceMap DXILResourceAnalysis::run(Module &M, ModuleAnalysisManager &AM) { @@ -890,6 +1007,14 @@ DXILResourceMap DXILResourceAnalysis::run(Module &M, return Data; } +DXILResourceBindingsInfo +DXILResourceBindingAnalysis::run(Module &M, ModuleAnalysisManager &AM) { + DXILResourceBindingsInfo Data; + DXILResourceTypeMap &DRTM = AM.getResult(M); + Data.populate(M, DRTM); + return Data; +} + PreservedAnalyses DXILResourcePrinterPass::run(Module &M, ModuleAnalysisManager &AM) { DXILResourceMap &DRM = AM.getResult(M); diff --git a/llvm/lib/Passes/PassRegistry.def b/llvm/lib/Passes/PassRegistry.def index d5d1b2173da69..ea792280ed975 100644 --- a/llvm/lib/Passes/PassRegistry.def +++ b/llvm/lib/Passes/PassRegistry.def @@ -24,6 +24,7 @@ MODULE_ANALYSIS("ctx-prof-analysis", CtxProfAnalysis()) MODULE_ANALYSIS("dxil-metadata", DXILMetadataAnalysis()) MODULE_ANALYSIS("dxil-resources", DXILResourceAnalysis()) MODULE_ANALYSIS("dxil-resource-type", DXILResourceTypeAnalysis()) +MODULE_ANALYSIS("dxil-resource-bindings", DXILResourceBindingAnalysis()) MODULE_ANALYSIS("inline-advisor", InlineAdvisorAnalysis()) MODULE_ANALYSIS("ir-similarity", IRSimilarityAnalysis()) MODULE_ANALYSIS("last-run-tracking", LastRunTrackingAnalysis()) diff --git a/llvm/unittests/Target/DirectX/CMakeLists.txt b/llvm/unittests/Target/DirectX/CMakeLists.txt index b1359b37ad521..2c135c1b4b499 100644 --- a/llvm/unittests/Target/DirectX/CMakeLists.txt +++ b/llvm/unittests/Target/DirectX/CMakeLists.txt @@ -22,4 +22,5 @@ add_llvm_target_unittest(DirectXTests PointerTypeAnalysisTests.cpp UniqueResourceFromUseTests.cpp RegisterCostTests.cpp + ResourceBindingAnalysisTests.cpp ) diff --git a/llvm/unittests/Target/DirectX/ResourceBindingAnalysisTests.cpp b/llvm/unittests/Target/DirectX/ResourceBindingAnalysisTests.cpp new file mode 100644 index 0000000000000..3ff091d67a886 --- /dev/null +++ b/llvm/unittests/Target/DirectX/ResourceBindingAnalysisTests.cpp @@ -0,0 +1,268 @@ +//===- llvm/unittests/Target/DirectX/ResourceBindingAnalysisTests.cpp -----===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "llvm/ADT/ArrayRef.h" +#include "llvm/Analysis/DXILResource.h" +#include "llvm/AsmParser/Parser.h" +#include "llvm/Passes/PassBuilder.h" +#include "llvm/Support/DXILABI.h" +#include "gtest/gtest.h" +#include + +using namespace llvm; +using namespace llvm::dxil; + +namespace { +class ResourceBindingAnalysisTest : public testing::Test { +protected: + PassBuilder *PB; + ModuleAnalysisManager *MAM; + LLVMContext *Context; + + virtual void SetUp() { + PB = new PassBuilder(); + MAM = new ModuleAnalysisManager(); + Context = new LLVMContext(); + PB->registerModuleAnalyses(*MAM); + MAM->registerPass([&] { return DXILResourceBindingAnalysis(); }); + } + + std::unique_ptr parseAsm(StringRef Asm) { + SMDiagnostic Error; + std::unique_ptr M = parseAssemblyString(Asm, Error, *Context); + EXPECT_TRUE(M) << "Bad assembly?: " << Error.getMessage(); + return M; + } + + virtual void TearDown() { + delete PB; + delete MAM; + delete Context; + } + + void checkExpectedSpaceAndFreeRanges( + DXILResourceBindingsInfo::RegisterSpace &RegSpace, uint32_t ExpSpace, + ArrayRef ExpValues) { + EXPECT_EQ(RegSpace.Space, ExpSpace); + EXPECT_EQ(RegSpace.FreeRanges.size() * 2, ExpValues.size()); + unsigned I = 0; + for (auto &R : RegSpace.FreeRanges) { + EXPECT_EQ(R.LowerBound, ExpValues[I]); + EXPECT_EQ(R.UpperBound, ExpValues[I + 1]); + I += 2; + } + } +}; + +TEST_F(ResourceBindingAnalysisTest, TestTrivialCase) { + // RWBuffer Buf : register(u5); + StringRef Assembly = R"( +define void @main() { +entry: + %handle = call target("dx.TypedBuffer", float, 1, 0, 0) @llvm.dx.resource.handlefrombinding(i32 0, i32 5, i32 1, i32 0, i1 false) + + call void @a.func(target("dx.TypedBuffer", float, 1, 0, 0) %handle) + ret void +} + +declare void @a.func(target("dx.RawBuffer", float, 1, 0) %handle) + )"; + + auto M = parseAsm(Assembly); + + DXILResourceBindingsInfo &DRBI = + MAM->getResult(*M); + + EXPECT_EQ(false, DRBI.containsImplicitBinding()); + EXPECT_EQ(false, DRBI.containsOverlappingBinding()); + + // check that UAV has exactly one gap + DXILResourceBindingsInfo::BindingSpaces &UAVSpaces = + DRBI.getBindingSpaces(ResourceClass::UAV); + EXPECT_EQ(UAVSpaces.ResClass, ResourceClass::UAV); + EXPECT_EQ(UAVSpaces.Spaces.size(), 1u); + checkExpectedSpaceAndFreeRanges(UAVSpaces.Spaces[0], 0, + {0, 4, 6, UINT32_MAX}); + + // check that other kinds of register spaces are all available + for (auto RC : + {ResourceClass::SRV, ResourceClass::CBuffer, ResourceClass::Sampler}) { + DXILResourceBindingsInfo::BindingSpaces &Spaces = DRBI.getBindingSpaces(RC); + EXPECT_EQ(Spaces.ResClass, RC); + EXPECT_EQ(Spaces.Spaces.size(), 1u); + checkExpectedSpaceAndFreeRanges(Spaces.Spaces[0], 0, {0, UINT32_MAX}); + } +} + +TEST_F(ResourceBindingAnalysisTest, TestManyBindings) { + // cbuffer CB : register(b3) { int a; } + // RWBuffer A[5] : register(u10, space20); + // StructuredBuffer B : register(t5); + // RWBuffer C : register(u5); + // StructuredBuffer D[5] : register(t0); + // RWBuffer E[2] : register(u2); + StringRef Assembly = R"( +%__cblayout_CB = type <{ i32 }> +define void @main() { +entry: + %handleCB = call target("dx.CBuffer", target("dx.Layout", %__cblayout_CB, 4, 0)) @llvm.dx.resource.handlefrombinding(i32 0, i32 3, i32 1, i32 0, i1 false) + %handleA = call target("dx.TypedBuffer", float, 1, 0, 0) @llvm.dx.resource.handlefrombinding(i32 20, i32 10, i32 5, i32 0, i1 false) + %handleB = call target("dx.RawBuffer", i32, 0, 0) @llvm.dx.resource.handlefrombinding(i32 0, i32 5, i32 1, i32 0, i1 false) + %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) + + call void @a.func(target("dx.CBuffer", target("dx.Layout", %__cblayout_CB, 4, 0)) %handleCB) + call void @a.func(target("dx.TypedBuffer", float, 1, 0, 0) %handleA) + call void @a.func(target("dx.TypedBuffer", float, 1, 0, 0) %handleC) + call void @a.func(target("dx.TypedBuffer", float, 1, 0, 0) %handleE) + call void @a.func(target("dx.RawBuffer", i32, 0, 0) %handleB) + call void @a.func(target("dx.RawBuffer", i32, 0, 0) %handleD) + + ret void +} + +declare void @a.func(target("dx.RawBuffer", float, 1, 0) %handle) + )"; + + auto M = parseAsm(Assembly); + + DXILResourceBindingsInfo &DRBI = + MAM->getResult(*M); + + EXPECT_EQ(false, DRBI.containsImplicitBinding()); + EXPECT_EQ(false, DRBI.containsOverlappingBinding()); + + DXILResourceBindingsInfo::BindingSpaces &SRVSpaces = + DRBI.getBindingSpaces(ResourceClass::SRV); + EXPECT_EQ(SRVSpaces.ResClass, ResourceClass::SRV); + EXPECT_EQ(SRVSpaces.Spaces.size(), 1u); + checkExpectedSpaceAndFreeRanges(SRVSpaces.Spaces[0], 0, {6, UINT32_MAX}); + + DXILResourceBindingsInfo::BindingSpaces &UAVSpaces = + DRBI.getBindingSpaces(ResourceClass::UAV); + EXPECT_EQ(UAVSpaces.ResClass, ResourceClass::UAV); + EXPECT_EQ(UAVSpaces.Spaces.size(), 2u); + checkExpectedSpaceAndFreeRanges(UAVSpaces.Spaces[0], 0, + {0, 1, 4, 4, 6, UINT32_MAX}); + checkExpectedSpaceAndFreeRanges(UAVSpaces.Spaces[1], 20, + {0, 9, 15, UINT32_MAX}); + + DXILResourceBindingsInfo::BindingSpaces &CBufferSpaces = + DRBI.getBindingSpaces(ResourceClass::CBuffer); + EXPECT_EQ(CBufferSpaces.ResClass, ResourceClass::CBuffer); + EXPECT_EQ(CBufferSpaces.Spaces.size(), 1u); + checkExpectedSpaceAndFreeRanges(CBufferSpaces.Spaces[0], 0, + {0, 2, 4, UINT32_MAX}); +} + +TEST_F(ResourceBindingAnalysisTest, TestUnboundedAndOverlap) { + // StructuredBuffer A[] : register(t5); + // StructuredBuffer B[3] : register(t0); + // StructuredBuffer C[] : register(t0, space2); + // StructuredBuffer D : register(t4, space2); /* overlapping */ + 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 10, i1 false) + %handleB = call target("dx.RawBuffer", float, 0, 0) @llvm.dx.resource.handlefrombinding(i32 0, i32 0, i32 3, i32 0, i1 false) + %handleC = call target("dx.RawBuffer", float, 0, 0) @llvm.dx.resource.handlefrombinding(i32 2, i32 0, i32 -1, i32 100, i1 false) + %handleD = call target("dx.RawBuffer", float, 0, 0) @llvm.dx.resource.handlefrombinding(i32 2, i32 4, i32 1, i32 0, i1 false) + + call void @a.func(target("dx.RawBuffer", float, 0, 0) %handleA) + call void @a.func(target("dx.RawBuffer", float, 0, 0) %handleB) + call void @a.func(target("dx.RawBuffer", float, 0, 0) %handleC) + call void @a.func(target("dx.RawBuffer", float, 0, 0) %handleD) + + ret void +} + +declare void @a.func(target("dx.RawBuffer", float, 0, 0) %handle) + )"; + + auto M = parseAsm(Assembly); + + DXILResourceBindingsInfo &DRBI = + MAM->getResult(*M); + + EXPECT_EQ(false, DRBI.containsImplicitBinding()); + EXPECT_EQ(true, DRBI.containsOverlappingBinding()); + + DXILResourceBindingsInfo::BindingSpaces &SRVSpaces = + DRBI.getBindingSpaces(ResourceClass::SRV); + EXPECT_EQ(SRVSpaces.ResClass, ResourceClass::SRV); + EXPECT_EQ(SRVSpaces.Spaces.size(), 2u); + checkExpectedSpaceAndFreeRanges(SRVSpaces.Spaces[0], 0, {3, 4}); + checkExpectedSpaceAndFreeRanges(SRVSpaces.Spaces[1], 2, {}); +} + +TEST_F(ResourceBindingAnalysisTest, TestEndOfRange) { + // RWBuffer A : register(u4294967295); /* UINT32_MAX */ + // RWBuffer B[10] : register(u4294967286, space1); + // /* range (UINT32_MAX - 9, UINT32_MAX )*/ + // RWBuffer C[10] : register(u2147483647, space2); + // /* range (INT32_MAX, INT32_MAX + 9) */ + StringRef Assembly = R"( +%__cblayout_CB = type <{ i32 }> +define void @main() { +entry: + %handleA = call target("dx.TypedBuffer", float, 1, 0, 0) @llvm.dx.resource.handlefrombinding(i32 0, i32 -1, i32 1, i32 0, i1 false) + %handleB = call target("dx.TypedBuffer", float, 1, 0, 0) @llvm.dx.resource.handlefrombinding(i32 1, i32 -10, i32 10, i32 50, i1 false) + %handleC = call target("dx.TypedBuffer", float, 1, 0, 0) @llvm.dx.resource.handlefrombinding(i32 2, i32 2147483647, i32 10, i32 100, i1 false) + + call void @a.func(target("dx.TypedBuffer", float, 1, 0, 0) %handleA) + call void @a.func(target("dx.TypedBuffer", float, 1, 0, 0) %handleB) + call void @a.func(target("dx.TypedBuffer", float, 1, 0, 0) %handleC) + + ret void +} + +declare void @a.func(target("dx.TypedBuffer", float, 1, 0, 0) %handle) + )"; + + auto M = parseAsm(Assembly); + + DXILResourceBindingsInfo &DRBI = + MAM->getResult(*M); + + EXPECT_EQ(false, DRBI.containsImplicitBinding()); + EXPECT_EQ(false, DRBI.containsOverlappingBinding()); + + DXILResourceBindingsInfo::BindingSpaces &UAVSpaces = + DRBI.getBindingSpaces(ResourceClass::UAV); + EXPECT_EQ(UAVSpaces.ResClass, 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}); + checkExpectedSpaceAndFreeRanges( + UAVSpaces.Spaces[2], 2, + {0, (uint32_t)INT32_MAX - 1, (uint32_t)INT32_MAX + 10, UINT32_MAX}); +} + +TEST_F(ResourceBindingAnalysisTest, TestImplicitFlag) { + // RWBuffer A; + StringRef Assembly = R"( +%__cblayout_CB = type <{ i32 }> +define void @main() { +entry: + %handleA = call target("dx.TypedBuffer", float, 1, 0, 0) @llvm.dx.resource.handlefromimplicitbinding(i32 0, i32 0, i32 1, i32 0) + call void @a.func(target("dx.TypedBuffer", float, 1, 0, 0) %handleA) + ret void +} +declare void @a.func(target("dx.TypedBuffer", float, 1, 0, 0) %handle) + )"; + + auto M = parseAsm(Assembly); + + DXILResourceBindingsInfo &DRBI = + MAM->getResult(*M); + EXPECT_EQ(true, DRBI.containsImplicitBinding()); +} + +} // namespace From d8a4a3f91efecebb71b12dff1cef3952bc705390 Mon Sep 17 00:00:00 2001 From: Helena Kotas Date: Fri, 25 Apr 2025 13:39:35 -0700 Subject: [PATCH 2/7] Rename DXILResourceBindingsInfo to DXILResourceBindingInfo --- llvm/include/llvm/Analysis/DXILResource.h | 10 +++---- llvm/lib/Analysis/DXILResource.cpp | 6 ++--- .../DirectX/ResourceBindingAnalysisTests.cpp | 26 +++++++++---------- 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/llvm/include/llvm/Analysis/DXILResource.h b/llvm/include/llvm/Analysis/DXILResource.h index 5019fc38665ff..475249994c4f3 100644 --- a/llvm/include/llvm/Analysis/DXILResource.h +++ b/llvm/include/llvm/Analysis/DXILResource.h @@ -591,7 +591,7 @@ ModulePass *createDXILResourceWrapperPassPass(); //===----------------------------------------------------------------------===// -// DXILResourceBindingsInfo stores the results of DXILResourceBindingAnalysis +// DXILResourceBindingInfo stores the results of DXILResourceBindingAnalysis // which analyses all llvm.dx.resource.handlefrombinding calls in the module // and puts together lists of used virtual register spaces and available // virtual register slot ranges for each binding type. @@ -618,7 +618,7 @@ ModulePass *createDXILResourceWrapperPassPass(); // } // } // -class DXILResourceBindingsInfo { +class DXILResourceBindingInfo { public: struct BindingRange { uint32_t LowerBound; @@ -653,7 +653,7 @@ class DXILResourceBindingsInfo { void populate(Module &M, DXILResourceTypeMap &DRTM); public: - DXILResourceBindingsInfo() + DXILResourceBindingInfo() : SRVSpaces(dxil::ResourceClass::SRV), UAVSpaces(dxil::ResourceClass::UAV), CBufferSpaces(dxil::ResourceClass::CBuffer), @@ -686,9 +686,9 @@ class DXILResourceBindingAnalysis static AnalysisKey Key; public: - using Result = DXILResourceBindingsInfo; + using Result = DXILResourceBindingInfo; - DXILResourceBindingsInfo run(Module &M, ModuleAnalysisManager &AM); + DXILResourceBindingInfo run(Module &M, ModuleAnalysisManager &AM); }; } // namespace llvm diff --git a/llvm/lib/Analysis/DXILResource.cpp b/llvm/lib/Analysis/DXILResource.cpp index 1ca73ce81789a..c55d70b2ef12d 100644 --- a/llvm/lib/Analysis/DXILResource.cpp +++ b/llvm/lib/Analysis/DXILResource.cpp @@ -883,7 +883,7 @@ SmallVector DXILResourceMap::findByUse(const Value *Key) { //===----------------------------------------------------------------------===// -void DXILResourceBindingsInfo::populate(Module &M, DXILResourceTypeMap &DRTM) { +void DXILResourceBindingInfo::populate(Module &M, DXILResourceTypeMap &DRTM) { struct Binding { ResourceClass ResClass; uint32_t Space; @@ -1007,9 +1007,9 @@ DXILResourceMap DXILResourceAnalysis::run(Module &M, return Data; } -DXILResourceBindingsInfo +DXILResourceBindingInfo DXILResourceBindingAnalysis::run(Module &M, ModuleAnalysisManager &AM) { - DXILResourceBindingsInfo Data; + DXILResourceBindingInfo Data; DXILResourceTypeMap &DRTM = AM.getResult(M); Data.populate(M, DRTM); return Data; diff --git a/llvm/unittests/Target/DirectX/ResourceBindingAnalysisTests.cpp b/llvm/unittests/Target/DirectX/ResourceBindingAnalysisTests.cpp index 3ff091d67a886..bfc4955c51251 100644 --- a/llvm/unittests/Target/DirectX/ResourceBindingAnalysisTests.cpp +++ b/llvm/unittests/Target/DirectX/ResourceBindingAnalysisTests.cpp @@ -46,7 +46,7 @@ class ResourceBindingAnalysisTest : public testing::Test { } void checkExpectedSpaceAndFreeRanges( - DXILResourceBindingsInfo::RegisterSpace &RegSpace, uint32_t ExpSpace, + DXILResourceBindingInfo::RegisterSpace &RegSpace, uint32_t ExpSpace, ArrayRef ExpValues) { EXPECT_EQ(RegSpace.Space, ExpSpace); EXPECT_EQ(RegSpace.FreeRanges.size() * 2, ExpValues.size()); @@ -75,14 +75,14 @@ declare void @a.func(target("dx.RawBuffer", float, 1, 0) %handle) auto M = parseAsm(Assembly); - DXILResourceBindingsInfo &DRBI = + DXILResourceBindingInfo &DRBI = MAM->getResult(*M); EXPECT_EQ(false, DRBI.containsImplicitBinding()); EXPECT_EQ(false, DRBI.containsOverlappingBinding()); // check that UAV has exactly one gap - DXILResourceBindingsInfo::BindingSpaces &UAVSpaces = + DXILResourceBindingInfo::BindingSpaces &UAVSpaces = DRBI.getBindingSpaces(ResourceClass::UAV); EXPECT_EQ(UAVSpaces.ResClass, ResourceClass::UAV); EXPECT_EQ(UAVSpaces.Spaces.size(), 1u); @@ -92,7 +92,7 @@ declare void @a.func(target("dx.RawBuffer", float, 1, 0) %handle) // check that other kinds of register spaces are all available for (auto RC : {ResourceClass::SRV, ResourceClass::CBuffer, ResourceClass::Sampler}) { - DXILResourceBindingsInfo::BindingSpaces &Spaces = DRBI.getBindingSpaces(RC); + DXILResourceBindingInfo::BindingSpaces &Spaces = DRBI.getBindingSpaces(RC); EXPECT_EQ(Spaces.ResClass, RC); EXPECT_EQ(Spaces.Spaces.size(), 1u); checkExpectedSpaceAndFreeRanges(Spaces.Spaces[0], 0, {0, UINT32_MAX}); @@ -132,19 +132,19 @@ declare void @a.func(target("dx.RawBuffer", float, 1, 0) %handle) auto M = parseAsm(Assembly); - DXILResourceBindingsInfo &DRBI = + DXILResourceBindingInfo &DRBI = MAM->getResult(*M); EXPECT_EQ(false, DRBI.containsImplicitBinding()); EXPECT_EQ(false, DRBI.containsOverlappingBinding()); - DXILResourceBindingsInfo::BindingSpaces &SRVSpaces = + DXILResourceBindingInfo::BindingSpaces &SRVSpaces = DRBI.getBindingSpaces(ResourceClass::SRV); EXPECT_EQ(SRVSpaces.ResClass, ResourceClass::SRV); EXPECT_EQ(SRVSpaces.Spaces.size(), 1u); checkExpectedSpaceAndFreeRanges(SRVSpaces.Spaces[0], 0, {6, UINT32_MAX}); - DXILResourceBindingsInfo::BindingSpaces &UAVSpaces = + DXILResourceBindingInfo::BindingSpaces &UAVSpaces = DRBI.getBindingSpaces(ResourceClass::UAV); EXPECT_EQ(UAVSpaces.ResClass, ResourceClass::UAV); EXPECT_EQ(UAVSpaces.Spaces.size(), 2u); @@ -153,7 +153,7 @@ declare void @a.func(target("dx.RawBuffer", float, 1, 0) %handle) checkExpectedSpaceAndFreeRanges(UAVSpaces.Spaces[1], 20, {0, 9, 15, UINT32_MAX}); - DXILResourceBindingsInfo::BindingSpaces &CBufferSpaces = + DXILResourceBindingInfo::BindingSpaces &CBufferSpaces = DRBI.getBindingSpaces(ResourceClass::CBuffer); EXPECT_EQ(CBufferSpaces.ResClass, ResourceClass::CBuffer); EXPECT_EQ(CBufferSpaces.Spaces.size(), 1u); @@ -188,13 +188,13 @@ declare void @a.func(target("dx.RawBuffer", float, 0, 0) %handle) auto M = parseAsm(Assembly); - DXILResourceBindingsInfo &DRBI = + DXILResourceBindingInfo &DRBI = MAM->getResult(*M); EXPECT_EQ(false, DRBI.containsImplicitBinding()); EXPECT_EQ(true, DRBI.containsOverlappingBinding()); - DXILResourceBindingsInfo::BindingSpaces &SRVSpaces = + DXILResourceBindingInfo::BindingSpaces &SRVSpaces = DRBI.getBindingSpaces(ResourceClass::SRV); EXPECT_EQ(SRVSpaces.ResClass, ResourceClass::SRV); EXPECT_EQ(SRVSpaces.Spaces.size(), 2u); @@ -228,13 +228,13 @@ declare void @a.func(target("dx.TypedBuffer", float, 1, 0, 0) %handle) auto M = parseAsm(Assembly); - DXILResourceBindingsInfo &DRBI = + DXILResourceBindingInfo &DRBI = MAM->getResult(*M); EXPECT_EQ(false, DRBI.containsImplicitBinding()); EXPECT_EQ(false, DRBI.containsOverlappingBinding()); - DXILResourceBindingsInfo::BindingSpaces &UAVSpaces = + DXILResourceBindingInfo::BindingSpaces &UAVSpaces = DRBI.getBindingSpaces(ResourceClass::UAV); EXPECT_EQ(UAVSpaces.ResClass, ResourceClass::UAV); EXPECT_EQ(UAVSpaces.Spaces.size(), 3u); @@ -260,7 +260,7 @@ declare void @a.func(target("dx.TypedBuffer", float, 1, 0, 0) %handle) auto M = parseAsm(Assembly); - DXILResourceBindingsInfo &DRBI = + DXILResourceBindingInfo &DRBI = MAM->getResult(*M); EXPECT_EQ(true, DRBI.containsImplicitBinding()); } From c718763b4343390c50f7179cc1abc3fefe260dfd Mon Sep 17 00:00:00 2001 From: Helena Kotas Date: Fri, 25 Apr 2025 19:02:52 -0700 Subject: [PATCH 3/7] Add wrapper pass --- llvm/include/llvm/Analysis/DXILResource.h | 20 +++++++++++++++ llvm/include/llvm/InitializePasses.h | 3 ++- llvm/lib/Analysis/Analysis.cpp | 3 ++- llvm/lib/Analysis/DXILResource.cpp | 30 +++++++++++++++++++++++ 4 files changed, 54 insertions(+), 2 deletions(-) diff --git a/llvm/include/llvm/Analysis/DXILResource.h b/llvm/include/llvm/Analysis/DXILResource.h index 1f09e12716248..556c15868290e 100644 --- a/llvm/include/llvm/Analysis/DXILResource.h +++ b/llvm/include/llvm/Analysis/DXILResource.h @@ -677,6 +677,7 @@ class DXILResourceBindingInfo { } friend class DXILResourceBindingAnalysis; + friend class DXILResourceBindingWrapperPass; }; class DXILResourceBindingAnalysis @@ -691,6 +692,25 @@ class DXILResourceBindingAnalysis DXILResourceBindingInfo run(Module &M, ModuleAnalysisManager &AM); }; +class DXILResourceBindingWrapperPass : public ModulePass { + std::unique_ptr BindingInfo; + +public: + static char ID; + + DXILResourceBindingWrapperPass(); + ~DXILResourceBindingWrapperPass() override; + + DXILResourceBindingInfo &getBindingInfo() { return *BindingInfo; } + const DXILResourceBindingInfo &getBindingInfo() const { return *BindingInfo; } + + void getAnalysisUsage(AnalysisUsage &AU) const override; + bool runOnModule(Module &M) override; + void releaseMemory() override; +}; + +ModulePass *createDXILResourceBindingWrapperPassPass(); + } // namespace llvm #endif // LLVM_ANALYSIS_DXILRESOURCE_H diff --git a/llvm/include/llvm/InitializePasses.h b/llvm/include/llvm/InitializePasses.h index 1ce36a95317b4..b84c6d6123d58 100644 --- a/llvm/include/llvm/InitializePasses.h +++ b/llvm/include/llvm/InitializePasses.h @@ -84,8 +84,9 @@ void initializeDAHPass(PassRegistry &); void initializeDCELegacyPassPass(PassRegistry &); void initializeDXILMetadataAnalysisWrapperPassPass(PassRegistry &); void initializeDXILMetadataAnalysisWrapperPrinterPass(PassRegistry &); -void initializeDXILResourceWrapperPassPass(PassRegistry &); +void initializeDXILResourceBindingWrapperPassPass(PassRegistry &); void initializeDXILResourceTypeWrapperPassPass(PassRegistry &); +void initializeDXILResourceWrapperPassPass(PassRegistry &); void initializeDeadMachineInstructionElimPass(PassRegistry &); void initializeDebugifyMachineModulePass(PassRegistry &); void initializeDependenceAnalysisWrapperPassPass(PassRegistry &); diff --git a/llvm/lib/Analysis/Analysis.cpp b/llvm/lib/Analysis/Analysis.cpp index 43ea30f3a9a81..484a456f49f1b 100644 --- a/llvm/lib/Analysis/Analysis.cpp +++ b/llvm/lib/Analysis/Analysis.cpp @@ -27,8 +27,9 @@ void llvm::initializeAnalysis(PassRegistry &Registry) { initializeCallGraphViewerPass(Registry); initializeCycleInfoWrapperPassPass(Registry); initializeDXILMetadataAnalysisWrapperPassPass(Registry); - initializeDXILResourceWrapperPassPass(Registry); + initializeDXILResourceBindingWrapperPassPass(Registry); initializeDXILResourceTypeWrapperPassPass(Registry); + initializeDXILResourceWrapperPassPass(Registry); initializeDependenceAnalysisWrapperPassPass(Registry); initializeDominanceFrontierWrapperPassPass(Registry); initializeDomViewerWrapperPassPass(Registry); diff --git a/llvm/lib/Analysis/DXILResource.cpp b/llvm/lib/Analysis/DXILResource.cpp index 5e46463e520ae..4cfe62766acae 100644 --- a/llvm/lib/Analysis/DXILResource.cpp +++ b/llvm/lib/Analysis/DXILResource.cpp @@ -1077,3 +1077,33 @@ char DXILResourceWrapperPass::ID = 0; ModulePass *llvm::createDXILResourceWrapperPassPass() { return new DXILResourceWrapperPass(); } + +DXILResourceBindingWrapperPass::DXILResourceBindingWrapperPass() + : ModulePass(ID) {} + +DXILResourceBindingWrapperPass::~DXILResourceBindingWrapperPass() = default; + +void DXILResourceBindingWrapperPass::getAnalysisUsage(AnalysisUsage &AU) const { + AU.addRequiredTransitive(); + AU.setPreservesAll(); +} + +bool DXILResourceBindingWrapperPass::runOnModule(Module &M) { + BindingInfo.reset(new DXILResourceBindingInfo()); + + DXILResourceTypeMap &DRTM = + getAnalysis().getResourceTypeMap(); + BindingInfo->populate(M, DRTM); + + return false; +} + +void DXILResourceBindingWrapperPass::releaseMemory() { BindingInfo.reset(); } + +INITIALIZE_PASS(DXILResourceBindingWrapperPass, "dxil-resource-binding", + "DXIL Resource Binding Analysis", false, true) +char DXILResourceBindingWrapperPass::ID = 0; + +ModulePass *llvm::createDXILResourceBindingWrapperPassPass() { + return new DXILResourceWrapperPass(); +} From 9a70f060a7accd3a142431422b536abb962e459b Mon Sep 17 00:00:00 2001 From: Helena Kotas Date: Mon, 28 Apr 2025 09:56:50 -0700 Subject: [PATCH 4/7] update signature --- llvm/include/llvm/IR/IntrinsicsDirectX.td | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/llvm/include/llvm/IR/IntrinsicsDirectX.td b/llvm/include/llvm/IR/IntrinsicsDirectX.td index 444600980fc1e..15209c5d229af 100644 --- a/llvm/include/llvm/IR/IntrinsicsDirectX.td +++ b/llvm/include/llvm/IR/IntrinsicsDirectX.td @@ -33,7 +33,7 @@ def int_dx_resource_handlefrombinding def int_dx_resource_handlefromimplicitbinding : DefaultAttrsIntrinsic< [llvm_any_ty], - [llvm_i32_ty, llvm_i32_ty, llvm_i32_ty, llvm_i32_ty], + [llvm_i32_ty, llvm_i32_ty, llvm_i32_ty, llvm_i32_ty, llvm_i1_ty], [IntrNoMem]>; def int_dx_resource_getpointer From bca1bf312e9eb43ae4a0cd5c0086ccdc15f22d74 Mon Sep 17 00:00:00 2001 From: Helena Kotas Date: Wed, 30 Apr 2025 13:01:41 -0700 Subject: [PATCH 5/7] code review feedback - rename flags, remove unnecessary functions, do not initialize space0 --- llvm/include/llvm/Analysis/DXILResource.h | 9 +-- llvm/lib/Analysis/DXILResource.cpp | 8 ++- .../DirectX/ResourceBindingAnalysisTests.cpp | 67 ++++++------------- 3 files changed, 30 insertions(+), 54 deletions(-) diff --git a/llvm/include/llvm/Analysis/DXILResource.h b/llvm/include/llvm/Analysis/DXILResource.h index 556c15868290e..569f5346b9e36 100644 --- a/llvm/include/llvm/Analysis/DXILResource.h +++ b/llvm/include/llvm/Analysis/DXILResource.h @@ -637,10 +637,7 @@ class DXILResourceBindingInfo { struct BindingSpaces { dxil::ResourceClass ResClass; llvm::SmallVector Spaces; - BindingSpaces(dxil::ResourceClass ResClass) : ResClass(ResClass) { - // initialize space0 - Spaces.emplace_back(0); - } + BindingSpaces(dxil::ResourceClass ResClass) : ResClass(ResClass) {} }; private: @@ -660,8 +657,8 @@ class DXILResourceBindingInfo { SamplerSpaces(dxil::ResourceClass::Sampler), ImplicitBinding(false), OverlappingBinding(false) {} - bool containsImplicitBinding() const { return ImplicitBinding; } - bool containsOverlappingBinding() const { return OverlappingBinding; } + bool hasImplicitBinding() const { return ImplicitBinding; } + bool hasOverlappingBinding() const { return OverlappingBinding; } BindingSpaces &getBindingSpaces(dxil::ResourceClass RC) { switch (RC) { diff --git a/llvm/lib/Analysis/DXILResource.cpp b/llvm/lib/Analysis/DXILResource.cpp index 4cfe62766acae..ce8e250a32ebe 100644 --- a/llvm/lib/Analysis/DXILResource.cpp +++ b/llvm/lib/Analysis/DXILResource.cpp @@ -927,8 +927,7 @@ void DXILResourceBindingInfo::populate(Module &M, DXILResourceTypeMap &DRTM) { break; } case Intrinsic::dx_resource_handlefromimplicitbinding: { - if (!F.user_empty()) - ImplicitBinding = true; + ImplicitBinding = true; break; } } @@ -957,7 +956,8 @@ void DXILResourceBindingInfo::populate(Module &M, DXILResourceTypeMap &DRTM) { // move to the next resource class spaces BS = &getBindingSpaces(B.ResClass); - RegisterSpace *S = &BS->Spaces.back(); + RegisterSpace *S = BS->Spaces.empty() ? &BS->Spaces.emplace_back(B.Space) + : &BS->Spaces.back(); assert(S->Space <= B.Space && "bindings not sorted correctly?"); if (B.Space != S->Space) // add new space @@ -983,6 +983,8 @@ void DXILResourceBindingInfo::populate(Module &M, DXILResourceTypeMap &DRTM) { if (B.UpperBound < UINT32_MAX) S->FreeRanges.emplace_back(B.UpperBound + 1, UINT32_MAX); } else { + // FIXME: This only detects overlapping bindings that are not an exact + // match (llvm/llvm-project#110723) OverlappingBinding = true; if (B.UpperBound < UINT32_MAX) LastFreeRange.LowerBound = diff --git a/llvm/unittests/Target/DirectX/ResourceBindingAnalysisTests.cpp b/llvm/unittests/Target/DirectX/ResourceBindingAnalysisTests.cpp index bfc4955c51251..091676619f791 100644 --- a/llvm/unittests/Target/DirectX/ResourceBindingAnalysisTests.cpp +++ b/llvm/unittests/Target/DirectX/ResourceBindingAnalysisTests.cpp @@ -65,12 +65,8 @@ TEST_F(ResourceBindingAnalysisTest, TestTrivialCase) { define void @main() { entry: %handle = call target("dx.TypedBuffer", float, 1, 0, 0) @llvm.dx.resource.handlefrombinding(i32 0, i32 5, i32 1, i32 0, i1 false) - - call void @a.func(target("dx.TypedBuffer", float, 1, 0, 0) %handle) ret void } - -declare void @a.func(target("dx.RawBuffer", float, 1, 0) %handle) )"; auto M = parseAsm(Assembly); @@ -78,8 +74,8 @@ declare void @a.func(target("dx.RawBuffer", float, 1, 0) %handle) DXILResourceBindingInfo &DRBI = MAM->getResult(*M); - EXPECT_EQ(false, DRBI.containsImplicitBinding()); - EXPECT_EQ(false, DRBI.containsOverlappingBinding()); + EXPECT_EQ(false, DRBI.hasImplicitBinding()); + EXPECT_EQ(false, DRBI.hasOverlappingBinding()); // check that UAV has exactly one gap DXILResourceBindingInfo::BindingSpaces &UAVSpaces = @@ -94,8 +90,7 @@ declare void @a.func(target("dx.RawBuffer", float, 1, 0) %handle) {ResourceClass::SRV, ResourceClass::CBuffer, ResourceClass::Sampler}) { DXILResourceBindingInfo::BindingSpaces &Spaces = DRBI.getBindingSpaces(RC); EXPECT_EQ(Spaces.ResClass, RC); - EXPECT_EQ(Spaces.Spaces.size(), 1u); - checkExpectedSpaceAndFreeRanges(Spaces.Spaces[0], 0, {0, UINT32_MAX}); + EXPECT_EQ(Spaces.Spaces.size(), 0u); } } @@ -116,18 +111,8 @@ 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) - - call void @a.func(target("dx.CBuffer", target("dx.Layout", %__cblayout_CB, 4, 0)) %handleCB) - call void @a.func(target("dx.TypedBuffer", float, 1, 0, 0) %handleA) - call void @a.func(target("dx.TypedBuffer", float, 1, 0, 0) %handleC) - call void @a.func(target("dx.TypedBuffer", float, 1, 0, 0) %handleE) - call void @a.func(target("dx.RawBuffer", i32, 0, 0) %handleB) - call void @a.func(target("dx.RawBuffer", i32, 0, 0) %handleD) - ret void } - -declare void @a.func(target("dx.RawBuffer", float, 1, 0) %handle) )"; auto M = parseAsm(Assembly); @@ -135,8 +120,8 @@ declare void @a.func(target("dx.RawBuffer", float, 1, 0) %handle) DXILResourceBindingInfo &DRBI = MAM->getResult(*M); - EXPECT_EQ(false, DRBI.containsImplicitBinding()); - EXPECT_EQ(false, DRBI.containsOverlappingBinding()); + EXPECT_EQ(false, DRBI.hasImplicitBinding()); + EXPECT_EQ(false, DRBI.hasOverlappingBinding()); DXILResourceBindingInfo::BindingSpaces &SRVSpaces = DRBI.getBindingSpaces(ResourceClass::SRV); @@ -174,16 +159,8 @@ define void @main() { %handleB = call target("dx.RawBuffer", float, 0, 0) @llvm.dx.resource.handlefrombinding(i32 0, i32 0, i32 3, i32 0, i1 false) %handleC = call target("dx.RawBuffer", float, 0, 0) @llvm.dx.resource.handlefrombinding(i32 2, i32 0, i32 -1, i32 100, i1 false) %handleD = call target("dx.RawBuffer", float, 0, 0) @llvm.dx.resource.handlefrombinding(i32 2, i32 4, i32 1, i32 0, i1 false) - - call void @a.func(target("dx.RawBuffer", float, 0, 0) %handleA) - call void @a.func(target("dx.RawBuffer", float, 0, 0) %handleB) - call void @a.func(target("dx.RawBuffer", float, 0, 0) %handleC) - call void @a.func(target("dx.RawBuffer", float, 0, 0) %handleD) - ret void } - -declare void @a.func(target("dx.RawBuffer", float, 0, 0) %handle) )"; auto M = parseAsm(Assembly); @@ -191,8 +168,8 @@ declare void @a.func(target("dx.RawBuffer", float, 0, 0) %handle) DXILResourceBindingInfo &DRBI = MAM->getResult(*M); - EXPECT_EQ(false, DRBI.containsImplicitBinding()); - EXPECT_EQ(true, DRBI.containsOverlappingBinding()); + EXPECT_EQ(false, DRBI.hasImplicitBinding()); + EXPECT_EQ(true, DRBI.hasOverlappingBinding()); DXILResourceBindingInfo::BindingSpaces &SRVSpaces = DRBI.getBindingSpaces(ResourceClass::SRV); @@ -215,15 +192,8 @@ define void @main() { %handleA = call target("dx.TypedBuffer", float, 1, 0, 0) @llvm.dx.resource.handlefrombinding(i32 0, i32 -1, i32 1, i32 0, i1 false) %handleB = call target("dx.TypedBuffer", float, 1, 0, 0) @llvm.dx.resource.handlefrombinding(i32 1, i32 -10, i32 10, i32 50, i1 false) %handleC = call target("dx.TypedBuffer", float, 1, 0, 0) @llvm.dx.resource.handlefrombinding(i32 2, i32 2147483647, i32 10, i32 100, i1 false) - - call void @a.func(target("dx.TypedBuffer", float, 1, 0, 0) %handleA) - call void @a.func(target("dx.TypedBuffer", float, 1, 0, 0) %handleB) - call void @a.func(target("dx.TypedBuffer", float, 1, 0, 0) %handleC) - ret void } - -declare void @a.func(target("dx.TypedBuffer", float, 1, 0, 0) %handle) )"; auto M = parseAsm(Assembly); @@ -231,8 +201,8 @@ declare void @a.func(target("dx.TypedBuffer", float, 1, 0, 0) %handle) DXILResourceBindingInfo &DRBI = MAM->getResult(*M); - EXPECT_EQ(false, DRBI.containsImplicitBinding()); - EXPECT_EQ(false, DRBI.containsOverlappingBinding()); + EXPECT_EQ(false, DRBI.hasImplicitBinding()); + EXPECT_EQ(false, DRBI.hasOverlappingBinding()); DXILResourceBindingInfo::BindingSpaces &UAVSpaces = DRBI.getBindingSpaces(ResourceClass::UAV); @@ -246,23 +216,30 @@ declare void @a.func(target("dx.TypedBuffer", float, 1, 0, 0) %handle) } TEST_F(ResourceBindingAnalysisTest, TestImplicitFlag) { - // RWBuffer A; + // RWBuffer A : register(u5, space100); + // RWBuffer B; StringRef Assembly = R"( -%__cblayout_CB = type <{ i32 }> define void @main() { entry: - %handleA = call target("dx.TypedBuffer", float, 1, 0, 0) @llvm.dx.resource.handlefromimplicitbinding(i32 0, i32 0, i32 1, i32 0) - call void @a.func(target("dx.TypedBuffer", float, 1, 0, 0) %handleA) + %handleA = call target("dx.TypedBuffer", float, 1, 0, 0) @llvm.dx.resource.handlefrombinding(i32 100, i32 5, i32 1, i32 0, i1 false) + %handleB = call target("dx.TypedBuffer", float, 1, 0, 0) @llvm.dx.resource.handlefromimplicitbinding(i32 0, i32 0, i32 1, i32 0, i1 false) ret void } -declare void @a.func(target("dx.TypedBuffer", float, 1, 0, 0) %handle) )"; auto M = parseAsm(Assembly); DXILResourceBindingInfo &DRBI = MAM->getResult(*M); - EXPECT_EQ(true, DRBI.containsImplicitBinding()); + EXPECT_EQ(true, DRBI.hasImplicitBinding()); + EXPECT_EQ(false, DRBI.hasOverlappingBinding()); + + DXILResourceBindingInfo::BindingSpaces &UAVSpaces = + DRBI.getBindingSpaces(ResourceClass::UAV); + EXPECT_EQ(UAVSpaces.ResClass, ResourceClass::UAV); + EXPECT_EQ(UAVSpaces.Spaces.size(), 1u); + checkExpectedSpaceAndFreeRanges(UAVSpaces.Spaces[0], 100, + {0, 4, 6, UINT32_MAX}); } } // namespace From 2f0aba1fdb082177d0ce2182538780ceb05fed3d Mon Sep 17 00:00:00 2001 From: Helena Kotas Date: Mon, 5 May 2025 12:47:45 -0700 Subject: [PATCH 6/7] Remove extra new line --- llvm/lib/Analysis/DXILResource.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/llvm/lib/Analysis/DXILResource.cpp b/llvm/lib/Analysis/DXILResource.cpp index ce8e250a32ebe..1f7c7116462f1 100644 --- a/llvm/lib/Analysis/DXILResource.cpp +++ b/llvm/lib/Analysis/DXILResource.cpp @@ -977,7 +977,6 @@ void DXILResourceBindingInfo::populate(Module &M, DXILResourceTypeMap &DRTM) { LastFreeRange.LowerBound = B.UpperBound + 1; else S->FreeRanges.pop_back(); - } else if (LastFreeRange.LowerBound < B.LowerBound) { LastFreeRange.UpperBound = B.LowerBound - 1; if (B.UpperBound < UINT32_MAX) From db3df8c373b5de176737ae604a06e0370bbbd982 Mon Sep 17 00:00:00 2001 From: Helena Kotas Date: Fri, 9 May 2025 00:39:35 -0700 Subject: [PATCH 7/7] code review feedback - renaming, improve tests, bug fix for duplicate bindings --- llvm/include/llvm/Analysis/DXILResource.h | 4 +- llvm/lib/Analysis/DXILResource.cpp | 24 ++++--- .../DirectX/ResourceBindingAnalysisTests.cpp | 62 ++++++++++++++++--- 3 files changed, 70 insertions(+), 20 deletions(-) diff --git a/llvm/include/llvm/Analysis/DXILResource.h b/llvm/include/llvm/Analysis/DXILResource.h index 569f5346b9e36..eb344d0a82e33 100644 --- a/llvm/include/llvm/Analysis/DXILResource.h +++ b/llvm/include/llvm/Analysis/DXILResource.h @@ -635,9 +635,9 @@ class DXILResourceBindingInfo { }; struct BindingSpaces { - dxil::ResourceClass ResClass; + dxil::ResourceClass RC; llvm::SmallVector Spaces; - BindingSpaces(dxil::ResourceClass ResClass) : ResClass(ResClass) {} + BindingSpaces(dxil::ResourceClass RC) : RC(RC) {} }; private: diff --git a/llvm/lib/Analysis/DXILResource.cpp b/llvm/lib/Analysis/DXILResource.cpp index 1f7c7116462f1..125d2420c6c9a 100644 --- a/llvm/lib/Analysis/DXILResource.cpp +++ b/llvm/lib/Analysis/DXILResource.cpp @@ -885,12 +885,14 @@ SmallVector 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 Bindings; @@ -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 @@ -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(); diff --git a/llvm/unittests/Target/DirectX/ResourceBindingAnalysisTests.cpp b/llvm/unittests/Target/DirectX/ResourceBindingAnalysisTests.cpp index 091676619f791..9be4cc3045195 100644 --- a/llvm/unittests/Target/DirectX/ResourceBindingAnalysisTests.cpp +++ b/llvm/unittests/Target/DirectX/ResourceBindingAnalysisTests.cpp @@ -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}); @@ -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); } } @@ -101,6 +101,8 @@ TEST_F(ResourceBindingAnalysisTest, TestManyBindings) { // RWBuffer C : register(u5); // StructuredBuffer D[5] : register(t0); // RWBuffer E[2] : register(u2); + // SamplerState S1 : register(s5, space2); + // SamplerState S2 : register(s4, space2); StringRef Assembly = R"( %__cblayout_CB = type <{ i32 }> define void @main() { @@ -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 } )"; @@ -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}); @@ -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) { @@ -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 A : register(t5); + // StructuredBuffer 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(*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 A : register(u4294967295); /* UINT32_MAX */ // RWBuffer B[10] : register(u4294967286, space1); @@ -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}); @@ -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});