Skip to content

GL backends: do-while continue skips the test; assigning to a scalar argument is a GLSL compile error #867

Closed

Description

@fuzzie360

Follow-up to #865, surfaced by its cross-backend regression tests (test/issues/865-cpu-control-flow.js). The cpu fixes on feature/webasm-backend exposed that the GL backends fail two of the same three shapes:

1. do-while continue skips the test. GLSL ES 1.00 has no do-while, so the backends emulate it as for (int safeI = 0; safeI < LOOP_MAX; safeI++) { body; if (!(test)) break; } — a continue in the body jumps past the trailing test check, exactly the mechanism the cpu backend had (#865). The do/while accumulator repro returns 61 on headlessgl where plain JS, cpu (fixed) and webasm give 48. The #300 loop normalization already contains the right transform (continue gains a copy of the exit check — see normalizeLoopHeader's do-while handling in src/backend/web-gl/function-node.js), but it only engages when the FXC pattern gates fire; the fix is to apply that continue rewrite to every emulated do-while, not only hoist-affected ones.

2. Assigning to a scalar argument fails to compile. Scalar arguments are GLSL uniforms; base = base + this.thread.x emits an assignment to a uniform and the shader is rejected: 'assign' : l-value required "user_base" (can't modify a uniform). Loud rather than silent, but the same legal-JavaScript shape now works on cpu and webasm (per-cell shadow binding); the GL fix is the same idea in GLSL — declare float user_base_cell = user_base; at kernel start and route references through it, exactly as the cpu backend now does with its user_X$cell shadows.

The corresponding rows in test/issues/865-cpu-control-flow.js are scoped to cpu+webasm with comments pointing here; widening them to all MODES is the acceptance test for this issue.

Activity

  1. fuzzie360 commented on Aug 2, 2026

    @fuzzie360
    MemberAuthor

    Fixed on feature/webasm-backend (PR #866, e9fb1f3), alongside the #865 cpu fixes that surfaced it.

    do-while continue: each loop-level continue in an emulated do-while now gains a copy of the exit check in front of it — the transform normalizeLoopHeader already applied to hoist-affected loops, now applied to every do-while. It runs pre-trace (from normalizeBlock) so the cloned test nodes are traced like the originals; continues belonging to nested loops keep theirs.

    scalar-argument assignment: assigned scalar arguments get a per-invocation shadow local declared at kernel start (float cellShadow_user_base=user_base;) with references routed through it. The cellShadow_ namespace cannot collide with user identifiers because those all emit with a user_ prefix — sanitizeName only replaces the first $/__ occurrence in a name, so a user_-suffix scheme like the cpu backend's $cell would not have been collision-proof in GLSL.

    The two rows in test/issues/865-cpu-control-flow.js scoped to cpu+webasm now run on all backends: both fail on the unfixed emitter (do-while accumulator 61 vs 48; can't modify a uniform) and pass fixed, on headlessgl and in a headed browser on webgl/webgl2. Full gates: Node suite 2699/0, browser 4077/0, SwiftShader failure set unchanged.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions