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.
36src/cpu/cpuRunner.ts Optional CPU column via createNumblSession
37 (numbl/browser) — numbl's own worker, f64, lazy-loaded
38 because it's a ~3 MB chunk.
39test/cases.ts One suite, two harnesses: scripts/test-node.ts (Dawn via
40 the `webgpu` npm package) and test/test-page.ts +
41 scripts/test-gpu.mjs (headless Chrome, SwiftShader
42 fallback).
43```
45## Key gotchas
47- **`resolve.preserveSymlinks: true` in vite.config.ts is load-bearing.**
48 numbl is a `file:` symlink; without it the dev server canonicalizes the
49 symlink for some import chains but not others, loading numbl's builtin
50 registry TWICE — patches.ts then patches one instance while the lowerer
51 consults the other ("JS-JIT 'rand' supports only the scalar form"), and
52 raw-realpath requests trip the fs allow list.
53- **The registry patch is per-module-instance.** The CPU runner is safe from
54 it because numbl/browser runs in its own worker; don't move patching
55 somewhere the CPU path could share.
56- **Everything is f32 and column-major.** `A(:)`, `reshape` and vector
57 transpose are buffer views (or plain copies when the source is reassigned);
58 matrix transpose is a real kernel.
59- **Aliased writes need scratch+copy-back** (`u = u + 1`, GEMM with output
60 aliasing an input): WebGPU forbids binding one buffer as both read-only and
61 read-write in a bind group.
62- **`toc` as a bare statement lowers to a Call named `toc_print`**, not `toc`.
63 String literals carry a `Char` type kind, not Numeric.
64- **Loop bodies may not contain host ops** (tic/toc/disp/fprintf/echoes) —
65 they're compiled once and replayed; the planner declines with a message.
66- **rand inside loops**: generator kernels bind every enclosing loop's
67 iteration counter and mix it into the hash, or every iteration would draw
68 identical values. Don't "simplify" that away.
69- numbl HEAD is pinned in both workflows (`NUMBL_REF`); the compiler surface
70 this repo relies on is declared in src/mgpu/numbl.d.ts, so a numbl change
71 breaks the build here with a type diff. CI builds numbl's dist-browser
72 (`npm run build:browser`) because it is not committed.
74## Testing
76- `npm run test:node` — 17-case correctness suite on desktop Dawn (real GPU
77 on this machine). References computed in f64 in the test file; tolerances
78 are f32-scale, loosened to 3e-4 where SwiftShader's transcendentals lag.
79- `npm run test:gpu` — same suite, headless Chrome.
80- `npx vite-node scripts/bench-probe.ts` — timing sanity (fused loop ~1
81 kernel/iteration, GEMM GFLOP/s printout).
82- `npx vite-node scripts/time-examples.ts` — runs every examples/*.m on Dawn
83 with per-tic..toc timings. The examples are sized to take ~1-4 s each on
84 this machine's Intel iGPU so tic/toc comparisons aren't noise; keep them in
85 that range when editing.