// 2015 — 2026 · W3C Community Group · Bytecode Alliance

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.

20171.0 MVP shipped
all four browsers in months
4engines on day-one
V8 · SpiderMonkey · JSC · Chakra
CM 1.0Component Model stable
2025 · typed module composition
0undeclared capabilities
no import → no syscall
modulememorytablei32funcimportexportWATwasm-packwasisimd128component
scroll
01

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.

Bytecode binary

A compact, verifiable binary format — not a source language, not an IR. Engines AOT-compile it on the spot.

Sandbox no-syscall

Static validation plus linear-memory isolation. No imports → no capabilities at all. The web's strongest sandbox.

Polyglot 12+ langs

Rust / C / C++ / Go / Zig / Swift / Kotlin / .NET / Java / Dart / OCaml / Python — over a dozen mainstream languages all compile in.

Composable components

Component Model + WIT lets wasm modules compose with typed interfaces. Modules finally behave like real software components.

add.jsJS
// 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
add.watWAT
(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 capabilities
02

History : 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."

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. 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.

  6. 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.

  7. 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.

  8. 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.

  9. 2021

    wasmtime + Compute@Edge GA

    Bytecode Alliance's wasmtime 1.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.

  10. 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.

  11. 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.

  12. 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.

  13. 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.

  14. 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.

  15. 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.

03

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.

A

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` export
B

Linear 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
);
C

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)))
D

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);
// → 5
E

Validation / 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.
F

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]
) 
G

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))
H

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

04

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 / shopify

The 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 compute

Near-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 build

Streaming 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 KB

The 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 binary

Designed 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) · Microsoft
05

Who'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.

06

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.

— Lin ClarkWebAssembly / WASI co-designer · paraphrased from talks
2
Pages that run wasm

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.

4
wasm modules per worker

/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.

1 GB
iOS Safari wasm memory ceiling

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.

SPOTLIGHT

cubeopt-wasm cs0x7f's multithreaded 3x3 optimal solver

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 · untouched

Wasm in production today

Figma
C++ design canvas · all-wasm in-browser
Photoshop Web
Full Adobe port · 2021
Google Earth
C++ client compiled to wasm
AutoCAD Web
Autodesk · production CAD
1Password
wasm core shared cross-platform
Cloudflare
Edge · 3M+ Workers apps
Fastly
wasmtime in production edge
Shopify
Checkout extensions · wasm
Disney+
Video client pipeline
Adobe Express
wasm imaging core
WasmEdge
CNCF sandbox runtime
Spin (Fermyon)
wasm microservices
Envoy
wasm filter ABI
Pyodide
Full CPython in wasm
Blazor
.NET → wasm in browser
cubeopt-wasm
Engine behind /scramble/solver
SPOTLIGHT 2

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 glue

In 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.

07

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.

JavaScriptNative (asm.js / NaCl)WebAssembly
FormatSource text · dynamicJS subset / proprietary pluginBinary · statically verifiable
Parse speedText parse + type inferenceText parse (asm.js)Binary + streaming
Startup latencyJIT warmup · slow hot pathNative · but requires installMilliseconds · AOT
Execution speed~50% native (best case)100%80–95% native
SandboxSame-origin + full DOMNaCl SFI / asm.js = JSValidation + linear mem + no-import-no-cap
Vendor supportAll four · but dialect driftNaCl Chrome-only · deprecatedAll four since spec-design phase
PortabilityBrowser + Node + edgePlatform-bound · repackageBrowser + edge + plugin · same bytes
Source languagesJS / TS (still JS)C/C++ via emscriptenRust/C/C++/Go/Zig/Swift/.NET/... (12+)
GC supportNativeBundle your own runtimeWasmGC (2024)
ComponentizationES modulesComponent Model 1.0 (2025)
Install neededNoNaCl / plugin yesNo
Tooling UXBest-in-class · 30 yrsC toolchainwabt / wasm-tools / wasm-opt · mature
08

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.

HOT · 2026

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.

MVP 1.0 → Component 1.09 yrs
Browser ship-day → today9 yrs
GC

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

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.

PLUGINS

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.

AI

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.

TOOLING

Toolchain comes of age

wabt (wat2wasm / wasm2wat / wasm-objdump), wasm-tools (component CLI), wasm-opt (binaryen optimizer), cargo-component, wit-bindgendebugging wasm now feels like debugging a native binary. Ten years ago it was printf.