Skip to content
Published on

WebAssembly and WASI Deep Dive — Bytecode, Sandbox, Component Model, Wasmtime, Edge Runtimes

Authors

Intro — "Smaller than Docker, faster than the JVM"

In 2019 Docker co-creator Solomon Hykes tweeted:

"If WASM+WASI existed in 2008, we wouldn't have needed to create Docker."

Controversial, but the point stuck. Wasm is not just a browser technology. In 2025, Cloudflare Workers, Fastly Compute@Edge, Shopify Functions, Figma plugins, and even SpinKube on top of Kubernetes all run Wasm as the engine.

This article covers:

  • The birth of WebAssembly — from asm.js to Wasm 1.0
  • Bytecode structure and the stack-VM execution model
  • The mathematical guarantees of the Wasm Sandbox
  • WASI — a system interface for Wasm outside the browser
  • Wasmtime / Wasmer / WasmEdge runtime comparison
  • The Component Model — the secret of language neutrality
  • Why Cloudflare Workers supports both Isolates and Wasm
  • Kubernetes + Wasm — runwasi and SpinKube
  • Real-world pitfalls and the future

1. Origins — what asm.js left behind

JavaScript's unlucky legacy

In the early 2010s the browser was effectively "an OS that only runs JavaScript." But JS has:

  • Dynamic types forcing the JIT to recheck assumptions constantly
  • GC pauses
  • Poor fit for high-performance numeric code

asm.js (Mozilla, 2013)

The answer was asm.js — a strict subset of JavaScript.

function add(x, y) {
  x = x|0;  // coerce to int with bitwise OR
  y = y|0;
  return (x + y)|0;
}

With hints like |0, the browser could compile to machine code ahead of time. Emscripten translated C/C++ to asm.js, and Unreal Engine actually ran in the browser.

But asm.js had ceilings: parsing cost (still text), zero readability, and an "informal" spec.

Wasm 1.0 (2017, W3C standard)

Four browser vendors (Mozilla, Google, Microsoft, Apple) co-designed a real bytecode. W3C recommendation in 2019. Name: WebAssembly.

  • Binary format (fast to parse)
  • Static typing for safety
  • Stack-based virtual machine
  • One linear memory plus a separate call stack

2. Anatomy of a Wasm module

Minimal example

(module
  (func $add (param $a i32) (param $b i32) (result i32)
    local.get $a
    local.get $b
    i32.add
  )
  (export "add" (func $add))
)

Written in WAT (WebAssembly Text format). Compiled it becomes a binary .wasm. Host calls add(3, 5).

Module sections

A .wasm file is a sequence of sections.

[0] Custom      - metadata (names, debug info)
[1] Type        - function signatures
[2] Import      - external bindings
[3] Function    - function to type mapping
[4] Table       - function pointer table (indirect calls)
[5] Memory      - linear memory definition
[6] Global      - globals
[7] Export      - externally visible items
[8] Start       - module init function
[9] Element     - table initializers
[10] Code       - function bodies (bytecode)
[11] Data       - memory initializers

Linear memory

A Wasm program sees one big contiguous byte array as its memory. 64KB pages. Initial and maximum sizes declared.

(memory 1 10)  ;; min 1 page (64KB), max 10 pages

What C/C++/Rust call a "pointer" is just an i32 offset into this array. The program manages allocation itself (malloc/free are also compiled to Wasm).

Crucially, Wasm cannot touch memory outside that array — not the host's, not another module's. That is the first line of the sandbox.

Stack machine

Wasm is a stack-based VM.

local.get $a    ;; push a
local.get $b    ;; push b
i32.add         ;; pop two, push sum

Stack instructions are short to encode and leave plenty of room for the runtime's JIT/AOT compiler to optimize.


3. Mathematical guarantees of the sandbox

Why Wasm is safe

Isolation layers:

  1. Memory isolation — no access outside linear memory, including host memory and other modules
  2. Control-flow integrity — function pointers only through tables; no arbitrary jumps
  3. No syscalls by default — only imported host functions can be called
  4. Determinism — same input yields same output (NaN bits normalized, etc.)

Capability-based security

Containers default to "all permissions on, turn off the risky ones" (deny-list). Wasm is the opposite: nothing is allowed unless a capability is explicitly passed in (allow-list).

Example: to read a file, a Wasm module must be given fd_read as an import, and only the file descriptors the host opened are reachable. Path traversal (../etc/passwd) is bounded by which directory the host rooted.

Containers vs Wasm

AspectContainerWasm
IsolationLinux namespace/cgroupVM plus type safety
Default permsMany (deny-list)None (allow-list)
Escape surfaceKernel CVEs (e.g. Dirty COW)Formally verified spec
SyscallsHundredsOnly imported ones
Startup100ms to secondsUnder 1ms

Fastly reports that its Wasm sandbox escapes, when they appear, typically look like "Wasmtime bug causes OOM," not cross-module compromise.


4. WASI — leaving the browser

The problem

Early Wasm leaned on browser APIs (fetch, DOM). There was no standard way to read a file or open a socket.

WASI 0.1 (2019) — POSIX-ish

Led by Lin Clark at Mozilla. A POSIX-looking system interface for Wasm.

wasi_snapshot_preview1
  - fd_read / fd_write
  - fd_seek
  - path_open
  - clock_time_get
  - random_get
  - args_get / environ_get

File descriptors are capabilities: only what the host opened is accessible.

WASI 0.2 (2024) — on the Component Model

Limits of 0.1: synchronous POSIX only, no standard for HTTP/sockets, C-style interfaces.

0.2 is rebuilt on the Component Model.

  • wasi:http — request and response
  • wasi:sockets — TCP and UDP
  • wasi:io/streams — async streams
  • wasi:cli — stdio for CLI apps

Language-neutral interface definitions via WIT.

WIT example

package wasi:http@0.2.0;

interface types {
  variant method {
    get,
    post,
    put,
    delete,
    other(string),
  }
  record request {
    method: method,
    uri: string,
    headers: list<tuple<string, string>>,
    body: option<body>,
  }
}

Bindings can be generated for Rust, Go, JavaScript, Python — a cross-language standard API.


5. Component Model — the key to language neutrality

Why modules are not enough

Wasm 1.0 modules only exchange numbers and bytes. Strings, arrays, and structs must be coordinated as linear memory offsets, and every language does it differently — interop is a nightmare.

Example: Rust to JavaScript string passing. Rust uses UTF-8 ptr+len; JS uses UTF-16. Glue was hand-written.

Component Model (stabilized 2024)

A higher unit called a component sits on top of Wasm. Between components:

  • High-level types (string, record, variant, list, option, result)
  • Automatic ABI shimming
  • Imports/exports declared in WIT

Benefits

  1. Plug a component built in language X into one built in language Y
  2. Versioned WIT interfaces with semver
  3. Registries like warg.io for component packages

Real scenario: a Rust image component, imported by a Go HTTP handler, orchestrated by a JavaScript component — "npm + Docker + gRPC rolled into one."


6. The three major runtimes

Wasmtime (Bytecode Alliance)

  • Engine: Cranelift (from the Rust compiler project)
  • Language: Rust
  • The reference implementation of WASI
  • Top-tier stability and security audits
  • wasmtime run module.wasm just works
  • Fastly Compute runs on it

Wasmer

  • Language: Rust
  • Pluggable compilers: Singlepass (fast JIT), Cranelift, LLVM (top throughput)
  • wapm package manager
  • Embedding APIs for C, Go, Python, etc.
  • Wasmer Edge cloud offering

WasmEdge (CNCF Sandbox)

  • Language: C++
  • AI workloads focus — tensor ops, ONNX
  • Active in Docker + Wasm (runwasi)
  • Strong embedded/IoT support

Performance (2025 Bytecode Alliance bench)

RuntimeStartupThroughputMemory
Wasmtime (Cranelift)0.3ms100%baseline
Wasmer (LLVM)2s compile / 0.1ms re-run115%1.3x
WasmEdge0.5ms95%0.8x

Pick by need: standards conformance — Wasmtime; peak throughput — Wasmer LLVM; small memory plus AI — WasmEdge.


7. Cloudflare Workers — Isolates first, Wasm too

Common misconception

"Cloudflare Workers = Wasm" is wrong. Actually:

  • The base is V8 Isolates — JavaScript isolation primitives
  • About 3MB memory, 5ms cold start per isolate
  • Wasm runs inside the isolate

Why Isolate plus Wasm

  • Keep npm and the JS ecosystem
  • Use Wasm for perf-critical paths (e.g. Cloudflare Pages image conversion in Rust to Wasm)

Fastly Compute@Edge — all-in on Wasm

  • One new Wasm instance per request
  • Languages: Rust, TinyGo, JS, AssemblyScript
  • Cold start around 35 microseconds

Sub-millisecond cold starts come from reusing AOT-compiled Wasm artifacts and the lightweight sandbox.


8. Wasm on Kubernetes

runwasi

A containerd shim that runs Wasm packaged as OCI images, so it fits into existing Kubernetes clusters.

apiVersion: v1
kind: Pod
spec:
  runtimeClassName: wasmtime
  containers:
  - image: ghcr.io/example/hello-wasm:latest
    name: hello

SpinKube (Fermyon, 2024)

Spin is Fermyon's Wasm framework; SpinKube exposes it through Kubernetes CRDs.

Pros: pods ready in milliseconds, far smaller memory than Deno/Node, serverless that feels "always on." Cons: debugging tools immature, not every library builds to Wasm, WASI 0.2 native support still transitional.

KWasm (Liquid Reply)

Installs a Wasm runtime on nodes via DaemonSet. Upgrades existing clusters to Wasm-capable.


9. Languages on Wasm

Rust — gold standard

  • cargo build --target wasm32-wasi
  • wasm-bindgen for browser JS FFI
  • wit-bindgen for the Component Model
  • Much of the crates ecosystem supports Wasm

Go — TinyGo saves the day

  • Standard Go has a big runtime (GC, goroutines)
  • Since Go 1.21: GOOS=wasip1 GOARCH=wasm official WASI
  • Binaries still multiple MB
  • TinyGo (LLVM-based) outputs tens of KB but drops some stdlib

JavaScript/TypeScript — AssemblyScript

  • TypeScript syntax compiling to Wasm
  • Gentle learning curve; full JS ecosystem unavailable

Python — Pyodide, py2wasm

  • CPython compiled to Wasm (10MB+)
  • Heavy for serverless, great for teaching and viz

C/C++ — Emscripten, wasi-sdk

  • Supported since the asm.js era
  • Unity/Unreal have Wasm builds

10. Performance — "80% of native," truly?

Theory

Wasm bytecode is JIT/AOT-compiled to machine code. For the same C source, Native (gcc) vs Wasm (clang to wasm plus Wasmtime AOT) usually lands at 80 to 95 percent of native.

Sources of overhead

  1. Bounds checks — modern runtimes use virtual memory tricks (mmap + guard pages) to get near zero cost
  2. SIMD — 128-bit Wasm SIMD standardized; parts of Rust std::simd map to it
  3. Atomics + threads — shared memory and atomics via web workers
  4. GC proposal — Wasm GC without relying on host GC, enabling TypeScript/Kotlin/Java

Reality — FFI is the bottleneck

Benchmarks where "Wasm beats native" or "Wasm loses to native" usually come down to FFI frequency. Calling host functions often costs boundary transitions. Mitigations: do more work inside Wasm, batch calls, share memory.


11. Who ships Wasm today

  • Figma — QuickJS plus Wasm for plugins, exposing limited capabilities
  • Shopify Functions — third-party checkout logic in Rust to Wasm
  • Adobe Photoshop Web — C++ Photoshop kernel built with Emscripten
  • 1Password — browser-extension crypto in Rust to Wasm
  • Envoy Proxy — WASM filters for dynamic extension (Istio's default)

12. Traps and limits

Threading

  • Still message-passing first (web workers)
  • Many runtimes lack reentrant execution
  • Falls short of goroutines / async Rust ergonomics

Networking

  • WASI 0.2 has wasi:sockets
  • Browser remains fetch-only (CORS/Same-Origin)
  • Server Wasm library support varies

Filesystem consistency

  • Capability-based fds are safe but porting path-based code is painful
  • No POSIX fork/exec (intentional)

Debugging

  • DWARF improving, but gdb/lldb still behind native
  • Chrome DevTools Wasm support exists but source maps have limits

Binary size

  • Rust hello world: about 200KB (wasm32-wasi)
  • TinyGo: about 50KB
  • AssemblyScript: about 10KB
  • CPython: about 10MB

Edge cold starts want tens of KB; even Rust's panic chain bloats the binary.


13. Security considerations

  1. Wasm itself is safe, but imports are risky — safety of the bindings matters
  2. Denial of service — use Fuel or Epoch interrupts for loops, memory limits, stack limits
  3. Side channels — Spectre-style timing attacks still possible; runtimes round timers
  4. Supply chain — verify signatures in component registries (warg), validate WIT compatibility

14. Where things are going

Near-term standards: WASI 0.3 (richer async/streams), exception handling, stack switching (coroutines/async), GC proposal (already in Chrome 119+/Firefox 120+), relaxed SIMD.

Trends:

  1. Serverless-native workloads migrate to Wasm
  2. Plugin systems rewriting on Wasm (Kafka Connect, VSCode extensions)
  3. Edge AI via WasmEdge + ONNX
  4. Wasm workloads replacing parts of pods
  5. Same Wasm module running in the browser and on the server

Limits: the JavaScript ecosystem's gravity, the mass of POSIX code bases, and the 2 to 3 years still needed for debugging and observability maturity.


15. Twelve-item practical checklist

  1. Adopt only for clear cases — plugins, serverless, high-perf clients
  2. Optimize binary size — --release, wasm-opt -Os, strip
  3. Target WASI 0.2 Component Model for forward compatibility
  4. Use Wasmtime as the reference runtime
  5. Minimum-capability principle — only open the fds you need
  6. Cap runtime via Fuel / Epoch against DoS
  7. Chain wasm-opt — size reductions of 30%+
  8. Pick the language by team skill — Rust is most mature, TinyGo is Go-friendly
  9. Hot path in Wasm, cold path in JS or host — hybrid by design
  10. Observability — surface logs/metrics from the host
  11. Memory limits and OOM handling wrapped by the host
  12. CI across multiple runtimes — Wasmtime, Wasmer, WasmEdge

Next post preview

If Wasm is the engine of "serverless without servers," CDNs and edge networks are the fabric that delivers it worldwide. Next up: the birth of CDNs, anycast routing, tiered caches, edge compute options, cache invalidation, DDoS defense, image/video processing at the edge, Zero Trust networks, and a vendor comparison across Cloudflare, Akamai, Fastly, and CloudFront.

"The internet is not flat. CDNs wrap the curvature of the Earth, and Wasm runs on top of them."