[llvm] [SPIRV] Add support for dynamic/heap resources (PR #224488)
Helena Kotas via llvm-commits
llvm-commits at lists.llvm.org
Tue Sep 22 12:51:19 PDT 2026
https://github.com/hekota updated https://github.com/llvm/llvm-project/pull/224488
>From 614dfe9129c43dbb0bd18c64848a2f8a0397dd63 Mon Sep 17 00:00:00 2001
From: Helena Kotas <hekotas at microsoft.com>
Date: Fri, 18 Sep 2026 14:09:31 -0700
Subject: [PATCH 1/4] [SPIRV] Rename pass to SPIRVLegalizeResourceBinding
---
llvm/lib/Target/SPIRV/CMakeLists.txt | 2 +-
llvm/lib/Target/SPIRV/SPIRV.h | 8 ++---
.../Target/SPIRV/SPIRVCodeGenPassBuilder.cpp | 2 +-
...g.cpp => SPIRVLegalizeResourceBinding.cpp} | 36 +++++++++----------
llvm/lib/Target/SPIRV/SPIRVPassRegistry.def | 2 +-
llvm/lib/Target/SPIRV/SPIRVTargetMachine.cpp | 4 +--
llvm/test/CodeGen/SPIRV/llc-pipeline.ll | 4 +--
... SPIRVLegalizeResourceBinding-Implicit.ll} | 2 +-
8 files changed, 30 insertions(+), 30 deletions(-)
rename llvm/lib/Target/SPIRV/{SPIRVLegalizeImplicitBinding.cpp => SPIRVLegalizeResourceBinding.cpp} (88%)
rename llvm/test/CodeGen/SPIRV/passes/{SPIRVLegalizeImplicitBinding.ll => SPIRVLegalizeResourceBinding-Implicit.ll} (94%)
diff --git a/llvm/lib/Target/SPIRV/CMakeLists.txt b/llvm/lib/Target/SPIRV/CMakeLists.txt
index 32026491fdaedb..ab8b425ba35ca9 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
+ SPIRVLegalizeResourceBinding.cpp
SPIRVLegalizeZeroSizeArrays.cpp
SPIRVLegalizePointerCast.cpp
SPIRVMergeRegionExitTargets.cpp
diff --git a/llvm/lib/Target/SPIRV/SPIRV.h b/llvm/lib/Target/SPIRV/SPIRV.h
index 8ac55ee28b13f0..ccc7aa52ec52c8 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 SPIRVLegalizeResourceBindingPass
+ : public RequiredPassInfoMixin<SPIRVLegalizeResourceBindingPass> {
public:
PreservedAnalyses run(Module &M, ModuleAnalysisManager &AM);
};
-ModulePass *createSPIRVLegalizeImplicitBindingPass();
+ModulePass *createSPIRVLegalizeResourceBindingPass();
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 initializeSPIRVLegalizeResourceBindingLegacyPass(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 f47e2e56fd8a77..34c8060e97de57 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(SPIRVLegalizeResourceBindingPass(), 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/SPIRVLegalizeResourceBinding.cpp
similarity index 88%
rename from llvm/lib/Target/SPIRV/SPIRVLegalizeImplicitBinding.cpp
rename to llvm/lib/Target/SPIRV/SPIRVLegalizeResourceBinding.cpp
index 40550ee9b29606..81a307a588f4d0 100644
--- a/llvm/lib/Target/SPIRV/SPIRVLegalizeImplicitBinding.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVLegalizeResourceBinding.cpp
@@ -1,4 +1,4 @@
-//===- SPIRVLegalizeImplicitBinding.cpp - Legalize implicit bindings ----*- C++
+//===- SPIRVLegalizeResourceBinding.cpp - Legalize resource bindings ----*- C++
//-*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
@@ -28,7 +28,7 @@
using namespace llvm;
namespace {
-class SPIRVLegalizeImplicitBindingImpl {
+class SPIRVLegalizeResourceBindingImpl {
public:
bool runOnModule(Module &M);
@@ -45,15 +45,15 @@ class SPIRVLegalizeImplicitBindingImpl {
bool MayHaveImplicitBindings = false;
};
-class SPIRVLegalizeImplicitBindingLegacy : public ModulePass {
+class SPIRVLegalizeResourceBindingLegacy : public ModulePass {
public:
static char ID;
- SPIRVLegalizeImplicitBindingLegacy() : ModulePass(ID) {}
+ SPIRVLegalizeResourceBindingLegacy() : ModulePass(ID) {}
StringRef getPassName() const override {
- return "SPIRV Legalize Implicit Binding";
+ return "SPIRV Legalize Resource Binding";
}
bool runOnModule(Module &M) override {
- return SPIRVLegalizeImplicitBindingImpl().runOnModule(M);
+ return SPIRVLegalizeResourceBindingImpl().runOnModule(M);
}
};
@@ -75,7 +75,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 SPIRVLegalizeResourceBindingImpl::collectBindingInfo(Module &M) {
auto addBinding = [&](uint32_t DescSet, uint32_t Binding) {
if (UsedBindings.size() <= DescSet) {
@@ -122,7 +122,7 @@ void SPIRVLegalizeImplicitBindingImpl::collectBindingInfo(Module &M) {
}
}
-uint32_t SPIRVLegalizeImplicitBindingImpl::getAndReserveFirstUnusedBinding(
+uint32_t SPIRVLegalizeResourceBindingImpl::getAndReserveFirstUnusedBinding(
uint32_t DescSet) {
if (UsedBindings.size() <= DescSet) {
UsedBindings.resize(DescSet + 1);
@@ -181,7 +181,7 @@ static void replaceWithCounterHandleFromBinding(Module &M, CallInst *CI,
CI->eraseFromParent();
}
-bool SPIRVLegalizeImplicitBindingImpl::replaceImplicitBindingCalls(Module &M) {
+bool SPIRVLegalizeResourceBindingImpl::replaceImplicitBindingCalls(Module &M) {
// Collect all implicit binding calls.
SmallVector<std::pair<uint32_t, CallInst *>> IBCalls;
bool Changed = false;
@@ -247,7 +247,7 @@ bool SPIRVLegalizeImplicitBindingImpl::replaceImplicitBindingCalls(Module &M) {
return Changed;
}
-bool SPIRVLegalizeImplicitBindingImpl::runOnModule(Module &M) {
+bool SPIRVLegalizeResourceBindingImpl::runOnModule(Module &M) {
collectBindingInfo(M);
bool Changed = false;
@@ -259,18 +259,18 @@ bool SPIRVLegalizeImplicitBindingImpl::runOnModule(Module &M) {
} // namespace
PreservedAnalyses
-SPIRVLegalizeImplicitBindingPass::run(Module &M, ModuleAnalysisManager &AM) {
- return SPIRVLegalizeImplicitBindingImpl().runOnModule(M)
+SPIRVLegalizeResourceBindingPass::run(Module &M, ModuleAnalysisManager &AM) {
+ return SPIRVLegalizeResourceBindingImpl().runOnModule(M)
? PreservedAnalyses::none()
: PreservedAnalyses::all();
}
-char SPIRVLegalizeImplicitBindingLegacy::ID = 0;
+char SPIRVLegalizeResourceBindingLegacy::ID = 0;
-INITIALIZE_PASS(SPIRVLegalizeImplicitBindingLegacy,
- "legalize-spirv-implicit-binding",
- "Legalize SPIR-V implicit bindings", false, false)
+INITIALIZE_PASS(SPIRVLegalizeResourceBindingLegacy,
+ "legalize-spirv-resource-binding",
+ "Legalize SPIR-V resource bindings", false, false)
-ModulePass *llvm::createSPIRVLegalizeImplicitBindingPass() {
- return new SPIRVLegalizeImplicitBindingLegacy();
+ModulePass *llvm::createSPIRVLegalizeResourceBindingPass() {
+ return new SPIRVLegalizeResourceBindingLegacy();
}
diff --git a/llvm/lib/Target/SPIRV/SPIRVPassRegistry.def b/llvm/lib/Target/SPIRV/SPIRVPassRegistry.def
index eb73ae03ad3660..4cd3a94b017b4c 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-resource-binding", SPIRVLegalizeResourceBindingPass())
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 6999afbaf21bec..4e1dd3647f78ab 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);
+ initializeSPIRVLegalizeResourceBindingLegacyPass(PR);
initializeSPIRVCtorDtorLoweringLegacyPass(PR);
initializeSPIRVFinalizeShaderLinkageLegacyPass(PR);
}
@@ -224,7 +224,7 @@ void SPIRVPassConfig::addISelPrepare() {
}
SPIRVTargetMachine &TM = getTM<SPIRVTargetMachine>();
addPass(createStripConvergenceIntrinsicsPass());
- addPass(createSPIRVLegalizeImplicitBindingPass());
+ addPass(createSPIRVLegalizeResourceBindingPass());
addPass(createSPIRVLegalizeZeroSizeArraysPass(TM));
addPass(createSPIRVCBufferAccessLegacyPass());
addPass(createSPIRVPushConstantAccessLegacyPass(&TM));
diff --git a/llvm/test/CodeGen/SPIRV/llc-pipeline.ll b/llvm/test/CodeGen/SPIRV/llc-pipeline.ll
index d79ee802861228..7b85311ef5bdaf 100644
--- a/llvm/test/CodeGen/SPIRV/llc-pipeline.ll
+++ b/llvm/test/CodeGen/SPIRV/llc-pipeline.ll
@@ -42,7 +42,7 @@
; SPIRV-O0-NEXT: Natural Loop Information
; SPIRV-O0-NEXT: Canonicalize natural loops
; SPIRV-O0-NEXT: Strip convergence intrinsics and operand bundles
-; SPIRV-O0-NEXT: SPIRV Legalize Implicit Binding
+; SPIRV-O0-NEXT: SPIRV Legalize Resource Binding
; SPIRV-O0-NEXT: SPIRV Legalize Zero-Size Arrays
; SPIRV-O0-NEXT: SPIRV CBuffer Access
; SPIRV-O0-NEXT: SPIRV push constant Access
@@ -153,7 +153,7 @@
; SPIRV-Opt-NEXT: Natural Loop Information
; SPIRV-Opt-NEXT: Canonicalize natural loops
; SPIRV-Opt-NEXT: Strip convergence intrinsics and operand bundles
-; SPIRV-Opt-NEXT: SPIRV Legalize Implicit Binding
+; SPIRV-Opt-NEXT: SPIRV Legalize Resource Binding
; SPIRV-Opt-NEXT: SPIRV Legalize Zero-Size Arrays
; SPIRV-Opt-NEXT: SPIRV CBuffer Access
; SPIRV-Opt-NEXT: SPIRV push constant Access
diff --git a/llvm/test/CodeGen/SPIRV/passes/SPIRVLegalizeImplicitBinding.ll b/llvm/test/CodeGen/SPIRV/passes/SPIRVLegalizeResourceBinding-Implicit.ll
similarity index 94%
rename from llvm/test/CodeGen/SPIRV/passes/SPIRVLegalizeImplicitBinding.ll
rename to llvm/test/CodeGen/SPIRV/passes/SPIRVLegalizeResourceBinding-Implicit.ll
index eb0d0311bedf0b..c69c52f4af81c5 100644
--- a/llvm/test/CodeGen/SPIRV/passes/SPIRVLegalizeImplicitBinding.ll
+++ b/llvm/test/CodeGen/SPIRV/passes/SPIRVLegalizeResourceBinding-Implicit.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-resource-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 6b88ca3a6932138b9967dd51f04db5a346da5043 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/4] [SPIRV] Add handling of heap resources
Fixes #208054
Assisted by GPT-5.6 Sol
---
llvm/include/llvm/IR/IntrinsicsSPIRV.td | 9 +
.../SPIRV/SPIRVLegalizeResourceBinding.cpp | 193 ++++++++++++++++--
.../hlsl-resources/HeapBinding-cvb-srv-uav.ll | 79 +++++++
.../HeapBinding-texture-sampler.ll | 68 ++++++
.../hlsl-resources/HeapBinding-uav-counter.ll | 60 ++++++
.../SPIRVLegalizeRegisterBinding-Heap.ll | 44 ++++
6 files changed, 441 insertions(+), 12 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/SPIRVLegalizeRegisterBinding-Heap.ll
diff --git a/llvm/include/llvm/IR/IntrinsicsSPIRV.td b/llvm/include/llvm/IR/IntrinsicsSPIRV.td
index 86b49a8ee446a2..d634253b1ccbe9 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/SPIRVLegalizeResourceBinding.cpp b/llvm/lib/Target/SPIRV/SPIRVLegalizeResourceBinding.cpp
index 81a307a588f4d0..4fc7bfb99fab67 100644
--- a/llvm/lib/Target/SPIRV/SPIRVLegalizeResourceBinding.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVLegalizeResourceBinding.cpp
@@ -8,14 +8,18 @@
//===----------------------------------------------------------------------===//
//
// 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.
//
//===----------------------------------------------------------------------===//
#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"
@@ -23,6 +27,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;
@@ -36,13 +41,16 @@ class SPIRVLegalizeResourceBindingImpl {
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.
+ // 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 MayHaveHeapBindings = false;
};
class SPIRVLegalizeResourceBindingLegacy : public ModulePass {
@@ -116,6 +124,10 @@ void SPIRVLegalizeResourceBindingImpl::collectBindingInfo(Module &M) {
case Intrinsic::spv_resource_counterhandlefromimplicitbinding:
MayHaveImplicitBindings = true;
break;
+ case Intrinsic::spv_resource_handlefromheap:
+ case Intrinsic::spv_resource_counterhandlefromheap:
+ MayHaveHeapBindings = true;
+ break;
default:
break;
}
@@ -142,18 +154,19 @@ uint32_t SPIRVLegalizeResourceBindingImpl::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 &&
- "unexpected implicit binding intrinsic");
+ assert((CI->getIntrinsicID() ==
+ Intrinsic::spv_resource_handlefromimplicitbinding ||
+ CI->getIntrinsicID() == Intrinsic::spv_resource_handlefromheap) &&
+ "unexpected binding intrinsic");
IRBuilder<> Builder(CI);
Value *DescSetOp = Builder.getInt32(DescSet);
Value *BindingOp = Builder.getInt32(Binding);
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();
@@ -164,9 +177,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 binding intrinsic");
IRBuilder<> Builder(CI);
Value *DescSetOp = Builder.getInt32(DescSet);
Value *BindingOp = Builder.getInt32(Binding);
@@ -247,12 +262,166 @@ bool SPIRVLegalizeResourceBindingImpl::replaceImplicitBindingCalls(Module &M) {
return Changed;
}
+GlobalVariable *createHeapNameString(Module &M, StringRef Name) {
+ SmallString<32> GlobalStringName(Name);
+ uint32_t HeapNameLen = Name.size();
+ for (unsigned Suffix = 1;; ++Suffix) {
+ GlobalStringName.append(".str");
+ if (!M.getNamedValue(GlobalStringName))
+ break;
+ GlobalStringName.resize(Name.size());
+ raw_svector_ostream(GlobalStringName) << '.' << Suffix;
+ 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;
+}
+
+// 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 SPIRVLegalizeResourceBindingImpl::replaceHeapBindingCalls(Module &M) {
+ // 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() || F.user_empty())
+ 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 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) {
+ TargetExtType *ResType = cast<TargetExtType>(F->getReturnType());
+ GlobalVariable *HeapNameGV = nullptr;
+ auto It = ResourceDescriptorHeaps.find(ResType);
+ if (It == ResourceDescriptorHeaps.end()) {
+ HeapNameGV = createHeapNameString(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 =
+ createHeapNameString(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 SPIRVLegalizeResourceBindingImpl::runOnModule(Module &M) {
collectBindingInfo(M);
bool Changed = false;
if (MayHaveImplicitBindings)
Changed |= replaceImplicitBindingCalls(M);
+ 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
new file mode 100644
index 00000000000000..b079f8959a157e
--- /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 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"
+
+%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 00000000000000..18caf302a5ebd0
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/hlsl-resources/HeapBinding-texture-sampler.ll
@@ -0,0 +1,68 @@
+; 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: [[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 00000000000000..036c369c80be75
--- /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 [[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
+
+; 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/SPIRVLegalizeRegisterBinding-Heap.ll b/llvm/test/CodeGen/SPIRV/passes/SPIRVLegalizeRegisterBinding-Heap.ll
new file mode 100644
index 00000000000000..e4afc7a956e16c
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/passes/SPIRVLegalizeRegisterBinding-Heap.ll
@@ -0,0 +1,44 @@
+; RUN: opt -S -passes=spirv-legalize-resource-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 2b940d469cd5716fc0b0d451bd48318f6cc44403 Mon Sep 17 00:00:00 2001
From: Helena Kotas <hekotas at microsoft.com>
Date: Mon, 21 Sep 2026 18:10:19 -0700
Subject: [PATCH 3/4] update validator environment
---
.../CodeGen/SPIRV/hlsl-resources/HeapBinding-cvb-srv-uav.ll | 2 +-
.../CodeGen/SPIRV/hlsl-resources/HeapBinding-texture-sampler.ll | 2 +-
.../CodeGen/SPIRV/hlsl-resources/HeapBinding-uav-counter.ll | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
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 b079f8959a157e..084bb40ecf8961 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,5 +1,5 @@
; 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 %}
+; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv1.6-vulkan1.3-library %s -o - -filetype=obj | spirv-val --target-env spv1.6 %}
; 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
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 18caf302a5ebd0..3c5b0fb68ae181 100644
--- a/llvm/test/CodeGen/SPIRV/hlsl-resources/HeapBinding-texture-sampler.ll
+++ b/llvm/test/CodeGen/SPIRV/hlsl-resources/HeapBinding-texture-sampler.ll
@@ -1,5 +1,5 @@
; 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 %}
+; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv1.6-vulkan1.3-library %s -o - -filetype=obj | spirv-val --target-env spv1.6 %}
; This test declares storage buffer Buf at descriptor set 0, binding 1, and
; creates one texture and two samplers by dynamically indexing into descriptor
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 036c369c80be75..4613b27d0aee31 100644
--- a/llvm/test/CodeGen/SPIRV/hlsl-resources/HeapBinding-uav-counter.ll
+++ b/llvm/test/CodeGen/SPIRV/hlsl-resources/HeapBinding-uav-counter.ll
@@ -1,5 +1,5 @@
; 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 %}
+; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv1.6-vulkan1.3-library %s -o - -filetype=obj | spirv-val --target-env spv1.6 %}
; 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.
>From a8345387a77cdf46f488ca5ab823d1b98448ad09 Mon Sep 17 00:00:00 2001
From: Helena Kotas <hekotas at microsoft.com>
Date: Tue, 22 Sep 2026 11:33:10 -0700
Subject: [PATCH 4/4] code review feedback - add check for existing strings and
other fixes
---
.../SPIRV/SPIRVLegalizeResourceBinding.cpp | 36 ++++++++++++++-----
.../HeapBinding-texture-sampler.ll | 2 +-
.../SPIRVLegalizeRegisterBinding-Heap2.ll | 26 ++++++++++++++
3 files changed, 55 insertions(+), 9 deletions(-)
create mode 100644 llvm/test/CodeGen/SPIRV/passes/SPIRVLegalizeRegisterBinding-Heap2.ll
diff --git a/llvm/lib/Target/SPIRV/SPIRVLegalizeResourceBinding.cpp b/llvm/lib/Target/SPIRV/SPIRVLegalizeResourceBinding.cpp
index 4fc7bfb99fab67..ced6932fb9bd49 100644
--- a/llvm/lib/Target/SPIRV/SPIRVLegalizeResourceBinding.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVLegalizeResourceBinding.cpp
@@ -262,19 +262,38 @@ bool SPIRVLegalizeResourceBindingImpl::replaceImplicitBindingCalls(Module &M) {
return Changed;
}
+bool moduleContainsConstantString(Module &M, StringRef Str) {
+ for (GlobalVariable &GV : M.globals()) {
+ if (!GV.hasInitializer())
+ continue;
+ if (ConstantDataArray *CDA =
+ dyn_cast<ConstantDataArray>(GV.getInitializer())) {
+ if (CDA->isString() && CDA->getAsCString() == Str)
+ return true;
+ }
+ }
+ return false;
+}
+
+// Creates unique global variable for the heap name string, making
+// sure that each heap name string is unique within the module.
GlobalVariable *createHeapNameString(Module &M, StringRef Name) {
SmallString<32> GlobalStringName(Name);
uint32_t HeapNameLen = Name.size();
+ StringRef HeapName;
for (unsigned Suffix = 1;; ++Suffix) {
GlobalStringName.append(".str");
- if (!M.getNamedValue(GlobalStringName))
- break;
+ if (!M.getNamedValue(GlobalStringName)) {
+ // Make sure the module does not already have a constant
+ // string with this value.
+ HeapName = GlobalStringName.substr(0, HeapNameLen);
+ if (!moduleContainsConstantString(M, HeapName))
+ break;
+ }
GlobalStringName.resize(Name.size());
raw_svector_ostream(GlobalStringName) << '.' << Suffix;
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,
@@ -290,7 +309,7 @@ GlobalVariable *createHeapNameString(Module &M, StringRef Name) {
// `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
@@ -300,7 +319,7 @@ GlobalVariable *createHeapNameString(Module &M, StringRef Name) {
// 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
@@ -341,7 +360,7 @@ bool SPIRVLegalizeResourceBindingImpl::replaceHeapBindingCalls(Module &M) {
if (!CbvSrvUavs.empty()) {
// For CBV/UAV/SRV resources we need to create a different
- // ResourceDecriptorHeap name for each unique resource type. They will all
+ // ResourceDescriptorHeap 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;
@@ -396,10 +415,11 @@ bool SPIRVLegalizeResourceBindingImpl::replaceHeapBindingCalls(Module &M) {
if (!Counters.empty()) {
uint32_t Binding = getAndReserveFirstUnusedBinding(DescSet);
+ [[maybe_unused]] Type *CounterHandleTy = Counters.front()->getReturnType();
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() &&
+ assert(F->getReturnType() == CounterHandleTy &&
"counter handle type mismatch");
for (User *U : make_early_inc_range(F->users())) {
if (CallInst *CI = dyn_cast<CallInst>(U)) {
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 3c5b0fb68ae181..8f29f498381744 100644
--- a/llvm/test/CodeGen/SPIRV/hlsl-resources/HeapBinding-texture-sampler.ll
+++ b/llvm/test/CodeGen/SPIRV/hlsl-resources/HeapBinding-texture-sampler.ll
@@ -62,7 +62,7 @@ entry:
%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
+ store <4 x float> %Value1, ptr addrspace(11) %BufPtr1, align 4
ret void
}
diff --git a/llvm/test/CodeGen/SPIRV/passes/SPIRVLegalizeRegisterBinding-Heap2.ll b/llvm/test/CodeGen/SPIRV/passes/SPIRVLegalizeRegisterBinding-Heap2.ll
new file mode 100644
index 00000000000000..aa469afa96d3ce
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/passes/SPIRVLegalizeRegisterBinding-Heap2.ll
@@ -0,0 +1,26 @@
+; RUN: opt -S -passes=spirv-legalize-resource-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
+ at .str = private unnamed_addr constant [23 x i8] c"ResourceDescriptorHeap\00", align 1
+
+; Verify that a unique string is created for the heap name if the constant
+; string "ResourceDescriptorHeap" already exists in the module.
+
+; CHECK-DAG: @ResourceDescriptorHeap.1.str = private unnamed_addr constant [25 x i8] c"ResourceDescriptorHeap.1\00", align 1
+; CHECK-NOT: @ResourceDescriptorHeap.str = private unnamed_addr constant [23 x i8] c"ResourceDescriptorHeap\00", align 1
+
+; CHECK-LABEL: define void @main(
+define void @main() local_unnamed_addr #0 {
+entry:
+
+; Heap resource - bound to resource array 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 0, i32 13, ptr @ResourceDescriptorHeap.1.str)
+ %heap_resource = 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)
+
+ 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
More information about the llvm-commits
mailing list