Skip to content

bug(spirv): Swizzle on local var as assignment lvalue fails with "expression ir.ExprSwizzle is not a pointer expression" #89

Description

@darkliquid

Summary

WGSL swizzle expressions on a local var used as an assignment target (lvalue) fail during SPIR-V generation. Reading a swizzle from a var (rvalue) works correctly. This is distinct from #45, which was about swizzles on uniform struct members and was fixed in v0.17.4.

Environment

  • naga version: v0.18.0 (github.com/gogpu/naga v0.18.0)
  • Go version: 1.26
  • Platform: Linux x86_64
  • Backend: SPIR-V (Vulkan)

Reproduction

Minimal failing shader

@fragment
fn fs_main() -> @location(0) vec4<f32> {
    var result = vec4<f32>(0.0, 0.0, 0.0, 0.0);
    let c = vec4<f32>(1.0, 0.5, 0.25, 0.8);
    result.rgb = c.rgb;
    return result;
}
$ nagac test.wgsl
Compilation error: SPIR-V generation error: SPIR-V generation error: expression ir.ExprSwizzle is not a pointer expression

Also fails with compound assignment

@fragment
fn fs_main() -> @location(0) vec4<f32> {
    var result = vec4<f32>(0.0, 0.0, 0.0, 0.0);
    let c = vec4<f32>(1.0, 0.5, 0.25, 0.8);
    result.rgb += c.rgb * c.a;
    return result;
}

Works correctly (rvalue swizzle on var)

@fragment
fn fs_main() -> @location(0) vec4<f32> {
    var result = vec4<f32>(0.0, 0.0, 0.0, 0.0);
    let x = result.rgb;   // reading .rgb from var works
    return vec4<f32>(x, 1.0);
}

Works correctly (uniform member swizzle — #45 fix)

struct U {
    v: vec4<f32>,
}
@group(0) @binding(0) var<uniform> u: U;
@fragment
fn fs_main() -> @location(0) vec4<f32> {
    return u.v.xyxy;
}

Expected behavior

All four shaders above should compile to valid SPIR-V. WGSL allows swizzle on any vector expression, including when used as an assignment target on a mutable local variable.

Actual behavior

When a swizzle appears on the left-hand side of an assignment (= or +=) targeting a local var, the SPIR-V backend panics with:

expression ir.ExprSwizzle is not a pointer expression

This suggests the SPIR-V codegen expects the assignment target to resolve to a pointer (for OpStore / OpAccessChain), but encounters an ExprSwizzle node that it doesn't know how to lower to a pointer expression. In SPIR-V, writing through a swizzle requires decomposing into per-component stores or using OpVectorShuffle + load/store patterns.

Workaround

Decompose swizzle assignments into scalar component operations:

// Instead of: result.rgb += c.rgb * c.a * (1.0 - result.a);
let oneMinusA = 1.0 - aa;
rr += c.r * c.a * oneMinusA;
gg += c.g * c.a * oneMinusA;
bb += c.b * c.a * oneMinusA;

Context

Found while implementing per-pixel linked-list OIT in ironwail-go (Quake engine port using gogpu). The resolve shader sorts and blends translucent fragments, accumulating results via result.rgb += ... which triggered this error.

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions