Skip to content

wasm — Roadmap

Where the WAT emitter sits today and what is planned. The order below is priority, not commitment.

Grow the kernel catalogue

Thirteen kernels ship today (matchlen, hex, hex_decode, popcount, toupper, memchr, isascii, utf8len, json_clean, adler32, base64_encode, indexany4, base64_decode) covering byte-compare, hex encode/decode, bit-count, ASCII case-fold, byte-search, ASCII / UTF-8 preflight, JSON-string preflight, checksum, base64 encode/decode, and multi-needle byte-search. Still on the wishlist:

  • ~~base64 encode~~ — done. Lemire's SSE algorithm ported to wasm-SIMD; PMULHUW emulated via i32x4.extmul_low/high_i16x8_u + i32x4.shr_u + i16x8.narrow_i32x4_u. See Kernels.
  • ~~adler32~~ — done via i16x8.extmul_low/high_i8x16_u for the weighted-byte sum. See Kernels.
  • utf-8 full validation (Lemire & Keiser) — the classic wasm-SIMD showcase. utf8len already handles rune-counting for known-valid input; the full-validation kernel would use v128.const LUTs and i8x16.swizzle to classify byte-triplets and reject any invalid sequence.
  • ~~base64 decode~~ — done. Byte-level shift+shuffle+OR approach with a 5-range char→index step; no new emit ops. See the Kernels page.
  • crc32 — checksum families used everywhere in zlib-family formats. Wasm-SIMD lacks PCLMULQDQ, so a table-based approach is more tractable than the carry-less-multiply route.
  • ~~bytes.IndexAny~~ — done as indexany4, a 4-needle fixed-size variant. See the Kernels page.

Adding a new kernel is a main.go in a new subdirectory plus a main_test.go with the golden-file pattern; adding a new op is one line in emit.go. Nothing structural changes.

~~Fold into the main asmgen module~~ — done

The wasm surface used to ship as a stand-alone go-asmgen/wasm module. As of go-asmgen/asmgen@v0.6.0 it lives inside the main asmgen module as a peer of amd64 / arm64 / …:

  • wasm/ — the emit surface.
  • examples/wasm/ — one subdirectory per shipped kernel, each with the go:generate line and the committed .wat.golden.
  • A wasm-e2e job in the top-level ci.yml regenerates every kernel, drift-gates against the committed golden, wat2wasm-compiles, and runs the wazero verifier — one job for all nine kernels because wasm-SIMD is arch-agnostic.

A future extension to the shared abi package would lower Go slice headers (base, len, cap) into wasm (i32, i32, i32) param triples for //go:wasmimport consumers automatically. Not blocking anything today — every shipped kernel hand-writes the ABI at its call sites.

Grow the consumer ecosystem

Each shipped kernel deserves a go-simd/*-wasm consumer alongside the existing go-simd/matchlen-wasm:

  • go-simd/hex-wasm
  • go-simd/popcount-wasm
  • go-simd/toupper-wasm
  • go-simd/memchr-wasm

Every consumer follows the same shape as matchlen-wasm: a //go:generate directive pinning the emitter version, wasm-drift.yml for the drift gate, wasm-bench.yml for the regression gate, and a small Go wrapper that fronts the //go:wasmimport.

Non-goals

  • wasm-binary encoding. WAT text plus wat2wasm is enough; the module will not grow a binary encoder.
  • Non-SIMD wasm. The point of the module is v128; a scalar-only wasm target is already served by Go's stock wasm output.
  • Runtime selection. The kernel signatures target the SIMD proposal unconditionally. Consumers that need a scalar fallback should ship one in Go alongside the wasmimport call — that's a consumer concern, not an emitter concern.