Using a WASM Library
Imports dot and norm_sq from mylinalg and computes cosine similarity. Run after saving the "WASM Library: SIMD Linalg" example as mylinalg.
/#
## Using a WASM library
This file is the **consumer half** of the wasm-library demo. It imports
two `wasm function`s — `dot` and `norm_sq` — from a sibling module
called `mylinalg` (which you should have saved in the playground from
the "WASM Library: SIMD Linalg" example).
The import is plain ESM. The library was authored as TJS source with
`export wasm function ...` declarations; the playground's loader
resolves `mylinalg` to the saved module and the wasm bootstrap runs
when this consumer loads.
### Caveat: composition isn't wired into the playground yet
In the integration tests, TJS pulls imported `wasm function`s into the
**consumer's own** `WebAssembly.Module` at transpile time (Phase 3
composition) — wasm-to-wasm calls cost single-digit nanoseconds and
there's no JS↔wasm boundary on the inner loop. This is the perf
optimization documented in `wasm-library-plan.md`.
The playground itself doesn't yet pass a `ModuleLoader` to the
transpiler, so cross-file imports are resolved at *runtime* via the
local-module store. Each call from the consumer into `dot`/`norm_sq`
still crosses the JS↔wasm boundary (the "boundary form" from Phase 4
of the plan). Correctness is identical; only the per-call cost differs.
Wiring the playground's `tjs()` invocation with a `ModuleLoader` is
tracked in `TODO.md` under "Playground - Module Management."
#/
import { dot, norm_sq } from 'mylinalg'
// Cosine similarity between two f32 vectors of length `n`.
// `n` must be a multiple of 4. (No type annotations on this thin wrapper —
// the wasm functions it calls already enforce types at the boundary.)
function cosine(a, b, n) {
const d = dot(a, b, n)
const ma = norm_sq(a, n)
const mb = norm_sq(b, n)
if (ma <= 0 || mb <= 0) return 0
return d / Math.sqrt(ma * mb)
}
// Build a small corpus: 4 vectors of dim 8
const DIM = 8
const corpus = [
// identical to query → cosine 1
[1, 2, 3, 4, 5, 6, 7, 8],
// orthogonal-ish → low cosine
[-8, 7, -6, 5, -4, 3, -2, 1],
// anti-parallel → cosine -1
[-1, -2, -3, -4, -5, -6, -7, -8],
// partially-aligned → moderate cosine
[2, 1, 4, 3, 6, 5, 8, 7],
]
const query = [1, 2, 3, 4, 5, 6, 7, 8]
// Use wasmBuffer for zero-copy memory sharing with wasm
const queryBuf = wasmBuffer(Float32Array, DIM)
for (let i = 0; i < DIM; i++) queryBuf[i] = query[i]
console.log('Cosine similarity of query against each row:')
for (let i = 0; i < corpus.length; i++) {
const rowBuf = wasmBuffer(Float32Array, DIM)
for (let j = 0; j < DIM; j++) rowBuf[j] = corpus[i][j]
const sim = cosine(queryBuf, rowBuf, DIM)
console.log(` row ${i}: ${sim.toFixed(4)}`)
}
// Expected output:
// row 0: 1.0000 (identical)
// row 1: 0.1111 (near-orthogonal)
// row 2: -1.0000 (anti-parallel)
// row 3: 0.9706 (mostly-aligned)