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

Open
#8,784 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug needs-triage spirv
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 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.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.