Skip to content

Reusing a kernel with Array then Float32Array never settles on the GL backends (argumentMismatch), but works on cpu #857

Description

@fuzzie360

Summary

Calling one kernel first with a plain Array and then with a Float32Array (or the reverse) throws on webgl/webgl2:

this kernel cannot run the arguments it was given (argumentMismatch); it did not
settle on a kernel for them after 4 attempts. Create a separate kernel for this
call's argument types.

The same code returns the correct answer on cpu, so the two backends disagree about whether this is legal.

This is adjacent to 5d1b70b ("reusing a kernel with a different argument type gives that argument's answer") but is a different failure: that one fixed a silently stale result, this one never converges and errors out. Erroring beats returning the previous call's framebuffer, so 2.20 is an improvement — but a plain array and a typed array holding the same numbers seem like they should be interchangeable.

Reproduction

gpu.js 2.20.0, Chrome 152 on macOS, WebGL2Kernel (ANGLE Metal, Apple M1 Max).

const gpu = new GPU({ mode });                       // 'gpu' or 'cpu'
const k = gpu.createKernel(function (a) {
  return a[this.thread.x] * 2;
}, { output: [4] });

k([1, 2, 3, 4]);                                     // [2, 4, 6, 8]
k(Float32Array.from([5, 6, 7, 8]));                  // expected [10, 12, 14, 16]
mode first call second call
cpu [2, 4, 6, 8] [10, 12, 14, 16]
gpu (WebGL2) [2, 4, 6, 8] throws argumentMismatch

Both orders fail (Float32Array first, then Array, fails identically).

Why it can't settle

The type layer and the value layer disagree, so the switch loop has nothing to converge on:

  • utils.getVariableType returns 'Array' for both, since utils.isArray is !isNaN(array.length) (src/utils.js:115, :165-169). The kernel's argument-type signature is therefore identical for the two calls.
  • But the GL kernel value rejects on constructor identity: if (value.constructor !== this.initialValueConstructor) { this.onUpdateValueMismatch(value.constructor); return; } (src/backend/web-gl/kernel-value/single-array.js:31, same in single-array1d-i.js:36 and siblings).
  • kernel-run-shortcut.js:22-35 then retries up to MAX_SWITCHES (4). Because the recorded type still says 'Array', each switched-to kernel is built with the same signature, its value again records initialValueConstructor = Array, rejects again, and requests another switch — hence "did not settle after 4 attempts".

cpu has no equivalent constructor check, which is why it just works.

Possible directions

  1. Tolerate array-likes in the value. updateValue immediately does utils.flattenTo(value, this.uploadValue), which already handles plain and typed arrays alike (src/utils.js:348), so for the numeric single-array values the constructor identity may not need to be load-bearing at all — the upload path never sees the original container.
  2. Make the switch produce a genuinely different kernel by including the container constructor in the argument-type signature, so the rebuilt kernel binds to the new constructor instead of re-deriving the old one.
  3. Fail fast with a clear message if interchangeability isn't intended — the current error costs four kernel rebuilds before surfacing, and doesn't say which argument or which two constructors disagreed.

Happy to send a PR if you have a preference between (1) and (2).

Impact

Found while writing GPGPU course material where a kernel is naturally called with a literal array first (k([1, 2, 3, 4])) and later with a Float32Array returned by a previous kernel — a very common shape when chaining passes. The workaround is to normalise every argument (Float32Array.from(...)) at every call site, which is easy once you know, and quite mysterious until then.

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