KhronosGroup / KhronosGroup/SPIRV-Registry
SPV_EXT_shader_invocation_reorder: OpTypeHitObjectEXT inside a composite validates, then crashes the GPU
- Dominant language
- HTML
- Stars
- 149
- Forks
- 99
- Avg merge
- 4d 21h
- Merged PRs (30d)
- 2
Description
# SPV_EXT_shader_invocation_reorder: OpTypeHitObjectEXT inside a composite validates, then crashes the GPU
## Summary
`SPV_EXT_shader_invocation_reorder` restricts hit objects three ways:
- `OpTypePointer` to `OpTypeHitObjectEXT` must use the *Private* or *Function* storage class.
- `OpLoad` / `OpStore`: the pointer's *Type* operand must not be `OpTypeHitObjectEXT`.
- `OpCopyMemory`: same restriction on *Target* / *Source*.
What the extension never addresses is **composite containment**: nothing forbids `OpTypeHitObjectEXT` as an
operand of `OpTypeStruct` / `OpTypeArray` / `OpTypeRuntimeArray`. That omission makes the load/store/copy
prohibition trivially launderable: wrap the hit object in a one-member struct, and loading/storing the
*struct* is legal by the letter of the spec while moving the hit object it contains.
Both modules below assemble and pass `spirv-val --target-env vulkan1.3` (SPIRV-Tools v2026.3) with zero
diagnostics. The first shape is also accepted at `vkCreateShaderModule` and pipeline creation by the first
NVIDIA driver exposing `VK_EXT_ray_tracing_invocation_reorder` with the validation layers fully clean, and
then produces **`VK_ERROR_DEVICE_LOST`** executing a minimal record + invoke sequence. The identical shader
logic with the hit object as a plain `Function`-storage `OpVariable` runs correctly on the same driver.
This shape is not contrived: DXC emits exactly it whenever an inline-SPIR-V hit object handle
(`vk::SpirvOpaqueType`) is declared as a member of a user struct, e.g. any attempt to
wrap the handle in a convenience struct carrying methods.
## Repro 1: struct containment + access-chained SER op (what compilers emit)
```spvasm
OpCapability RayTracingKHR
OpCapability ShaderInvocationReorderEXT
OpExtension "SPV_KHR_ray_tracing"
OpExtension "SPV_EXT_shader_invocation_reorder"
OpMemoryModel Logical GLSL450
OpEntryPoint RayGenerationKHR %main "main"
%void = OpTypeVoid
%fnty = OpTypeFunction %void
%int = OpTypeInt 32 1
%int_0 = OpConstant %int 0
%ho = OpTypeHitObjectEXT
%wrap = OpTypeStruct %ho ; <- accepted today, nothing forbids it
%pwrap = OpTypePointer Function %wrap
%pho = OpTypePointer Function %ho
%main = OpFunction %void None %fnty
%entry = OpLabel
%var = OpVariable %pwrap Function
%member = OpAccessChain %pho %var %int_0
OpHitObjectRecordEmptyEXT %member
OpReturn
OpFunctionEnd
```
## Repro 2: the laundered copy the load/store rule was meant to forbid
```spvasm
OpCapability RayTracingKHR
OpCapability ShaderInvocationReorderEXT
OpExtension "SPV_KHR_ray_tracing"
OpExtension "SPV_EXT_shader_invocation_reorder"
OpMemoryModel Logical GLSL450
OpEntryPoint RayGenerationKHR %main "main"
%void = OpTypeVoid
%fnty = OpTypeFunction %void
%int = OpTypeInt 32 1
%int_0 = OpConstant %int 0
%ho = OpTypeHitObjectEXT
%wrap = OpTypeStruct %ho
%pwrap = OpTypePointer Function %wrap
%pho = OpTypePointer Function %ho
%main = OpFunction %void None %fnty
%entry = OpLabel
%var0 = OpVariable %pwrap Function
%var1 = OpVariable %pwrap Function
%member0 = OpAccessChain %pho %var0 %int_0
OpHitObjectRecordEmptyEXT %member0
%copied = OpLoad %wrap %var0 ; loads a composite CONTAINING a hit object
OpStore %var1 %copied ; ...and stores it: a hit object copy, laundered
%member1 = OpAccessChain %pho %var1 %int_0
OpHitObjectRecordEmptyEXT %member1
OpReturn
OpFunctionEnd
```
Verification of both:
```
spirv-as --target-env vulkan1.3 repro.spvasm -o repro.spv # exit 0
spirv-val --target-env vulkan1.3 repro.spv # exit 0, no diagnostics
```
## Ask
1. Spec: add an explicit constraint, e.g. *"OpTypeHitObjectEXT must not be used as an operand of
OpTypeStruct, OpTypeArray or OpTypeRuntimeArray"* (or equivalent "may only be used as the *Type* of an
OpVariable" wording), mirroring the intent of the existing pointer/load/store restrictions.
2. Validator: once worded, have spirv-val reject composite types containing `OpTypeHitObjectEXT`, and,
until then, consider flagging `OpLoad`/`OpStore` of composites that contain one, which is the
launderable hole repro 2 demonstrates.
The practical cost of the gap: a clean-validating module that GPU-faults is close to the worst debugging
experience available, everything green until the device disappears.
## Which side is the bug on?
We can't tell from outside, which is why this is filed here rather than as a driver bug. As written, the
module is valid, so one of two things is true:
- Composite containment was **intended to be legal**, then the driver must handle it, and the device loss
is a driver bug (which would have to be redirected).
- It was **intended to be forbidden**, the surrounding restrictions (Private/Function-only pointers,
no load/store/copy of hit objects) strongly suggest hit objects were meant to be non-aggregatable opaque
state, then the spec has a wording gap and the validator can't enforce the intent.
Either way, the current state, a module spirv-val accepts with zero diagnostics that then GPU-faults,
needs the spec to pick a side before anyone downstream can fix the right thing.
Contributor guide
No contributing guide indexed for this repository
Assessment
This issue has not been assessed yet.