diff --git a/llvm/include/llvm/Analysis/DXILResource.h b/llvm/include/llvm/Analysis/DXILResource.h index 2631c3cb94c8a..eb344d0a82e33 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,125 @@ class DXILResourceWrapperPass : public ModulePass { ModulePass *createDXILResourceWrapperPassPass(); +//===----------------------------------------------------------------------===// + +// 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. +// 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 DXILResourceBindingInfo { +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 RC; + llvm::SmallVector Spaces; + BindingSpaces(dxil::ResourceClass RC) : RC(RC) {} + }; + +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: + DXILResourceBindingInfo() + : SRVSpaces(dxil::ResourceClass::SRV), + UAVSpaces(dxil::ResourceClass::UAV), + CBufferSpaces(dxil::ResourceClass::CBuffer), + SamplerSpaces(dxil::ResourceClass::Sampler), ImplicitBinding(false), + OverlappingBinding(false) {} + + bool hasImplicitBinding() const { return ImplicitBinding; } + bool hasOverlappingBinding() 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; + friend class DXILResourceBindingWrapperPass; +}; + +class DXILResourceBindingAnalysis + : public AnalysisInfoMixin { + friend AnalysisInfoMixin; + + static AnalysisKey Key; + +public: + using Result = DXILResourceBindingInfo; + + 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/IR/IntrinsicsDirectX.td b/llvm/include/llvm/IR/IntrinsicsDirectX.td index b1a27311e2a9c..15209c5d229af 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, llvm_i1_ty], + [IntrNoMem]>; + def int_dx_resource_getpointer : DefaultAttrsIntrinsic<[llvm_anyptr_ty], [llvm_any_ty, llvm_i32_ty], [IntrNoMem]>; diff --git a/llvm/include/llvm/InitializePasses.h b/llvm/include/llvm/InitializePasses.h index bff0526d4177a..5a282d17b72c8 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 96e1b44a17f30..125d2420c6c9a 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,126 @@ SmallVector DXILResourceMap::findByUse(const Value *Key) { //===----------------------------------------------------------------------===// +void DXILResourceBindingInfo::populate(Module &M, DXILResourceTypeMap &DRTM) { + struct Binding { + ResourceClass RC; + uint32_t Space; + uint32_t LowerBound; + uint32_t UpperBound; + Binding(ResourceClass RC, uint32_t Space, uint32_t LowerBound, + uint32_t UpperBound) + : RC(RC), Space(Space), LowerBound(LowerBound), UpperBound(UpperBound) { + } + }; + 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: { + ImplicitBinding = true; + break; + } + } + } + + // sort all the collected bindings + llvm::stable_sort(Bindings, [](auto &LHS, auto &RHS) { + return std::tie(LHS.RC, LHS.Space, LHS.LowerBound) < + std::tie(RHS.RC, RHS.Space, RHS.LowerBound); + }); + + // remove duplicates + 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 + // 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->RC != B.RC) + // move to the next resource class spaces + BS = &getBindingSpaces(B.RC); + + 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 + 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 { + // 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 = + 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 +1012,14 @@ DXILResourceMap DXILResourceAnalysis::run(Module &M, return Data; } +DXILResourceBindingInfo +DXILResourceBindingAnalysis::run(Module &M, ModuleAnalysisManager &AM) { + DXILResourceBindingInfo Data; + DXILResourceTypeMap &DRTM = AM.getResult(M); + Data.populate(M, DRTM); + return Data; +} + PreservedAnalyses DXILResourcePrinterPass::run(Module &M, ModuleAnalysisManager &AM) { DXILResourceMap &DRM = AM.getResult(M); @@ -952,3 +1082,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(); +} 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..9be4cc3045195 --- /dev/null +++ b/llvm/unittests/Target/DirectX/ResourceBindingAnalysisTests.cpp @@ -0,0 +1,291 @@ +//===- 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( + DXILResourceBindingInfo::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) + ret void +} + )"; + + auto M = parseAsm(Assembly); + + DXILResourceBindingInfo &DRBI = + MAM->getResult(*M); + + EXPECT_EQ(false, DRBI.hasImplicitBinding()); + EXPECT_EQ(false, DRBI.hasOverlappingBinding()); + + // check that UAV has exactly one gap + DXILResourceBindingInfo::BindingSpaces &UAVSpaces = + DRBI.getBindingSpaces(ResourceClass::UAV); + EXPECT_EQ(UAVSpaces.RC, 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}) { + DXILResourceBindingInfo::BindingSpaces &Spaces = DRBI.getBindingSpaces(RC); + EXPECT_EQ(Spaces.RC, RC); + EXPECT_EQ(Spaces.Spaces.size(), 0u); + } +} + +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); + // SamplerState S1 : register(s5, space2); + // SamplerState S2 : register(s4, space2); + 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) + %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 +} + )"; + + auto M = parseAsm(Assembly); + + DXILResourceBindingInfo &DRBI = + MAM->getResult(*M); + + EXPECT_EQ(false, DRBI.hasImplicitBinding()); + EXPECT_EQ(false, DRBI.hasOverlappingBinding()); + + DXILResourceBindingInfo::BindingSpaces &SRVSpaces = + DRBI.getBindingSpaces(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.RC, 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}); + + DXILResourceBindingInfo::BindingSpaces &CBufferSpaces = + DRBI.getBindingSpaces(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) { + // 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) + ret void +} + )"; + + auto M = parseAsm(Assembly); + + DXILResourceBindingInfo &DRBI = + MAM->getResult(*M); + + EXPECT_EQ(false, DRBI.hasImplicitBinding()); + EXPECT_EQ(true, DRBI.hasOverlappingBinding()); + + DXILResourceBindingInfo::BindingSpaces &SRVSpaces = + DRBI.getBindingSpaces(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); + // /* 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) + ret void +} + )"; + + auto M = parseAsm(Assembly); + + DXILResourceBindingInfo &DRBI = + MAM->getResult(*M); + + EXPECT_EQ(false, DRBI.hasImplicitBinding()); + EXPECT_EQ(false, DRBI.hasOverlappingBinding()); + + DXILResourceBindingInfo::BindingSpaces &UAVSpaces = + DRBI.getBindingSpaces(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}); + checkExpectedSpaceAndFreeRanges( + UAVSpaces.Spaces[2], 2, + {0, (uint32_t)INT32_MAX - 1, (uint32_t)INT32_MAX + 10, UINT32_MAX}); +} + +TEST_F(ResourceBindingAnalysisTest, TestImplicitFlag) { + // RWBuffer A : register(u5, space100); + // RWBuffer B; + StringRef Assembly = R"( +define void @main() { +entry: + %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 +} + )"; + + auto M = parseAsm(Assembly); + + DXILResourceBindingInfo &DRBI = + MAM->getResult(*M); + EXPECT_EQ(true, DRBI.hasImplicitBinding()); + EXPECT_EQ(false, DRBI.hasOverlappingBinding()); + + DXILResourceBindingInfo::BindingSpaces &UAVSpaces = + DRBI.getBindingSpaces(ResourceClass::UAV); + EXPECT_EQ(UAVSpaces.RC, ResourceClass::UAV); + EXPECT_EQ(UAVSpaces.Spaces.size(), 1u); + checkExpectedSpaceAndFreeRanges(UAVSpaces.Spaces[0], 100, + {0, 4, 6, UINT32_MAX}); +} + +} // namespace