[llvm] [DirectX] Add support for heap resources to `DXILResourceMap` (PR #216454)
Helena Kotas via llvm-commits
llvm-commits at lists.llvm.org
Sun Aug 16 23:02:23 PDT 2026
https://github.com/hekota updated https://github.com/llvm/llvm-project/pull/216454
>From 04956d92acfbad11f078f33916c63c53b1bbf608 Mon Sep 17 00:00:00 2001
From: Helena Kotas <hekotas at microsoft.com>
Date: Fri, 14 Aug 2026 18:24:20 -0700
Subject: [PATCH 1/3] [DirectX] Add support for heap resources to
`DXILResourceMap`.
- `DXILResourceMap` now handles a new `llvm.dx.resource.handlefromheap` intrisics
and adds the heap resources to the resource map.
- A new member `HeapResourceID` has been added to `ResourceInfo` to distinguish
between heap resource instances created from different indices. The `HeapResourceID`
is unique for each heap index `Value*`, so multiple handle creation calls using
the same index `Value*` resolve to the same resource.
- Heap resources do not have register bindings, so the `Binding` member
on `ResourceInfo` is now optional.
- All places that were always expecting binding are updated to handle
heap resources. In most cases that means skipping them, such as when
generating DXIL resource metadata, creating PSV resource entries or
pretty-printing the resource table comment for the module disassembly
output.
- Diagnostics of conflicting increment and decrement operations now works on heap
resources.
---
llvm/include/llvm/Analysis/DXILResource.h | 31 +++-
llvm/include/llvm/IR/IntrinsicsDirectX.td | 10 +
llvm/lib/Analysis/DXILResource.cpp | 64 +++++--
.../lib/Target/DirectX/DXContainerGlobals.cpp | 8 +
llvm/lib/Target/DirectX/DXILPrettyPrinter.cpp | 2 +
.../Target/DirectX/DXILTranslateMetadata.cpp | 14 +-
.../Analysis/DXILResource/buffer-fromheap.ll | 174 ++++++++++++++++++
.../resource_from_heap_counter_error.ll | 12 ++
8 files changed, 289 insertions(+), 26 deletions(-)
create mode 100644 llvm/test/Analysis/DXILResource/buffer-fromheap.ll
create mode 100644 llvm/test/CodeGen/DirectX/resource_from_heap_counter_error.ll
diff --git a/llvm/include/llvm/Analysis/DXILResource.h b/llvm/include/llvm/Analysis/DXILResource.h
index b0ac8f94875a9..f68e5ab34c7b4 100644
--- a/llvm/include/llvm/Analysis/DXILResource.h
+++ b/llvm/include/llvm/Analysis/DXILResource.h
@@ -21,6 +21,7 @@
#include "llvm/Support/Compiler.h"
#include "llvm/Support/DXILABI.h"
#include <cstdint>
+#include <optional>
namespace llvm {
class CallInst;
@@ -402,7 +403,8 @@ class ResourceInfo {
};
private:
- ResourceBinding Binding;
+ std::optional<ResourceBinding> Binding;
+ uint32_t HeapResourceID = -1U;
TargetExtType *HandleTy;
StringRef Name;
GlobalVariable *Symbol = nullptr;
@@ -415,16 +417,30 @@ class ResourceInfo {
ResourceInfo(uint32_t Space, uint32_t LowerBound, uint32_t Size,
TargetExtType *HandleTy, StringRef Name = "",
GlobalVariable *Symbol = nullptr)
- : Binding{0, Space, LowerBound, Size}, HandleTy(HandleTy), Name(Name),
- Symbol(Symbol) {}
+ : Binding{ResourceBinding{0, Space, LowerBound, Size}},
+ HandleTy(HandleTy), Name(Name), Symbol(Symbol) {}
- void setBindingID(unsigned ID) { Binding.BindingID = ID; }
+ ResourceInfo(uint32_t HeapResourceID, TargetExtType *HandleTy)
+ : Binding{std::nullopt}, HeapResourceID(HeapResourceID),
+ HandleTy(HandleTy), Name(""), Symbol(nullptr) {}
+
+ bool hasBinding() const { return Binding.has_value(); }
+ void setBindingID(unsigned ID) {
+ assert(hasBinding() && "Resource does not have a binding");
+ Binding->BindingID = ID;
+ }
bool hasCounter() const {
return CounterDirection != ResourceCounterDirection::Unknown;
}
- const ResourceBinding &getBinding() const { return Binding; }
+ const ResourceBinding &getBinding() const {
+ assert(hasBinding() && "Resource does not have a binding");
+ return Binding.value();
+ }
+
+ uint32_t getSize() const { return Binding ? Binding->Size : 1; }
+
TargetExtType *getHandleTy() const { return HandleTy; }
StringRef getName() const { return Name; }
@@ -436,8 +452,9 @@ class ResourceInfo {
getAnnotateProps(Module &M, dxil::ResourceTypeInfo &RTI) const;
bool operator==(const ResourceInfo &RHS) const {
- return std::tie(Binding, HandleTy, Symbol, Name) ==
- std::tie(RHS.Binding, RHS.HandleTy, RHS.Symbol, RHS.Name);
+ return std::tie(Binding, HandleTy, Symbol, Name, HeapResourceID) ==
+ std::tie(RHS.Binding, RHS.HandleTy, RHS.Symbol, RHS.Name,
+ RHS.HeapResourceID);
}
bool operator!=(const ResourceInfo &RHS) const { return !(*this == RHS); }
bool operator<(const ResourceInfo &RHS) const {
diff --git a/llvm/include/llvm/IR/IntrinsicsDirectX.td b/llvm/include/llvm/IR/IntrinsicsDirectX.td
index 9c9b2032035e3..944d17202c5e0 100644
--- a/llvm/include/llvm/IR/IntrinsicsDirectX.td
+++ b/llvm/include/llvm/IR/IntrinsicsDirectX.td
@@ -36,6 +36,16 @@ def int_dx_resource_handlefromimplicitbinding
[llvm_i32_ty, llvm_i32_ty, llvm_i32_ty, llvm_i32_ty, llvm_ptr_ty],
[IntrNoMem]>;
+// Create resource handle from a descriptor heap. Returns a `target("dx.")`
+// type appropriate for the kind of resource given a heap index, a boolean
+// indicator whether the index is for a CVB/SRV/UAV heap or a Sampler
+// heap.
+def int_dx_resource_handlefromheap
+ : DefaultAttrsIntrinsic<
+ [llvm_any_ty],
+ [llvm_i32_ty, llvm_i1_ty],
+ [IntrNoMem]>;
+
def int_dx_resource_getpointer
: DefaultAttrsIntrinsic<[llvm_anyptr_ty], [llvm_any_ty, llvm_any_ty],
[IntrConvergent, IntrNoMem]>;
diff --git a/llvm/lib/Analysis/DXILResource.cpp b/llvm/lib/Analysis/DXILResource.cpp
index 413521ac64d61..d5339d84d0096 100644
--- a/llvm/lib/Analysis/DXILResource.cpp
+++ b/llvm/lib/Analysis/DXILResource.cpp
@@ -660,7 +660,7 @@ void ResourceTypeInfo::print(raw_ostream &OS, const DataLayout &DL) const {
GlobalVariable *ResourceInfo::createSymbol(Module &M, StructType *Ty) {
assert(!Symbol && "Symbol has already been created");
Type *ResTy = Ty;
- int64_t Size = Binding.Size;
+ int64_t Size = getSize();
if (Size != 1)
// unbounded arrays are represented as zero-sized arrays in LLVM IR
ResTy = ArrayType::get(Ty, Size == ~0u ? 0 : Size);
@@ -672,6 +672,8 @@ GlobalVariable *ResourceInfo::createSymbol(Module &M, StructType *Ty) {
MDTuple *ResourceInfo::getAsMetadata(Module &M,
dxil::ResourceTypeInfo &RTI) const {
+ assert(hasBinding() && "Resource must not be from heap to get metadata");
+
LLVMContext &Ctx = M.getContext();
const DataLayout &DL = M.getDataLayout();
@@ -688,13 +690,13 @@ MDTuple *ResourceInfo::getAsMetadata(Module &M,
Constant::getIntegerValue(I1Ty, APInt(1, V)));
};
- MDVals.push_back(getIntMD(Binding.BindingID));
+ MDVals.push_back(getIntMD(Binding->BindingID));
assert(Symbol && "Cannot yet create useful resource metadata without symbol");
MDVals.push_back(ValueAsMetadata::get(Symbol));
MDVals.push_back(MDString::get(Ctx, Name));
- MDVals.push_back(getIntMD(Binding.Space));
- MDVals.push_back(getIntMD(Binding.LowerBound));
- MDVals.push_back(getIntMD(Binding.Size == 0 ? ~0u : Binding.Size));
+ MDVals.push_back(getIntMD(Binding->Space));
+ MDVals.push_back(getIntMD(Binding->LowerBound));
+ MDVals.push_back(getIntMD(Binding->Size == 0 ? ~0u : Binding->Size));
if (RTI.isCBuffer()) {
MDVals.push_back(getIntMD(RTI.getCBufferSize(DL)));
@@ -799,11 +801,15 @@ void ResourceInfo::print(raw_ostream &OS, dxil::ResourceTypeInfo &RTI,
OS << "\n";
}
- OS << " Binding:\n"
- << " Binding ID: " << Binding.BindingID << "\n"
- << " Space: " << Binding.Space << "\n"
- << " Lower Bound: " << Binding.LowerBound << "\n"
- << " Size: " << Binding.Size << "\n";
+ if (hasBinding()) {
+ OS << " Binding:\n"
+ << " Binding ID: " << Binding->BindingID << "\n"
+ << " Space: " << Binding->Space << "\n"
+ << " Lower Bound: " << Binding->LowerBound << "\n"
+ << " Size: " << Binding->Size << "\n";
+ } else {
+ OS << " HeapIndexID: " << HeapResourceID << "\n";
+ }
OS << " Globally Coherent: " << GloballyCoherent << "\n";
OS << " Has Atomic64 Use: " << HasAtomic64Use << "\n";
@@ -865,6 +871,12 @@ void DXILResourceMap::populateResourceInfos(Module &M,
DXILResourceTypeMap &DRTM) {
SmallVector<std::tuple<CallInst *, ResourceInfo, ResourceTypeInfo>> CIToInfos;
+ // We needs to assign a unique ID to each resource that is created
+ // from a heap. The ID must be unique for each unique Index value so
+ // we can differentiate between resources instances of the same type.
+ DenseMap<Value *, uint32_t> IndexToHeapResID;
+ uint32_t NextHeapResID = 0;
+
for (Function &F : M.functions()) {
if (!F.isDeclaration())
continue;
@@ -896,6 +908,28 @@ void DXILResourceMap::populateResourceInfos(Module &M,
break;
}
+ case Intrinsic::dx_resource_handlefromheap: {
+ auto *HandleTy = cast<TargetExtType>(F.getReturnType());
+ ResourceTypeInfo &RTI = DRTM[HandleTy];
+
+ for (User *U : F.users()) {
+ if (CallInst *CI = dyn_cast<CallInst>(U)) {
+ LLVM_DEBUG(dbgs() << " Visiting: " << *U << "\n");
+ Value *Index = CI->getArgOperand(0);
+ uint32_t HeapResID;
+ auto Pos = IndexToHeapResID.find(Index);
+ if (Pos == IndexToHeapResID.end()) {
+ HeapResID = NextHeapResID++;
+ IndexToHeapResID[Index] = HeapResID;
+ } else {
+ HeapResID = Pos->second;
+ }
+ ResourceInfo RI = ResourceInfo{HeapResID, HandleTy};
+ CIToInfos.emplace_back(CI, RI, RTI);
+ }
+ }
+ break;
+ }
}
}
@@ -938,8 +972,8 @@ void DXILResourceMap::populateResourceInfos(Module &M,
FirstCBuffer = std::min({FirstCBuffer, FirstSampler});
FirstUAV = std::min({FirstUAV, FirstCBuffer});
- // Adjust the resource binding to use the next ID.
- RI.setBindingID(NextID++);
+ if (RI.hasBinding())
+ RI.setBindingID(NextID++);
}
}
@@ -1046,9 +1080,11 @@ SmallVector<dxil::ResourceInfo *> DXILResourceMap::findByUse(const Value *Key) {
switch (CI->getIntrinsicID()) {
// Found the create, return the binding
- case Intrinsic::dx_resource_handlefrombinding: {
+ case Intrinsic::dx_resource_handlefrombinding:
+ case Intrinsic::dx_resource_handlefromheap: {
auto Pos = CallMap.find(CI);
- assert(Pos != CallMap.end() && "HandleFromBinding must be in resource map");
+ assert(Pos != CallMap.end() &&
+ "handle initialization call must be in resource map");
return {&Infos[Pos->second]};
}
default:
diff --git a/llvm/lib/Target/DirectX/DXContainerGlobals.cpp b/llvm/lib/Target/DirectX/DXContainerGlobals.cpp
index c4cc76d457367..7182c30ead174 100644
--- a/llvm/lib/Target/DirectX/DXContainerGlobals.cpp
+++ b/llvm/lib/Target/DirectX/DXContainerGlobals.cpp
@@ -285,17 +285,23 @@ void DXContainerGlobals::addResourcesForPSV(Module &M, PSVRuntimeInfo &PSV) {
};
for (const dxil::ResourceInfo &RI : DRM.cbuffers()) {
+ if (!RI.hasBinding())
+ continue;
const dxil::ResourceInfo::ResourceBinding &Binding = RI.getBinding();
PSV.Resources.push_back(MakeBinding(Binding, dxbc::PSV::ResourceType::CBV,
dxil::ResourceKind::CBuffer));
}
for (const dxil::ResourceInfo &RI : DRM.samplers()) {
+ if (!RI.hasBinding())
+ continue;
const dxil::ResourceInfo::ResourceBinding &Binding = RI.getBinding();
PSV.Resources.push_back(MakeBinding(Binding,
dxbc::PSV::ResourceType::Sampler,
dxil::ResourceKind::Sampler));
}
for (const dxil::ResourceInfo &RI : DRM.srvs()) {
+ if (!RI.hasBinding())
+ continue;
const dxil::ResourceInfo::ResourceBinding &Binding = RI.getBinding();
dxil::ResourceTypeInfo &TypeInfo = DRTM[RI.getHandleTy()];
@@ -311,6 +317,8 @@ void DXContainerGlobals::addResourcesForPSV(Module &M, PSVRuntimeInfo &PSV) {
MakeBinding(Binding, ResType, TypeInfo.getResourceKind()));
}
for (const dxil::ResourceInfo &RI : DRM.uavs()) {
+ if (!RI.hasBinding())
+ continue;
const dxil::ResourceInfo::ResourceBinding &Binding = RI.getBinding();
dxil::ResourceTypeInfo &TypeInfo = DRTM[RI.getHandleTy()];
diff --git a/llvm/lib/Target/DirectX/DXILPrettyPrinter.cpp b/llvm/lib/Target/DirectX/DXILPrettyPrinter.cpp
index 272e2db675431..35990848e0e1d 100644
--- a/llvm/lib/Target/DirectX/DXILPrettyPrinter.cpp
+++ b/llvm/lib/Target/DirectX/DXILPrettyPrinter.cpp
@@ -243,6 +243,8 @@ static void prettyPrintResources(raw_ostream &OS, const DXILResourceMap &DRM,
// TODO: Do we want to sort these by binding or something like that?
for (const dxil::ResourceInfo &RI : DRM) {
+ if (!RI.hasBinding())
+ continue;
const dxil::ResourceTypeInfo &RTI = DRTM[RI.getHandleTy()];
dxil::ResourceClass RC = RTI.getResourceClass();
diff --git a/llvm/lib/Target/DirectX/DXILTranslateMetadata.cpp b/llvm/lib/Target/DirectX/DXILTranslateMetadata.cpp
index 240d5814d33d4..bde0729e38e0c 100644
--- a/llvm/lib/Target/DirectX/DXILTranslateMetadata.cpp
+++ b/llvm/lib/Target/DirectX/DXILTranslateMetadata.cpp
@@ -92,19 +92,23 @@ static NamedMDNode *emitResourceMetadata(Module &M, DXILResourceMap &DRM,
LLVMContext &Context = M.getContext();
for (ResourceInfo &RI : DRM)
- if (!RI.hasSymbol())
+ if (RI.hasBinding() && !RI.hasSymbol())
RI.createSymbol(M,
DRTM[RI.getHandleTy()].createElementStruct(RI.getName()));
SmallVector<Metadata *> SRVs, UAVs, CBufs, Smps;
for (const ResourceInfo &RI : DRM.srvs())
- SRVs.push_back(RI.getAsMetadata(M, DRTM[RI.getHandleTy()]));
+ if (RI.hasBinding())
+ SRVs.push_back(RI.getAsMetadata(M, DRTM[RI.getHandleTy()]));
for (const ResourceInfo &RI : DRM.uavs())
- UAVs.push_back(RI.getAsMetadata(M, DRTM[RI.getHandleTy()]));
+ if (RI.hasBinding())
+ UAVs.push_back(RI.getAsMetadata(M, DRTM[RI.getHandleTy()]));
for (const ResourceInfo &RI : DRM.cbuffers())
- CBufs.push_back(RI.getAsMetadata(M, DRTM[RI.getHandleTy()]));
+ if (RI.hasBinding())
+ CBufs.push_back(RI.getAsMetadata(M, DRTM[RI.getHandleTy()]));
for (const ResourceInfo &RI : DRM.samplers())
- Smps.push_back(RI.getAsMetadata(M, DRTM[RI.getHandleTy()]));
+ if (RI.hasBinding())
+ Smps.push_back(RI.getAsMetadata(M, DRTM[RI.getHandleTy()]));
Metadata *SRVMD = SRVs.empty() ? nullptr : MDNode::get(Context, SRVs);
Metadata *UAVMD = UAVs.empty() ? nullptr : MDNode::get(Context, UAVs);
diff --git a/llvm/test/Analysis/DXILResource/buffer-fromheap.ll b/llvm/test/Analysis/DXILResource/buffer-fromheap.ll
new file mode 100644
index 0000000000000..18318690d8459
--- /dev/null
+++ b/llvm/test/Analysis/DXILResource/buffer-fromheap.ll
@@ -0,0 +1,174 @@
+; RUN: opt -S -disable-output -passes="print<dxil-resources>" < %s 2>&1 | FileCheck %s
+
+%struct.S = type { <4 x float>, <4 x i32> }
+%P = type <{ float }>
+%Q = type <{ <{ [2 x <{ float, target("dx.Padding", 12) }>], float }> }>
+
+; The resources in this test are created in the same order as they appear after
+; sorting in the ResourceInfo list because FileCheck cannot match multiline sections
+; of text in arbitrary order.
+
+define void @test_typedbuffer() {
+
+ %idx = tail call i32 @llvm.dx.thread.id.in.group(i32 0)
+
+ ; Buffer<uint4> Buf2 = ResourceDescriptorHeap[ID.x + 1];
+ %add0 = add i32 %idx, 1
+ %srv0 = tail call target("dx.TypedBuffer", <4 x i32>, 0, 0, 0)
+ @llvm.dx.resource.handlefromheap.tdx.TypedBuffer_v4i32_0_0_0t(i32 %add0, i1 false)
+; CHECK: Resource [[SRV0:0]]:
+; CHECK-NEXT: HeapIndexID: {{[0-9]+}}
+; CHECK-NEXT: Globally Coherent: 0
+; CHECK-NEXT: Has Atomic64 Use: 0
+; CHECK-NEXT: Counter Direction: Unknown
+; CHECK-NEXT: Class: SRV
+; CHECK-NEXT: Kind: Buffer
+; CHECK-NEXT: Element Type: u32
+; CHECK-NEXT: Element Count: 4
+
+ ; ByteAddressBuffer Buf0 = ResourceDescriptorHeap[5];
+ %srv1 = tail call target("dx.RawBuffer", i8, 0, 0)
+ @llvm.dx.resource.handlefromheap.tdx.RawBuffer_i8_0_0t(i32 5, i1 false)
+; CHECK: Resource [[SRV1:1]]:
+; CHECK-NEXT: HeapIndexID: {{[0-9]+}}
+; CHECK-NEXT: Globally Coherent: 0
+; CHECK-NEXT: Has Atomic64 Use: 0
+; CHECK-NEXT: Counter Direction: Unknown
+; CHECK-NEXT: Class: SRV
+; CHECK-NEXT: Kind: RawBuffer
+
+ ; struct S { float4 a; uint4 b; };
+ ; StructuredBuffer<S> Buf1 = ResourceDescriptorHeap[ID.x];
+ %srv2 = tail call target("dx.RawBuffer", %struct.S, 0, 0)
+ @llvm.dx.resource.handlefromheap.tdx.RawBuffer_s_struct.Ss_0_0t(i32 %idx, i1 false)
+; CHECK-DAG: Resource [[SRV2:2]]:
+; CHECK: HeapIndexID: {{[0-9]+}}
+; CHECK-NEXT: Globally Coherent: 0
+; CHECK-NEXT: Has Atomic64 Use: 0
+; CHECK-NEXT: Counter Direction: Unknown
+; CHECK-NEXT: Class: SRV
+; CHECK-NEXT: Kind: StructuredBuffer
+; CHECK-NEXT: Buffer Stride: 32
+; CHECK-NEXT: Alignment: 4
+
+; Make sure this was the last SRV resource in the list.
+; CHECK-NOT: Class: SRV
+
+ ; RWBuffer<int> Buf3 = ResourceDescriptorHeap[ID.x + 2];
+ %add2 = add i32 %idx, 2
+ %uav0 = tail call target("dx.TypedBuffer", i32, 1, 0, 1)
+ @llvm.dx.resource.handlefromheap.tdx.TypedBuffer_i32_1_0_1t(i32 %add2, i1 false)
+; CHECK: Resource [[UAV0:3]]:
+; CHECK-NEXT: HeapIndexID: 7
+; CHECK-NEXT: Globally Coherent: 0
+; CHECK-NEXT: Has Atomic64 Use: 0
+; CHECK-NEXT: Counter Direction: Unknown
+; CHECK-NEXT: Class: UAV
+; CHECK-NEXT: Kind: Buffer
+; CHECK-NEXT: IsROV: 0
+; CHECK-NEXT: Element Type: i32
+; CHECK-NEXT: Element Count: 1
+
+ ; RWStructuredBuffer<double> Buf6 = ResourceDescriptorHeap[ID.x + 5];
+ %add5 = add i32 %idx, 5
+ %uav3 = tail call target("dx.RawBuffer", double, 1, 0)
+ @llvm.dx.resource.handlefromheap.tdx.RawBuffer_f64_1_0t(i32 %add5, i1 false)
+; CHECK: Resource [[UAV1:4]]:
+; CHECK-NEXT: HeapIndexID: 2
+; CHECK-NEXT: Globally Coherent: 0
+; CHECK-NEXT: Has Atomic64 Use: 0
+; CHECK-NEXT: Counter Direction: Unknown
+; CHECK-NEXT: Class: UAV
+; CHECK-NEXT: Kind: StructuredBuffer
+; CHECK-NEXT: IsROV: 0
+; CHECK-NEXT: Buffer Stride: 8
+; CHECK-NEXT: Alignment: 0
+
+ ; RWStructuredBuffer<float4> Buf4 = ResourceDescriptorHeap[ID.x + 3];
+ ; Buf4.DecrementCounter();
+ %add3 = add i32 %idx, 3
+ %uav1 = tail call target("dx.RawBuffer", <4 x float>, 1, 0)
+ @llvm.dx.resource.handlefromheap.tdx.RawBuffer_v4f32_1_0t(i32 %add3, i1 false)
+ %count0 = tail call noundef i32 @llvm.dx.resource.updatecounter.tdx.RawBuffer_v4f32_1_0t(target("dx.RawBuffer", <4 x float>, 1, 0) %uav1, i8 -1)
+; CHECK: Resource [[UAV2:5]]:
+; CHECK-NEXT: HeapIndexID: 5
+; CHECK-NEXT: Globally Coherent: 0
+; CHECK-NEXT: Has Atomic64 Use: 0
+; CHECK-NEXT: Counter Direction: Decrement
+; CHECK-NEXT: Class: UAV
+; CHECK-NEXT: Kind: StructuredBuffer
+; CHECK-NEXT: IsROV: 0
+; CHECK-NEXT: Buffer Stride: 16
+; CHECK-NEXT: Alignment: 0
+
+ ; RWStructuredBuffer<float4> Buf5 = ResourceDescriptorHeap[ID.x + 4];
+ ; Buf5.DecrementCounter();
+ ; Buf5.IncrementCounter();
+ %add4 = add i32 %idx, 4
+ %uav2 = tail call target("dx.RawBuffer", <4 x float>, 1, 0)
+ @llvm.dx.resource.handlefromheap.tdx.RawBuffer_v4f32_1_0t(i32 %add4, i1 false)
+ %14 = tail call noundef i32 @llvm.dx.resource.updatecounter.tdx.RawBuffer_v4f32_1_0t(target("dx.RawBuffer", <4 x float>, 1, 0) %uav2, i8 -1)
+ %15 = tail call noundef i32 @llvm.dx.resource.updatecounter.tdx.RawBuffer_v4f32_1_0t(target("dx.RawBuffer", <4 x float>, 1, 0) %uav2, i8 1)
+; CHECK: Resource [[UAV3:6]]:
+; CHECK-NEXT: HeapIndexID: 6
+; CHECK-NEXT: Globally Coherent: 0
+; CHECK-NEXT: Has Atomic64 Use: 0
+; CHECK-NEXT: Counter Direction: Invalid
+; CHECK-NEXT: Class: UAV
+; CHECK-NEXT: Kind: StructuredBuffer
+; CHECK-NEXT: IsROV: 0
+; CHECK-NEXT: Buffer Stride: 16
+; CHECK-NEXT: Alignment: 0
+
+; Make sure this was the last SRV resource in the list.
+; CHECK-NOT: Class: UAV
+
+ ; struct P { float a; };
+ ; ConstantBuffer<P> CB1 = ResourceDescriptorHeap[ID.x + 6];
+ %add6 = add i32 %idx, 6
+ %cbv0 = tail call target("dx.CBuffer", %P)
+ @llvm.dx.resource.handlefromheap.tdx.CBuffer_s_Pst(i32 %add6, i1 false)
+; CHECK: Resource [[CVB0:7]]:
+; CHECK-NEXT: HeapIndexID: 0
+; CHECK-NEXT: Globally Coherent: 0
+; CHECK-NEXT: Has Atomic64 Use: 0
+; CHECK-NEXT: Counter Direction: Unknown
+; CHECK-NEXT: Class: CBV
+; CHECK-NEXT: Kind: CBuffer
+; CHECK-NEXT: CBuffer size: 4
+
+ ; struct Q { float b[3]; };
+ ; ConstantBuffer<Q> CB2 = ResourceDescriptorHeap[ID.x + 7];
+ %add7 = add i32 %idx, 7
+ %cvb1 = tail call target("dx.CBuffer", %Q)
+ @llvm.dx.resource.handlefromheap.tdx.CBuffer_s_Qst(i32 %add7, i1 false)
+; CHECK: Resource [[CVB1:8]]:
+; CHECK-NEXT: HeapIndexID: 1
+; CHECK-NEXT: Globally Coherent: 0
+; CHECK-NEXT: Has Atomic64 Use: 0
+; CHECK-NEXT: Counter Direction: Unknown
+; CHECK-NEXT: Class: CBV
+; CHECK-NEXT: Kind: CBuffer
+; CHECK-NEXT: CBuffer size: 36
+
+; CHECK-NOT: Class: CVB
+
+; Duplicated resources should not be added to the list
+; (created from heap with the same index).
+ %srv2_dupl = tail call target("dx.RawBuffer", %struct.S, 0, 0)
+ @llvm.dx.resource.handlefromheap.tdx.RawBuffer_s_struct.Ss_0_0t(i32 %idx, i1 false)
+ %cvb1_dupl = tail call target("dx.CBuffer", %Q)
+ @llvm.dx.resource.handlefromheap.tdx.CBuffer_s_Qst(i32 %add7, i1 false)
+
+ ret void
+}
+
+; CHECK-DAG: Call bound to [[SRV0]]: %srv0 = tail call target("dx.TypedBuffer", <4 x i32>, 0, 0, 0) @llvm.dx.resource.handlefromheap.tdx.TypedBuffer_v4i32_0_0_0t(i32 %add0, i1 false)
+; CHECK-DAG: Call bound to [[SRV1]]: %srv1 = tail call target("dx.RawBuffer", i8, 0, 0) @llvm.dx.resource.handlefromheap.tdx.RawBuffer_i8_0_0t(i32 5, i1 false)
+; CHECK-DAG: Call bound to [[SRV2]]: %srv2 = tail call target("dx.RawBuffer", %struct.S, 0, 0) @llvm.dx.resource.handlefromheap.tdx.RawBuffer_s_struct.Ss_0_0t(i32 %idx, i1 false)
+; CHECK-DAG: Call bound to [[UAV0]]: %uav0 = tail call target("dx.TypedBuffer", i32, 1, 0, 1) @llvm.dx.resource.handlefromheap.tdx.TypedBuffer_i32_1_0_1t(i32 %add2, i1 false)
+; CHECK-DAG: Call bound to [[UAV1]]: %uav3 = tail call target("dx.RawBuffer", double, 1, 0) @llvm.dx.resource.handlefromheap.tdx.RawBuffer_f64_1_0t(i32 %add5, i1 false)
+; CHECK-DAG: Call bound to [[UAV2]]: %uav1 = tail call target("dx.RawBuffer", <4 x float>, 1, 0) @llvm.dx.resource.handlefromheap.tdx.RawBuffer_v4f32_1_0t(i32 %add3, i1 false)
+; CHECK-DAG: Call bound to [[UAV3]]: %uav2 = tail call target("dx.RawBuffer", <4 x float>, 1, 0) @llvm.dx.resource.handlefromheap.tdx.RawBuffer_v4f32_1_0t(i32 %add4, i1 false)
+; CHECK-DAG: Call bound to [[CVB0]]: %cbv0 = tail call target("dx.CBuffer", %P) @llvm.dx.resource.handlefromheap.tdx.CBuffer_s_Pst(i32 %add6, i1 false)
+; CHECK-DAG: Call bound to [[CVB1]]: %cvb1 = tail call target("dx.CBuffer", %Q) @llvm.dx.resource.handlefromheap.tdx.CBuffer_s_Qst(i32 %add7, i1 false)
diff --git a/llvm/test/CodeGen/DirectX/resource_from_heap_counter_error.ll b/llvm/test/CodeGen/DirectX/resource_from_heap_counter_error.ll
new file mode 100644
index 0000000000000..dd5101b262b8e
--- /dev/null
+++ b/llvm/test/CodeGen/DirectX/resource_from_heap_counter_error.ll
@@ -0,0 +1,12 @@
+; RUN: not opt -S -passes='dxil-post-optimization-validation' -mtriple=dxil-pc-shadermodel6.3-library %s 2>&1 | FileCheck %s
+; CHECK: RWStructuredBuffers may increment or decrement their counters, but not both.
+
+define void @inc_and_dec() {
+entry:
+ %handle = call target("dx.RawBuffer", float, 1, 0) @llvm.dx.resource.handlefromheap(i32 10, i1 false)
+ call i32 @llvm.dx.resource.updatecounter(target("dx.RawBuffer", float, 1, 0) %handle, i8 -1)
+
+ %handle2 = call target("dx.RawBuffer", float, 1, 0) @llvm.dx.resource.handlefromheap(i32 10, i1 false)
+ call i32 @llvm.dx.resource.updatecounter(target("dx.RawBuffer", float, 1, 0) %handle2, i8 1)
+ ret void
+}
>From a045cb57f62541ff59a16a27eda77df631db01cd Mon Sep 17 00:00:00 2001
From: Helena Kotas <hekotas at microsoft.com>
Date: Sun, 16 Aug 2026 21:51:50 -0700
Subject: [PATCH 2/3] Update size check in DXILShaderFlags
---
llvm/lib/Target/DirectX/DXILShaderFlags.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/llvm/lib/Target/DirectX/DXILShaderFlags.cpp b/llvm/lib/Target/DirectX/DXILShaderFlags.cpp
index 64d8dc33e3e60..668f2453d9157 100644
--- a/llvm/lib/Target/DirectX/DXILShaderFlags.cpp
+++ b/llvm/lib/Target/DirectX/DXILShaderFlags.cpp
@@ -342,7 +342,7 @@ ModuleShaderFlags::gatherGlobalModuleFlags(const Module &M,
if (MMDI.ValidatorVersion < VersionTuple(1, 6)) {
NumUAVs++;
} else { // MMDI.ValidatorVersion >= VersionTuple(1, 6)
- uint32_t Size = UAV.getBinding().Size;
+ uint32_t Size = UAV.getSize();
uint32_t NewNum = NumUAVs + (Size == 0 ? ~0U : Size);
if (NewNum < NumUAVs)
NewNum = ~0U;
>From 63816eb8715ec96d19a0dd04260603c731e22d45 Mon Sep 17 00:00:00 2001
From: Helena Kotas <hekotas at microsoft.com>
Date: Sun, 16 Aug 2026 23:01:42 -0700
Subject: [PATCH 3/3] add test to verify that heap resources do not appear in
printed resource table or metadata
---
.../DirectX/Metadata/cbuffer-metadata.ll | 20 ++++++++++++++++++-
.../CodeGen/DirectX/Metadata/srv_metadata.ll | 14 ++++++++++++-
.../CodeGen/DirectX/Metadata/uav_metadata.ll | 14 ++++++++++++-
3 files changed, 45 insertions(+), 3 deletions(-)
diff --git a/llvm/test/CodeGen/DirectX/Metadata/cbuffer-metadata.ll b/llvm/test/CodeGen/DirectX/Metadata/cbuffer-metadata.ll
index e1e17328f355a..1fb20d6575aba 100644
--- a/llvm/test/CodeGen/DirectX/Metadata/cbuffer-metadata.ll
+++ b/llvm/test/CodeGen/DirectX/Metadata/cbuffer-metadata.ll
@@ -1,6 +1,6 @@
; RUN: opt -S -dxil-translate-metadata < %s | FileCheck %s
; RUN: opt -S --passes="dxil-pretty-printer" < %s 2>&1 | FileCheck %s --check-prefix=PRINT
-; RUN: llc %s -o - -disable-dxil-remove-unused-resources < %s 2>&1 | FileCheck %s --check-prefixes=CHECK,PRINT
+; RUN: llc %s -o - -disable-dxil-remove-unused-resources -stop-before=dxil-op-lower 2>&1 | FileCheck %s --check-prefixes=CHECK,PRINT
target triple = "dxil-pc-shadermodel6.6-compute"
@@ -22,7 +22,12 @@ target triple = "dxil-pc-shadermodel6.6-compute"
@MyConstants.cb = global target("dx.CBuffer", %__cblayout_MyConstants) poison
@MyConstants.str = private unnamed_addr constant [12 x i8] c"MyConstants\00", align 1
+%__cblayout_HeapCB = type <{
+ <2 x i16>
+}>
+
; PRINT:; Resource Bindings:
+; PRINT-NOT: ; HeapCB
; PRINT-NEXT:;
; PRINT-NEXT:; Name Type Format Dim ID HLSL Bind Count
; PRINT-NEXT:; ----
@@ -67,11 +72,24 @@ define void @test() #0 {
%CB3.cb_h = call target("dx.CBuffer", %__cblayout_MyConstants)
@llvm.dx.resource.handlefrombinding(i32 15, i32 5, i32 1, i32 0, ptr @MyConstants.str)
+ ; Resource from heap should not appear anywhere in the resource list
+ ; since it does not have a binding.
+ ;
+ ; struct HeapCB {
+ ; int16_t2 v;
+ ; };
+ ; ConstantBuffer CB4<HeapCB> = ResourceDescriptorHeap[10];
+ %CB4.cb_h = call target("dx.CBuffer", %__cblayout_HeapCB)
+ @llvm.dx.resource.handlefromheap(i32 10, i1 false)
+
ret void
}
attributes #0 = { noinline nounwind "hlsl.shader"="compute" }
+; Constant buffer from heap is the only one using { <2 x i16> } and it should not appear in list.
+; CHECK-NOT: = type { <2 x i16> }
+
; CHECK: %CBuffer.CB1 = type { { float, i32, double, <2 x i32> } }
; CHECK: %CBuffer.CB2 = type { { float, double, float, half, i16, i64, i32 } }
; CHECK: %CBuffer.MyConstants = type { { double, <3 x float>, float, <3 x double>, half, <2 x double>, float, <3 x half>, <3 x half> } }
diff --git a/llvm/test/CodeGen/DirectX/Metadata/srv_metadata.ll b/llvm/test/CodeGen/DirectX/Metadata/srv_metadata.ll
index cac3c3381837b..67053edb2ca3b 100644
--- a/llvm/test/CodeGen/DirectX/Metadata/srv_metadata.ll
+++ b/llvm/test/CodeGen/DirectX/Metadata/srv_metadata.ll
@@ -1,6 +1,6 @@
; RUN: opt -S -dxil-translate-metadata < %s | FileCheck %s
; RUN: opt -S --passes="dxil-pretty-printer" < %s 2>&1 | FileCheck %s --check-prefix=PRINT
-; RUN: llc %s -o - -disable-dxil-remove-unused-resources 2>&1 | FileCheck %s --check-prefixes=CHECK,PRINT
+; RUN: llc %s -o - -disable-dxil-remove-unused-resources -stop-before=dxil-op-lower 2>&1 | FileCheck %s --check-prefixes=CHECK,PRINT
target datalayout = "e-m:e-p:32:32-i1:32-i8:8-i16:16-i32:32-i64:64-f16:16-f32:32-f64:64-n8:16:32:64"
target triple = "dxil-pc-shadermodel6.6-compute"
@@ -16,6 +16,9 @@ target triple = "dxil-pc-shadermodel6.6-compute"
@Array.str = private unnamed_addr constant [6 x i8] c"Array\00", align 1
@Array2.str = private unnamed_addr constant [7 x i8] c"Array2\00", align 1
+; Make sure heap resource does not appear in the resource list.
+; PRINT-NOT: ; i16
+
; PRINT:; Resource Bindings:
; PRINT-NEXT:;
; PRINT-NEXT:; Name Type Format Dim ID HLSL Bind Count
@@ -84,11 +87,20 @@ define void @test() #0 {
%Array2_20_h = call target("dx.TypedBuffer", double, 0, 0, 0)
@llvm.dx.resource.handlefrombinding(i32 4, i32 2, i32 0, i32 20, ptr @Array2.str)
+ ; Resource from heap should not appear anywhere in the resource list
+ ; since it does not have a binding. Use <2 x i16> element type to make sure
+ ; it does not match any of the other resources.
+ %heap_resource = tail call target("dx.TypedBuffer", <2 x i16>, 0, 0, 0)
+ @llvm.dx.resource.handlefromheap(i32 5, i1 false)
+
ret void
}
attributes #0 = { noinline nounwind "hlsl.shader"="compute" }
+; Heap resource is the only one using <2 x i16> and it should not appear in list
+; CHECK-NOT: = type { <2 x i16> }
+
; CHECK: %"Buffer<half4>" = type { <4 x half> }
; CHECK: %"Buffer<float2>" = type { <2 x float> }
; CHECK: %"Buffer<double>" = type { double }
diff --git a/llvm/test/CodeGen/DirectX/Metadata/uav_metadata.ll b/llvm/test/CodeGen/DirectX/Metadata/uav_metadata.ll
index 9ab87829730f8..0e9e74dbcafcb 100644
--- a/llvm/test/CodeGen/DirectX/Metadata/uav_metadata.ll
+++ b/llvm/test/CodeGen/DirectX/Metadata/uav_metadata.ll
@@ -1,6 +1,6 @@
; RUN: opt -S -dxil-translate-metadata < %s | FileCheck %s
; RUN: opt -S --passes="dxil-pretty-printer" < %s 2>&1 | FileCheck %s --check-prefix=PRINT
-; RUN: llc %s -o - -disable-dxil-remove-unused-resources 2>&1 | FileCheck %s --check-prefixes=CHECK,PRINT
+; RUN: llc %s -o - -disable-dxil-remove-unused-resources -stop-before=dxil-op-lower 2>&1 | FileCheck %s --check-prefixes=CHECK,PRINT
target datalayout = "e-m:e-p:32:32-i1:32-i8:8-i16:16-i32:32-i64:64-f16:16-f32:32-f64:64-n8:16:32:64"
target triple = "dxil-pc-shadermodel6.6-compute"
@@ -19,6 +19,9 @@ target triple = "dxil-pc-shadermodel6.6-compute"
@Array.str = private unnamed_addr constant [6 x i8] c"Array\00", align 1
@Array2.str = private unnamed_addr constant [7 x i8] c"Array2\00", align 1
+; Make sure heap resource does not appear in the resource list.
+; PRINT-NOT: ; i16
+
; PRINT:; Resource Bindings:
; PRINT-NEXT:;
; PRINT-NEXT:; Name Type Format Dim ID HLSL Bind Count
@@ -101,11 +104,20 @@ define void @test() #0 {
%Ten_h = call target("dx.TypedBuffer", i64, 1, 0, 0)
@llvm.dx.resource.handlefrombinding(i32 5, i32 22, i32 1, i32 0, ptr @Ten.str)
+ ; Resource from heap should not appear anywhere in the resource list
+ ; since it does not have a binding. Use <2 x i16> element type to make sure
+ ; it does not match any of the other resources.
+ %heap_resource = tail call target("dx.TypedBuffer", <2 x i16>, 1, 0, 0)
+ @llvm.dx.resource.handlefromheap(i32 5, i1 false)
+
ret void
}
attributes #0 = { noinline nounwind "hlsl.shader"="compute" }
+; Heap resource is the only one using <2 x i16> and it should not appear in list
+; CHECK-NOT: = type { <2 x i16> }
+
; CHECK: %"RWBuffer<half4>" = type { <4 x half> }
; CHECK: %"RWBuffer<float2>" = type { <2 x float> }
; CHECK: %"RWBuffer<double>" = type { double }
More information about the llvm-commits
mailing list