# CLAUDE.md Tips for future agents working in this repo. ## Architecture ``` src/mgpu/compile.ts MATLAB source -> numbl IR: parseMFile + lowerProgram (whole-script lowering, NOT turing-surface's per-function specialization), numbl's inlinePass, then fuse.ts. src/mgpu/patches.ts Type-rule patches applied to numbl's JIT builtin registry (registerBuiltin overwrites by name): precise shapes for comparisons, tensor &/|/~, two-arg max/min, matrix rand, and randn registered from scratch. Only types — numbl's emitters never run here. src/mgpu/fuse.ts Folds single-use _mtoc2_* temps numbl's inline pass declines (tensor-producing Calls like sin(x), logicals), so one source line = one kernel. src/mgpu/wgsl.ts Fused elementwise emitter -> one WGSL kernel per Assign. Also: inline generators (rand/randn/linspace/ranges/eye computed from the linear index), loop-var uniforms, runtime scalars as 1-element buffers, exact-value folding at every node. src/mgpu/kernels.ts Non-elementwise WGSL: tiled column-major GEMM, tiled transpose, 2-pass full reduction + per-column reduction, all with shapes baked in as constants. src/mgpu/plan.ts IR statements -> static op sequence: buffers, pipelines (cached by WGSL text), bind groups, aliasing (X(:) etc. as views when the source has exactly one assignment), scratch+copy-back for in-place updates, `for` bodies planned once with per-iteration dynamic-offset uniform slots (256 B each), host ops (tic/toc/disp/fprintf/echo). src/mgpu/run.ts Executor: streams ops into command encoders; tic/toc flush + await onSubmittedWorkDone (that's what makes toc MATLAB-comparable); readbacks for printing. test/cases.ts One suite, two harnesses: scripts/test-node.ts (Dawn via the `webgpu` npm package) and test/test-page.ts + scripts/test-gpu.mjs (headless Chrome, SwiftShader fallback). ``` ## Key gotchas - **`resolve.preserveSymlinks: true` in vite.config.ts is load-bearing.** numbl is a `file:` symlink; without it the dev server canonicalizes the symlink for some import chains but not others, loading numbl's builtin registry TWICE — patches.ts then patches one instance while the lowerer consults the other ("JS-JIT 'rand' supports only the scalar form"), and raw-realpath requests trip the fs allow list. - **The registry patch is per-module-instance** — anything that would run numbl's own engine in this page (a CPU-comparison feature was removed in 2026-08) must live in a separate worker so it never sees patched builtins. - **Everything is f32 and column-major.** `A(:)`, `reshape` and vector transpose are buffer views (or plain copies when the source is reassigned); matrix transpose is a real kernel. - **Aliased writes need scratch+copy-back** (`u = u + 1`, GEMM with output aliasing an input): WebGPU forbids binding one buffer as both read-only and read-write in a bind group. - **`toc` as a bare statement lowers to a Call named `toc_print`**, not `toc`. String literals carry a `Char` type kind, not Numeric. - **Loop bodies may not contain host ops** (tic/toc/disp/fprintf/echoes) — they're compiled once and replayed; the planner declines with a message. - **rand inside loops**: generator kernels bind every enclosing loop's iteration counter and mix it into the hash, or every iteration would draw identical values. Don't "simplify" that away. - numbl HEAD is pinned in both workflows (`NUMBL_REF`); the compiler surface this repo relies on is declared in src/mgpu/numbl.d.ts, so a numbl change breaks the build here with a type diff. CI needs no numbl build — the imported compiler slice is self-contained TypeScript. ## Testing - `npm run test:node` — 17-case correctness suite on desktop Dawn (real GPU on this machine). References computed in f64 in the test file; tolerances are f32-scale, loosened to 3e-4 where SwiftShader's transcendentals lag. - `npm run test:gpu` — same suite, headless Chrome. - `npx vite-node scripts/bench-probe.ts` — timing sanity (fused loop ~1 kernel/iteration, GEMM GFLOP/s printout). - `npx vite-node scripts/time-examples.ts` — runs every examples/*.m on Dawn with per-tic..toc timings. The examples are sized to take ~1-4 s each on this machine's Intel iGPU so tic/toc comparisons aren't noise; keep them in that range when editing.