Skip to content

CPU backend disagrees with plain JavaScript on three control-flow shapes #865

Closed

Description

@fuzzie360

Found by adversarial review while verifying the WebAssembly backend (feature/webasm-backend), which matches plain JS on all three where cpu does not. All three are pre-existing on develop and unrelated to that branch.

1. Early return inside a loop returns the post-loop value for every cell.

gpu.createKernel(function () {
  for (let i = 0; i < 20; i++) {
    if (i * i > this.thread.x) { return i * 100 + this.thread.x; }
  }
  return -1;
}, { output: [6] });
// cpu:      [-1, -1, -1, -1, -1, -1]
// plain JS: [100, 201, 202, 203, 304, 305]   (webasm agrees)

The translated loop keeps running after the return.

2. Do-while with continue does not jump to the test correctly — a do/while accumulating with if (i % 3 === 0) continue; returns 56 on cpu where plain JS gives 45.

3. Assignment to a scalar argument leaks across cells — base = base + this.thread.x; return base; with base = 10 returns [10, 11, 13, 16, ...] on cpu (the mutation accumulates across the cell loop) where per-cell JS, the GL backends and webasm give [10, 11, 12, 13].

The webasm suite pins these shapes against per-cell plain-JS references (test/features/webasm/control-flow.js) precisely because the cpu backend cannot serve as ground truth for them. Fixing cpu should add the same three shapes to a cpu regression file.

Activity

  1. fuzzie360 commented on Aug 2, 2026

    @fuzzie360
    MemberAuthor

    Fixed on feature/webasm-backend (PR #866) in 11b4c0e — the PR that discovered these now also fixes them, since its webasm suite supplied ready-made plain-JS-referenced fixtures.

    All three mechanisms as diagnosed: the early-return continue targeting the user's loop (now a kernelBody: labeled block with break kernelBody; from any depth), the for-wrapped do-while skipping its trailing test on continue (now a native do-while with the loopMaxIterations cap riding in the condition), and run-wide argument bindings (now per-cell user_X$cell shadows routed through both the identifier and member-expression emitters, so reassigned array arguments read correctly too).

    test/issues/865-cpu-control-flow.js pins the shapes against plain-JS references across backends; 5 of 10 runnable assertions fail with the fix reverted. Suites: Node 2697/0, browser 4071/0, SwiftShader baseline identical.

    One discovery from the cross-backend rows: the GL backends fail the do-while and assigned-argument shapes by their own mechanisms (skipped trailing test in the do-while emulation; can't modify a uniform) — filed as #867, with the scoped test rows as its acceptance test.

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