Skip to content

fix(msl): writeSwizzle missing parenthesization for binary expressions #83

Description

@kolkov

Summary

writeSwizzle in MSL backend is missing needsParensInContext check, producing invalid MSL when a swizzle is applied to a binary expression result.

Generated MSL (incorrect)

metal::float3 world_normal = _e18 * metal::float4(in.normal, 0.0).xyz;

C++ operator precedence: . (precedence 2) binds tighter than * (precedence 5), so Metal compiler parses this as mat4x4 * float3 = type mismatch.

Expected MSL (correct)

metal::float3 world_normal = (_e18 * metal::float4(in.normal, 0.0)).xyz;

WGSL source (valid)

let world_normal = (object.normal_model * vec4<f32>(in.normal, 0.0)).xyz;

The WGSL is correct — parentheses are present. The naga MSL codegen drops them.

Root cause

expressions.go:1098writeSwizzle calls writeExpression(swizzle.Vector) without needsParensInContext check.

Compare with writeAccess at line 886 which correctly has:

needParens := w.needsParensInContext(access.Base)
if needParens {
    w.write("(")
}

Fix

Add the same needsParensInContext guard to writeSwizzle:

func (w *Writer) writeSwizzle(swizzle ir.ExprSwizzle) error {
    needsUnpack := w.isPackedVec3Access(swizzle.Vector)
    // ...existing unpack logic...

    needParens := w.needsParensInContext(swizzle.Vector)  // ADD
    if needParens {                                        // ADD
        w.write("(")                                      // ADD
    }                                                     // ADD

    if err := w.writeExpression(swizzle.Vector); err != nil {
        return err
    }

    if needParens {                                        // ADD
        w.write(")")                                      // ADD
    }                                                     // ADD
    // ...rest of swizzle...
}

Reproduction

Any WGSL shader with (mat4 * vec4).xyz pattern. g3d's standard.wgsl triggers this:

let world_normal = (object.normal_model * vec4<f32>(in.normal, 0.0)).xyz;

Reported by user on Apple M5 (macOS 26.5.2) but reproducible on any macOS version — the generated MSL is syntactically invalid regardless of Metal compiler version.

Impact

  • g3d standard.wgsl fails on Metal backend (g3d#7)
  • basic.wgsl works (no swizzle on binary expression)
  • All non-MSL backends work (SPIR-V, GLSL, HLSL, DXIL)

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions