Tech Guides
0x00000000 — Module Header

WebAssembly

The portable binary instruction format for a stack-based virtual machine. Compile once, run everywhere — at near-native speed, in every browser and beyond.

Binary Format Stack Machine W3C Standard
01

Quick Reference

The essential commands, magic bytes, and API calls every WASM developer needs at their fingertips.

0x00 The Magic Bytes

00000000  00 61 73 6D  01 00 00 00    ; \0asm followed by version 1
                                               ; Every valid .wasm file starts here

A WebAssembly binary module always begins with the 4-byte magic number \0asm (hex 00 61 73 6D) followed by a 4-byte version field. The current version is 1.

0x08 Essential CLI Commands

Compile C/C++
Emscripten

Compile C/C++ to WASM with Emscripten

emcc hello.c -o hello.wasm
Compile Rust
wasm-pack

Build Rust to WASM with npm bindings

wasm-pack build --target web
Inspect
wasm-objdump

Dump sections, imports, exports

wasm-objdump -x module.wasm
Text Format
wasm2wat

Decompile binary to readable WAT

wasm2wat module.wasm -o module.wat
Binary Format
wat2wasm

Assemble WAT text to .wasm binary

wat2wasm module.wat -o module.wasm
Optimize
wasm-opt

Binaryen optimizer — shrink & speed up

wasm-opt -O3 input.wasm -o output.wasm

0x10 Minimal Browser Usage

// Fetch, compile, and instantiate a WASM module
const response = await fetch('module.wasm');
const bytes = await response.arrayBuffer();
const { instance } = await WebAssembly.instantiate(bytes, importObject);

// Call an exported function
const result = instance.exports.add(40, 2);
console.log(result); // 42

// Streaming compilation (preferred — starts compiling while downloading)
const { instance: inst } = await WebAssembly.instantiateStreaming(
    fetch('module.wasm'),
    importObject
);

0x18 File Extensions

Extension Format Purpose
.wasm Binary Compiled WebAssembly module (the runtime artifact)
.wat Text Human-readable WebAssembly Text Format
.wast Text Extended text format for tests (assertions, modules)
.wit Text WebAssembly Interface Types (Component Model IDL)
02

Core Concepts

WebAssembly is a binary instruction format for a stack-based virtual machine. Understanding the execution model, module structure, and type system is foundational.

0x20 What Is WebAssembly?

WebAssembly (WASM) is a low-level, portable bytecode format designed as a compilation target for high-level languages. It runs in a sandboxed virtual machine with near-native performance and is supported by all major browsers, plus server-side runtimes like Wasmtime, Wasmer, and WasmEdge.

Key Insight

WASM is not a replacement for JavaScript. It is a complement. JavaScript handles DOM manipulation and high-level orchestration; WASM handles computationally intensive work like codecs, physics, cryptography, and image processing.

Four design goals drive every decision in the spec:

  • Fast — Near-native execution speed via ahead-of-time or just-in-time compilation
  • Safe — Memory-safe sandboxed execution; no access to host resources without explicit imports
  • Portable — Hardware-independent binary format that runs on any platform with a conforming runtime
  • Compact — Dense binary encoding designed for fast decoding and small file sizes

0x28 The Stack Machine

WASM uses a stack-based execution model. Instructions push values onto and pop values from an implicit operand stack. There are no general-purpose registers — the stack is the register file.

;; Add two i32 values: (2 + 3)
i32.const 2    ;; stack: [2]
i32.const 3    ;; stack: [2, 3]
i32.add        ;; pops both, pushes result: [5]

In addition to the operand stack, functions have local variables (indexed slots for parameters and temporaries) and modules have global variables (shared mutable or immutable values).

0x30 Value Types

Type Size Description
i32 32-bit Integer (used for booleans, pointers, and int arithmetic)
i64 64-bit Long integer
f32 32-bit IEEE 754 single-precision float
f64 64-bit IEEE 754 double-precision float
v128 128-bit SIMD vector (4xi32, 2xf64, 16xi8, etc.)
funcref ref Reference to a function (for indirect calls via tables)
externref ref Opaque host reference (e.g., a JS object)

0x38 Module Structure

A WASM binary module is organized into numbered sections. Each section is optional and appears at most once, in a fixed order:

ID Section Contents
0CustomName section, debug info, metadata (repeatable)
1TypeFunction signatures (parameter & return types)
2ImportFunctions, tables, memories, globals from host
3FunctionFunction index → type index mapping
4TableTables of references (for indirect calls)
5MemoryLinear memory declarations (initial/max pages)
6GlobalGlobal variables with mutability & init expressions
7ExportFunctions, tables, memories, globals exposed to host
8StartIndex of function to call on instantiation
9ElementSegments to initialize table entries
10CodeFunction bodies (locals + instructions)
11DataSegments to initialize linear memory
12Data CountNumber of data segments (for single-pass validation)

0x40 Linear Memory

WASM modules access a contiguous, byte-addressable array of bytes called linear memory. Memory is measured in 64 KiB pages and can grow at runtime via memory.grow.

;; Declare 1 page (64 KiB) of memory, max 256 pages (16 MiB)
(memory 1 256)

;; Store the i32 value 42 at byte offset 0
i32.const 0      ;; address
i32.const 42     ;; value
i32.store        ;; store 4 bytes at address 0

;; Load 4 bytes from address 0
i32.const 0      ;; address
i32.load         ;; pushes 42 onto stack
Bounds Safety

All memory accesses are bounds-checked by the runtime. An out-of-bounds load or store traps immediately — there is no undefined behavior, no buffer overflow. This is a core security guarantee of the WASM sandbox.

03

WAT — Text Format

The WebAssembly Text Format (WAT) is a human-readable S-expression syntax that corresponds 1:1 with the binary format. Essential for learning, debugging, and small hand-written modules.

0x48 Hello, WAT

(module
  ;; Import console.log from JavaScript
  (import "console" "log" (func $log (param i32)))

  ;; Define a function that adds two numbers
  (func $add (export "add") (param $a i32) (param $b i32) (result i32)
    local.get $a
    local.get $b
    i32.add
  )

  ;; Define a function that adds and logs
  (func (export "addAndLog") (param i32 i32)
    local.get 0
    local.get 1
    call $add
    call $log
  )
)

0x50 Control Flow

WASM uses structured control flow — no arbitrary goto. All branches target labeled blocks, and the type checker ensures the stack is balanced at every join point.

;; If/else (returns i32)
(func $max (param $a i32) (param $b i32) (result i32)
  (if (result i32) (i32.gt_s (local.get $a) (local.get $b))
    (then (local.get $a))
    (else (local.get $b))
  )
)

;; Loop: sum 1 to N
(func $sum (param $n i32) (result i32)
  (local $i i32)
  (local $acc i32)
  (local.set $i (i32.const 1))
  (block $break
    (loop $continue
      ;; acc += i
      (local.set $acc (i32.add (local.get $acc) (local.get $i)))
      ;; i += 1
      (local.set $i (i32.add (local.get $i) (i32.const 1)))
      ;; if i <= n, continue
      (br_if $continue (i32.le_s (local.get $i) (local.get $n)))
    )
  )
  local.get $acc
)

;; Switch via br_table
(block $default
  (block $case2
    (block $case1
      (block $case0
        (br_table $case0 $case1 $case2 $default (local.get $selector))
      ) ;; case 0
      (return (i32.const 100))
    ) ;; case 1
    (return (i32.const 200))
  ) ;; case 2
  (return (i32.const 300))
) ;; default
(i32.const -1)

0x58 Tables & Indirect Calls

Tables store references (usually funcref) and enable indirect/dynamic dispatch — the WASM equivalent of function pointers or vtables.

(module
  (type $callback (func (param i32) (result i32)))

  (func $double (param i32) (result i32)
    (i32.mul (local.get 0) (i32.const 2)))

  (func $square (param i32) (result i32)
    (i32.mul (local.get 0) (local.get 0)))

  ;; Table with 2 function references
  (table 2 funcref)
  (elem (i32.const 0) $double $square)

  ;; Call function at table index
  (func (export "apply") (param $idx i32) (param $val i32) (result i32)
    (call_indirect (type $callback) (local.get $val) (local.get $idx))
  )
)
04

Browser API

The JavaScript WebAssembly API for compiling, instantiating, and interacting with WASM modules in the browser.

0x60 Compilation & Instantiation

// === Method 1: Streaming (recommended) ===
// Compiles while downloading — best for large modules
const { module, instance } = await WebAssembly.instantiateStreaming(
    fetch('module.wasm'),
    importObject                // { env: { memory, ... }, js: { log, ... } }
);

// === Method 2: Two-step ===
const bytes = await fetch('module.wasm').then(r => r.arrayBuffer());
const module = await WebAssembly.compile(bytes);
const instance = await WebAssembly.instantiate(module, importObject);

// === Method 3: Synchronous (small modules only, <4 KB) ===
const module = new WebAssembly.Module(bytes);
const instance = new WebAssembly.Instance(module, importObject);

// === Caching compiled modules in IndexedDB ===
// WebAssembly.Module is structured-cloneable
const db = await openDB('wasm-cache');
await db.put('modules', module, 'my-module');
const cached = await db.get('modules', 'my-module');
const inst2 = await WebAssembly.instantiate(cached, importObject);

0x68 WebAssembly.Memory

Create and share linear memory between JavaScript and WASM. Memory is a resizable ArrayBuffer accessible from both sides.

// Create shared memory: 1 page initial (64 KiB), 10 pages max
const memory = new WebAssembly.Memory({ initial: 1, maximum: 10 });

// Pass to WASM via imports
const importObject = {
    env: { memory }
};

// Read/write from JavaScript
const view = new Uint8Array(memory.buffer);
view[0] = 0x48;    // 'H'
view[1] = 0x69;    // 'i'

// Grow memory by 2 pages (returns old page count)
const oldPages = memory.grow(2);
// IMPORTANT: .buffer reference changes after grow!
const newView = new Uint8Array(memory.buffer);

// Read a string that WASM wrote to memory
function readString(ptr, len) {
    const bytes = new Uint8Array(memory.buffer, ptr, len);
    return new TextDecoder().decode(bytes);
}

0x70 WebAssembly.Table

// Create a table of function references
const table = new WebAssembly.Table({
    element: 'anyfunc',
    initial: 2,
    maximum: 10
});

// After instantiation, the table contains exported functions
// Get a function reference from the table
const fn = table.get(0);
console.log(fn(42));   // Call it like a normal JS function

// Set a table entry from JavaScript
table.set(1, instance.exports.myFunc);

// Grow the table
table.grow(3);

0x78 WebAssembly.Global

// Create a mutable global (shared between JS and WASM)
const counter = new WebAssembly.Global(
    { value: 'i32', mutable: true },
    0   // initial value
);

const importObject = {
    env: { counter }
};

// Read and write from JavaScript
console.log(counter.value);   // 0
counter.value = 100;

// WASM can also read/write via global.get/global.set

0x80 Import Object Pattern

// A complete import object showing all import kinds
const importObject = {
    // Environment namespace
    env: {
        memory: new WebAssembly.Memory({ initial: 256 }),
        table: new WebAssembly.Table({ element: 'anyfunc', initial: 0 }),
        __stack_pointer: new WebAssembly.Global({ value: 'i32', mutable: true }, 0),
        abort: (msg, file, line, col) => { throw new Error('abort'); },
    },
    // Custom JS functions callable from WASM
    js: {
        log: (value) => console.log('WASM says:', value),
        now: () => Date.now(),
        random: () => Math.random(),
    },
    // WASI preview1 (if using WASI in browser)
    wasi_snapshot_preview1: {
        fd_write: (fd, iovs, iovsLen, nwritten) => { /* ... */ },
        proc_exit: (code) => { /* ... */ },
    }
};
05

Toolchains

The compilers, optimizers, and build tools that transform high-level code into production WASM binaries.

0x88 Emscripten (C/C++)

The original WASM toolchain. Emscripten compiles C/C++ via LLVM to WASM, providing a POSIX-like environment with filesystem emulation, SDL/OpenGL bindings, and automatic JS glue code.

# Install via emsdk
git clone https://github.com/emscripten-core/emsdk.git
cd emsdk && ./emsdk install latest && ./emsdk activate latest
source ./emsdk_env.sh

# Compile C to WASM + JS glue
emcc hello.c -o hello.html                    # Full page with shell
emcc hello.c -o hello.js                      # JS glue + .wasm
emcc hello.c -o hello.wasm -s STANDALONE_WASM # Standalone (no JS glue)

# Optimization levels
emcc -O0 app.c -o app.js      # No optimization (debug)
emcc -O2 app.c -o app.js      # Production
emcc -O3 app.c -o app.js      # Aggressive (may increase size)
emcc -Oz app.c -o app.js      # Optimize for size

# Key flags
emcc app.c -o app.js \
    -s WASM=1 \                # Emit WASM (default)
    -s EXPORTED_FUNCTIONS="['_main','_add']" \
    -s EXPORTED_RUNTIME_METHODS="['ccall','cwrap']" \
    -s ALLOW_MEMORY_GROWTH=1 \ # Dynamic memory growth
    -s INITIAL_MEMORY=16777216  # 16 MB initial heap

0x90 wasm-pack & wasm-bindgen (Rust)

wasm-pack is the Rust-to-WASM build tool. It wraps cargo, runs wasm-bindgen for JS interop, and generates npm-ready packages.

# Install
cargo install wasm-pack
rustup target add wasm32-unknown-unknown

# Build for different targets
wasm-pack build --target web        # ES modules (native browser)
wasm-pack build --target bundler    # For webpack/vite/rollup
wasm-pack build --target nodejs     # Node.js CommonJS
wasm-pack build --target no-modules # Global script tag

# The generated pkg/ contains:
#   package.json, *.js, *.wasm, *.d.ts
use wasm_bindgen::prelude::*;

// Export a function to JavaScript
#[wasm_bindgen]
pub fn greet(name: &str) -> String {
    format!("Hello, {}!", name)
}

// Import a JavaScript function
#[wasm_bindgen]
extern "C" {
    #[wasm_bindgen(js_namespace = console)]
    fn log(s: &str);
}

// Work with DOM
#[wasm_bindgen]
pub fn set_title(title: &str) {
    let window = web_sys::window().unwrap();
    let document = window.document().unwrap();
    document.set_title(title);
}

// Use web_sys for full Web API bindings
// Cargo.toml: web-sys = { version = "0.3", features = ["Document", "Window"] }

0x98 WABT — WebAssembly Binary Toolkit

Low-level tools for working directly with the binary and text formats. Essential for debugging, inspection, and education.

Decompile
wasm2wat

Binary → text format

wasm2wat app.wasm -o app.wat
Assemble
wat2wasm

Text format → binary

wat2wasm app.wat -o app.wasm
Inspect
wasm-objdump

Section headers, imports, exports

wasm-objdump -dhx app.wasm
Validate
wasm-validate

Check module validity

wasm-validate app.wasm

0xA0 Binaryen Optimizer

Binaryen is the WASM-specific optimizer used by Emscripten and other toolchains. Its wasm-opt tool can significantly reduce binary size and improve performance.

# Optimization passes
wasm-opt -O1 input.wasm -o output.wasm     # Basic optimizations
wasm-opt -O2 input.wasm -o output.wasm     # Standard (good default)
wasm-opt -O3 input.wasm -o output.wasm     # Aggressive speed
wasm-opt -Oz input.wasm -o output.wasm     # Optimize for size
wasm-opt -O4 input.wasm -o output.wasm     # O3 + more flattening

# Specific passes
wasm-opt --strip-debug input.wasm -o out.wasm    # Remove debug info
wasm-opt --strip-producers input.wasm -o out.wasm # Remove producer data
wasm-opt --vacuum input.wasm -o out.wasm          # Remove dead code

# Typical savings: 10-30% smaller, 5-15% faster
06

Languages → WASM

The languages, toolchains, and trade-offs for compiling to WebAssembly. Not all languages are created equal as WASM targets.

Rust
wasm-pack + wasm-bindgen

Best-in-class WASM support. No runtime overhead, tiny binaries, wasm-bindgen provides seamless JS interop. The ecosystem leader.

C / C++
Emscripten / clang --target=wasm32

The original WASM compilation path. Emscripten provides libc, SDL, OpenGL emulation. Mature but generates larger glue code.

Go
GOOS=js GOARCH=wasm

Built-in WASM target since Go 1.11. Includes the Go runtime & GC, so binaries are large (~2+ MB minimum). TinyGo produces much smaller output.

AssemblyScript
asc (AssemblyScript compiler)

TypeScript-like syntax compiling directly to WASM. Small binaries, no GC needed. Ideal for TypeScript developers wanting WASM performance.

Zig
zig build -Dtarget=wasm32-freestanding

First-class WASM target with no hidden allocations. Excellent for tiny, zero-dependency WASM modules. No runtime overhead.

Kotlin
Kotlin/Wasm (experimental)

JetBrains' Kotlin/Wasm target leverages WASM GC proposal. Currently experimental; requires WASM GC-enabled browsers.

Swift
SwiftWasm

Community-maintained fork of Swift targeting WASM. Supports Foundation, async/await. Growing ecosystem for server-side WASM.

C#
Blazor WebAssembly / NativeAOT

Blazor runs .NET in the browser via WASM. Full .NET runtime ships with the app. NativeAOT WASM (experimental) skips the runtime.

0xA8 Language Comparison

Language Min Binary GC Required JS Interop Maturity
Rust ~200 B No Excellent (wasm-bindgen) Production
C/C++ ~1 KB No Good (Embind / ccall) Production
Go ~2 MB Yes (bundled) Basic (syscall/js) Production
TinyGo ~10 KB Yes (minimal) Good Stable
AssemblyScript ~300 B Optional Good (loader) Stable
Zig ~200 B No Manual Maturing
Kotlin/Wasm ~500 KB Yes (WASM GC) Good Experimental
C# (Blazor) ~2 MB Yes (bundled) Excellent (JSInterop) Production

0xB0 Go → WASM Example

// main.go
package main

import (
    "fmt"
    "syscall/js"
)

func add(this js.Value, args []js.Value) interface{} {
    return args[0].Int() + args[1].Int()
}

func main() {
    // Register Go function as JavaScript global
    js.Global().Set("goAdd", js.FuncOf(add))
    fmt.Println("Go WASM initialized")

    // Block forever (required — Go runtime must stay alive)
    select {}
}
# Build
GOOS=js GOARCH=wasm go build -o main.wasm main.go

# Copy the JS glue (required for Go WASM)
cp "$(go env GOROOT)/misc/wasm/wasm_exec.js" .

# TinyGo alternative (much smaller binary)
tinygo build -o main.wasm -target wasm ./main.go

0xB8 AssemblyScript Example

// assembly/index.ts — TypeScript-like syntax
export function fibonacci(n: i32): i32 {
    if (n <= 1) return n;
    return fibonacci(n - 1) + fibonacci(n - 2);
}

// Strings, arrays, and classes work too
export function reverseString(s: string): string {
    let result = "";
    for (let i = s.length - 1; i >= 0; i--) {
        result += s.charAt(i);
    }
    return result;
}
# Install and build
npm install -g assemblyscript
asc assembly/index.ts -o build/module.wasm --optimize
07

WASI

The WebAssembly System Interface — a standardized set of POSIX-like APIs that let WASM modules access filesystems, network sockets, clocks, and environment variables outside the browser.

0xC0 What Is WASI?

WASI (WebAssembly System Interface) defines a portable, capability-based interface for WASM modules to interact with the operating system. It enables WASM to run as a general-purpose computing platform, not just a browser technology.

Your Application Code | v +-------------------+ | WASM Module | Portable binary +-------------------+ | imports WASI functions v +-------------------+ | WASI Interface | Standardized syscall layer +-------------------+ | capability-based security v +-------------------+ | Runtime | Wasmtime / Wasmer / WasmEdge +-------------------+ | v Host OS (Linux, macOS, Windows)

0xC8 WASI Runtimes

Bytecode Alliance
Wasmtime

Reference implementation. Cranelift JIT, fast startup, production-grade. Used by Fastly, Shopify.

wasmtime run app.wasm
Wasmer Inc.
Wasmer

Multiple backends (Cranelift, LLVM, Singlepass). Package registry (wapm). Embeddable in many languages.

wasmer run app.wasm
CNCF Sandbox
WasmEdge

Optimized for cloud-native & edge. WASI-NN for AI inference, Kubernetes integration, tiny footprint.

wasmedge app.wasm
Mozilla
wasm-tools

CLI for WASM component model operations: compose, validate, inspect, and wire components.

wasm-tools component new mod.wasm

0xD0 Capability-Based Security

WASI uses a capability-based security model. Modules cannot access any resource unless the host explicitly grants a capability at startup. This is fundamentally different from POSIX, where a process inherits ambient authority.

# Grant specific directory access
wasmtime run --dir=/data/input::input --dir=/data/output::output app.wasm
#                ^^^^^^^^^^^^^^^^^^^^^   ^^^^^^^^^^^^^^^^^^^^^^^^^^
#                maps host /data/input   maps host /data/output
#                to guest path "input"   to guest path "output"

# Grant environment variables
wasmtime run --env API_KEY=secret --env MODE=production app.wasm

# Grant network access (Wasmtime 14+)
wasmtime run --tcplisten 127.0.0.1:8080 server.wasm

# No flags = fully sandboxed (no FS, no network, no env)
Security Model

A WASM module with no granted capabilities is fully sandboxed: it cannot read files, make network requests, access environment variables, or observe the clock. Capabilities must be explicitly passed by the host, making WASI ideal for running untrusted code.

0xD8 WASI in Rust

// Compile: cargo build --target wasm32-wasip1
// Run:     wasmtime run --dir=.::. target/wasm32-wasip1/release/app.wasm

use std::fs;
use std::io::{self, Read, Write};

fn main() -> io::Result<()> {
    // File I/O works via WASI (if dir capability is granted)
    let contents = fs::read_to_string("input/data.txt")?;
    println!("Read {} bytes", contents.len());   // stdout via fd_write

    // Write output
    fs::write("output/result.txt", contents.to_uppercase())?;

    // Environment variables (if --env is granted)
    if let Ok(mode) = std::env::var("MODE") {
        println!("Running in {} mode", mode);
    }

    // Command-line args work too
    let args: Vec<String> = std::env::args().collect();
    println!("Args: {:?}", args);

    Ok(())
}
08

Performance Patterns

How to maximize WASM performance: minimize JS-WASM boundary crossings, optimize memory layout, and leverage SIMD.

0xE0 The JS-WASM Boundary

Every call between JavaScript and WASM has overhead. The single most important optimization is minimizing boundary crossings.

Anti-pattern
Per-pixel calls

Calling WASM once per pixel to process an image. Millions of boundary crossings.

for (let i = 0; i < pixels.length; i++) wasm.process(i);
Best practice
Batch in WASM

Copy the buffer to WASM memory, process it in one call, read results back.

wasm.processImage(ptr, width, height);
// Pattern: Bulk data transfer via shared memory
function processImage(imageData) {
    const { width, height, data } = imageData;

    // 1. Allocate space in WASM memory
    const ptr = instance.exports.alloc(data.length);

    // 2. Copy pixels into WASM linear memory (one bulk copy)
    new Uint8Array(memory.buffer, ptr, data.length).set(data);

    // 3. Process entirely in WASM (no boundary crossings)
    instance.exports.applyFilter(ptr, width, height);

    // 4. Read results back (one bulk copy)
    data.set(new Uint8Array(memory.buffer, ptr, data.length));

    // 5. Free WASM memory
    instance.exports.dealloc(ptr, data.length);
}

0xE8 SIMD (128-bit Vector Operations)

WASM SIMD provides 128-bit vector operations that process 4x i32, 4x f32, 2x f64, or 16x i8 values in a single instruction. Supported in all modern browsers.

;; Multiply four f32 values simultaneously
(func $simd_mul (param $a v128) (param $b v128) (result v128)
    local.get $a
    local.get $b
    f32x4.mul
)

;; Create a vector from four f32 values
(func $make_vec (result v128)
    v128.const f32x4 1.0 2.0 3.0 4.0
)
// C with Emscripten SIMD intrinsics
#include <wasm_simd128.h>

void add_vectors(float* a, float* b, float* result, int n) {
    for (int i = 0; i < n; i += 4) {
        v128_t va = wasm_v128_load(&a[i]);
        v128_t vb = wasm_v128_load(&b[i]);
        v128_t vr = wasm_f32x4_add(va, vb);
        wasm_v128_store(&result[i], vr);
    }
}
// Compile: emcc -msimd128 simd.c -o simd.wasm

0xF0 Memory Optimization

  • Pre-allocate — Set initial memory pages to avoid frequent memory.grow calls (each grow zeros new pages and may invalidate ArrayBuffer references)
  • Struct-of-Arrays — For SIMD processing, store data in column-major layout (all x values together, then all y values) rather than interleaved structs
  • Arena allocation — Use bump allocators for short-lived data; avoid per-object malloc/free overhead
  • Stack allocation — Prefer stack-allocated locals over heap allocation for temporary buffers
  • LTO — Enable link-time optimization to eliminate dead code across compilation units
// Rust: use wee_alloc for smaller binary size
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;

// Use #[inline(always)] for hot functions
#[inline(always)]
fn hot_path(x: f64) -> f64 {
    x * x + 2.0 * x + 1.0
}

// Compile with LTO and size optimization
// Cargo.toml:
// [profile.release]
// lto = true
// opt-level = 'z'     # Optimize for size
// strip = true         # Strip debug symbols

0xF8 Binary Size Reduction

Technique Savings How
wasm-opt -Oz 10-30% Binaryen size optimizer
LTO (link-time optimization) 15-40% Eliminates dead code across units
Strip debug info 20-50% wasm-opt --strip-debug
wee_alloc (Rust) ~10 KB Smaller allocator vs default
gzip / brotli 60-80% WASM compresses extremely well
Avoid string formatting Varies format! / println! pull in large code
Compression Tip

WASM binaries compress exceptionally well (70%+ with Brotli) because of their structured, repetitive encoding. Always serve .wasm files with Content-Encoding: br or gzip in production. A 500 KB binary typically compresses to under 150 KB.

09

Use Cases

Where WASM delivers real value: the workloads that justify the complexity of compiling to a new target.

0x100 Browser Use Cases

Multimedia
Image & Video Processing

Photoshop-class filters, video encoding/decoding, real-time effects. Squoosh, FFmpeg.wasm, libheif.

Interactive
Game Engines

Unity, Unreal, Godot all export to WASM. Full 3D engines running in a browser tab at 60fps.

Security
Cryptography

Password hashing (Argon2), symmetric encryption, digital signatures. Constant-time operations without JIT interference.

Data
In-Browser Databases

SQLite compiled to WASM (sql.js), DuckDB-WASM for analytical queries on Parquet files, full Postgres via PGlite.

Developer Tools
Language Runtimes

Python (Pyodide), Ruby (ruby.wasm), PHP, Lua — entire language runtimes compiled to run in the browser.

Productivity
Desktop Apps in Browser

Figma (C++ → WASM), AutoCAD Web, Google Earth. Complex native apps ported to the browser.

0x108 Server & Edge Use Cases

Serverless
Edge Functions

Cloudflare Workers, Fastly Compute, Fermyon Spin. Sub-millisecond cold start vs seconds for containers.

Extensibility
Plugin Systems

Envoy proxy filters, database UDFs, game mod systems. Run untrusted user code safely in a sandbox.

Portable
Universal Binaries

Write once in any language, run on any OS/arch. No cross-compilation matrix. wasm32 is the universal target.

IoT / Embedded
Tiny Runtimes

WASM interpreters (WAMR, wasm3) run on microcontrollers. Update device logic by swapping a .wasm file.

0x110 When NOT to Use WASM

WASM Is Not Always Faster

JavaScript V8/SpiderMonkey JITs are remarkably good. For typical web app code (DOM manipulation, event handling, string processing, JSON parsing), JS is often faster than WASM because: (1) no boundary-crossing overhead, (2) the JIT has direct access to browser internals, (3) WASM cannot touch the DOM directly.

  • DOM-heavy apps — WASM cannot manipulate the DOM directly; every DOM call bounces through JS
  • Simple CRUD / REST APIs — The overhead of a WASM runtime on the server outweighs the benefits for I/O-bound work
  • Small scripts — The download + compile time of a .wasm binary may exceed the total JS execution time
  • String-heavy code — WASM linear memory uses raw bytes; encoding/decoding strings across the boundary is expensive
10

Component Model

The next evolution of WASM: a higher-level composition layer that lets modules written in different languages interoperate through shared interface types.

0x118 The Problem Components Solve

Core WASM modules can only exchange i32, i64, f32, f64, and raw memory bytes. Passing a string, a list, a record, or an enum requires hand-rolled serialization. The Component Model solves this with:

  • WIT (WebAssembly Interface Types) — an IDL for defining rich interfaces with strings, lists, records, variants, results, and resources
  • Canonical ABI — a standard binary layout for complex types in linear memory
  • Composition — linking components together (e.g., a Rust HTTP handler + a Python ML model) into a single portable unit

0x120 WIT — Interface Definition

// greeter.wit — defines a component interface
package example:greeter@1.0.0;

interface greet {
    // A record (struct) with named fields
    record greeting {
        message: string,
        language: string,
    }

    // A variant (tagged union)
    variant greeting-style {
        formal,
        casual,
        custom(string),
    }

    // Functions
    greet: func(name: string, style: greeting-style) -> greeting;
    greet-many: func(names: list<string>) -> list<greeting>;
}

// The world defines what a component imports and exports
world greeter-world {
    import wasi:logging/logging;
    export greet;
}

0x128 Building Components

# Install component tooling
cargo install cargo-component    # Rust component builder
cargo install wasm-tools         # Component composition tools

# Create a new component project (Rust)
cargo component new my-greeter
cd my-greeter

# Build a component (produces .wasm with component metadata)
cargo component build --release

# Compose two components (wire exports to imports)
wasm-tools compose handler.wasm --adapt ml-model.wasm -o composed.wasm

# Inspect a component's WIT interface
wasm-tools component wit composed.wasm

0x130 WASI 0.2 + Components

WASI Preview 2 (WASI 0.2) is built entirely on the Component Model. Instead of POSIX-style flat function imports, WASI 0.2 defines interfaces in WIT:

  • wasi:filesystem/types — file and directory operations
  • wasi:sockets/tcp — TCP stream sockets
  • wasi:http/incoming-handler — HTTP request handling
  • wasi:clocks/monotonic-clock — timing and measurement
  • wasi:random/random — cryptographic randomness
  • wasi:cli/environment — environment variables and args
11

Threads & Shared Memory

WASM threads enable true parallelism through shared linear memory and atomic operations, mirroring the pthreads model inside the browser.

0x138 How WASM Threads Work

WASM threads are built on Web Workers. Multiple workers share the same WebAssembly.Memory (backed by a SharedArrayBuffer), and synchronize via atomic instructions.

Main Thread
SharedArrayBuffer
Web Worker 1
SharedArrayBuffer
Web Worker 2
Web Worker N
// Create shared memory (required headers: COOP + COEP)
const memory = new WebAssembly.Memory({
    initial: 256,
    maximum: 512,
    shared: true      // Backed by SharedArrayBuffer
});

// Main thread: instantiate and spawn workers
const { instance } = await WebAssembly.instantiateStreaming(
    fetch('threaded.wasm'),
    { env: { memory } }
);

// Spawn worker threads
for (let i = 0; i < navigator.hardwareConcurrency; i++) {
    const worker = new Worker('worker.js');
    worker.postMessage({ memory, wasmBytes });
}

0x140 Required HTTP Headers

Cross-Origin Isolation Required

SharedArrayBuffer (and therefore WASM threads) requires these HTTP response headers on your page. Without them, the browser will refuse to create shared memory.

Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
# nginx configuration
add_header Cross-Origin-Opener-Policy same-origin always;
add_header Cross-Origin-Embedder-Policy require-corp always;

0x148 Atomic Operations

;; Atomic operations on shared memory
memory.atomic.wait32    ;; Block until memory location changes (futex)
memory.atomic.notify    ;; Wake waiting threads
i32.atomic.load         ;; Atomic load (seq_cst)
i32.atomic.store        ;; Atomic store
i32.atomic.rmw.add      ;; Atomic read-modify-write add
i32.atomic.rmw.cmpxchg  ;; Compare-and-swap

;; Example: atomic increment
(func $atomic_inc (param $addr i32) (result i32)
    (i32.atomic.rmw.add (local.get $addr) (i32.const 1))
)
// C with Emscripten pthreads
#include <pthread.h>
#include <stdatomic.h>

atomic_int counter = 0;

void* worker(void* arg) {
    for (int i = 0; i < 1000000; i++) {
        atomic_fetch_add(&counter, 1);
    }
    return NULL;
}

// Compile: emcc -pthread -sPTHREAD_POOL_SIZE=4 threads.c -o threads.js
12

Debugging & Tooling

Debugging WASM at the source level, profiling hot paths, and the developer tool ecosystem.

0x150 Chrome DevTools DWARF Debugging

Chrome DevTools supports source-level debugging of WASM via DWARF debug info. You can set breakpoints, inspect variables, and step through C/C++/Rust source code in the browser.

  1. Install the C/C++ DevTools Support (DWARF) Chrome extension
  2. Compile with debug info: emcc -g app.c -o app.js or cargo build --target wasm32-unknown-unknown (debug profile)
  3. Open DevTools → Sources → set breakpoints in your C/Rust source files
  4. Inspect local variables, step in/out/over, view call stack
# Emscripten: full debug info + source maps
emcc -g4 -gsource-map app.c -o app.js

# Rust: keep debug info in release (for profiling)
# Cargo.toml
[profile.release]
debug = 1    # Line tables only (minimal size impact)

0x158 Console Logging from WASM

// Rust: use web-sys or console_log crate
use web_sys::console;

pub fn debug_log(msg: &str) {
    console::log_1(&msg.into());
}

// Or use the console_log crate for println!-style macros
// console_log::init_with_level(log::Level::Debug).unwrap();
// log::info!("WASM initialized with {} bytes", size);
// C: Emscripten provides printf → console.log
#include <stdio.h>
#include <emscripten.h>

// Direct JavaScript call
EM_JS(void, console_warn, (const char* msg), {
    console.warn(UTF8ToString(msg));
});

printf("Debug: value = %d\n", x);  // Goes to console.log
console_warn("Watch out!");         // Goes to console.warn

0x160 Profiling

  • Chrome Performance tab — WASM functions appear in flame charts (with names if Name section is present)
  • Firefox Profiler — Excellent WASM support, shows function names and time-per-function
  • performance.measure() — Bracket WASM calls with JS performance markers for custom timing
  • console.time() — Quick benchmarking: console.time('wasm'); f(); console.timeEnd('wasm');
// Benchmark a WASM function
function benchmark(fn, iterations = 1000) {
    // Warmup (let JIT/AOT settle)
    for (let i = 0; i < 100; i++) fn();

    const start = performance.now();
    for (let i = 0; i < iterations; i++) fn();
    const elapsed = performance.now() - start;

    console.log(`${iterations} iterations in ${elapsed.toFixed(2)}ms`);
    console.log(`${(elapsed / iterations).toFixed(4)}ms per call`);
}

0x168 Inspection Tools

CLI
wasm-objdump

Print section info, imports, exports, disassembly.

wasm-objdump -dhx module.wasm
CLI
twiggy

Code size profiler — find what makes your .wasm large.

twiggy top module.wasm
Web
WebAssembly Code Explorer

Visualize binary structure, section layout, function bodies in the browser.

CLI
wasm-decompile

Decompile to pseudo-C (more readable than WAT for large modules).

wasm-decompile module.wasm -o readable.dcmp

0x170 Common Errors & Fixes

Error Cause Fix
CompileError: magic header not detected Fetched HTML instead of .wasm (404 page) Check fetch URL, verify .wasm MIME type
LinkError: import not found importObject missing required function Check wasm-objdump -j Import for required imports
RuntimeError: unreachable WASM hit unreachable instruction (panic/abort) Check for Rust panics, C++ exceptions, or assertion failures
RuntimeError: out of bounds memory Read/write past memory.buffer.byteLength Increase initial pages or enable ALLOW_MEMORY_GROWTH
TypeError: Incorrect response MIME type instantiateStreaming needs application/wasm Configure server: AddType application/wasm .wasm
Detached ArrayBuffer memory.grow() invalidates old buffer reference Re-read memory.buffer after every grow call
0x0178 45 4E 44 20 4F 46 20 47 55 49 44 45 END OF GUIDE