Fast matrix operations for JavaScript runtimes, powered by AssemblyScript and WebAssembly SIMD.
WASMatrix gives you a small TypeScript API on top of a row-major Float32 WASM core. It is built for projects that want real linear algebra without turning every operation into a JavaScript heap round trip.
- WebAssembly SIMD is required.
- Browser, Node, Deno, and Bun are first-class targets.
- The npm package ships ESM JavaScript and WASM as separate files.
- Matrix buffers stay in WASM memory until you explicitly read them back.
- Lazy views, expression fusion, factorization caches, and structural shortcuts are enabled by default where they preserve normal semantics.
Repository: github.com/ihasq/wasmatrix
npm install wasmatrixWASMatrix is ESM-only. The published entrypoint uses top-level await to instantiate the adjacent WASM asset before exports are available:
import Matrix from "wasmatrix";
const a = Matrix.from(2, 2, [4, 7, 2, 6]);
const inverse = a.inverse();
console.log(a.determinant()); // 10
console.log(a.matmul(inverse).equalsApprox(Matrix.identity(2))); // trueThe package publishes:
dist/index.jsdist/index.d.tsdist/wasmatrix.wasm
dist/index.js loads the WASM file with:
new URL("./wasmatrix.wasm", import.meta.url)That shape is intentional. npm installs keep JavaScript and WASM as separate files, while CDNs and bundlers such as esm.sh can rewrite, host, or inline the WASM asset using their own pipeline.
Node local file: imports are handled internally. Browser, Deno, Bun, and CDN usage follow the normal fetchable asset path.
import Matrix, { configure, isSimdSupported } from "wasmatrix";
console.log(isSimdSupported()); // true when the loaded WASM validates
configure({
fastMath: false,
cacheLimitBytes: 64 * 1024 * 1024
});
const a = Matrix.from(2, 3, [
1, 2, 3,
4, 5, 6
]);
const row = Matrix.from(1, 3, [10, 20, 30]);
const b = a.add(row).scale(0.5).sqrt();
console.log(b.toArray()); // explicit readback from WASM memoryMost operations return another Matrix. The data stays in WASM memory until you ask for data, toFloat32Array(), toFlatArray(), toArray(), row(), column(), or diagonal().
Creation:
Matrix.from(rows, cols, values);
Matrix.zeros(rows, cols);
Matrix.ones(rows, cols);
Matrix.identity(size);
Matrix.diagonal(values);
Matrix.random(rows, cols);
Matrix.outer(a, b);Elementwise and scalar operations:
a.add(b);
a.subtract(b);
a.hadamard(b);
a.divide(b);
a.scale(2);
a.addScalar(1);
a.clamp(-1, 1);
a.sqrt();Matrix operations:
a.transpose();
a.matmul(b);
a.matvec(vector);
Matrix.matmulChain(a, b, c, d);Reductions and linear algebra:
a.sum();
a.minValue();
a.maxValue();
a.trace();
a.frobeniusNorm();
a.determinant();
a.logDet();
a.inverse();
a.solve(rhs);
a.leastSquares(rhs);
a.rank();Utilities:
a.at(row, col);
a.set(row, col, value);
a.reshape(rows, cols);
a.equalsApprox(other);
a.dispose();WASMatrix tries to remove avoidable work before it reaches the kernel layer.
Elementwise chains are fused into a single WASM pass where possible:
A.add(B).subtract(0.25).hadamard(C).clamp(-2, 2).sqrt();Scalar chains fold into affine forms:
A.scale(2).scale(3).addScalar(1); // one pass: 6 * x + 1Broadcast row and column vectors stay compact:
A.add(rowVector);
A.add(columnVector);
A.hadamard(rowVector);Linear algebra operations share factorization work:
const det = A.determinant();
const x = A.solve(B);
const inv = A.inverse();The same matrix can reuse LU, Cholesky, QR, transpose, packed GEMM operands, reductions, and materialized expression results as long as its version has not changed.
Some algebraic rewrites can change floating-point edge cases around NaN, Infinity, -0, or rounding. These remain conservative by default. More aggressive identity folding can be enabled explicitly:
configure({ fastMath: true });Matrix instances own WASM-side buffers. A JavaScript typed array is created only when you read data back.
const result = A.matmul(B).add(C).sqrt(); // stays in WASM/lazy form
const values = result.toFloat32Array(); // snapshot copied to JSUse dispose() when you know a matrix is no longer needed, especially in long-running pipelines. FinalizationRegistry is used when available, but explicit disposal gives tighter control over WASM heap pressure.
WASMatrix needs:
- ESM
- top-level await
- WebAssembly SIMD
Modern SIMD-capable browsers satisfy these requirements. Node support follows the package engines field. Deno and Bun work through the same ESM + WASM asset path.
When bundling, make sure your tool treats new URL("./wasmatrix.wasm", import.meta.url) as an asset reference. This is the format expected by modern bundlers and CDN transforms.
npm install
npm run build
npm test
npm run coverage
npm run test:e2e
npm run benchmarknpm run build compiles src/wasmatrix.ts with SIMD enabled, emits build/wasmatrix.wasm and build/wasmatrix.wat, compiles src/index.ts to dist/index.js, and copies the WASM binary to dist/wasmatrix.wasm.
npm test runs unit tests and E2E transparency tests. npm run coverage writes coverage/lcov.info; CI derives coverage/codecov.lcov.info from it for the Codecov badge. npm run benchmark runs the E2E benchmark suite and prints a JSON summary with timings, speedups, and checksums.
Benchmark sizes can be adjusted with environment variables such as WASMATRIX_BENCH_MATMUL_SIZE, WASMATRIX_BENCH_ELEMENT_ROWS, WASMATRIX_BENCH_ELEMENT_COLS, and WASMATRIX_BENCH_LINALG_SIZE.
WASMatrix is early software. The API is already useful for dense f32 workflows, but more structures and higher-level algebraic representations are still being explored. Issues and focused benchmarks are welcome on GitHub.