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 an OpBufferPointerEXT value whose type is OpTypePointer StorageBuffer. Passing it to a function would require the callee parameter to have the same pointer type, which in turn requires the VariablePointersStorageBuffer capability and matching OpFunctionParameter types that the compiler does not currently generate.
- Image aliases (
RWTexture2D, RWBuffer, etc.): the alias's descriptor index is tracked per-VarDecl in 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 different VarDecl that is absent from that map. Non-atomic reads and writes (OpImageRead/OpImageWrite) work correctly because the image handle value itself is sufficient. Atomic operations require OpImageTexelPointer, which needs the heap slot — the emitter falls back to emitting a plain OpStore to an OpTypeImage-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
in or inout param (atomic operations): DXC produces no diagnostic and exits successfully, but emits invalid SPIR-V. The SPIR-V validator then reports:
fatal error: generated SPIR-V is invalid: [VUID-StandaloneSpirv-OpTypeImage-06924] Cannot store to OpTypeImage
The callee's parameter VarDecl is absent from the internal alias map, so emitDescriptorHeapImageTexelPointer returns nullptr. The fallback emits OpStore to an OpTypeImage-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/OpImageWrite only need the image handle value, which survives function call boundaries. Only operations requiring OpImageTexelPointer (atomics) are affected.
Expected Behavior
The descriptor index should propagate across function call boundaries. The correct SPIR-V requires either:
VariablePointersStorageBuffer capability so that OpBufferPointerEXT values 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.
Apart of #8517
Description
Under
-fspv-use-descriptor-heap, a local variable initialized fromResourceDescriptorHeap[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:
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.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):
Buffer alias returning from function (DXC compile error):
Image alias passing to function as
inparam (DXC silent miscompile, atomic only):Image alias passing to function as
inoutparam (DXC silent miscompile, atomic only):Image alias returning from function, caller uses atomic (DXC silent miscompile):
Actual Behavior
inorinoutparam (atomic operations): DXC produces no diagnostic and exits successfully, but emits invalid SPIR-V. The SPIR-V validator then reports:VarDeclis absent from the internal alias map, soemitDescriptorHeapImageTexelPointerreturnsnullptr. The fallback emitsOpStoreto anOpTypeImage-typed temporary — invalid per the SPIR-V spec.[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.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.