# CLAUDE.md Tips for future agents working in this repo. ## Architecture ``` wasm/matmul.c naive + blocked/SIMD (f64x2) kernels, plus a pthreads matmul_blocked_mt (rows split across threads, -DMATMUL_MT). wasm/build-wasm.sh (emsdk) builds it twice: - st ES module -> wasm/dist/matmul.{js,wasm} - threaded -> public/matmul/matmul_mt.{js,wasm} blis/matmul_blis.c thin wrapper over libFLAME's dgemm_ (BLIS-backed); built st + mt (pthreads) by blis/build-blis-wasm.sh, linking the prebuilt .a libraries from the sibling libflame2wasm checkout (../../../libflame2wasm), into public/matmul/matmul_blis_{st,mt}.{js,wasm} (committed) native/bench_native.c same style dgemm benchmark, linked against OpenBLAS, run standalone outside the browser src/methods/ one file per method, all implementing the MatmulMethod interface in types.ts (id, precision, available(), run?()) src/methods/benchWorker.ts + workerClient.ts JS and both non-threaded custom WASM kernels run inside a Vite module Web Worker — naive JS/WASM at n=2048 take ~10s and would freeze the tab on the main thread. WebGPU runs on the main thread (async). The worker regenerates inputs from (n, seed) rather than receiving them over postMessage. src/methods/threadedClient.ts + public/matmul/worker.js ALL pthread-capable modules (threaded custom C, BLIS st + mt) run in ONE shared classic worker (not a Vite module worker) — threaded builds spawn pthread workers from it (nested workers), and importScripts of the emscripten glue is the proven path (mirrors libflame2wasm's bench_worker.js). worker.js has a kind->module REGISTRY; every module exports a (a,b,c,n,nthreads) function (st builds ignore nthreads). Served verbatim from public/, out of Vite's module graph. src/components/ BenchmarkRunner (orchestrates runs, thread selector) + ResultsTable src/data/nativeReference.ts hardcoded numbers from native/bench_native, hand-copied in — no runtime fetch public/coi-serviceworker.js vendored (gzuidhof, MIT) — adds COOP/COEP so SharedArrayBuffer works on GitHub Pages (no header control) ``` ## Key gotchas - **WASM .wasm URL resolution.** `wasm/dist/matmul.js` (emscripten glue) is imported directly; its sibling `matmul.wasm` is resolved via Vite's `?url` import and passed through `locateFile` — same pattern qhull-wasm-demo uses for `qhull-wasm/dist/qhull.wasm`. Don't rely on emscripten's default `import.meta.url`-relative lookup working under Vite's bundler. - **Two threaded builds, one classic worker.** The threaded custom kernel (`matmul_mt`, from `wasm/matmul.c` with `-pthread -DMATMUL_MT`) and the threaded BLIS build (`matmul_blis_mt`) both use pthreads/SharedArrayBuffer and both dispatch through `public/matmul/worker.js`. The non-threaded custom kernels (`matmul.c` st build) have no `-pthread` and run in the Vite module worker instead. - **Cross-origin isolation for threaded methods.** `matmul_mt` and `matmul_blis_mt` need `crossOriginIsolated === true` (COOP: same-origin, COEP: require-corp). Vite dev/preview set these headers (see vite.config.ts). GitHub Pages can't set headers, so `public/coi-serviceworker.js` installs them via a service worker (page reloads once on first visit to gain control). Their `available()` gates on `crossOriginIsolated`; without it the cell = n/a. - **What's committed vs CI-built under public/matmul/.** The BLIS modules (`matmul_blis_{st,mt}.*`) are committed — `blis/build-blis-wasm.sh` links the ~30 MB of prebuilt `.a` files in `../../../libflame2wasm`, which aren't in this repo and can't be rebuilt in CI. The threaded custom module (`matmul_mt.*`) is gitignored and CI-built (only needs `matmul.c` + emsdk, like `wasm/dist`). `worker.js` is a committed source file. Rebuild BLIS locally after changing `matmul_blis.c` or the upstream libs. - **Row-major via operand swap.** dgemm_ is column-major; `matmul_blis` calls it with operands swapped — `dgemm_(B, A)` computes row-major C = A*B in the same flat buffer — so BLIS `c[0]` matches the other (row-major) methods. - **Deterministic seeded inputs, not transferred arrays.** Every method for a given `n` multiplies bit-identical A/B (from `generateMatrix(n, seed)`); the worker regenerates them from the same seed rather than receiving the arrays over `postMessage`, since structured-cloning multi-megabyte `Float64Array`s per run would be slower than regenerating. - Rebuilding `wasm/dist/*` requires emsdk on PATH or at `~/emsdk` (`wasm/build-wasm.sh` sources `~/emsdk/emsdk_env.sh` if `emcc` isn't found). CI installs it via `mymindstorm/setup-emsdk`. ## Testing - `npm run dev`, click through each method's run button at a small size (n=128) first and check the cross-check panel — the f64 methods (JS, the three WASM kernels, both BLIS builds) should agree to ~1e-9, WebGPU (f32) should be close but not identical. The "cross-origin isolated ✓" chip must be green for the threaded methods to be available. - `wasm/build-wasm.sh && npm run build` to verify the CI path locally (build-wasm.sh emits both the st module and the threaded matmul_mt). - `blis/build-blis-wasm.sh` rebuilds the committed BLIS artifacts (needs the sibling libflame2wasm checkout + emsdk). - `native/build.sh && native/bench_native` regenerates the values pasted into `src/data/nativeReference.ts` (no automated round-trip — hand-copy after running).