Skip to content

[SPIR-V] SPV_EXT_descriptor_heap crashes, or miscompiles shaders that work in emulated mode (images, buffers, ConstantBuffer, counters) #9019

Open

Description

Summary

With -fspv-use-descriptor-heap (SPV_EXT_descriptor_heap), a large set of shaders that compile and validate in the emulated mode (ResourceDescriptorHeap as bound runtime arrays) either fail to compile in native mode, produce invalid SPIR-V, crash the compiler, or silently generate wrong code.

The root problems

The native implementation handles the simplest case where a heap resource is fetched and used in the same place. Most shaders don't necessarily follow that constraint, and the design has no way to go further.

1. Heap provenance is a per-variable compile-time property, not a value

The emitter records "this variable was initialized from the heap" in a side table keyed on VarDecl. The table is flow-insensitive and only describes named local variables.

That is insufficient because the provenance is load-bearing at code generation. An image atomic must rebuild the access from the descriptor index, so the index has to be recoverable at the atomic site. Parameters, return values, out/inout arguments, struct fields, and array elements have no VarDecl entry, so provenance is dropped at every one of those edges. The compiler then either rejects the shader or falls back to storing an image handle to memory, which is invalid SPIR-V (VUID-06924).

At run time a heap resource is a descriptor index plus a resource kind. It has to be represented as a value that follows the resource through calls and aggregates, not as an annotation on a declaration.

2. Heap buffers exist only as a pointer at the access site

A heap buffer is materialized as an OpBufferPointerEXT pointer at the point of use. There is no representation for the buffer as something to pass, return, or place in an aggregate. ConstantBuffer by value, counter operations, and append/consume buffers are each rejected for the same reason: nothing describes a heap buffer whose lifetime extends beyond the expression that creates it.

Counters add a second gap. A heap buffer has no statically known counter variable, so counter access needs its own storage model keyed off the heap index.

3. Rejection paths are unsafe

When the emitter declines a construct, it often returns a null SpirvInstruction* or an OpUndef and continues. A null result propagates into later emitter stages and crashes (access violation, LLVM assert). An OpUndef result produces SPIR-V that validates and uses an undefined pointer or the wrong buffer, such as the caller of an out/inout buffer parameter reading an undefined pointer or the pre-call binding.

There is no invariant that a rejection must emit a located error and yield a defined value. A silent miscompile is a worse failure mode than a missing feature.

Activity

  1. changed the title [-][SPIR-V] SPV_EXT_descriptor_heap: native mode fails, crashes, or miscompiles shaders that work in emulated mode (images, buffers, ConstantBuffer, counters)[/-] [+][SPIR-V] SPV_EXT_descriptor_heap crashes, or miscompiles shaders that work in emulated mode (images, buffers, ConstantBuffer, counters)[/+] on Oct 8, 2026
  2. jzakharovnv commented on Oct 8, 2026

    @jzakharovnv
    CollaboratorAuthor

    Just a note that this is filed as a catch-all umbrella issue for issues relating to #8517.

    Moreover, the rewrite fixing all of these is complete locally just needs to be sent out several layered PRs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugBug, regression, crashspirvWork related to SPIR-V

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions