Three related normalization holes in the SPIR-V backend. In each, the backing-type op is correct but the result isn't masked back into the strange-int range, so high bits leak into anything that later widens the value.
- airArithOp: route the result of integer ops through
cg.normalize so wrapping +% / -% / *% on strange ints stays in range. The .integer arm of normalize is a no-op, so this is free for normal ints.
- airReduce: rewrite the fold loop using
Temporary + buildBinary (matching the rest of the file) and normalize each cumulative step for strange-int Add/Mul. Other reductions skip it.
- airIntCast: in the same-backing-bits branch, normalize when narrowing (e.g.
@truncate(u3, u8)). Widening still returns the source unchanged.
Effect
Each row is a small kernel that takes its input from a storage buffer and writes @intCast(u32, result) back. With strange ints stored in a u8 backing type, the buggy path returns the unmasked u8; the fix returns the value mod 8.
| Kernel |
Operation |
Inputs |
Buggy out |
Fixed out |
| wrap |
a +% b with a, b: u3 |
a=5, b=6 |
11 |
(5+6) & 7 = 3 |
| reduce |
@reduce(.Add, v) with v: @Vector(4,u3) |
(5,6,5,6) |
22 |
22 & 7 = 6 |
| cast |
@truncate(u3, w) with w: u8 |
w=255 |
255 |
255 & 7 = 7 |
spirv-val accepts both shapes: these are correctness bugs, not validation bugs.
Three related normalization holes in the SPIR-V backend. In each, the backing-type op is correct but the result isn't masked back into the strange-int range, so high bits leak into anything that later widens the value.
- **airArithOp**: route the result of integer ops through `cg.normalize` so wrapping `+%` / `-%` / `*%` on strange ints stays in range. The `.integer` arm of normalize is a no-op, so this is free for normal ints.
- **airReduce**: rewrite the fold loop using `Temporary` + `buildBinary` (matching the rest of the file) and normalize each cumulative step for strange-int Add/Mul. Other reductions skip it.
- **airIntCast**: in the same-backing-bits branch, normalize when narrowing (e.g. `@truncate(u3, u8)`). Widening still returns the source unchanged.
## Effect
Each row is a small kernel that takes its input from a storage buffer and writes `@intCast(u32, result)` back. With strange ints stored in a `u8` backing type, the buggy path returns the unmasked u8; the fix returns the value mod 8.
| Kernel | Operation | Inputs | Buggy `out` | Fixed `out` |
|--------|----------------------------------------|-----------|-------------|---------------|
| wrap | `a +% b` with `a, b: u3` | a=5, b=6 | 11 | (5+6) & 7 = 3 |
| reduce | `@reduce(.Add, v)` with `v: @Vector(4,u3)` | (5,6,5,6) | 22 | 22 & 7 = 6 |
| cast | `@truncate(u3, w)` with `w: u8` | w=255 | 255 | 255 & 7 = 7 |
spirv-val accepts both shapes: these are correctness bugs, not validation bugs.