WebAssembly:Web
A universal bytecode for the web. From the 2015 W3C Community Group to Component Model 1.0 in 2025 — one .wasm binary runs the same code across browsers, edge, serverless, and plugin hosts. Figma, Photoshop, Cloudflare, and Shopify all stand on it.
all four browsers in months
V8 · SpiderMonkey · JSC · Chakra
2025 · typed module composition
no import → no syscall
What is WebAssembly
WebAssembly (wasm for short) is a portable low-level binary format — not a language, not a runtime, but a W3C-standardized bytecode plus a statically verifiable execution model. The same .wasm bytes run unchanged in browsers, edge nodes, serverless, and plugin hosts; validation guarantees it can do nothing at all without explicit imports.
A compact, verifiable binary format — not a source language, not an IR. Engines AOT-compile it on the spot.
Static validation plus linear-memory isolation. No imports → no capabilities at all. The web's strongest sandbox.
Rust / C / C++ / Go / Zig / Swift / Kotlin / .NET / Java / Dart / OCaml / Python — over a dozen mainstream languages all compile in.
Component Model + WIT lets wasm modules compose with typed interfaces. Modules finally behave like real software components.
// pure JS: interpreted, slow JIT warmup
export function add(
a, b
) {
// types unknown
// possible V8 deopt
return a + b;
}
// caller surface = anything JS can do(module
(func $add
(param i32 i32)
(result i32)
local.get 0
local.get 1
i32.add)
(export "add"
(func $add)))
;; static types · AOT-compiled · no imports = 0 capabilitiesHistory : Timeline
From Emscripten compiling C to a JS subset in 2011, through four browsers shipping 1.0 in the same year (2017), to Component Model stabilizing in 2025 — fifteen years from "fringe hack" to "first-class web citizen."
- 2011·08
Emscripten arrives
Mozilla's Alon Zakai releases Emscripten — an LLVM IR-to-JavaScript backend (the JS subset later named asm.js). This is the first engineered "compile C/C++ to run in the browser" pipeline, and the true origin point of WebAssembly.
- 2013
asm.js goes public
Mozilla publishes the asm.js spec — a typed subset of JS that engines can AOT-compile. SpiderMonkey hits "~50% of native" speed, proving "browser as a native-code platform" is viable. But V8 and WebKit find asm.js too ugly to commit to.
- 2015·04
W3C Community Group formed
April: the W3C WebAssembly Community Group is chartered. Engineers from all four browser engines (V8, SpiderMonkey, JSC, Chakra) sit at one table to design a binary format that all four can implement — no longer Mozilla pushing alone.
- 2015·06
All four vendors endorse
Brendan Eich blogs the public announcement: all four browsers are committing to WebAssembly, paired with simultaneous statements from Mozilla, Google, Microsoft, and Apple. The first time in Web-platform history that all four vendors agreed on a new format up front.
- 2017·03
1.0 MVP — all four ship in months
March: Firefox 52 ships wasm on by default. Within months Chrome 57, Safari 11, and Edge 16 all follow. Four engines shipping the same spec in the same year — unprecedented in Web history.
- 2018·03
WASI · Solomon Hykes's tweet
Mozilla's Lin Clark and Till Schneidereit announce WASI (WebAssembly System Interface) — extracting wasm from the browser to run on any host. The same day, Docker founder Solomon Hykes tweeted "If WASM+WASI existed in 2008 we wouldn't have needed to create Docker." That single tweet pulled wasm into server-side awareness for the first time.
- 2019·11
Bytecode Alliance founded
Mozilla, Fastly, Intel, and Red Hat jointly form the Bytecode Alliance — a non-profit hosting
wasmtime,wasm-tools, and WASI. Wasm gains its first institutionally-neutral home, instead of being only a W3C spec with every vendor reinventing tooling. - 2019·12
W3C Recommendation
December 5: WebAssembly Core Spec 1.0 is published as a W3C Recommendation, putting it on par with HTML, CSS, and the DOM. Exactly 4.5 years from the 2015 CG charter.
- 2021
wasmtime + Compute@Edge GA
Bytecode Alliance's
wasmtime1.0 stabilizes; Fastly Compute@Edge reaches GA — the first mainstream edge platform to expose wasm as the only customer-facing runtime. "Run user code at the edge" now has wasm as the default answer. - 2022
SIMD / bulk-mem / refs land everywhere
This year SIMD 128, bulk memory, reference types, and multi-value all hit stable in every mainstream engine. WASI Preview 1 is declared stable the same year — wasm's first real "you can write a serious business system" surface.
- 2023
Component Model design lands
The long-debated Component Model (composing wasm modules via typed WIT interfaces) reaches its first implementable design. The same year, Shopify Functions moves checkout and discount extensions entirely to wasm — user-authored wasm now runs on e-commerce critical paths.
- 2024
GC proposal ships
V8 and SpiderMonkey both ship WasmGC — wasm gets native GC types for the first time. Overnight Kotlin/Wasm, Dart/Wasm, OCaml/Wasm, and the Java teleport-VM shift from proof-of-concept to production-credible. GC languages no longer need to bundle a full runtime into the bytecode.
- 2024·12
WASI Preview 2 + Component Model
December: WASI Preview 2 ships, built on the Component Model — every system interface (IO, files, HTTP, sockets, clocks) is now described in WIT. For the first time wasm modules talk to each other and to the host through a real typed interface standard.
- 2025
Component Model 1.0 stabilizes
Component Model 1.0 goes stable. Figma, Photoshop Web, and Google Earth (web) are all running entirely on wasm; Cloudflare Workers publicly reports 3M+ wasm applications on its platform. This year wasm stops being "the future" — it's underneath nearly every web app you open.
- 2026·05
WASI Preview 3 · async + io
First half of 2026: WASI Preview 3 is in flight — pulling async and structured IO directly into the spec layer (Promise-like, cancellation, timeouts standardized). At the same time, Component Model 2.0 drafts begin: wasm modules will be published, versioned, and composed as real typed software components.
Essentials : WasmAlphabet
The whole wasm platform stands on these eight primitives — module, linear memory, table, imports, validation, numeric types, WAT, and the Component Model. Simple enough to read in one evening; every one of them carries all the way to production.
Module / Instance
A .wasm binary is a module — it describes code and types only, holding no memory. An instance is module + memory + tables + imports, alive. One module can be instantiated many times cheaply.
(module
(func $add
(param i32 i32)
(result i32)
local.get 0
local.get 1
i32.add)
(export "add"
(func $add)))
;; the entire WAT for an `add` exportLinear memory
Each instance holds a single contiguous byte array, grown in 64 KB pages — host and wasm both index this raw memory directly. (memory 1 256) declares 1 page initial, 256 pages max (16 MB).
(memory 1 256)
(export "mem" (memory 0))
;; JS side:
const u8 = new Uint8Array(
instance.exports.mem.buffer
);Table + reference types
Function pointers don't live in memory — they live in a table, indexed by typed handles. funcref / externref are wasm's own reference types, and host objects cross into wasm through them.
(table 2 funcref)
(elem (i32.const 0)
$add $sub)
(func (param i32)
local.get 0
(call_indirect (type $bin)))Imports / Exports
A wasm module knows nothing about the world. To call a host function (console.log, fetch, file IO) it must declare an import; the host only sees what's in the export table. The glue is one line of WebAssembly.instantiate.
const { instance } =
await WebAssembly.instantiate(
bytes,
{ env: { log: console.log } } );
instance.exports.add(2, 3);
// → 5Validation / sandbox
Every .wasm is statically validated before it runs: stack types proven, branch targets in range, memory access bounded to its own linear memory. No imports means no syscalls. The strongest sandbox the web has ever had.
// what wasm cannot do without imports:
// - read a file
// - open a socket
// - access process / env / clock
// - touch any memory but its own
// every dangerous capability must
// be handed in as an import — explicit.Numeric types only
Core wasm has only i32 / i64 / f32 / f64 (plus SIMD's v128). No strings, no structs — every high-level type is "an agreed memory layout between you and the host." The GC proposal is changing that, but the default is still this.
;; pass a string from JS:
;; — write UTF-8 bytes into wasm memory
;; — pass (ptr, len) as two i32 params
(func $greet
(param $ptr i32)
(param $len i32)
;; read from memory[ptr..ptr+len]
) WAT text format
An S-expression format that maps 1:1 to the binary — every wasm module has an equivalent readable WAT. The wabt toolchain: wat2wasm text→binary, wasm2wat the other way. Debugging wasm starts in WAT.
$ wat2wasm hello.wat -o hello.wasm
$ wasm2wat hello.wasm
(module
(func (export "hi")
(result i32)
i32.const 42))Component Model + WIT
Use WIT (WebAssembly Interface Types) to describe inter-module contracts as typed interfaces — string, list, record, variant, option, result are all defined. From WASI Preview 2 onward, host interfaces are also WIT-described. Wasm finally behaves like a real software component.
// greet.wit
package cuberoot:demo;
interface greet {
hello: func(
name: string
) -> string;
}Less is more
Core wasm has only four numeric types plus linear memory, tables, and functions. You can read the full core spec in one evening. Yet it carries the whole modern web — Figma's design canvas, Photoshop's engine, Cloudflare's 3M+ workers all run on these eight primitives.
"WebAssembly isn't another fast runtime. It's a binary contract that anyone can verify." — Lin Clark, Mozilla
Why Wasm : WhyWasm
Wasm is not "faster JS" or "Java applets in the browser." It is the first time the web has had a language-neutral, capability-controlled, native-workload-capable bytecode. The nine cards below are what wasm has actually won.
One .wasm, every platform
Browsers, edge runtimes, serverless, plugin hosts, database extensions — the same binary, byte-for-byte, runs everywhere. The original wasm promise, and the one it actually delivers.
// same .wasm in:
// chrome / firefox / safari
// wasmtime / wasmer / wasm3
// cloudflare / fastly / shopifyThe strongest web sandbox
No filesystem, no network, no syscalls, no out-of-bounds memory — unless the host explicitly imports them. The first time the web platform makes "run a stranger's binary" the safe default.
// no imports → no I/O at all
WebAssembly.instantiate(bytes, {});
// pure, deterministic computeNear-native speed
The binary is itself a statically validated low-level format, so engines AOT-compile it directly. V8 uses a Liftoff / TurboFan tiering pair; wasmtime uses Cranelift. Typical workloads land at 90%+ of native.
// V8 path:
// liftoff (~ms compile, ~2x slower)
// turbofan (slow compile, ~native)Genuine polyglot substrate
Rust, C, C++, Go, Zig, Swift, Kotlin, .NET, Java, Dart, OCaml, Python (Pyodide) — over a dozen mainstream languages compile to wasm. The thickest shared substrate for language interop the industry has ever had.
// rust: cargo build --target wasm32-...
// c/c++: emcc / clang --target=wasm32
// go: GOOS=js GOARCH=wasm go buildStreaming compilation
The browser starts compiling on the first byte of the .wasm stream — download and compile happen in parallel. One line of WebAssembly.instantiateStreaming(fetch(...)) consumes a fetch response directly; no arrayBuffer() staging.
await WebAssembly.instantiateStreaming(
fetch("app.wasm"),
imports
);Tiny binaries
A Rust hello-world wasm comes in around 3 KB after wasm-opt -Oz — smaller than the minified-JS equivalent. Cold-start latency measured in milliseconds.
$ wasm-opt -Oz app.wasm -o app.opt.wasm
$ ls -la app.opt.wasm
// 3.1 KBThe natural plugin substrate
Shopify Functions, Figma plugins, Envoy filters, eBPF-style extensions, Postgres extensions, database UDFs — anywhere you need "run user code, do not trust it," wasm is the answer.
// Shopify Function · Rust → wasm
fn discount(cart: Cart) -> Discount { ... }One spec, many runtimes
V8, SpiderMonkey, JSC, wasmtime, wasmer, wasm3, WAMR, WasmEdge — they all read the same .wasm. No language ecosystem has this many independent, interchangeable implementations.
// pick the runtime that fits:
// wasm3 — interpreter, ~64 KB
// wasmtime — full JIT + WASI
// browsers — same binaryDesigned by four vendors together
WebAssembly isn't a single-vendor "we open-sourced it for you" project. It's been co-designed in the open by four browser engines + W3C since 2015. No single owner can unilaterally break it — the root of wasm's long-term trustworthiness.
// W3C WG members shipping wasm:
// Google (V8) · Mozilla (SM)
// Apple (JSC) · MicrosoftWho's Using : Production
By 2025, wasm runs underneath nearly every web app you open. Figma's design canvas, Photoshop's engine, Google Earth's C++ client, AutoCAD's production CAD — and this site's /scramble/solver on cs0x7f's cubeopt.
Wasm inside cuberoot.me : In our stack
Telling the wasm story in the abstract is boring. Look at the receipts: this site's /scramble/solver and /scramble/analyzer both run real wasm — and we've already stepped on every wasm pitfall: SharedArrayBuffer, COOP+COEP, multi-module global pollution, the iOS Safari memory ceiling. The notes below are real.
"WebAssembly gave the web two things that used to be mutually exclusive: the ability to run native workloads, and a provable capability boundary against the host. Before wasm, running someone else's code always meant choosing between performance and safety.
Only /scramble/solver and /scramble/analyzer run wasm; the other 24 cards stay completely clean. COOP/COEP headers are emitted only when an nginx map $request_uri regex matches — the login callback /me is untouched.
/scramble/analyzer loads four emscripten modules in a row inside the same worker: crossSolver, pseudoCrossSolver, EOCrossSolver, F2L_PairingSolver. Each one monkey-patches the globals Module / HEAPU8 / wasmExports / _malloc, so the worker has to delete them between calls or state leaks between modules.
iOS Safari caps wasm linear memory at roughly 1 GB — exceed it and the page OOM-crashes. Mobile UA defaults to cube48opt1 (the smallest prune table); desktop gets cube48opt2 or full cubeopt. No vendor doc says this — only post-mortem patches do.
cubeopt-wasm
The engine behind /scramble/solver is cs0x7f's (author of csTimer) cubeopt-wasm — a 3x3 optimal solver written in C, compiled to wasm, driven by public/cubeopt/wasm-worker.js in a worker. Multithreaded; requires SharedArrayBuffer.
- SAB needs cross-origin isolated context
- COOP same-origin · main-frame isolation
- COEP require-corp · refuses cross-origin assets
- map $request_uri only emits headers on solver/analyzer
The real engineering hurdle isn't wasm itself — it's that only two cards on the whole site should enter cross-origin isolated context; everything else has to stay unaffected. Solution: nginx uses map $request_uri ~ ^/scramble/(solver|analyzer) to gate the COOP+COEP emissions, and the service worker no longer injects those headers.
# nginx · COOP/COEP only for solver/analyzer
map $request_uri $coop {
~^/scramble/(solver|analyzer)
"same-origin";
default "";
}
map $request_uri $coep {
~^/scramble/(solver|analyzer)
"require-corp";
default "";
}
add_header Cross-Origin-Opener-Policy $coop;
add_header Cross-Origin-Embedder-Policy $coep;
# /me · login callback · untouchedWasm in production today
analyzer · four modules, one worker
/scramble/analyzer loads four emscripten-built wasm modules in a row inside the same worker — cross / pseudo-cross / EOCross / F2L-Pairing. This is one of the most common wasm engineering pitfalls: each module ships with emscripten's default glue that hangs Module / HEAPU8 / wasmExports / _malloc on the global object. The second module to load simply overwrites the first.
The fix is unglamorous but effective: the worker manually deletes those globals around every call, so each module enters from a clean slate. An implicit "module unload" convention keeps them from cross-contaminating. The Component Model is meant to solve exactly this — but emscripten glue hasn't fully adopted it yet.
That's the truth about wasm engineering: the spec is beautiful, the glue is a mess, and unification is happening but is not yet done.
// analyzer worker · global cleanup
async function callModule(
url: string,
scramble: string,
) {
delete (self as any).Module;
delete (self as any).HEAPU8;
delete (self as any).wasmExports;
delete (self as any)._malloc;
importScripts(url);
await (self as any).Module();
return (self as any).solve(scramble);
}
// // the cost of emscripten's default glueIn one line: WebAssembly isn't the web's "next generation" — it's the web's current generation. Figma, Photoshop, Cloudflare, and this site's solver all run on it. The rest is just cleaning up the toolchain, the Component Model, and the glue.
vs JS / Native : Wasm vs JS vs Native
Three ways to run code on the web, three worldviews. JS bets on dynamism + ubiquity; native paths (asm.js, NaCl, plugins) bet on raw performance; wasm bets on multi-vendor + statically verifiable — one binary runs everywhere with a provable capability boundary.
| JavaScript | Native (asm.js / NaCl) | WebAssembly | |
|---|---|---|---|
| Format | Source text · dynamic | JS subset / proprietary plugin | Binary · statically verifiable |
| Parse speed | Text parse + type inference | Text parse (asm.js) | Binary + streaming |
| Startup latency | JIT warmup · slow hot path | Native · but requires install | Milliseconds · AOT |
| Execution speed | ~50% native (best case) | 100% | 80–95% native |
| Sandbox | Same-origin + full DOM | NaCl SFI / asm.js = JS | Validation + linear mem + no-import-no-cap |
| Vendor support | All four · but dialect drift | NaCl Chrome-only · deprecated | All four since spec-design phase |
| Portability | Browser + Node + edge | Platform-bound · repackage | Browser + edge + plugin · same bytes |
| Source languages | JS / TS (still JS) | C/C++ via emscripten | Rust/C/C++/Go/Zig/Swift/.NET/... (12+) |
| GC support | Native | Bundle your own runtime | WasmGC (2024) |
| Componentization | ES modules | — | Component Model 1.0 (2025) |
| Install needed | No | NaCl / plugin yes | No |
| Tooling UX | Best-in-class · 30 yrs | C toolchain | wabt / wasm-tools / wasm-opt · mature |
Outlook : TheNext9Years
From the 2017 MVP to Component Model 1.0 in 2025: nine years from four numeric types to a typed component platform. The next nine years' agenda is already drawn — async lands in the spec, GC languages move in for real, edge consolidates, on-device AI ships.
Component Model 1.0 + WASI Preview 3
2025 stabilized Component Model 1.0; the first half of 2026 brings WASI Preview 3 — async and structured IO (Promise-like, cancellation, timeouts) landing in the spec itself. Wasm modules stop being "snippets of code" and become real software components with typed signatures.
The 1.0 MVP in 2017 had only four numeric types plus linear memory. Nine years later, wasm has string / list / record / variant / option / result, plus async and IO. From bytecode to a component platform, and not one of the nine years was wasted.
GC languages move in
2024: V8 and SpiderMonkey both ship WasmGC, giving wasm native GC types. Kotlin, Dart, OCaml, and the Java teleport-VM can now compile to wasm without bundling their own GC. The pivot moment for dynamic and functional languages on wasm.
Edge consolidates
Cloudflare, Fastly, Akamai, Vercel — every major edge platform has picked wasm as the unit of multi-tenant compute isolation. One v8 isolate runs thousands of customers' wasm at millisecond cold-start with rock-solid isolation. The race is over; wasm won.
Plugin ecosystems explode
Shopify Functions (checkout logic), Figma plugins (design automation), Envoy filters (traffic rewriting), Postgres extensions, Hugging Face Transformers plugins, browser extensions — every "run user-authored code on my platform" need is converging on wasm.
On-device AI inference
Transformers.js, llama.cpp wasm, Whisper wasm — LLM and speech models compiled to wasm to run in-browser and on mobile, no server required. SIMD + threads + GPU (via WebGPU) finally make on-device AI a real production path.
Toolchain comes of age
wabt (wat2wasm / wasm2wat / wasm-objdump), wasm-tools (component CLI), wasm-opt (binaryen optimizer), cargo-component, wit-bindgen — debugging wasm now feels like debugging a native binary. Ten years ago it was printf.