microsoft / microsoft/DirectXShaderCompiler
[SPIR-V] SPV_EXT_descriptor_heap: cross-function heap buffer/image alias propagation not implemented, passing or returning a heap alias is rejected or miscompiles
Nobody has claimed this yet.
- Dominant language
- C++
- Stars
- 3.7k
- Forks
- 900
- Avg merge
- 2d 11h
- Merged PRs (30d)
- 44
Description
Apart of #8517
Description
Under -fspv-use-descriptor-heap, a local variable initialized from ResourceDescriptorHeap[i] (heap alias) cannot be passed to a user function or returned from one. Depending on the resource family, DXC either emits a compile-time error and rejects the shader, or silently emits invalid SPIR-V that passes DXC but is rejected by the SPIR-V validator.
Two resource families are affected:
- Buffer aliases (
StructuredBuffer,ConstantBuffer,ByteAddressBuffer, etc.): the alias is backed by anOpBufferPointerEXTvalue whose type isOpTypePointer StorageBuffer. Passing it to a function would require the callee parameter to have the same pointer type, which in turn requires theVariablePointersStorageBuffercapability and matchingOpFunctionParametertypes that the compiler does not currently generate. - Image aliases (
RWTexture2D,RWBuffer, etc.): the alias's descriptor index is tracked per-VarDeclin the emitter's internal alias map. When an image alias is passed to or returned from a function, the callee's parameter (or the caller's return-value variable) is a differentVarDeclthat is absent from that map. Non-atomic reads and writes (OpImageRead/OpImageWrite) work correctly because the image handle value itself is sufficient. Atomic operations requireOpImageTexelPointer, which needs the heap slot — the emitter falls back to emitting a plainOpStoreto anOpTypeImage-typed temporary, which the SPIR-V validator rejects. DXC emits no diagnostic for this case.
Steps to Reproduce
Buffer alias passing to function (DXC compile error):
// dxc -T cs_6_6 -E main -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -spirv repro_buf_param.hlsl
RWByteAddressBuffer out : register(u0);
uint consume(StructuredBuffer<uint> buf) { return buf[0]; }
[numthreads(1, 1, 1)]
void main() {
StructuredBuffer<uint> sb = ResourceDescriptorHeap[0];
out.Store(0, consume(sb));
}
Buffer alias returning from function (DXC compile error):
// dxc -T cs_6_6 -E main -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -spirv repro_buf_ret.hlsl
RWByteAddressBuffer out : register(u0);
StructuredBuffer<uint> makeAlias() {
StructuredBuffer<uint> sb = ResourceDescriptorHeap[0];
return sb;
}
[numthreads(1, 1, 1)]
void main() { out.Store(0, makeAlias()[0]); }
Image alias passing to function as in param (DXC silent miscompile, atomic only):
// dxc -T cs_6_6 -E main -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -spirv repro_img_param.hlsl
RWByteAddressBuffer out : register(u0);
void bump(RWTexture2D<uint> t, uint2 c, out uint orig) { InterlockedAdd(t[c], 1, orig); }
[numthreads(1, 1, 1)]
void main(uint3 tid : SV_DispatchThreadID) {
RWTexture2D<uint> tex = ResourceDescriptorHeap[0];
uint r;
bump(tex, tid.xy, r);
out.Store(0, r);
}
Image alias passing to function as inout param (DXC silent miscompile, atomic only):
// dxc -T cs_6_6 -E main -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -spirv repro_img_inout.hlsl
RWByteAddressBuffer out : register(u0);
void bump(inout RWTexture2D<uint> t, uint2 c, out uint orig) { InterlockedAdd(t[c], 1, orig); }
[numthreads(1, 1, 1)]
void main(uint3 tid : SV_DispatchThreadID) {
RWTexture2D<uint> tex = ResourceDescriptorHeap[0];
uint r;
bump(tex, tid.xy, r);
out.Store(0, r);
}
Image alias returning from function, caller uses atomic (DXC silent miscompile):
// dxc -T cs_6_6 -E main -fspv-use-descriptor-heap -fspv-target-env=vulkan1.3 -spirv repro_img_ret.hlsl
RWByteAddressBuffer out : register(u0);
RWTexture2D<uint> makeAlias() {
RWTexture2D<uint> tex = ResourceDescriptorHeap[0];
return tex;
}
[numthreads(1, 1, 1)]
void main(uint3 tid : SV_DispatchThreadID) {
uint orig;
InterlockedAdd(makeAlias()[tid.xy], 1, orig);
out.Store(0, orig);
}
Actual Behavior
- Buffer alias passed to function: DXC emits a compile-time error and rejects the shader:
error: heap buffer alias cannot be passed to a user function; access the buffer element directly at the call site - Buffer alias returned from function: DXC emits a compile-time error and rejects the shader:
error: heap buffer alias cannot be returned from a function; access the buffer element directly at the return site - Image alias passed to function as
inorinoutparam (atomic operations): DXC produces no diagnostic and exits successfully, but emits invalid SPIR-V. The SPIR-V validator then reports:
The callee's parameterfatal error: generated SPIR-V is invalid: [VUID-StandaloneSpirv-OpTypeImage-06924] Cannot store to OpTypeImageVarDeclis absent from the internal alias map, soemitDescriptorHeapImageTexelPointerreturnsnullptr. The fallback emitsOpStoreto anOpTypeImage-typed temporary — invalid per the SPIR-V spec. - Image alias returned from function, caller uses atomic: DXC produces no diagnostic and exits successfully, but emits invalid SPIR-V with the same
[VUID-06924]validator error. The return path (doReturnStmt) only guards buffer aliases; image aliases fall through to a plain load of the image handle, stripping alias metadata. The caller's use of the returned value for atomics hits the same fallback. - Image alias passed to function or returned (non-atomic load/store): works correctly —
OpImageRead/OpImageWriteonly need the image handle value, which survives function call boundaries. Only operations requiringOpImageTexelPointer(atomics) are affected.
Expected Behavior
The descriptor index should propagate across function call boundaries. The correct SPIR-V requires either:
VariablePointersStorageBuffercapability so thatOpBufferPointerEXTvalues can be passed as function parameters and returned as values for buffer aliases.- Descriptor-index out-parameters (or return-value encoding) for image aliases so that the callee's atomic operations target the heap slot from the caller.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the SPIR-V emitter's alias map, emitDescriptorHeapImageTexelPointer, and doReturnStmt, then reproduce the buffer and image cases using the commands in the issue. Trace how aliases cross parameters and returns, and validate generated output; done means supported buffer and image alias calls and returns preserve descriptor indices without compile errors or invalid SPIR-V.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp
- Domain
- compilers
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100