LabHub

Blog

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

한국어English日本語

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:


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:

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.


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.

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:

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)

Wasmer

WasmEdge (CNCF Sandbox)

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:

Why Isolate plus Wasm

Fastly Compute@Edge — all-in on Wasm

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

Go — TinyGo saves the day

JavaScript/TypeScript — AssemblyScript

Python — Pyodide, py2wasm

C/C++ — Emscripten, wasi-sdk


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


12. Traps and limits

Threading

Networking

Filesystem consistency

Debugging

Binary size

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

Comments

No comments yet.

Sign in to leave a comment