KhronosGroup / KhronosGroup/SPIRV-Registry

SPV_EXT_shader_invocation_reorder: OpTypeHitObjectEXT inside a composite validates, then crashes the GPU

Open
#445 4 comments 0 reactions 1 assignee Claimed by @dgkoch View on GitHub
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.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.