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