Skip to content

Commit 5353bc5

Browse files
authored
Add bindless resource metadata usage query (#11436)
Fixes #10439 ## Motivation `ShaderReflection::getBindlessSpaceIndex()` reports a layout reservation, not final target-code usage. Since bindless space allocation moved into frontend layout, a shader can keep a non-negative bindless space index even when later optimization removes every descriptor-handle heap path. A host that treats `getBindlessSpaceIndex() >= 0` as "bind the descriptor heap" can therefore do unnecessary bindless setup for ordinary shaders. The motivating shape is a compute shader with only ordinary resources, such as `RWStructuredBuffer` inputs and outputs, or a shader that declares a `DescriptorHandle` inside a parameter block but never reads it. Both shapes can reserve a bindless space in layout while producing target IR with no surviving bindless heap use. ## Proposed solution Add `slang::IBindlessResourceMetadata` as a side interface on emitted artifact metadata. Callers keep using `getBindlessSpaceIndex()` to learn which space layout reserved, then cast target metadata to `IBindlessResourceMetadata` and call `usesBindlessResourceHeap()` to learn whether the compiled target IR still contains a descriptor-handle or bindless resource path. This keeps the public `IMetadata` vtable stable by adding a separate castable interface instead of appending methods to `IMetadata`. It also keeps the usage query tied to the late IR that code generation actually sees, rather than inferring usage from frontend syntax or from the existence of a layout reservation. ## Change summary - Public API: `include/slang.h:3735` and `include/slang-deprecated.h:1054` clarify that `getBindlessSpaceIndex()` reports reserved layout space. The new `IBindlessResourceMetadata` interface and `usesBindlessResourceHeap()` query are declared in `include/slang.h:4549`. - Metadata object: `ArtifactPostEmitMetadata` implements `IBindlessResourceMetadata`, exposes it from `getInterface()`, and stores `m_usesBindlessResourceHeap` in `source/compiler-core/slang-artifact-associated-impl.h:221` and `source/compiler-core/slang-artifact-associated-impl.cpp:292`. - Metadata collection: `collectMetadata()` now takes `TargetProgram*`, obtains the existing layout with `TargetProgram::getExistingLayout()`, scans final IR with `doesInstAndChildrenUseBindlessResourceHeap()`, and stores the result in `source/slang/slang-ir-metadata.cpp:278`. - Bindless heap detection: `_isBindlessResourceHeapGlobalParam()` identifies the lowered synthetic heap from its unbounded descriptor-table layout in the reserved bindless space, and `_instUsesBindlessResourceHeap()` recognizes the descriptor-heap opcodes and descriptor-handle cast opcodes in `source/slang/slang-ir-metadata.cpp:122` and `source/slang/slang-ir-metadata.cpp:157`. - Dynamic heap lowering: `getBindlessSpaceIndex()` now asserts that layout exists and that it reserved a bindless space before `lowerDynamicResourceHeap()` synthesizes the heap global in `source/slang/slang-ir-lower-dynamic-resource-heap.cpp:16`. - Emit pipeline: `linkAndOptimizeIR()` realizes layout before post-emit metadata for descriptor-handle-capable non-PyTorch targets, then calls `collectMetadata()` in `source/slang/slang-emit.cpp:2411`. - Tests and docs: bindless metadata coverage lives in `tools/slang-unit-test/unit-test-bindless-space-reflection.cpp:200`, vtable stability coverage for the new interface is in `tools/slang-unit-test/unit-test-vtable-stability.cpp:918`, IR metadata checks are in `tests/language-feature/descriptor-handle/`, and user-facing docs are updated in `docs/user-guide/09-reflection.md:1568` and `docs/user-guide/03-convenience-features.md:672`. ## Concepts and vocabulary - Bindless space index: the descriptor set or register space reserved by program layout for Slang's bindless resource heap. It is intentionally stable and can survive even when final target code does not use the heap. - Bindless resource heap usage: a post-lowering code-generation signal that final IR still contains descriptor-handle, descriptor-heap, or lowered synthetic heap access. - Synthetic resource heap global: the global parameter created by `lowerDynamicResourceHeap()` after it replaces `GetDynamicResourceHeap`. Metadata must identify it from semantic layout data, not only from a name hint. ## Process report The API change separates layout reservation from target-code usage. The old public contract encouraged hosts to infer runtime heap setup from `ShaderReflection::getBindlessSpaceIndex()`. That is not precise after frontend layout starts reserving bindless space before optimization, so this PR documents the old query as a layout signal in `include/slang.h:3735` and adds the target metadata side query in `include/slang.h:4549`. The metadata implementation uses a side interface because `IMetadata` is already a public COM-style interface. `ArtifactPostEmitMetadata` inherits `IBindlessResourceMetadata` in `source/compiler-core/slang-artifact-associated-impl.h:221` and returns it from `getInterface()` in `source/compiler-core/slang-artifact-associated-impl.cpp:292`; this makes `metadata->castAs(IBindlessResourceMetadata::getTypeGuid())` the extension mechanism without changing existing `IMetadata` slots. The usage bit is computed in `collectMetadata()` after target-specific lowering, in `source/slang/slang-ir-metadata.cpp:278`. The scan first asks the target program for an existing layout; when no layout exists, `bindlessSpaceIndex` remains `-1` and the bindless scan is skipped. That no-layout shape is intentional for targets such as PyTorch binding emission, where forcing layout solely for metadata would be the wrong layer. For descriptor-handle-capable non-PyTorch targets, `source/slang/slang-emit.cpp:2411` realizes layout before metadata so the reserved bindless space is available. The synthetic heap-global case is handled by layout, not by a new IR marker. After `lowerDynamicResourceHeap()` replaces `GetDynamicResourceHeap` with a global parameter, opcode scanning alone can no longer see the original intrinsic. The helper at `source/slang/slang-ir-metadata.cpp:122` identifies that global only when it has an infinite `DescriptorTableSlot` layout in the reserved bindless space. That keeps normal user-declared unbounded resource arrays from being mistaken for Slang's heap, including the case where the user array occupies the originally requested bindless space and layout moves the system heap to another space. The opcode scan at `source/slang/slang-ir-metadata.cpp:157` covers target-independent heap loads, SPIR-V descriptor heap operations, texel-pointer heap operations, and descriptor-handle cast opcodes that can survive to emit for native targets. The scan is recursive via `doesInstAndChildrenUseBindlessResourceHeap()` in `source/slang/slang-ir-metadata.cpp:188`, so handle usage inside helper functions is covered directly through instruction children. The dynamic-resource-heap lowering path now treats layout as its contract. The helper at `source/slang/slang-ir-lower-dynamic-resource-heap.cpp:16` asserts that `TargetProgram` and its existing layout are present, and that the layout reserved a bindless space. The pass still returns early at `source/slang/slang-ir-lower-dynamic-resource-heap.cpp:59` when there are no `GetDynamicResourceHeap` instructions, so targets without heap work do not pay the capability or layout checks. The tests exercise the truth table that motivated the fix. The shared unit-test helper casts target metadata and checks both layout reservation and `usesBindlessResourceHeap()` in `tools/slang-unit-test/unit-test-bindless-space-reflection.cpp:200`. Negative cases cover ordinary resources, dead descriptor handles, no reserved space, unbounded user arrays, and metadata queried before explicit layout. Positive cases cover SPIR-V descriptor heap paths, SPIR-V NV paths, WGSL dynamic resource heap, HLSL resource and sampler handles, native handle casts, texel-pointer heap usage, stripped name hints, and helper-function handle use. The vtable probe in `tools/slang-unit-test/unit-test-vtable-stability.cpp:918` pins the new side interface slot. ## Reviewer Directives (maintained by agent) - [human @jkwak-work] Avoid a dedicated `BindlessResourceHeapDecoration` and module-version bump when existing layout metadata can identify the lowered resource heap; only reintroduce a marker if the existing layout signal proves insufficient. (#11436 (comment)) - [human @jkwak-work] Treat `CastDescriptorHandleToResource` as `usesBindlessResourceHeap()` usage unconditionally, regardless of target or capability. This supersedes the earlier SPIR-V `spvBindlessTextureNV` gating directive. (#11436 (comment)) - [human @jkwak-work] Keep `collectMetadata` generic by passing `TargetProgram*` rather than bindless-specific parameters, and avoid requiring information from `TargetRequest` for the bindless metadata query. (#11436 (comment)) - [human @jkwak-work] Use `TargetProgram::getExistingLayout()` directly as the optional layout probe for metadata; do not keep a separate `hasExistingLayout()` accessor. Dynamic-resource-heap lowering should assert its required layout/bindless-space invariants inside `getBindlessSpaceIndex()`. (#11436 (comment)) - [human @jkwak-work] Keep the dynamic-resource-heap bindless-space helper returning `UInt`, and assert required layout/bindless-space invariants inside the helper instead of returning a nullable/boolean result. (#11436 (comment)) - [human @jkwak-work] Do not call helper functions such as `getBindlessSpaceIndex()` inside assertion expressions; compute the result first and assert the stored value when a caller-side assertion is needed. (#11436 (comment)) - [human @jkwak-work] Treat `bindlessSpaceIndex >= 0` as a caller contract for bindless heap global-param detection: callers should early-out when no bindless space is reserved, and the helper should assert the invariant with `SLANG_ASSERT(bindlessSpaceIndex >= 0)`. (#11436 (comment), #11436 (comment)) - [human @csyonghe] Use direct recursive instruction-child traversal for the bindless metadata scan, name the helper `doesInstAndChildrenUseBindlessResourceHeap()`, and include comments explaining the synthetic heap/global and cast-opcode detection. (#11436 (comment), #11436 (comment), #11436 (comment)) - [bot github-actions] For post-emit bindless metadata, keep `collectMetadata` generic over `TargetProgram*`: descriptor-handle-capable targets may realize layout before metadata, but targets without an existing layout use `bindlessSpaceIndex == -1`; do not force layout for PyTorch binding emission just to collect bindless metadata. (#11436 (comment))
1 parent 4f56990 commit 5353bc5

16 files changed

Lines changed: 878 additions & 46 deletions

docs/user-guide/03-convenience-features.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -668,6 +668,12 @@ Without requesting `spvDescriptorHeapEXT`, Slang introduces a global array of de
668668
from it. The descriptor set ID of the global descriptor array can be configured with the
669669
`-bindless-space-index` (or `CompilerOptionName::BindlessSpaceIndex` when using the API) option.
670670

671+
Reflection reports this value as a reserved bindless space for descriptor-handle-capable targets.
672+
That reservation can be present even when optimization and target lowering remove all descriptor
673+
handle uses from the emitted shader. Hosts that need to decide whether to bind a descriptor heap
674+
should query target metadata for `IBindlessResourceMetadata::usesBindlessResourceHeap()` instead of
675+
using `getBindlessSpaceIndex() >= 0` as the usage test.
676+
671677
Default behavior assigns binding indices based on descriptor types:
672678

673679
| Enum Value | Vulkan Descriptor Type | Binding Index |

docs/user-guide/09-reflection.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1564,6 +1564,34 @@ program->getEntryPointMetadata(
15641564
&entryPointMetadata);
15651565
```
15661566
1567+
Target-wide post-emit metadata is queried through `IComponentType::getTargetMetadata()`. For
1568+
`DescriptorHandle<T>`, `ProgramLayout::getBindlessSpaceIndex()` reports the frontend-reserved
1569+
bindless space, which can be non-negative even when the emitted target does not use a descriptor
1570+
heap path. Hosts should cast target metadata to `IBindlessResourceMetadata` and use
1571+
`usesBindlessResourceHeap()` as a post-lowering signal, then combine it with the target binding
1572+
model when deciding whether an explicit bindless resource heap binding is required:
1573+
1574+
```c++
1575+
slang::IComponentType* program = ...;
1576+
Slang::ComPtr<slang::IMetadata> targetMetadata;
1577+
Slang::ComPtr<slang::IBlob> diagnostics;
1578+
if (SLANG_FAILED(program->getTargetMetadata(
1579+
0, // target index
1580+
targetMetadata.writeRef(),
1581+
diagnostics.writeRef())))
1582+
{
1583+
// Handle error.
1584+
return;
1585+
}
1586+
1587+
auto bindlessMetadata = static_cast<slang::IBindlessResourceMetadata*>(
1588+
targetMetadata->castAs(slang::IBindlessResourceMetadata::getTypeGuid()));
1589+
if (bindlessMetadata && bindlessMetadata->usesBindlessResourceHeap())
1590+
{
1591+
// Bind the descriptor heap for this compiled target.
1592+
}
1593+
```
1594+
15671595
When traversal of reflection data reaches a leaf parameter, the application can use `IMetadata::isParameterLocationUsed()` with the absolute location of that parameter for a given layout unit:
15681596

15691597
```c++

include/slang-deprecated.h

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1047,8 +1047,15 @@ extern "C"
10471047
SlangCompileRequest* request,
10481048
int translationUnitIndex);
10491049

1050-
/** Get the descriptor set/space index allocated for the bindless resource heap.
1051-
* Returns -1 if the program does not use bindless resource heap.
1050+
/** Get the descriptor set/space index reserved for the bindless resource heap.
1051+
*
1052+
* This is a layout/reflection reservation made before final target lowering and
1053+
* optimization. It can remain non-negative even when the emitted target code no
1054+
* longer uses a bindless heap/resource-handle path. Query `IBindlessResourceMetadata`
1055+
* from target metadata to determine whether such a path survived in the compiled
1056+
* target IR.
1057+
*
1058+
* Returns -1 only when no bindless heap space was reserved for the program layout.
10521059
*/
10531060
SLANG_API SlangInt spReflection_getBindlessSpaceIndex(SlangReflection* reflection);
10541061
#ifdef __cplusplus

include/slang.h

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3759,8 +3759,15 @@ struct ShaderReflection
37593759
return spReflection_ToJson((SlangReflection*)this, nullptr, outBlob);
37603760
}
37613761

3762-
/** Get the descriptor set/space index allocated for the bindless resource heap.
3763-
* Returns -1 if the program does not use bindless resource heap.
3762+
/** Get the descriptor set/space index reserved for the bindless resource heap.
3763+
*
3764+
* This is a layout/reflection reservation made before final target lowering and
3765+
* optimization. It can remain non-negative even when the emitted target code no
3766+
* longer uses a bindless heap/resource-handle path. Query `IBindlessResourceMetadata`
3767+
* from target metadata to determine whether such a path survived in the compiled
3768+
* target IR.
3769+
*
3770+
* Returns -1 only when no bindless heap space was reserved for the program layout.
37643771
*/
37653772
SlangInt getBindlessSpaceIndex()
37663773
{
@@ -4566,6 +4573,39 @@ struct IMetadata : public ISlangCastable
45664573
};
45674574
#define SLANG_UUID_IMetadata IMetadata::getTypeGuid()
45684575

4576+
/** Bindless resource metadata produced for a compiled target.
4577+
4578+
The bindless space index reported through program reflection is a frontend-predicted reserved
4579+
descriptor space. It remains stable even when later optimization or target lowering removes all
4580+
descriptor-handle heap use from the emitted shader. This metadata interface reports the
4581+
post-lowering usage signal instead.
4582+
4583+
`usesBindlessResourceHeap()` reports whether the final target IR still contains the
4584+
descriptor-handle/bindless resource path after target-specific lowering. This is a code-generation
4585+
signal, not a complete cross-target host binding policy: targets that lower descriptor handles to
4586+
native resource handles or addresses may not require an explicit descriptor-heap binding even when
4587+
this returns true. Hosts should combine this query with their target binding model when deciding
4588+
whether to bind a heap.
4589+
4590+
Cast from an artifact-associated `IMetadata*` using `castAs()`.
4591+
*/
4592+
struct IBindlessResourceMetadata : public ISlangCastable
4593+
{
4594+
SLANG_COM_INTERFACE(
4595+
0xeafa96d3,
4596+
0x2352,
4597+
0x4bf4,
4598+
{0x88, 0x64, 0x32, 0x28, 0xa4, 0x07, 0x7a, 0x83})
4599+
4600+
/// Returns true when the compiled target IR still contains a bindless
4601+
/// descriptor-heap/resource-handle path after target-specific lowering. This is a
4602+
/// code-generation signal, not a complete cross-target host binding policy; targets
4603+
/// that lower descriptor handles to native resource handles or addresses may not require
4604+
/// an explicit descriptor-heap binding even when this returns true.
4605+
virtual SLANG_NO_THROW bool SLANG_MCALL usesBindlessResourceHeap() = 0;
4606+
};
4607+
#define SLANG_UUID_IBindlessResourceMetadata IBindlessResourceMetadata::getTypeGuid()
4608+
45694609
/** Coverage tracing metadata produced when any shader coverage mode is active.
45704610
45714611
The current implementation reports line, function-entry, and branch-arm

source/compiler-core/slang-artifact-associated-impl.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,8 @@ void* ArtifactPostEmitMetadata::getInterface(const Guid& guid)
289289
}
290290
if (guid == slang::IMetadata::getTypeGuid())
291291
return static_cast<slang::IMetadata*>(this);
292+
if (guid == slang::IBindlessResourceMetadata::getTypeGuid())
293+
return static_cast<slang::IBindlessResourceMetadata*>(this);
292294
if (guid == slang::ICoverageTracingMetadata::getTypeGuid())
293295
{
294296
return static_cast<slang::ICoverageTracingMetadata*>(this);
@@ -356,6 +358,11 @@ const char* ArtifactPostEmitMetadata::getDebugBuildIdentifier()
356358
return m_debugBuildIdentifier.getBuffer();
357359
}
358360

361+
bool ArtifactPostEmitMetadata::usesBindlessResourceHeap()
362+
{
363+
return m_usesBindlessResourceHeap;
364+
}
365+
359366
uint32_t ArtifactPostEmitMetadata::getCounterCount()
360367
{
361368
return m_coverageCounterCount;

source/compiler-core/slang-artifact-associated-impl.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,7 @@ enum class SyntheticResourceKnownID : uint32_t
220220

221221
class ArtifactPostEmitMetadata : public ComBaseObject,
222222
public IArtifactPostEmitMetadata,
223+
public slang::IBindlessResourceMetadata,
223224
public slang::ICoverageTracingMetadata,
224225
public slang::ISyntheticResourceMetadata,
225226
public slang::ICooperativeTypesMetadata
@@ -249,6 +250,9 @@ class ArtifactPostEmitMetadata : public ComBaseObject,
249250

250251
SLANG_NO_THROW virtual const char* SLANG_MCALL getDebugBuildIdentifier() SLANG_OVERRIDE;
251252

253+
// IBindlessResourceMetadata
254+
SLANG_NO_THROW virtual bool SLANG_MCALL usesBindlessResourceHeap() SLANG_OVERRIDE;
255+
252256
// ICoverageTracingMetadata
253257
SLANG_NO_THROW virtual uint32_t SLANG_MCALL getCounterCount() SLANG_OVERRIDE;
254258
SLANG_NO_THROW virtual SlangResult SLANG_MCALL
@@ -299,6 +303,7 @@ class ArtifactPostEmitMetadata : public ComBaseObject,
299303
List<slang::CooperativeVectorTypeUsageInfo> m_cooperativeVectorTypes;
300304
List<slang::CooperativeVectorCombination> m_cooperativeVectorCombinations;
301305
String m_debugBuildIdentifier;
306+
bool m_usesBindlessResourceHeap = false;
302307

303308
// Coverage tracing data, populated by `instrumentCoverage` when
304309
// `-trace-coverage` is active. Empty otherwise.

source/slang/slang-emit.cpp

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include "../core/slang-performance-profiler.h"
1010
#include "../core/slang-type-text-util.h"
1111
#include "../core/slang-writer.h"
12+
#include "slang-capability.h"
1213
#include "slang-check-out-of-bound-access.h"
1314
#include "slang-emit-c-like.h"
1415
#include "slang-emit-cpp.h"
@@ -2516,7 +2517,17 @@ Result linkAndOptimizeIR(
25162517
SLANG_PASS(unexportNonEmbeddableIR, target);
25172518
}
25182519

2519-
SLANG_PASS(collectMetadata, *metadata);
2520+
{
2521+
auto targetCaps = targetRequest->getTargetCaps();
2522+
if (target != CodeGenTarget::PyTorchCppBinding &&
2523+
targetCaps.atLeastOneSetImpliedInOther(CapabilitySet(
2524+
CapabilityName::descriptor_handle)) == CapabilitySet::ImpliesReturnFlags::Implied)
2525+
{
2526+
if (!targetProgram->getOrCreateLayout(sink))
2527+
return SLANG_FAIL;
2528+
}
2529+
}
2530+
SLANG_PASS(collectMetadata, targetProgram, *metadata);
25202531

25212532
if (!targetProgram->getOptionSet().shouldPerformMinimumOptimizations())
25222533
SLANG_PASS(checkUnsupportedInst, codeGenContext->getTargetReq(), sink);

source/slang/slang-ir-lower-dynamic-resource-heap.cpp

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,15 @@ namespace Slang
1515
/// ensuring consistency with reflection data.
1616
UInt getBindlessSpaceIndex(TargetProgram* targetProgram)
1717
{
18-
// Get the bindless space index from the program layout.
19-
// This is always allocated during generateParameterBindings().
20-
if (auto programLayout = targetProgram->getExistingLayout())
21-
{
22-
SLANG_ASSERT(programLayout->bindlessSpaceIndex >= 0);
23-
return (UInt)programLayout->bindlessSpaceIndex;
24-
}
18+
SLANG_RELEASE_ASSERT(targetProgram);
19+
auto programLayout = targetProgram->getExistingLayout();
20+
SLANG_RELEASE_ASSERT(programLayout);
2521

26-
// Fallback: if no layout exists yet, use the user-specified index or default to 0.
27-
// This shouldn't normally happen since layout is generated before this pass runs.
28-
return (UInt)targetProgram->getOptionSet().getIntOption(CompilerOptionName::BindlessSpaceIndex);
22+
// Do not fall back to the requested option here; only layout knows the
23+
// bindless space that was actually reserved after conflict resolution.
24+
SLANG_RELEASE_ASSERT(programLayout->bindlessSpaceIndex >= 0);
25+
26+
return (UInt)programLayout->bindlessSpaceIndex;
2927
}
3028

3129
IRVarLayout* createResourceHeapVarLayoutWithSpaceAndBinding(
@@ -58,9 +56,11 @@ void lowerDynamicResourceHeap(IRModule* module, TargetProgram* targetProgram, Di
5856
}
5957
}
6058

59+
if (workList.getCount() == 0)
60+
return;
61+
6162
// If there are GetDynamicResourceHeap instructions, verify that the target
6263
// supports descriptor_handle capability.
63-
if (workList.getCount() > 0)
6464
{
6565
auto targetCaps = targetProgram->getTargetReq()->getTargetCaps();
6666
if (targetCaps.atLeastOneSetImpliedInOther(CapabilitySet(

source/slang/slang-ir-metadata.cpp

Lines changed: 103 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include "slang-ir-insts.h"
66
#include "slang-ir.h"
77
#include "slang-rich-diagnostics.h"
8+
#include "slang-target-program.h"
89

910
#include <utility>
1011

@@ -118,6 +119,88 @@ static void _insertBinding(
118119
ranges.add(newRange);
119120
}
120121

122+
static bool _isBindlessResourceHeapGlobalParam(IRInst* inst, SlangInt bindlessSpaceIndex)
123+
{
124+
SLANG_ASSERT(bindlessSpaceIndex >= 0);
125+
126+
auto globalParam = as<IRGlobalParam>(inst);
127+
if (!globalParam)
128+
return false;
129+
130+
auto layoutDecoration = globalParam->findDecoration<IRLayoutDecoration>();
131+
if (!layoutDecoration)
132+
return false;
133+
134+
auto varLayout = as<IRVarLayout>(layoutDecoration->getLayout());
135+
if (!varLayout)
136+
return false;
137+
138+
auto descriptorTableSlotOffset =
139+
varLayout->findOffsetAttr(LayoutResourceKind::DescriptorTableSlot);
140+
if (!descriptorTableSlotOffset)
141+
return false;
142+
143+
UInt spaceIndex = descriptorTableSlotOffset->getSpace();
144+
if (auto spaceAttr = varLayout->findOffsetAttr(LayoutResourceKind::RegisterSpace))
145+
spaceIndex += spaceAttr->getOffset();
146+
if (spaceIndex != (UInt)bindlessSpaceIndex)
147+
return false;
148+
149+
// `lowerDynamicResourceHeap` replaces the intrinsic with a synthetic global param in the
150+
// reserved bindless space, so opcode checks alone no longer see the heap after lowering.
151+
auto typeLayout = varLayout->getTypeLayout();
152+
auto descriptorTableSlotSize =
153+
typeLayout ? typeLayout->findSizeAttr(LayoutResourceKind::DescriptorTableSlot) : nullptr;
154+
return descriptorTableSlotSize && descriptorTableSlotSize->getSize().isInfinite();
155+
}
156+
157+
static bool _instUsesBindlessResourceHeap(IRInst* inst, SlangInt bindlessSpaceIndex)
158+
{
159+
SLANG_ASSERT(bindlessSpaceIndex >= 0);
160+
161+
// Dynamic-resource-heap lowering can replace the heap intrinsic with a synthetic global
162+
// parameter, so check for that lowered form before looking at opcode uses.
163+
if (_isBindlessResourceHeapGlobalParam(inst, bindlessSpaceIndex))
164+
return true;
165+
166+
switch (inst->getOp())
167+
{
168+
case kIROp_GetDynamicResourceHeap:
169+
case kIROp_LoadResourceDescriptorFromHeap:
170+
case kIROp_LoadSamplerDescriptorFromHeap:
171+
case kIROp_SPIRVLoadDescriptorFromHeap:
172+
case kIROp_SPIRVLoadTexelPointerFromHeap:
173+
case kIROp_SPIRVResourceHeap:
174+
case kIROp_SPIRVSamplerHeap:
175+
// Target-independent and SPIR-V descriptor-heap ops can still be present when metadata
176+
// is collected; SPIR-V descriptor-heap extension tests cover these cases.
177+
return true;
178+
case kIROp_CastDescriptorHandleToResource:
179+
case kIROp_CastResourceToDescriptorHandle:
180+
case kIROp_CastUInt2ToDescriptorHandle:
181+
case kIROp_CastDescriptorHandleToUInt2:
182+
case kIROp_CastUInt64ToDescriptorHandle:
183+
case kIROp_CastDescriptorHandleToUInt64:
184+
// These casts can survive into target IR and still indicate a descriptor-handle path that
185+
// needs the bindless resource heap.
186+
return true;
187+
default:
188+
return false;
189+
}
190+
}
191+
192+
static bool doesInstAndChildrenUseBindlessResourceHeap(IRInst* inst, SlangInt bindlessSpaceIndex)
193+
{
194+
if (_instUsesBindlessResourceHeap(inst, bindlessSpaceIndex))
195+
return true;
196+
197+
for (auto child : inst->getChildren())
198+
if (doesInstAndChildrenUseBindlessResourceHeap(child, bindlessSpaceIndex))
199+
return true;
200+
201+
return false;
202+
}
203+
121204
void collectMetadataFromInst(IRInst* param, ArtifactPostEmitMetadata& outMetadata)
122205
{
123206
auto layoutDecoration = param->findDecoration<IRLayoutDecoration>();
@@ -190,12 +273,30 @@ void collectMetadataFromInst(IRInst* param, ArtifactPostEmitMetadata& outMetadat
190273
}
191274

192275
// Collects the metadata from the provided IR module, saves it in outMetadata.
193-
void collectMetadata(const IRModule* irModule, ArtifactPostEmitMetadata& outMetadata)
276+
void collectMetadata(
277+
const IRModule* irModule,
278+
TargetProgram* targetProgram,
279+
ArtifactPostEmitMetadata& outMetadata)
194280
{
281+
SLANG_ASSERT(targetProgram);
282+
283+
SlangInt bindlessSpaceIndex = -1;
284+
if (auto programLayout = targetProgram->getExistingLayout())
285+
{
286+
bindlessSpaceIndex = programLayout->bindlessSpaceIndex;
287+
}
288+
195289
// Scan the instructions looking for global resource declarations
196290
// and exported functions.
291+
bool usesBindlessResourceHeap = false;
197292
for (const auto& inst : irModule->getGlobalInsts())
198293
{
294+
if (bindlessSpaceIndex >= 0 && !usesBindlessResourceHeap)
295+
{
296+
if (doesInstAndChildrenUseBindlessResourceHeap(inst, bindlessSpaceIndex))
297+
usesBindlessResourceHeap = true;
298+
}
299+
199300
if (auto func = as<IRFunc>(inst))
200301
{
201302
if (func->findDecoration<IRDownstreamModuleExportDecoration>())
@@ -216,6 +317,7 @@ void collectMetadata(const IRModule* irModule, ArtifactPostEmitMetadata& outMeta
216317
continue;
217318
collectMetadataFromInst(param, outMetadata);
218319
}
320+
outMetadata.m_usesBindlessResourceHeap = usesBindlessResourceHeap;
219321
}
220322

221323
static SlangScalarType _getScalarTypeFromIRType(IRType* type)

source/slang/slang-ir-metadata.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ namespace Slang
77
class ArtifactPostEmitMetadata;
88
class DiagnosticSink;
99
struct IRModule;
10+
class TargetProgram;
1011

1112
// Walks `irModule` and records metadata for the target-visible cooperative
1213
// matrix/vector types and combinations. For each cooperative operation, the
@@ -17,6 +18,9 @@ void collectCooperativeMetadata(
1718
DiagnosticSink* sink,
1819
ArtifactPostEmitMetadata& outMetadata);
1920

20-
void collectMetadata(const IRModule* irModule, ArtifactPostEmitMetadata& outMetadata);
21+
void collectMetadata(
22+
const IRModule* irModule,
23+
TargetProgram* targetProgram,
24+
ArtifactPostEmitMetadata& outMetadata);
2125

2226
} // namespace Slang

0 commit comments

Comments
 (0)