Skip to main content
Technology & EngineeringFile Formats363 lines

WebAssembly

WebAssembly (Wasm) binary format — a portable, high-performance binary instruction format for running compiled code in browsers and server-side runtimes.

Quick Summary26 lines
You are a file format specialist with deep expertise in WebAssembly (Wasm), including the binary module format, WAT text representation, WASI system interface, Rust/C/Go compilation targets, wasm-bindgen JavaScript interop, and server-side runtimes (Wasmtime, Wasmer, WasmEdge).

## Key Points

- `i32`: 32-bit integer
- `i64`: 64-bit integer
- `f32`: 32-bit float (IEEE 754)
- `f64`: 64-bit float (IEEE 754)
- `v128`: 128-bit SIMD vector (SIMD proposal)
- `funcref`: Function reference
- `externref`: Opaque host reference
- **Stack machine**: Instructions operate on an implicit value stack.
- **Linear memory**: Byte-addressable, bounds-checked memory accessible as an ArrayBuffer from JS.
- **Structured control flow**: `block`, `loop`, `if/else`, `br` (branch) — no arbitrary `goto`.
- **Deterministic execution**: Same inputs produce same outputs (except NaN bit patterns).
- **Sandboxed**: No direct access to OS, filesystem, or network — only what the host provides via imports.

## Quick Example

```javascript
import init, { greet, process_data } from './pkg/my_module.js';
await init();
console.log(greet("World"));  // "Hello, World!"
```
skilldb get file-formats-skills/WebAssemblyFull skill: 363 lines
Paste into your CLAUDE.md or agent config

You are a file format specialist with deep expertise in WebAssembly (Wasm), including the binary module format, WAT text representation, WASI system interface, Rust/C/Go compilation targets, wasm-bindgen JavaScript interop, and server-side runtimes (Wasmtime, Wasmer, WasmEdge).

WebAssembly — Binary Instruction Format

Overview

WebAssembly (Wasm) is a binary instruction format designed as a portable compilation target for high-level languages like C, C++, Rust, Go, and AssemblyScript. Initially developed for the web browser (all major browsers support it since 2017), Wasm has expanded into server-side computing, edge functions, plugin systems, and blockchain smart contracts. Wasm executes at near-native speed in a sandboxed environment, providing both performance and security guarantees.

Core Philosophy

WebAssembly (WASM) is a binary instruction format designed to be a portable compilation target for high-level languages (C, C++, Rust, Go, AssemblyScript) that runs at near-native speed in web browsers and increasingly in server-side environments. WASM's philosophy is that the web platform should not be limited to JavaScript — any language should be able to target the browser with predictable, high performance.

WASM achieves its performance through a compact binary format with a design that enables fast compilation, validation, and execution. Unlike JavaScript, which must be parsed, compiled, and optimized at runtime, WASM arrives in a format that is close to machine code and can be compiled to native instructions in a single pass. This makes WASM suitable for computationally intensive tasks that JavaScript handles poorly: image/video processing, 3D rendering, scientific computation, cryptography, and game engines.

WASM is not a JavaScript replacement — it is a JavaScript complement. WASM modules interact with the browser through JavaScript bindings, and most WASM applications use JavaScript for DOM manipulation, event handling, and API calls while offloading compute-heavy work to WASM. Outside the browser, WASM's sandboxed execution model makes it attractive for serverless functions, plugin systems, and cross-platform application runtimes (Wasmtime, Wasmer, WasmEdge).

Technical Specifications

Binary Format (.wasm)

A .wasm file is a compact binary encoding of a module:

┌──────────────────────────┐
│ Magic number: \0asm      │  4 bytes: 0x00 0x61 0x73 0x6D
│ Version: 1               │  4 bytes: 0x01 0x00 0x00 0x00
├──────────────────────────┤
│ Section 1: Type          │  Function signatures
│ Section 2: Import        │  Imported functions/memories/tables
│ Section 3: Function      │  Function declarations
│ Section 4: Table         │  Indirect function call tables
│ Section 5: Memory        │  Linear memory declarations
│ Section 6: Global        │  Global variables
│ Section 7: Export        │  Exported functions/memories
│ Section 8: Start         │  Module initialization function
│ Section 9: Element       │  Table initialization data
│ Section 10: Code         │  Function bodies (bytecode)
│ Section 11: Data         │  Memory initialization data
│ Custom sections          │  Name section, debug info, etc.
└──────────────────────────┘

Text Format (.wat)

The human-readable text representation uses S-expressions:

(module
  ;; Import a function from the host environment
  (import "env" "log" (func $log (param i32)))

  ;; Define memory (1 page = 64KB)
  (memory (export "memory") 1)

  ;; Global variable
  (global $counter (mut i32) (i32.const 0))

  ;; Function definition
  (func $add (export "add") (param $a i32) (param $b i32) (result i32)
    local.get $a
    local.get $b
    i32.add
  )

  ;; Fibonacci example
  (func $fib (export "fib") (param $n i32) (result i32)
    (if (i32.le_s (local.get $n) (i32.const 1))
      (then (return (local.get $n)))
    )
    (i32.add
      (call $fib (i32.sub (local.get $n) (i32.const 1)))
      (call $fib (i32.sub (local.get $n) (i32.const 2)))
    )
  )
)

Core Value Types

  • i32: 32-bit integer
  • i64: 64-bit integer
  • f32: 32-bit float (IEEE 754)
  • f64: 64-bit float (IEEE 754)
  • v128: 128-bit SIMD vector (SIMD proposal)
  • funcref: Function reference
  • externref: Opaque host reference

Key Characteristics

  • Stack machine: Instructions operate on an implicit value stack.
  • Linear memory: Byte-addressable, bounds-checked memory accessible as an ArrayBuffer from JS.
  • Structured control flow: block, loop, if/else, br (branch) — no arbitrary goto.
  • Deterministic execution: Same inputs produce same outputs (except NaN bit patterns).
  • Sandboxed: No direct access to OS, filesystem, or network — only what the host provides via imports.
  • Streaming compilation: Browsers can compile Wasm while downloading.

How to Work With It

Compiling to Wasm

# Rust (most popular Wasm source language)
rustup target add wasm32-unknown-unknown
cargo build --target wasm32-unknown-unknown --release
# Or with wasm-pack for JS interop:
wasm-pack build --target web

# C/C++ via Emscripten
emcc program.c -o program.wasm -s STANDALONE_WASM
emcc program.c -o program.js    # generates JS glue + .wasm

# Go
GOOS=js GOARCH=wasm go build -o main.wasm

# AssemblyScript (TypeScript-like)
asc assembly/index.ts -o build/module.wasm --optimize

# TinyGo (smaller Go output)
tinygo build -o main.wasm -target wasm ./main.go

Browser Usage

// Load and instantiate
const response = await fetch('module.wasm');
const bytes = await response.arrayBuffer();
const { instance } = await WebAssembly.instantiate(bytes, {
  env: {
    log: (value) => console.log('Wasm says:', value),
  },
});

// Call exported functions
const result = instance.exports.add(40, 2);  // 42
const fib10 = instance.exports.fib(10);       // 55

// Access linear memory
const memory = new Uint8Array(instance.exports.memory.buffer);

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

Rust + wasm-bindgen (Browser)

use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn greet(name: &str) -> String {
    format!("Hello, {}!", name)
}

#[wasm_bindgen]
pub fn process_data(data: &[u8]) -> Vec<u8> {
    data.iter().map(|b| b.wrapping_add(1)).collect()
}
import init, { greet, process_data } from './pkg/my_module.js';
await init();
console.log(greet("World"));  // "Hello, World!"

Server-Side / WASI

# WASI (WebAssembly System Interface) — POSIX-like system calls for Wasm
# Compile Rust to WASI
rustup target add wasm32-wasip1
cargo build --target wasm32-wasip1 --release

# Run with Wasmtime
wasmtime run program.wasm --dir /data

# Run with Wasmer
wasmer run program.wasm

# Run with WasmEdge
wasmedge program.wasm

Tooling

# wasm-tools — Wasm CLI toolkit
wasm-tools parse module.wat -o module.wasm     # WAT to Wasm
wasm-tools print module.wasm                    # Wasm to WAT
wasm-tools validate module.wasm                 # validate
wasm-tools strip module.wasm -o stripped.wasm   # remove debug info

# wasm-opt (Binaryen) — optimize Wasm binaries
wasm-opt -O3 module.wasm -o optimized.wasm

# wasm2wat / wat2wasm (WABT toolkit)
wasm2wat module.wasm -o module.wat
wat2wasm module.wat -o module.wasm

# Twiggy — code size profiler
twiggy top module.wasm
twiggy dominators module.wasm

Common Use Cases

  • Browser computation: Image/video processing, physics engines, CAD, crypto, codecs.
  • Gaming: Unity, Unreal Engine export to Wasm; game engines in the browser.
  • Edge computing: Cloudflare Workers, Fastly Compute, Fermyon Spin.
  • Plugin systems: Envoy proxy filters, Figma plugins, database UDFs.
  • Blockchain: Smart contracts (Near, Polkadot, Cosmos/CosmWasm).
  • Serverless: Spin, wasmCloud — sub-millisecond cold starts.
  • Portable CLI tools: Run the same binary on any OS via Wasm runtimes.
  • Embedded/IoT: Lightweight sandboxed execution on constrained devices.

Pros & Cons

Pros

  • Near-native performance — typically within 10-20% of native code.
  • Sandboxed by design — memory-safe, capability-based security model.
  • Portable — same binary runs on any platform with a Wasm runtime.
  • Language-agnostic — compile from Rust, C/C++, Go, AssemblyScript, etc.
  • Streaming compilation — browsers start executing before download completes.
  • Small binary size — compact encoding (especially with wasm-opt).
  • Deterministic execution — reproducible behavior across platforms.
  • Sub-millisecond cold start — much faster than containers/VMs.

Cons

  • No direct DOM access — must go through JavaScript for browser APIs.
  • Garbage collection support is recent (WasmGC) — GC languages produce large binaries.
  • Limited threading (SharedArrayBuffer + Atomics) — complex concurrency model.
  • WASI is still evolving — not all system interfaces are standardized.
  • Debugging is harder than native code — source maps help but aren't universal.
  • String passing between Wasm and host is awkward (linear memory encoding).
  • Cannot outperform native code — the abstraction layer has overhead.
  • Go and Java/Kotlin produce significantly larger Wasm binaries than Rust/C.

Compatibility

RuntimePlatformNotes
V8Chrome, Node.jsJIT + optimizing compiler
SpiderMonkeyFirefoxJIT compilation
JavaScriptCoreSafariFull support since 2017
WasmtimeServer/CLIBytecode Alliance reference
WasmerServer/CLIUniversal runtime, multiple backends
WasmEdgeEdge/cloudCNCF project, WASI + AI support
wazeroGoPure Go, zero dependencies
WAMREmbedded/IoTLightweight (Intel)

MIME type: application/wasm. File extensions: .wasm (binary), .wat (text).

Practical Usage

Build and optimize a Rust library for browser use

# Set up Rust for Wasm
rustup target add wasm32-unknown-unknown
cargo install wasm-pack wasm-opt

# Build with wasm-pack (handles JS bindings automatically)
wasm-pack build --target web --release

# Further optimize the binary with Binaryen
wasm-opt -O3 pkg/my_module_bg.wasm -o pkg/my_module_bg.wasm

# Profile binary size to find optimization targets
cargo install twiggy
twiggy top pkg/my_module_bg.wasm | head -20
twiggy dominators pkg/my_module_bg.wasm | head -20

# Cargo.toml settings for smallest binaries:
# [profile.release]
# opt-level = "s"    # optimize for size
# lto = true         # link-time optimization
# strip = true       # strip debug symbols
# codegen-units = 1  # better optimization at cost of compile time

Load and interact with a Wasm module in the browser

// Streaming compilation (preferred — compiles while downloading)
async function loadWasm() {
    const { instance } = await WebAssembly.instantiateStreaming(
        fetch('module.wasm'),
        {
            env: {
                // Provide host functions to Wasm
                log_value: (val) => console.log('Result:', val),
                get_time: () => Date.now(),
            }
        }
    );

    // Call exported functions
    const result = instance.exports.process_data(42);

    // Pass arrays via linear memory
    const memory = new Float32Array(instance.exports.memory.buffer);
    const inputPtr = instance.exports.alloc(1024);
    const inputView = new Float32Array(
        instance.exports.memory.buffer, inputPtr, 256
    );
    inputView.set(myData);
    instance.exports.transform(inputPtr, 256);
    const output = new Float32Array(
        instance.exports.memory.buffer, inputPtr, 256
    );
}

Run Wasm server-side with WASI

# Compile Rust to WASI target
rustup target add wasm32-wasip1
cargo build --target wasm32-wasip1 --release

# Run with Wasmtime (grant filesystem access to /data)
wasmtime run --dir /data target/wasm32-wasip1/release/my_app.wasm

# Run with Wasmer and measure performance
wasmer run target/wasm32-wasip1/release/my_app.wasm

# Use as a Cloudflare Worker (wrangler)
# wrangler.toml:
# main = "build/worker.wasm"
# compatibility_date = "2024-01-01"
npx wrangler dev

Anti-Patterns

Compiling an entire Go or Java runtime into Wasm for a small utility function. GC-language runtimes (Go, Java/Kotlin, Python) add 2-10 MB of overhead to the Wasm binary even for trivial functions. For performance-critical browser modules, use Rust, C/C++, or AssemblyScript which compile to compact Wasm without bundling a garbage collector. Reserve Go/Java for server-side WASI workloads where binary size matters less.

Passing data between JavaScript and Wasm through JSON serialization. Serializing objects to JSON strings, copying them into linear memory, and deserializing inside Wasm is extremely slow and negates the performance benefits of Wasm. Instead, use shared linear memory with typed arrays (Float32Array, Uint8Array) to pass bulk data by reference, and use wasm-bindgen or the Component Model for structured interop.

Calling Wasm functions individually in a tight JavaScript loop. Each cross-boundary call between JS and Wasm incurs overhead for context switching and type marshaling. Instead of calling a Wasm function 10,000 times from JS, pass an array into Wasm and process it in a single call. Batch processing amortizes the boundary crossing cost.

Shipping debug-mode Wasm binaries to production. Debug builds include function names, DWARF debugging information, and unoptimized code that can be 5-10x larger than release builds. Always build with --release (Rust) or -O2 (Emscripten), run wasm-opt -O3, and strip custom sections with wasm-tools strip before deployment.

Assuming WASI provides full POSIX compatibility for server-side workloads. WASI deliberately omits networking, process spawning, shared memory, and many system calls for security reasons. Code that relies on fork(), raw sockets, or mmap will not work. Design WASI applications around capability-based I/O (pre-opened file descriptors, environment variables) and check WASI feature availability upfront.

Related Formats

  • JavaScript: The language Wasm complements (not replaces) in browsers.
  • LLVM IR: Intermediate representation — many languages compile to LLVM then to Wasm.
  • JVM bytecode: Similar concept (portable bytecode) for the Java ecosystem.
  • CLR / .NET IL: Microsoft's equivalent portable bytecode format.
  • eBPF: Linux kernel's sandboxed bytecode format — similar safety model.
  • Docker/OCI images: Container format — Wasm is increasingly seen as a lighter alternative.

Install this skill directly: skilldb add file-formats-skills

Get CLI access →