KhronosGroup / KhronosGroup/SPIRV-LLVM-Translator
Reverse translation of OpCompositeConstruct with a vector constituent produces invalid insertelement
- Dominant language
- LLVM
- Stars
- 625
- Forks
- 279
- Avg merge
- 3d 5h
- Merged PRs (30d)
- 34
Description
## Summary
`llvm-spirv -r` translates an `OpCompositeConstruct` whose result is a vector and one of whose constituents is a **smaller vector** (e.g. a `vec4` built from a `vec3` plus a scalar) into an invalid `insertelement` that inserts the whole sub-vector as a single scalar element. The resulting module fails the LLVM verifier:
```
Invalid insertelement operands!
%6 = insertelement <4 x i32> poison, <3 x i32> %5, i32 0
```
Per the SPIR-V spec, an `OpCompositeConstruct` result-vector constituent that is itself a vector contributes **all of its components, in order**. So `OpCompositeConstruct %v4uint %v3 %scalar` should yield a 4-component vector from the three components of `%v3` followed by `%scalar`. The reverse translator instead treats the `<3 x i32>` constituent as if it were a single scalar element of the `<4 x i32>`.
## Reproducer
Image-independent, minimal (`repro.spvasm`, SPIR-V 1.0):
```
OpCapability Addresses
OpCapability Kernel
OpMemoryModel Physical64 OpenCL
OpEntryPoint Kernel %k "k"
%void = OpTypeVoid
%uint = OpTypeInt 32 0
%v3uint = OpTypeVector %uint 3
%v4uint = OpTypeVector %uint 4
%ptr_g = OpTypePointer CrossWorkgroup %v4uint
%fnty = OpTypeFunction %void %ptr_g %uint
%uint_0 = OpConstant %uint 0
%k = OpFunction %void None %fnty
%out = OpFunctionParameter %ptr_g
%n = OpFunctionParameter %uint
%entry = OpLabel
%rt3 = OpCompositeConstruct %v3uint %n %n %n
%v4 = OpCompositeConstruct %v4uint %rt3 %uint_0
OpStore %out %v4 Aligned 16
OpReturn
OpFunctionEnd
```
```console
$ spirv-as repro.spvasm -o repro.spv # SPIRV-Tools v2026.1; spirv-val repro.spv passes
$ llvm-spirv -r repro.spv -o repro.bc
Fails to verify module: Invalid insertelement operands!
%6 = insertelement <4 x i32> poison, <3 x i32> %5, i32 0
```
The input passes `spirv-val`; the failure is purely in the reverse translation. (The `poison` base of the `insertelement` is normal LLVM vector-build idiom; the defect is the `<3 x i32>` value operand where a scalar `i32` is required.)
## Versions tested
Reproduces on `llvm-spirv` **14.0.6, 18.1.8, 19.1.7, 20.1.8, 21.1.8, and 22.1.2** (the newest release available). 17.x segfaults on this input for an apparently unrelated reason.
## Note
`OpVectorShuffle` widening of the same vec3 to vec4 (`OpVectorShuffle %v4uint %rt3 %zero_v3 0 1 2 3`) translates without error on all the versions above — this is the form Clang emits for `(uint4)(v3, 0)`, which is presumably why the `OpCompositeConstruct` form has stayed latent for OpenCL C. It is reachable from non-Clang SPIR-V producers ([rust-gpu](https://github.com/Rust-GPU/rust-gpu) in my case).
The runtime-vector-constituent test added in #2296 (`test/composite_construct_vector_non_constant.spvasm`) constructs a vector from scalar constituents; it does not include a case with a (smaller) vector constituent.
## Expected
`OpCompositeConstruct` with a vector constituent should lower to a `shufflevector` (or a per-component `insertelement` sequence that spreads the sub-vector's components), matching the "contributes all its components in order" semantics.
Contributor guide
Research direction
Start by running the provided repro.spvasm through spirv-as, spirv-val, and llvm-spirv -r, then read test/composite_construct_vector_non_constant.spvasm. Extend coverage for a smaller vector constituent in OpCompositeConstruct and verify that reverse translation produces valid LLVM IR, using shufflevector or per-component insertion as the expected behavior.
Written by the indexing model from the issue text.
Assessment
- Domain
- compilers
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 74/100