[llvm] [SPIRV] Add support for heap resources (PR #224488)
Helena Kotas via llvm-commits
llvm-commits at lists.llvm.org
Thu Sep 17 18:53:09 PDT 2026
https://github.com/hekota updated https://github.com/llvm/llvm-project/pull/224488
>From 603557e71c5c47ec614655ac930cf92d388825aa Mon Sep 17 00:00:00 2001
From: Helena Kotas <hekotas at microsoft.com>
Date: Tue, 8 Sep 2026 13:30:11 -0700
Subject: [PATCH 1/3] Rename pass to SPIRVLegalizeImplicitAndHeapBinding
---
llvm/lib/Target/SPIRV/CMakeLists.txt | 2 +-
llvm/lib/Target/SPIRV/SPIRV.h | 8 ++---
.../Target/SPIRV/SPIRVCodeGenPassBuilder.cpp | 2 +-
...> SPIRVLegalizeImplicitAndHeapBinding.cpp} | 31 ++++++++++---------
llvm/lib/Target/SPIRV/SPIRVPassRegistry.def | 2 +-
llvm/lib/Target/SPIRV/SPIRVTargetMachine.cpp | 4 +--
.../passes/SPIRVLegalizeImplicitBinding.ll | 2 +-
7 files changed, 27 insertions(+), 24 deletions(-)
rename llvm/lib/Target/SPIRV/{SPIRVLegalizeImplicitBinding.cpp => SPIRVLegalizeImplicitAndHeapBinding.cpp} (89%)
diff --git a/llvm/lib/Target/SPIRV/CMakeLists.txt b/llvm/lib/Target/SPIRV/CMakeLists.txt
index 32026491fdaed..8e22242bb0ebd 100644
--- a/llvm/lib/Target/SPIRV/CMakeLists.txt
+++ b/llvm/lib/Target/SPIRV/CMakeLists.txt
@@ -28,7 +28,7 @@ add_llvm_target(SPIRVCodeGen
SPIRVGlobalRegistry.cpp
SPIRVInstrInfo.cpp
SPIRVInstructionSelector.cpp
- SPIRVLegalizeImplicitBinding.cpp
+ SPIRVLegalizeImplicitAndHeapBinding.cpp
SPIRVLegalizeZeroSizeArrays.cpp
SPIRVLegalizePointerCast.cpp
SPIRVMergeRegionExitTargets.cpp
diff --git a/llvm/lib/Target/SPIRV/SPIRV.h b/llvm/lib/Target/SPIRV/SPIRV.h
index 8ac55ee28b13f..c2952c0abdea9 100644
--- a/llvm/lib/Target/SPIRV/SPIRV.h
+++ b/llvm/lib/Target/SPIRV/SPIRV.h
@@ -69,13 +69,13 @@ class SPIRVMergeRegionExitTargetsPass
FunctionPass *createSPIRVMergeRegionExitTargetsPass();
-class SPIRVLegalizeImplicitBindingPass
- : public RequiredPassInfoMixin<SPIRVLegalizeImplicitBindingPass> {
+class SPIRVLegalizeImplicitAndHeapBindingPass
+ : public RequiredPassInfoMixin<SPIRVLegalizeImplicitAndHeapBindingPass> {
public:
PreservedAnalyses run(Module &M, ModuleAnalysisManager &AM);
};
-ModulePass *createSPIRVLegalizeImplicitBindingPass();
+ModulePass *createSPIRVLegalizeImplicitAndHeapBindingPass();
class SPIRVLegalizeZeroSizeArraysPass
: public RequiredPassInfoMixin<SPIRVLegalizeZeroSizeArraysPass> {
@@ -193,7 +193,7 @@ void initializeSPIRVRegularizerLegacyPass(PassRegistry &);
void initializeSPIRVMergeRegionExitTargetsLegacyPass(PassRegistry &);
void initializeSPIRVPrepareFunctionsLegacyPass(PassRegistry &);
void initializeSPIRVPrepareGlobalsLegacyPass(PassRegistry &);
-void initializeSPIRVLegalizeImplicitBindingLegacyPass(PassRegistry &);
+void initializeSPIRVLegalizeImplicitAndHeapBindingLegacyPass(PassRegistry &);
void initializeSPIRVLegalizeZeroSizeArraysLegacyPass(PassRegistry &);
void initializeSPIRVFinalizeShaderLinkageLegacyPass(PassRegistry &);
void initializeSPIRVCtorDtorLoweringLegacyPass(PassRegistry &);
diff --git a/llvm/lib/Target/SPIRV/SPIRVCodeGenPassBuilder.cpp b/llvm/lib/Target/SPIRV/SPIRVCodeGenPassBuilder.cpp
index f47e2e56fd8a7..eb8c323e808cb 100644
--- a/llvm/lib/Target/SPIRV/SPIRVCodeGenPassBuilder.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVCodeGenPassBuilder.cpp
@@ -140,7 +140,7 @@ void SPIRVCodeGenPassBuilder::addISelPrepare(PassManagerWrapper &PMW) {
}
addFunctionPass(StripConvergenceIntrinsicsPass(), PMW);
flushFPMsToMPM(PMW);
- addModulePass(SPIRVLegalizeImplicitBindingPass(), PMW);
+ addModulePass(SPIRVLegalizeImplicitAndHeapBindingPass(), PMW);
addModulePass(SPIRVLegalizeZeroSizeArraysPass(getTM()), PMW);
addModulePass(SPIRVCBufferAccessPass(), PMW);
addModulePass(SPIRVPushConstantAccessPass(getTM()), PMW);
diff --git a/llvm/lib/Target/SPIRV/SPIRVLegalizeImplicitBinding.cpp b/llvm/lib/Target/SPIRV/SPIRVLegalizeImplicitAndHeapBinding.cpp
similarity index 89%
rename from llvm/lib/Target/SPIRV/SPIRVLegalizeImplicitBinding.cpp
rename to llvm/lib/Target/SPIRV/SPIRVLegalizeImplicitAndHeapBinding.cpp
index 40550ee9b2960..3d185e134c0f7 100644
--- a/llvm/lib/Target/SPIRV/SPIRVLegalizeImplicitBinding.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVLegalizeImplicitAndHeapBinding.cpp
@@ -1,4 +1,5 @@
-//===- SPIRVLegalizeImplicitBinding.cpp - Legalize implicit bindings ----*- C++
+//===- SPIRVLegalizeImplicitAndHeapBinding.cpp - Legalize implicit bindings
+//----*- C++
//-*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
@@ -28,7 +29,7 @@
using namespace llvm;
namespace {
-class SPIRVLegalizeImplicitBindingImpl {
+class SPIRVLegalizeImplicitAndHeapBindingImpl {
public:
bool runOnModule(Module &M);
@@ -45,15 +46,15 @@ class SPIRVLegalizeImplicitBindingImpl {
bool MayHaveImplicitBindings = false;
};
-class SPIRVLegalizeImplicitBindingLegacy : public ModulePass {
+class SPIRVLegalizeImplicitAndHeapBindingLegacy : public ModulePass {
public:
static char ID;
- SPIRVLegalizeImplicitBindingLegacy() : ModulePass(ID) {}
+ SPIRVLegalizeImplicitAndHeapBindingLegacy() : ModulePass(ID) {}
StringRef getPassName() const override {
return "SPIRV Legalize Implicit Binding";
}
bool runOnModule(Module &M) override {
- return SPIRVLegalizeImplicitBindingImpl().runOnModule(M);
+ return SPIRVLegalizeImplicitAndHeapBindingImpl().runOnModule(M);
}
};
@@ -75,7 +76,7 @@ static uint32_t getDescSet(const CallInst *CI) {
// Collect all of the bindings used by llvm.spv.resource.handlefrombinding
// and llvm.spv.resource.counterhandlefrombinding calls. Also check if there
// are any implicit binding calls.
-void SPIRVLegalizeImplicitBindingImpl::collectBindingInfo(Module &M) {
+void SPIRVLegalizeImplicitAndHeapBindingImpl::collectBindingInfo(Module &M) {
auto addBinding = [&](uint32_t DescSet, uint32_t Binding) {
if (UsedBindings.size() <= DescSet) {
@@ -122,7 +123,8 @@ void SPIRVLegalizeImplicitBindingImpl::collectBindingInfo(Module &M) {
}
}
-uint32_t SPIRVLegalizeImplicitBindingImpl::getAndReserveFirstUnusedBinding(
+uint32_t
+SPIRVLegalizeImplicitAndHeapBindingImpl::getAndReserveFirstUnusedBinding(
uint32_t DescSet) {
if (UsedBindings.size() <= DescSet) {
UsedBindings.resize(DescSet + 1);
@@ -247,7 +249,7 @@ bool SPIRVLegalizeImplicitBindingImpl::replaceImplicitBindingCalls(Module &M) {
return Changed;
}
-bool SPIRVLegalizeImplicitBindingImpl::runOnModule(Module &M) {
+bool SPIRVLegalizeImplicitAndHeapBindingImpl::runOnModule(Module &M) {
collectBindingInfo(M);
bool Changed = false;
@@ -259,18 +261,19 @@ bool SPIRVLegalizeImplicitBindingImpl::runOnModule(Module &M) {
} // namespace
PreservedAnalyses
-SPIRVLegalizeImplicitBindingPass::run(Module &M, ModuleAnalysisManager &AM) {
- return SPIRVLegalizeImplicitBindingImpl().runOnModule(M)
+SPIRVLegalizeImplicitAndHeapBindingPass::run(Module &M,
+ ModuleAnalysisManager &AM) {
+ return SPIRVLegalizeImplicitAndHeapBindingImpl().runOnModule(M)
? PreservedAnalyses::none()
: PreservedAnalyses::all();
}
-char SPIRVLegalizeImplicitBindingLegacy::ID = 0;
+char SPIRVLegalizeImplicitAndHeapBindingLegacy::ID = 0;
-INITIALIZE_PASS(SPIRVLegalizeImplicitBindingLegacy,
+INITIALIZE_PASS(SPIRVLegalizeImplicitAndHeapBindingLegacy,
"legalize-spirv-implicit-binding",
"Legalize SPIR-V implicit bindings", false, false)
-ModulePass *llvm::createSPIRVLegalizeImplicitBindingPass() {
- return new SPIRVLegalizeImplicitBindingLegacy();
+ModulePass *llvm::createSPIRVLegalizeImplicitAndHeapBindingPass() {
+ return new SPIRVLegalizeImplicitAndHeapBindingLegacy();
}
diff --git a/llvm/lib/Target/SPIRV/SPIRVPassRegistry.def b/llvm/lib/Target/SPIRV/SPIRVPassRegistry.def
index eb73ae03ad366..8c471c1733174 100644
--- a/llvm/lib/Target/SPIRV/SPIRVPassRegistry.def
+++ b/llvm/lib/Target/SPIRV/SPIRVPassRegistry.def
@@ -21,7 +21,7 @@ MODULE_PASS("spirv-asm-printer-end", SPIRVAsmPrinterEndPass())
MODULE_PASS("spirv-cbuffer-access", SPIRVCBufferAccessPass())
MODULE_PASS("spirv-emit-intrinsics", SPIRVEmitIntrinsicsPass(*static_cast<const SPIRVTargetMachine *>(this)))
MODULE_PASS("spirv-finalize-shader-linkage", SPIRVFinalizeShaderLinkagePass(*static_cast<const SPIRVTargetMachine *>(this)))
-MODULE_PASS("spirv-legalize-implicit-binding", SPIRVLegalizeImplicitBindingPass())
+MODULE_PASS("spirv-legalize-implicit-and-heap-binding", SPIRVLegalizeImplicitAndHeapBindingPass())
MODULE_PASS("spirv-legalize-zero-size-arrays", SPIRVLegalizeZeroSizeArraysPass(*static_cast<const SPIRVTargetMachine *>(this)))
MODULE_PASS("spirv-lower-ctor-dtor", SPIRVCtorDtorLoweringPass())
MODULE_PASS("spirv-prepare-functions", SPIRVPrepareFunctionsPass(*static_cast<const SPIRVTargetMachine *>(this)))
diff --git a/llvm/lib/Target/SPIRV/SPIRVTargetMachine.cpp b/llvm/lib/Target/SPIRV/SPIRVTargetMachine.cpp
index 6999afbaf21be..f0e9534d0c332 100644
--- a/llvm/lib/Target/SPIRV/SPIRVTargetMachine.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVTargetMachine.cpp
@@ -62,7 +62,7 @@ extern "C" LLVM_ABI LLVM_EXTERNAL_VISIBILITY void LLVMInitializeSPIRVTarget() {
initializeSPIRVEmitIntrinsicsLegacyPass(PR);
initializeSPIRVPrepareFunctionsLegacyPass(PR);
initializeSPIRVPrepareGlobalsLegacyPass(PR);
- initializeSPIRVLegalizeImplicitBindingLegacyPass(PR);
+ initializeSPIRVLegalizeImplicitAndHeapBindingLegacyPass(PR);
initializeSPIRVCtorDtorLoweringLegacyPass(PR);
initializeSPIRVFinalizeShaderLinkageLegacyPass(PR);
}
@@ -224,7 +224,7 @@ void SPIRVPassConfig::addISelPrepare() {
}
SPIRVTargetMachine &TM = getTM<SPIRVTargetMachine>();
addPass(createStripConvergenceIntrinsicsPass());
- addPass(createSPIRVLegalizeImplicitBindingPass());
+ addPass(createSPIRVLegalizeImplicitAndHeapBindingPass());
addPass(createSPIRVLegalizeZeroSizeArraysPass(TM));
addPass(createSPIRVCBufferAccessLegacyPass());
addPass(createSPIRVPushConstantAccessLegacyPass(&TM));
diff --git a/llvm/test/CodeGen/SPIRV/passes/SPIRVLegalizeImplicitBinding.ll b/llvm/test/CodeGen/SPIRV/passes/SPIRVLegalizeImplicitBinding.ll
index eb0d0311bedf0..877dac9bcaf06 100644
--- a/llvm/test/CodeGen/SPIRV/passes/SPIRVLegalizeImplicitBinding.ll
+++ b/llvm/test/CodeGen/SPIRV/passes/SPIRVLegalizeImplicitBinding.ll
@@ -1,4 +1,4 @@
-; RUN: opt -S -passes=spirv-legalize-implicit-binding -mtriple=spirv1.6-vulkan1.3-library < %s | FileCheck %s
+; RUN: opt -S -passes=spirv-legalize-implicit-and-heap-binding -mtriple=spirv1.6-vulkan1.3-library < %s | FileCheck %s
@.str.b = private unnamed_addr constant [2 x i8] c"b\00", align 1
@.str.c = private unnamed_addr constant [2 x i8] c"c\00", align 1
>From d2d786b4b4ec7d0b20f07d8cfca22b89b2e081e7 Mon Sep 17 00:00:00 2001
From: Helena Kotas <hekotas at microsoft.com>
Date: Thu, 10 Sep 2026 09:55:19 -0700
Subject: [PATCH 2/3] [SPIRV] Add handling of heap resources
Fixes #208054
Assisted by GPT-5.6 Sol
---
llvm/include/llvm/IR/IntrinsicsSPIRV.td | 9 +
.../SPIRVLegalizeImplicitAndHeapBinding.cpp | 165 +++++++++++++++++-
.../hlsl-resources/HeapBinding-cvb-srv-uav.ll | 79 +++++++++
.../HeapBinding-texture-sampler.ll | 70 ++++++++
.../hlsl-resources/HeapBinding-uav-counter.ll | 60 +++++++
.../SPIRV/passes/SPIRVLegalizeHeapBinding.ll | 44 +++++
6 files changed, 418 insertions(+), 9 deletions(-)
create mode 100644 llvm/test/CodeGen/SPIRV/hlsl-resources/HeapBinding-cvb-srv-uav.ll
create mode 100644 llvm/test/CodeGen/SPIRV/hlsl-resources/HeapBinding-texture-sampler.ll
create mode 100644 llvm/test/CodeGen/SPIRV/hlsl-resources/HeapBinding-uav-counter.ll
create mode 100644 llvm/test/CodeGen/SPIRV/passes/SPIRVLegalizeHeapBinding.ll
diff --git a/llvm/include/llvm/IR/IntrinsicsSPIRV.td b/llvm/include/llvm/IR/IntrinsicsSPIRV.td
index 86b49a8ee446a..d634253b1ccbe 100644
--- a/llvm/include/llvm/IR/IntrinsicsSPIRV.td
+++ b/llvm/include/llvm/IR/IntrinsicsSPIRV.td
@@ -207,6 +207,15 @@ def int_spv_rsqrt : DefaultAttrsIntrinsic<[LLVMMatchType<0>], [llvm_anyfloat_ty]
[llvm_any_ty, llvm_i32_ty, llvm_i32_ty],
[IntrNoMem, IntrConvergent]>;
+ def int_spv_resource_handlefromheap
+ : DefaultAttrsIntrinsic<[llvm_any_ty],
+ [llvm_i32_ty],
+ [IntrNoMem]>;
+ def int_spv_resource_counterhandlefromheap
+ : DefaultAttrsIntrinsic<[llvm_any_ty],
+ [llvm_any_ty],
+ [IntrNoMem, IntrConvergent]>;
+
def int_spv_firstbituhigh : DefaultAttrsIntrinsic<[LLVMScalarOrSameVectorWidth<0, llvm_i32_ty>], [llvm_anyint_ty], [IntrNoMem]>;
def int_spv_firstbitshigh : DefaultAttrsIntrinsic<[LLVMScalarOrSameVectorWidth<0, llvm_i32_ty>], [llvm_anyint_ty], [IntrNoMem]>;
def int_spv_firstbitlow : DefaultAttrsIntrinsic<[LLVMScalarOrSameVectorWidth<0, llvm_i32_ty>], [llvm_anyint_ty], [IntrNoMem]>;
diff --git a/llvm/lib/Target/SPIRV/SPIRVLegalizeImplicitAndHeapBinding.cpp b/llvm/lib/Target/SPIRV/SPIRVLegalizeImplicitAndHeapBinding.cpp
index 3d185e134c0f7..adc74729315aa 100644
--- a/llvm/lib/Target/SPIRV/SPIRVLegalizeImplicitAndHeapBinding.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVLegalizeImplicitAndHeapBinding.cpp
@@ -17,6 +17,7 @@
#include "SPIRV.h"
#include "llvm/ADT/BitVector.h"
#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallString.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/IR/IRBuilder.h"
#include "llvm/IR/InstVisitor.h"
@@ -24,6 +25,7 @@
#include "llvm/IR/IntrinsicsSPIRV.h"
#include "llvm/IR/Module.h"
#include "llvm/Pass.h"
+#include "llvm/Support/raw_ostream.h"
#include <vector>
using namespace llvm;
@@ -37,13 +39,15 @@ class SPIRVLegalizeImplicitAndHeapBindingImpl {
void collectBindingInfo(Module &M);
uint32_t getAndReserveFirstUnusedBinding(uint32_t DescSet);
bool replaceImplicitBindingCalls(Module &M);
+ bool replaceHeapBindingCalls(Module &M);
// A map from descriptor set to a bit vector of used binding numbers.
std::vector<BitVector> UsedBindings;
// Set to true by collectBindingInfo() if there are any implicit binding
- // declarations in the module.
+ // or heap binding calls in the module.
bool MayHaveImplicitBindings = false;
+ bool HasHeapBinding = false;
};
class SPIRVLegalizeImplicitAndHeapBindingLegacy : public ModulePass {
@@ -117,6 +121,10 @@ void SPIRVLegalizeImplicitAndHeapBindingImpl::collectBindingInfo(Module &M) {
case Intrinsic::spv_resource_counterhandlefromimplicitbinding:
MayHaveImplicitBindings = true;
break;
+ case Intrinsic::spv_resource_handlefromheap:
+ case Intrinsic::spv_resource_counterhandlefromheap:
+ HasHeapBinding = true;
+ break;
default:
break;
}
@@ -144,10 +152,11 @@ SPIRVLegalizeImplicitAndHeapBindingImpl::getAndReserveFirstUnusedBinding(
// Replace the implicit binding call with a new call using explicit binding.
static void replaceWithHandleFromBinding(Module &M, CallInst *CI,
uint32_t DescSet, uint32_t Binding,
- Value *IndexOp, Value *RangeOp,
+ Value *RangeOp, Value *IndexOp,
Value *Name) {
- assert(CI->getIntrinsicID() ==
- Intrinsic::spv_resource_handlefromimplicitbinding &&
+ assert((CI->getIntrinsicID() ==
+ Intrinsic::spv_resource_handlefromimplicitbinding ||
+ CI->getIntrinsicID() == Intrinsic::spv_resource_handlefromheap) &&
"unexpected implicit binding intrinsic");
IRBuilder<> Builder(CI);
Value *DescSetOp = Builder.getInt32(DescSet);
@@ -155,7 +164,7 @@ static void replaceWithHandleFromBinding(Module &M, CallInst *CI,
Function *NewFunc = Intrinsic::getOrInsertDeclaration(
&M, Intrinsic::spv_resource_handlefrombinding, {CI->getType()});
CallInst *NewCI = Builder.CreateCall(
- NewFunc, {DescSetOp, BindingOp, IndexOp, RangeOp, Name});
+ NewFunc, {DescSetOp, BindingOp, RangeOp, IndexOp, Name});
NewCI->setCallingConv(CI->getCallingConv());
CI->replaceAllUsesWith(NewCI);
CI->eraseFromParent();
@@ -166,9 +175,11 @@ static void replaceWithHandleFromBinding(Module &M, CallInst *CI,
static void replaceWithCounterHandleFromBinding(Module &M, CallInst *CI,
uint32_t DescSet,
uint32_t Binding) {
- assert(CI->getIntrinsicID() ==
- Intrinsic::spv_resource_counterhandlefromimplicitbinding &&
- "unexpected implicit binding intrinsic");
+ assert(
+ (CI->getIntrinsicID() ==
+ Intrinsic::spv_resource_counterhandlefromimplicitbinding ||
+ CI->getIntrinsicID() == Intrinsic::spv_resource_counterhandlefromheap) &&
+ "unexpected implicit binding intrinsic");
IRBuilder<> Builder(CI);
Value *DescSetOp = Builder.getInt32(DescSet);
Value *BindingOp = Builder.getInt32(Binding);
@@ -183,7 +194,8 @@ static void replaceWithCounterHandleFromBinding(Module &M, CallInst *CI,
CI->eraseFromParent();
}
-bool SPIRVLegalizeImplicitBindingImpl::replaceImplicitBindingCalls(Module &M) {
+bool SPIRVLegalizeImplicitAndHeapBindingImpl::replaceImplicitBindingCalls(
+ Module &M) {
// Collect all implicit binding calls.
SmallVector<std::pair<uint32_t, CallInst *>> IBCalls;
bool Changed = false;
@@ -249,12 +261,147 @@ bool SPIRVLegalizeImplicitBindingImpl::replaceImplicitBindingCalls(Module &M) {
return Changed;
}
+GlobalVariable *createHeapString(Module &M, StringRef Name) {
+ SmallString<32> GlobalStringName(Name);
+ uint32_t HeapNameLen = Name.size();
+ for (unsigned Suffix = 0;; ++Suffix) {
+ GlobalStringName.append(".str");
+ if (!M.getNamedValue(GlobalStringName))
+ break;
+ GlobalStringName.resize(Name.size());
+ raw_svector_ostream(GlobalStringName) << '.' << Suffix + 1;
+ HeapNameLen = GlobalStringName.size();
+ }
+ StringRef HeapName = GlobalStringName.substr(0, HeapNameLen);
+
+ Constant *Init = ConstantDataArray::getString(M.getContext(), HeapName);
+ GlobalVariable *HeapNameGV = new GlobalVariable(
+ M, Init->getType(), /*isConstant=*/true, GlobalValue::PrivateLinkage,
+ Init, GlobalStringName, /*InsertBefore=*/nullptr,
+ GlobalVariable::NotThreadLocal, /*AddressSpace=*/0);
+ HeapNameGV->setUnnamedAddr(GlobalValue::UnnamedAddr::Global);
+ HeapNameGV->setAlignment(Align(1));
+ return HeapNameGV;
+}
+
+bool SPIRVLegalizeImplicitAndHeapBindingImpl::replaceHeapBindingCalls(
+ Module &M) {
+ // First we need to collect all heap binding calls based on their kind
+ // because heap bindings are assigned in the order (CbvUavSrv, Samplers,
+ // Counters).
+ SmallVector<Function *, 8> CbvSrvUavs;
+ SmallVector<Function *, 8> Samplers;
+ SmallVector<Function *, 8> Counters;
+ bool Changed = false;
+
+ for (Function &F : M) {
+ if (!F.isDeclaration())
+ continue;
+
+ if (F.getIntrinsicID() == Intrinsic::spv_resource_handlefromheap) {
+ TargetExtType *ResType = cast<TargetExtType>(F.getReturnType());
+ if (ResType->getName() == "spirv.Sampler")
+ Samplers.emplace_back(&F);
+ else
+ CbvSrvUavs.emplace_back(&F);
+ } else if (F.getIntrinsicID() ==
+ Intrinsic::spv_resource_counterhandlefromheap) {
+ Counters.emplace_back(&F);
+ } else
+ continue;
+ }
+
+ if (CbvSrvUavs.empty() && Samplers.empty() && Counters.empty())
+ return false;
+
+ // Heap resources are always mapped to descriptor set 0 as an unbounded
+ // runtime array.
+ constexpr uint32_t DescSet = 0;
+ Value *Zero =
+ llvm::ConstantInt::get(llvm::Type::getInt32Ty(M.getContext()), 0);
+
+ if (!CbvSrvUavs.empty()) {
+ // For CBV/UAV/SRV resources we need to create separate
+ // ResourceDecriptorHeap runtime array for each different resource type, and
+ // they are all going to be mapped to the same descriptor set.
+ uint32_t Binding = getAndReserveFirstUnusedBinding(DescSet);
+ SmallDenseMap<TargetExtType *, GlobalVariable *> ResourceDescriptorHeaps;
+ for (Function *F : CbvSrvUavs) {
+ TargetExtType *ResType = cast<TargetExtType>(F->getReturnType());
+ GlobalVariable *HeapNameGV = nullptr;
+ auto It = ResourceDescriptorHeaps.find(ResType);
+ if (It == ResourceDescriptorHeaps.end()) {
+ HeapNameGV = createHeapString(M, "ResourceDescriptorHeap");
+ auto [InsertedIt, Inserted] =
+ ResourceDescriptorHeaps.try_emplace(ResType, HeapNameGV);
+ assert(Inserted && "resource heap name already exists");
+ } else {
+ HeapNameGV = It->second;
+ }
+
+ for (User *U : make_early_inc_range(F->users())) {
+ if (CallInst *CI = dyn_cast<CallInst>(U)) {
+ Value *HeapIdx = CI->getArgOperand(0);
+ replaceWithHandleFromBinding(M, CI, DescSet, Binding, Zero, HeapIdx,
+ HeapNameGV);
+ Changed = true;
+ }
+ }
+ F->eraseFromParent();
+ }
+ }
+
+ if (!Samplers.empty()) {
+ uint32_t Binding = getAndReserveFirstUnusedBinding(DescSet);
+ // The Sampler handle type should be the same for all samplers
+ // (target("spirv.Sampler")).
+ TargetExtType *SamplerHandleType =
+ cast<TargetExtType>(Samplers.front()->getReturnType());
+
+ GlobalVariable *HeapNameGV = createHeapString(M, "SamplerDescriptorHeap");
+ for (Function *F : Samplers) {
+ assert(F->getReturnType() == SamplerHandleType &&
+ "sampler handle type mismatch");
+ for (User *U : make_early_inc_range(F->users())) {
+ if (CallInst *CI = dyn_cast<CallInst>(U)) {
+ Value *HeapIdx = CI->getArgOperand(0);
+ replaceWithHandleFromBinding(M, CI, DescSet, Binding, Zero, HeapIdx,
+ HeapNameGV);
+ Changed = true;
+ }
+ }
+ F->eraseFromParent();
+ }
+ }
+
+ if (!Counters.empty()) {
+ uint32_t Binding = getAndReserveFirstUnusedBinding(DescSet);
+ for (Function *F : Counters) {
+ // The counter handle type should be the same for all resource types
+ // that have a counter (target("spirv.VulkanBuffer", i32, 12, 1)).
+ assert(F->getReturnType() == Counters.front()->getReturnType() &&
+ "counter handle type mismatch");
+ for (User *U : make_early_inc_range(F->users())) {
+ if (CallInst *CI = dyn_cast<CallInst>(U)) {
+ replaceWithCounterHandleFromBinding(M, CI, DescSet, Binding);
+ Changed = true;
+ }
+ }
+ F->eraseFromParent();
+ }
+ }
+
+ return Changed;
+}
+
bool SPIRVLegalizeImplicitAndHeapBindingImpl::runOnModule(Module &M) {
collectBindingInfo(M);
bool Changed = false;
if (MayHaveImplicitBindings)
Changed |= replaceImplicitBindingCalls(M);
+ if (HasHeapBinding)
+ Changed |= replaceHeapBindingCalls(M);
return Changed;
}
diff --git a/llvm/test/CodeGen/SPIRV/hlsl-resources/HeapBinding-cvb-srv-uav.ll b/llvm/test/CodeGen/SPIRV/hlsl-resources/HeapBinding-cvb-srv-uav.ll
new file mode 100644
index 0000000000000..2d6eb659c49b9
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/hlsl-resources/HeapBinding-cvb-srv-uav.ll
@@ -0,0 +1,79 @@
+; RUN: llc -O0 -verify-machineinstrs -mtriple=spirv1.6-vulkan1.3-library %s -o - | FileCheck %s
+; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv1.6-vulkan1.3-library %s -o - -filetype=obj | spirv-val --target-env vulkan1.3 %}
+
+; This test declares Buf at descriptor set 0, binding 1, and creates one CBV,
+; SRV, and UAV resource from the descriptor heap. Legalization creates a
+; distinct runtime-array type for each heap resource type. All three arrays
+; share descriptor set 0, binding 0 (the first available bindings).
+
+; ModuleID = 'dyn-res-cvb-srv-uav.hlsl'
+target datalayout = "e-ve-i64:64-n8:16:32:64-G10"
+
+%S = type <{ <4 x i32> }>
+ at .str = private unnamed_addr constant [4 x i8] c"Buf\00", align 1
+
+; CHECK-DAG: OpCapability RuntimeDescriptorArrayEXT
+
+; CHECK-DAG: OpName [[Buf:%[0-9]+]] "Buf"
+; CHECK-DAG: OpDecorate [[Buf]] DescriptorSet 0
+; CHECK-DAG: OpDecorate [[Buf]] Binding 1
+
+; CHECK-DAG: OpName [[Heap0:%[0-9]+]] "ResourceDescriptorHeap"
+; CHECK-DAG: OpDecorate [[Heap0]] DescriptorSet 0
+; CHECK-DAG: OpDecorate [[Heap0]] Binding 0
+
+; CHECK-DAG: OpName [[Heap1:%[0-9]+]] "ResourceDescriptorHeap.1"
+; CHECK-DAG: OpDecorate [[Heap1]] DescriptorSet 0
+; CHECK-DAG: OpDecorate [[Heap1]] Binding 0
+
+; CHECK-DAG: OpName [[Heap2:%[0-9]+]] "ResourceDescriptorHeap.2"
+; CHECK-DAG: OpDecorate [[Heap2]] DescriptorSet 0
+; CHECK-DAG: OpDecorate [[Heap2]] Binding 0
+
+; Types of ResourceDescriptorHeap arrays
+
+; CHECK: [[Int32:%[0-9]+]] = OpTypeInt 32 0
+; CHECK: [[Int32Vec:%[0-9]+]] = OpTypeVector [[Int32]] 4
+; CHECK: [[StructS:%[0-9]+]] = OpTypeStruct [[Int32Vec]]
+; CHECK: [[ImageType:%[0-9]+]] = OpTypeImage [[Int32]] Buffer 2 0 0 2 R32i
+; CHECK: [[RTArrayInt32Vec:%[0-9]+]] = OpTypeRuntimeArray [[Int32Vec]]
+; CHECK: [[Heap1Struct:%[0-9]+]] = OpTypeStruct [[RTArrayInt32Vec]]
+; CHECK: [[Heap0Struct:%[0-9]+]] = OpTypeStruct [[StructS]]
+; CHECK: [[RTArrayImage:%[0-9]+]] = OpTypeRuntimeArray [[ImageType]]
+; CHECK: [[Heap0RTArrayPtr:%[0-9]+]] = OpTypePointer UniformConstant [[RTArrayImage]]
+; CHECK: [[Heap1RTArray:%[0-9]+]] = OpTypeRuntimeArray [[Heap1Struct]]
+; CHECK: [[Heap1RTArrayPtr:%[0-9]+]] = OpTypePointer StorageBuffer [[Heap1RTArray]]
+; CHECK: [[Heap0RTArray:%[0-9]+]] = OpTypeRuntimeArray [[Heap0Struct]]
+; CHECK: [[Heap2RTArrayPtr:%[0-9]+]] = OpTypePointer Uniform [[Heap0RTArray]]
+
+; CHECK: [[Heap2:%[0-9]+]] = OpVariable [[Heap2RTArrayPtr]] Uniform
+; CHECK: [[Heap1:%[0-9]+]] = OpVariable [[Heap1RTArrayPtr]] StorageBuffer
+; CHECK: [[Heap0:%[0-9]+]] = OpVariable [[Heap0RTArrayPtr]] UniformConstant
+
+define void @test(i32 %CbvIndex, i32 %SrvIndex, i32 %UavIndex) {
+entry:
+ %Buf = tail call target("spirv.VulkanBuffer", [0 x <4 x i32>], 12, 1) @llvm.spv.resource.handlefrombinding.tspirv.VulkanBuffer_a0v4i32_12_1t(i32 0, i32 1, i32 1, i32 0, ptr nonnull @.str)
+
+ %TId = tail call i32 @llvm.spv.thread.id.in.group.i32(i32 0)
+
+ %HeapCvb = tail call target("spirv.VulkanBuffer", %S, 2, 0) @llvm.spv.resource.handlefromheap.tspirv.VulkanBuffer_s_Ss_2_0t(i32 %CbvIndex)
+ %HeapSrv = tail call target("spirv.VulkanBuffer", [0 x <4 x i32>], 12, 0) @llvm.spv.resource.handlefromheap.tspirv.VulkanBuffer_a0v4i32_12_0t(i32 %SrvIndex)
+ %HeapUav = tail call target("spirv.SignedImage", i32, 5, 2, 0, 0, 2, 24) @llvm.spv.resource.handlefromheap.tspirv.SignedImage_i32_5_2_0_0_2_24t(i32 %UavIndex)
+
+ %CvbPtr = call noundef align 1 dereferenceable(16) ptr addrspace(12) @llvm.spv.resource.getbasepointer.p12.tspirv.VulkanBuffer_s_Ss_2_0t(target("spirv.VulkanBuffer", %S, 2, 0) %HeapCvb)
+ %CvbVal = load <4 x i32>, ptr addrspace(12) %CvbPtr, align 4
+
+ %SrvPtr = call noundef align 4 dereferenceable(16) ptr addrspace(11) @llvm.spv.resource.getpointer.p11.tspirv.VulkanBuffer_a0v4i32_12_0t.i32(target("spirv.VulkanBuffer", [0 x <4 x i32>], 12, 0) %HeapSrv, i32 %TId)
+ %SrvVal = load <4 x i32>, ptr addrspace(11) %SrvPtr, align 4
+
+ %UavPtr = call noundef align 4 dereferenceable(4) ptr addrspace(11) @llvm.spv.resource.getpointer.p11.tspirv.SignedImage_i32_5_2_0_0_2_24t.i32(target("spirv.SignedImage", i32, 5, 2, 0, 0, 2, 24) %HeapUav, i32 %TId)
+ %UavVal = load i32, ptr addrspace(11) %UavPtr, align 4
+
+ %0 = add <4 x i32> %SrvVal, %CvbVal
+ %1 = insertelement <4 x i32> %0, i32 %UavVal, i64 0
+
+ %BufPtr = call noundef align 4 dereferenceable(16) ptr addrspace(11) @llvm.spv.resource.getpointer.p11.tspirv.VulkanBuffer_a0v4i32_12_1t.i32(target("spirv.VulkanBuffer", [0 x <4 x i32>], 12, 1) %Buf, i32 %TId)
+ store <4 x i32> %1, ptr addrspace(11) %BufPtr, align 4
+
+ ret void
+}
diff --git a/llvm/test/CodeGen/SPIRV/hlsl-resources/HeapBinding-texture-sampler.ll b/llvm/test/CodeGen/SPIRV/hlsl-resources/HeapBinding-texture-sampler.ll
new file mode 100644
index 0000000000000..982ec26a312db
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/hlsl-resources/HeapBinding-texture-sampler.ll
@@ -0,0 +1,70 @@
+; RUN: llc -O0 -verify-machineinstrs -mtriple=spirv1.6-vulkan1.3-library %s -o - | FileCheck %s
+; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv1.6-vulkan1.3-library %s -o - -filetype=obj | spirv-val --target-env vulkan1.3 %}
+
+; This test declares storage buffer Buf at descriptor set 0, binding 1, and
+; creates one texture and two samplers by dynamically indexing into descriptor
+; heaps. SPIR-V legalization creates separate runtime arrays for resource and
+; sampler descriptors heaps, assigning the resource heap to binding 0 and the
+; sampler heap to binding 2 (the first two available bindings).
+
+ at .str = private unnamed_addr constant [4 x i8] c"Buf\00", align 1
+
+; CHECK-DAG: OpCapability RuntimeDescriptorArrayEXT
+
+; CHECK-DAG: OpName [[Buf:%[0-9]+]] "Buf"
+; CHECK-DAG: OpDecorate [[Buf]] DescriptorSet 0
+; CHECK-DAG: OpDecorate [[Buf]] Binding 1
+
+; CHECK-DAG: OpName [[ResourceHeap:%[0-9]+]] "ResourceDescriptorHeap"
+; CHECK-DAG: OpDecorate [[ResourceHeap]] DescriptorSet 0
+; CHECK-DAG: OpDecorate [[ResourceHeap]] Binding 0
+
+; CHECK-DAG: OpName [[SamplerHeap:%[0-9]+]] "SamplerDescriptorHeap"
+; CHECK-DAG: OpDecorate [[SamplerHeap]] DescriptorSet 0
+; CHECK-DAG: OpDecorate [[SamplerHeap]] Binding 2
+
+; Types of descriptor heap arrays
+
+; CHECK: [[Float32:%[0-9]+]] = OpTypeFloat 32
+; CHECK: [[SamplerType:%[0-9]+]] = OpTypeSampler
+; CHECK: [[ImageType:%[0-9]+]] = OpTypeImage [[Float32]] 2D 2 0 0 1 Unknown
+; CHECK: [[BufType:%[0-9]+]] = OpTypeImage [[Float32]] Buffer 2 0 0 2 Rgba32f
+; CHECK: [[BufPtr:%[0-9]+]] = OpTypePointer UniformConstant [[BufType]]
+; CHECK: [[ResourceRTArray:%[0-9]+]] = OpTypeRuntimeArray [[ImageType]]
+; CHECK: [[ResourceRTArrayPtr:%[0-9]+]] = OpTypePointer UniformConstant [[ResourceRTArray]]
+; CHECK: [[SamplerRTArray:%[0-9]+]] = OpTypeRuntimeArray [[SamplerType]]
+; CHECK: [[SamplerRTArrayPtr:%[0-9]+]] = OpTypePointer UniformConstant [[SamplerRTArray]]
+
+; CHECK: [[ResourceHeap]] = OpVariable [[ResourceRTArrayPtr]] UniformConstant
+; CHECK: [[SamplerHeap]] = OpVariable [[SamplerRTArrayPtr]] UniformConstant
+
+define void @test(i32 %TexIndex, i32 %Samp0Index, i32 %Samp1Index) {
+entry:
+ %Buf = tail call target("spirv.Image", float, 5, 2, 0, 0, 2, 1) @llvm.spv.resource.handlefrombinding.tspirv.Image_f32_5_2_0_0_2_1t(i32 0, i32 1, i32 1, i32 0, ptr nonnull @.str)
+
+ %Texture = tail call target("spirv.Image", float, 1, 2, 0, 0, 1, 0) @llvm.spv.resource.handlefromheap.tspirv.Image_f32_1_2_0_0_1_0t(i32 %TexIndex)
+ %Samp0 = tail call target("spirv.Sampler") @llvm.spv.resource.handlefromheap.tspirv.Samplert(i32 %Samp0Index)
+ %Samp1 = tail call target("spirv.Sampler") @llvm.spv.resource.handlefromheap.tspirv.Samplert(i32 %Samp1Index)
+
+ %Value0 = tail call reassoc nnan ninf nsz arcp afn noundef nofpclass(nan inf) <4 x float>
+ @llvm.spv.resource.samplelevel.v4f32.tspirv.Image_f32_1_2_0_0_1_0t.tspirv.Samplert.v2f32.v2i32(
+ target("spirv.Image", float, 1, 2, 0, 0, 1, 0) %Texture, target("spirv.Sampler") %Samp0,
+ <2 x float> <float 4.000000e-01, float 5.000000e-01>, float -1.000000e+00, <2 x i32> zeroinitializer)
+
+ %BufPtr0 = call noundef align 4 dereferenceable(16) ptr addrspace(11)
+ @llvm.spv.resource.getpointer.p11.tspirv.Image_f32_5_2_0_0_2_1t.i32(target("spirv.Image", float, 5, 2, 0, 0, 2, 1) %Buf, i32 0)
+
+ store <4 x float> %Value0, ptr addrspace(11) %BufPtr0, align 4
+
+ %Value1 = tail call reassoc nnan ninf nsz arcp afn noundef nofpclass(nan inf) <4 x float>
+ @llvm.spv.resource.samplelevel.v4f32.tspirv.Image_f32_1_2_0_0_1_0t.tspirv.Samplert.v2f32.v2i32(
+ target("spirv.Image", float, 1, 2, 0, 0, 1, 0) %Texture, target("spirv.Sampler") %Samp1,
+ <2 x float> <float 4.000000e-01, float 5.000000e-01>, float -1.000000e+00, <2 x i32> zeroinitializer)
+
+ %BufPtr1 = call noundef align 4 dereferenceable(16) ptr addrspace(11)
+ @llvm.spv.resource.getpointer.p11.tspirv.Image_f32_5_2_0_0_2_1t.i32(target("spirv.Image", float, 5, 2, 0, 0, 2, 1) %Buf, i32 1)
+
+ store <4 x float> %Value0, ptr addrspace(11) %BufPtr1, align 4
+
+ ret void
+}
diff --git a/llvm/test/CodeGen/SPIRV/hlsl-resources/HeapBinding-uav-counter.ll b/llvm/test/CodeGen/SPIRV/hlsl-resources/HeapBinding-uav-counter.ll
new file mode 100644
index 0000000000000..974c2d46ebf10
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/hlsl-resources/HeapBinding-uav-counter.ll
@@ -0,0 +1,60 @@
+; RUN: llc -O0 -verify-machineinstrs -mtriple=spirv1.6-vulkan1.3-library %s -o - | FileCheck %s
+; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv1.6-vulkan1.3-library %s -o - -filetype=obj | spirv-val --target-env vulkan1.3 %}
+
+; This test declares storage buffer Buf at descriptor set 0, binding 1, and
+; creates a UAV and its counter by dynamically indexing into descriptor heap.
+; SPIR-V legalization creates separate runtime arrays for the resource and
+; counter descriptors, assigning the resource heap to binding 0 and the
+; counter heap to binding 2 (the first two available bindings).
+
+ at .str = private unnamed_addr constant [4 x i8] c"Buf\00", align 1
+
+; CHECK-DAG: OpCapability RuntimeDescriptorArrayEXT
+
+; CHECK-DAG: OpName [[Heap:%[0-9]+]] "ResourceDescriptorHeap"
+; CHECK-DAG: OpDecorate [[Heap]] DescriptorSet 0
+; CHECK-DAG: OpDecorate [[Heap]] Binding 0
+
+; CHECK-DAG: OpName [[Buf:%[0-9]+]] "Buf"
+; CHECK-DAG: OpDecorate [[Buf]] DescriptorSet 0
+; CHECK-DAG: OpDecorate [[Buf]] Binding 1
+
+; CHECK-DAG: OpName [[CounterHeap:%[0-9]+]] "ResourceDescriptorHeap.counter"
+; CHECK-DAG: OpDecorate [[CounterHeap]] DescriptorSet 0
+; CHECK-DAG: OpDecorate [[CounterHeap]] Binding 2
+
+; Types of ResourceDescriptorHeap arrays
+
+; CHECK: [[Int32:%[0-9]+]] = OpTypeInt 32 0
+; CHECK: [[Float32:%[0-9]+]] = OpTypeFloat 32
+; CHECK: [[RTArrayFloat:%[0-9]+]] = OpTypeRuntimeArray [[Float32]]
+; CHECK: [[HeapStruct:%[0-9]+]] = OpTypeStruct [[RTArrayFloat]]
+; CHECK: [[CounterStruct:%[0-9]+]] = OpTypeStruct [[Int32]]
+; CHECK: [[CounterRTArray:%[0-9]+]] = OpTypeRuntimeArray [[CounterStruct]]
+; CHECK: [[CounterRTArrayPtr:%[0-9]+]] = OpTypePointer StorageBuffer [[CounterRTArray]]
+; CHECK: [[HeapRTArray:%[0-9]+]] = OpTypeRuntimeArray [[HeapStruct]]
+; CHECK: [[HeapRTArrayPtr:%[0-9]+]] = OpTypePointer StorageBuffer [[HeapRTArray]]
+
+; CHECK: [[Heap]] = OpVariable [[HeapRTArrayPtr]] StorageBuffer
+; CHECK: [[CounterHeap]] = OpVariable [[CounterRTArrayPtr]] StorageBuffer
+
+define void @test(i32 %Index) {
+entry:
+ %TId = tail call i32 @llvm.spv.thread.id.in.group.i32(i32 0)
+
+ %HeapUav = tail call target("spirv.VulkanBuffer", [0 x float], 12, 1) @llvm.spv.resource.handlefromheap.tspirv.VulkanBuffer_a0f32_12_1t(i32 %Index)
+ %HeapUavCounter = call target("spirv.VulkanBuffer", i32, 12, 1) @llvm.spv.resource.counterhandlefromheap.tspirv.VulkanBuffer_i32_12_1t.tspirv.VulkanBuffer_a0f32_12_1t(target("spirv.VulkanBuffer", [0 x float], 12, 1) %HeapUav)
+
+ %UavPtr = call noundef align 4 dereferenceable(4) ptr addrspace(11) @llvm.spv.resource.getpointer.p11.tspirv.VulkanBuffer_a0f32_12_1t.i32(target("spirv.VulkanBuffer", [0 x float], 12, 1) %HeapUav, i32 0)
+ %UavVal = load float, ptr addrspace(11) %UavPtr, align 4
+
+ %CounterVal = tail call noundef i32 @llvm.spv.resource.updatecounter.tspirv.VulkanBuffer_i32_12_1t(target("spirv.VulkanBuffer", i32, 12, 1) %HeapUavCounter, i8 1)
+ %CounterValFloat = uitofp reassoc nnan ninf nsz arcp afn i32 %CounterVal to float
+
+ %Buf = tail call target("spirv.VulkanBuffer", [0 x float], 12, 1) @llvm.spv.resource.handlefrombinding.tspirv.VulkanBuffer_a0f32_12_1t(i32 0, i32 1, i32 1, i32 0, ptr nonnull @.str)
+ %BufPtr = call noundef align 4 dereferenceable(4) ptr addrspace(11) @llvm.spv.resource.getpointer.p11.tspirv.VulkanBuffer_a0f32_12_1t.i32(target("spirv.VulkanBuffer", [0 x float], 12, 1) %Buf, i32 0)
+
+ %add = fadd reassoc nnan ninf nsz arcp afn float %CounterValFloat, %UavVal
+ store float %add, ptr addrspace(11) %BufPtr, align 4
+ ret void
+}
diff --git a/llvm/test/CodeGen/SPIRV/passes/SPIRVLegalizeHeapBinding.ll b/llvm/test/CodeGen/SPIRV/passes/SPIRVLegalizeHeapBinding.ll
new file mode 100644
index 0000000000000..6aba3e2426818
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/passes/SPIRVLegalizeHeapBinding.ll
@@ -0,0 +1,44 @@
+; RUN: opt -S -passes=spirv-legalize-implicit-and-heap-binding -mtriple=spirv1.6-vulkan1.3-library < %s | FileCheck %s
+
+ at .str.used = private unnamed_addr constant [5 x i8] c"used\00", align 1
+
+; Verify heap-binding intrinsic calls are assigned bindings after bindings
+; already used in descriptor set 0 and use the heap index as the array index.
+
+; CHECK-DAG: @ResourceDescriptorHeap.str = private unnamed_addr constant [23 x i8] c"ResourceDescriptorHeap\00", align 1
+; CHECK-DAG: @ResourceDescriptorHeap.1.str = private unnamed_addr constant [25 x i8] c"ResourceDescriptorHeap.1\00", align 1
+; CHECK-DAG: @SamplerDescriptorHeap.str = private unnamed_addr constant [22 x i8] c"SamplerDescriptorHeap\00", align 1
+
+; CHECK-LABEL: define void @main(
+define void @main() local_unnamed_addr #0 {
+entry:
+
+; Resource bound at desc 0 binding 0
+; CHECK: call target("spirv.SignedImage", i32, 5, 2, 0, 0, 2, 0) @llvm.spv.resource.handlefrombinding{{.*}}(i32 0, i32 0, i32 1, i32 0, ptr @.str.used)
+ %used = call target("spirv.SignedImage", i32, 5, 2, 0, 0, 2, 0) @llvm.spv.resource.handlefrombinding.tspirv.SignedImage_i32_5_2_0_0_2_0t(i32 0, i32 0, i32 1, i32 0, ptr @.str.used)
+
+; Heap resource - bound to resource array at desc 0 binding 1
+; CHECK: [[HEAPHANDLE:%.*]] = call target("spirv.VulkanBuffer", [0 x i32], 12, 1) @llvm.spv.resource.handlefrombinding{{.*}}(i32 0, i32 1, i32 0, i32 7, ptr @ResourceDescriptorHeap.1.str)
+ %heap_resource_type1 = call target("spirv.VulkanBuffer", [0 x i32], 12, 1) @llvm.spv.resource.handlefromheap.tspirv.VulkanBuffer_a0i32_12_1t(i32 7)
+
+; Heap resource of different type - also bound to desc 0 binding 1
+; CHECK: call target("spirv.SignedImage", i32, 5, 2, 0, 0, 2, 0) @llvm.spv.resource.handlefrombinding{{.*}}(i32 0, i32 1, i32 0, i32 13, ptr @ResourceDescriptorHeap.str)
+; CHECK: call target("spirv.SignedImage", i32, 5, 2, 0, 0, 2, 0) @llvm.spv.resource.handlefrombinding{{.*}}(i32 0, i32 1, i32 0, i32 19, ptr @ResourceDescriptorHeap.str)
+ %heap_resource_type2 = call target("spirv.SignedImage", i32, 5, 2, 0, 0, 2, 0) @llvm.spv.resource.handlefromheap.tspirv.SignedImage_i32_5_2_0_0_2_0t(i32 13)
+ %heap_resource_type2_again = call target("spirv.SignedImage", i32, 5, 2, 0, 0, 2, 0) @llvm.spv.resource.handlefromheap.tspirv.SignedImage_i32_5_2_0_0_2_0t(i32 19)
+
+; Heap sampler - bound to desc 0 binding 2
+; CHECK: call target("spirv.Sampler") @llvm.spv.resource.handlefrombinding{{.*}}(i32 0, i32 2, i32 0, i32 8, ptr @SamplerDescriptorHeap.str)
+ %heap_sampler = call target("spirv.Sampler") @llvm.spv.resource.handlefromheap.tspirv.Samplert(i32 8)
+
+; Counters for heap resources - bound to desc 0 binding 3
+; CHECK: call target("spirv.VulkanBuffer", i32, 12, 1) @llvm.spv.resource.counterhandlefrombinding{{.*}}(target("spirv.VulkanBuffer", [0 x i32], 12, 1) [[HEAPHANDLE]], i32 0, i32 3)
+ %heap_resource_counter = call target("spirv.VulkanBuffer", i32, 12, 1) @llvm.spv.resource.counterhandlefromheap.tspirv.VulkanBuffer_i32_12_1t.tspirv.VulkanBuffer_a0i32_12_1t(target("spirv.VulkanBuffer", [0 x i32], 12, 1) %heap_resource_type1)
+
+ ret void
+}
+
+; CHECK-NOT: @llvm.spv.resource.handlefromheap
+; CHECK-NOT: @llvm.spv.resource.counterhandlefromheap
+
+attributes #0 = { "hlsl.numthreads"="1,1,1" "hlsl.shader"="compute" }
\ No newline at end of file
>From d83021db7f3c5a033d67050c8ac51b2f737c7beb Mon Sep 17 00:00:00 2001
From: Helena Kotas <hekotas at microsoft.com>
Date: Thu, 17 Sep 2026 18:50:50 -0700
Subject: [PATCH 3/3] self review
---
.../SPIRVLegalizeImplicitAndHeapBinding.cpp | 70 ++++++++++++-------
.../hlsl-resources/HeapBinding-cvb-srv-uav.ll | 8 +--
.../HeapBinding-texture-sampler.ll | 2 -
.../hlsl-resources/HeapBinding-uav-counter.ll | 8 +--
4 files changed, 54 insertions(+), 34 deletions(-)
diff --git a/llvm/lib/Target/SPIRV/SPIRVLegalizeImplicitAndHeapBinding.cpp b/llvm/lib/Target/SPIRV/SPIRVLegalizeImplicitAndHeapBinding.cpp
index adc74729315aa..71417c24c9f4d 100644
--- a/llvm/lib/Target/SPIRV/SPIRVLegalizeImplicitAndHeapBinding.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVLegalizeImplicitAndHeapBinding.cpp
@@ -1,6 +1,4 @@
-//===- SPIRVLegalizeImplicitAndHeapBinding.cpp - Legalize implicit bindings
-//----*- C++
-//-*-===//
+//===- SPIRVLegalizeImplicitAndHeapBinding.cpp ---------------------------*- C++
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
@@ -9,8 +7,11 @@
//===----------------------------------------------------------------------===//
//
// This pass legalizes the @llvm.spv.resource.handlefromimplicitbinding
-// intrinsic by replacing it with a call to
-// @llvm.spv.resource.handlefrombinding.
+// and @llvm.spv.resource.handlefromheap intrinsics by replacing them with a
+// call to @llvm.spv.resource.handlefrombinding.
+// It also replaces any @llvm.spv.resource.counterhandlefromimplicitbinding and
+// @llvm.spv.resource.counterhandlefromheap intrinsics with calls to
+// @llvm.spv.resource.counterhandlefrombinding.
//
//===----------------------------------------------------------------------===//
@@ -44,10 +45,11 @@ class SPIRVLegalizeImplicitAndHeapBindingImpl {
// A map from descriptor set to a bit vector of used binding numbers.
std::vector<BitVector> UsedBindings;
- // Set to true by collectBindingInfo() if there are any implicit binding
- // or heap binding calls in the module.
+ // Set to true by collectBindingInfo() if there are possibly any implicit
+ // binding or heap binding calls in the module (if the module contains a
+ // declaration of implicit binding or heap intrinsic).
bool MayHaveImplicitBindings = false;
- bool HasHeapBinding = false;
+ bool MayHaveHeapBindings = false;
};
class SPIRVLegalizeImplicitAndHeapBindingLegacy : public ModulePass {
@@ -123,7 +125,7 @@ void SPIRVLegalizeImplicitAndHeapBindingImpl::collectBindingInfo(Module &M) {
break;
case Intrinsic::spv_resource_handlefromheap:
case Intrinsic::spv_resource_counterhandlefromheap:
- HasHeapBinding = true;
+ MayHaveHeapBindings = true;
break;
default:
break;
@@ -157,7 +159,7 @@ static void replaceWithHandleFromBinding(Module &M, CallInst *CI,
assert((CI->getIntrinsicID() ==
Intrinsic::spv_resource_handlefromimplicitbinding ||
CI->getIntrinsicID() == Intrinsic::spv_resource_handlefromheap) &&
- "unexpected implicit binding intrinsic");
+ "unexpected binding intrinsic");
IRBuilder<> Builder(CI);
Value *DescSetOp = Builder.getInt32(DescSet);
Value *BindingOp = Builder.getInt32(Binding);
@@ -179,7 +181,7 @@ static void replaceWithCounterHandleFromBinding(Module &M, CallInst *CI,
(CI->getIntrinsicID() ==
Intrinsic::spv_resource_counterhandlefromimplicitbinding ||
CI->getIntrinsicID() == Intrinsic::spv_resource_counterhandlefromheap) &&
- "unexpected implicit binding intrinsic");
+ "unexpected binding intrinsic");
IRBuilder<> Builder(CI);
Value *DescSetOp = Builder.getInt32(DescSet);
Value *BindingOp = Builder.getInt32(Binding);
@@ -261,15 +263,15 @@ bool SPIRVLegalizeImplicitAndHeapBindingImpl::replaceImplicitBindingCalls(
return Changed;
}
-GlobalVariable *createHeapString(Module &M, StringRef Name) {
+GlobalVariable *createHeapNameString(Module &M, StringRef Name) {
SmallString<32> GlobalStringName(Name);
uint32_t HeapNameLen = Name.size();
- for (unsigned Suffix = 0;; ++Suffix) {
+ for (unsigned Suffix = 1;; ++Suffix) {
GlobalStringName.append(".str");
if (!M.getNamedValue(GlobalStringName))
break;
GlobalStringName.resize(Name.size());
- raw_svector_ostream(GlobalStringName) << '.' << Suffix + 1;
+ raw_svector_ostream(GlobalStringName) << '.' << Suffix;
HeapNameLen = GlobalStringName.size();
}
StringRef HeapName = GlobalStringName.substr(0, HeapNameLen);
@@ -284,18 +286,37 @@ GlobalVariable *createHeapString(Module &M, StringRef Name) {
return HeapNameGV;
}
+// The SPIR-V backend represents dynamic resources as unbounded resource arrays.
+// This function scans the module for calls to
+// `llvm.spv.resource.handlefromheap` and groups them according to whether they
+// create CBV/SRV/UAV resources or samplers. It also collects calls to
+// `llvm.spv.resource.counterhandlefromheap` intrinsics that form a third group.
+///
+// The function assigns the first available binding to each non-empty heap group
+// in this order: CBV/SRV/UAV resources, samplers, and counters. For each group,
+// it will replace the heap intrinsic calls with their explicit
+// `handlefrombinding` equivalents using the assigned binding.
+//
+// The function does not actually create the unbounded resource-array globals
+// itself. It only assigns a unique name that is shared by all resources
+// belonging to the same heap type; the existing `SPIRVInstructionSelector` will
+// create the globals.
+
+// Because the CBV/SRV/UAV group can contain resources of different types, these
+// resources must be represented by separate arrays, one for each unique
+// resource type. All of these arrays will use the same binding and therefore
+// overlap.
bool SPIRVLegalizeImplicitAndHeapBindingImpl::replaceHeapBindingCalls(
Module &M) {
- // First we need to collect all heap binding calls based on their kind
- // because heap bindings are assigned in the order (CbvUavSrv, Samplers,
- // Counters).
+ // First we collect all used heap binding declarations and group them based on
+ // their kind.
SmallVector<Function *, 8> CbvSrvUavs;
SmallVector<Function *, 8> Samplers;
SmallVector<Function *, 8> Counters;
bool Changed = false;
for (Function &F : M) {
- if (!F.isDeclaration())
+ if (!F.isDeclaration() || F.user_empty())
continue;
if (F.getIntrinsicID() == Intrinsic::spv_resource_handlefromheap) {
@@ -321,9 +342,9 @@ bool SPIRVLegalizeImplicitAndHeapBindingImpl::replaceHeapBindingCalls(
llvm::ConstantInt::get(llvm::Type::getInt32Ty(M.getContext()), 0);
if (!CbvSrvUavs.empty()) {
- // For CBV/UAV/SRV resources we need to create separate
- // ResourceDecriptorHeap runtime array for each different resource type, and
- // they are all going to be mapped to the same descriptor set.
+ // For CBV/UAV/SRV resources we need to create a different
+ // ResourceDecriptorHeap name for each unique resource type. They will all
+ // share the same binding and will overlap.
uint32_t Binding = getAndReserveFirstUnusedBinding(DescSet);
SmallDenseMap<TargetExtType *, GlobalVariable *> ResourceDescriptorHeaps;
for (Function *F : CbvSrvUavs) {
@@ -331,7 +352,7 @@ bool SPIRVLegalizeImplicitAndHeapBindingImpl::replaceHeapBindingCalls(
GlobalVariable *HeapNameGV = nullptr;
auto It = ResourceDescriptorHeaps.find(ResType);
if (It == ResourceDescriptorHeaps.end()) {
- HeapNameGV = createHeapString(M, "ResourceDescriptorHeap");
+ HeapNameGV = createHeapNameString(M, "ResourceDescriptorHeap");
auto [InsertedIt, Inserted] =
ResourceDescriptorHeaps.try_emplace(ResType, HeapNameGV);
assert(Inserted && "resource heap name already exists");
@@ -358,7 +379,8 @@ bool SPIRVLegalizeImplicitAndHeapBindingImpl::replaceHeapBindingCalls(
TargetExtType *SamplerHandleType =
cast<TargetExtType>(Samplers.front()->getReturnType());
- GlobalVariable *HeapNameGV = createHeapString(M, "SamplerDescriptorHeap");
+ GlobalVariable *HeapNameGV =
+ createHeapNameString(M, "SamplerDescriptorHeap");
for (Function *F : Samplers) {
assert(F->getReturnType() == SamplerHandleType &&
"sampler handle type mismatch");
@@ -400,7 +422,7 @@ bool SPIRVLegalizeImplicitAndHeapBindingImpl::runOnModule(Module &M) {
bool Changed = false;
if (MayHaveImplicitBindings)
Changed |= replaceImplicitBindingCalls(M);
- if (HasHeapBinding)
+ if (MayHaveHeapBindings)
Changed |= replaceHeapBindingCalls(M);
return Changed;
diff --git a/llvm/test/CodeGen/SPIRV/hlsl-resources/HeapBinding-cvb-srv-uav.ll b/llvm/test/CodeGen/SPIRV/hlsl-resources/HeapBinding-cvb-srv-uav.ll
index 2d6eb659c49b9..b079f8959a157 100644
--- a/llvm/test/CodeGen/SPIRV/hlsl-resources/HeapBinding-cvb-srv-uav.ll
+++ b/llvm/test/CodeGen/SPIRV/hlsl-resources/HeapBinding-cvb-srv-uav.ll
@@ -1,10 +1,10 @@
; RUN: llc -O0 -verify-machineinstrs -mtriple=spirv1.6-vulkan1.3-library %s -o - | FileCheck %s
; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv1.6-vulkan1.3-library %s -o - -filetype=obj | spirv-val --target-env vulkan1.3 %}
-; This test declares Buf at descriptor set 0, binding 1, and creates one CBV,
-; SRV, and UAV resource from the descriptor heap. Legalization creates a
-; distinct runtime-array type for each heap resource type. All three arrays
-; share descriptor set 0, binding 0 (the first available bindings).
+; This test declares storage buffer Buf at descriptor set 0, binding 1, and
+; creates one CBV, SRV, and UAV resource from the descriptor heap. Legalization
+; creates a distinct runtime-array type for each heap resource type. All three
+; arrays share descriptor set 0, binding 0 (the first available bindings).
; ModuleID = 'dyn-res-cvb-srv-uav.hlsl'
target datalayout = "e-ve-i64:64-n8:16:32:64-G10"
diff --git a/llvm/test/CodeGen/SPIRV/hlsl-resources/HeapBinding-texture-sampler.ll b/llvm/test/CodeGen/SPIRV/hlsl-resources/HeapBinding-texture-sampler.ll
index 982ec26a312db..18caf302a5ebd 100644
--- a/llvm/test/CodeGen/SPIRV/hlsl-resources/HeapBinding-texture-sampler.ll
+++ b/llvm/test/CodeGen/SPIRV/hlsl-resources/HeapBinding-texture-sampler.ll
@@ -28,8 +28,6 @@
; CHECK: [[Float32:%[0-9]+]] = OpTypeFloat 32
; CHECK: [[SamplerType:%[0-9]+]] = OpTypeSampler
; CHECK: [[ImageType:%[0-9]+]] = OpTypeImage [[Float32]] 2D 2 0 0 1 Unknown
-; CHECK: [[BufType:%[0-9]+]] = OpTypeImage [[Float32]] Buffer 2 0 0 2 Rgba32f
-; CHECK: [[BufPtr:%[0-9]+]] = OpTypePointer UniformConstant [[BufType]]
; CHECK: [[ResourceRTArray:%[0-9]+]] = OpTypeRuntimeArray [[ImageType]]
; CHECK: [[ResourceRTArrayPtr:%[0-9]+]] = OpTypePointer UniformConstant [[ResourceRTArray]]
; CHECK: [[SamplerRTArray:%[0-9]+]] = OpTypeRuntimeArray [[SamplerType]]
diff --git a/llvm/test/CodeGen/SPIRV/hlsl-resources/HeapBinding-uav-counter.ll b/llvm/test/CodeGen/SPIRV/hlsl-resources/HeapBinding-uav-counter.ll
index 974c2d46ebf10..036c369c80be7 100644
--- a/llvm/test/CodeGen/SPIRV/hlsl-resources/HeapBinding-uav-counter.ll
+++ b/llvm/test/CodeGen/SPIRV/hlsl-resources/HeapBinding-uav-counter.ll
@@ -11,14 +11,14 @@
; CHECK-DAG: OpCapability RuntimeDescriptorArrayEXT
-; CHECK-DAG: OpName [[Heap:%[0-9]+]] "ResourceDescriptorHeap"
-; CHECK-DAG: OpDecorate [[Heap]] DescriptorSet 0
-; CHECK-DAG: OpDecorate [[Heap]] Binding 0
-
; CHECK-DAG: OpName [[Buf:%[0-9]+]] "Buf"
; CHECK-DAG: OpDecorate [[Buf]] DescriptorSet 0
; CHECK-DAG: OpDecorate [[Buf]] Binding 1
+; CHECK-DAG: OpName [[Heap:%[0-9]+]] "ResourceDescriptorHeap"
+; CHECK-DAG: OpDecorate [[Heap]] DescriptorSet 0
+; CHECK-DAG: OpDecorate [[Heap]] Binding 0
+
; CHECK-DAG: OpName [[CounterHeap:%[0-9]+]] "ResourceDescriptorHeap.counter"
; CHECK-DAG: OpDecorate [[CounterHeap]] DescriptorSet 0
; CHECK-DAG: OpDecorate [[CounterHeap]] Binding 2
More information about the llvm-commits
mailing list