Inside WebAssembly

The format, the machine and the host, for people who compile to it

Written for WebAssembly 3.0, checked against Node 22, wabt 1.0.39 and Wasmtime 50, November 2026

A book about WebAssembly for programmers who want to generate it: the stack machine, the module structure, the binary encoding, the JavaScript host, the post-MVP features, and how to test a compiler that targets it. Every example in it is assembled, validated and run as part of building the book, and the output shown is the output the tools gave.

Download

Contents

  1. Part IGetting Started

    The first five chapters set up the machine. You will write modules by hand in the text format, run them in the same engine a browser uses, and learn the handful of ideas everything else is built on: a typed operand stack, four number types whose signedness lives in the instructions, and functions whose call stack you cannot see.

  2. 1Introduction
    1. 1.1Who this book is for
    2. 1.2What WebAssembly is, and is not
    3. 1.3How the book is organised
    4. 1.4The tools
    5. 1.5How the examples are checked
    6. 1.6Notes in the margin
    7. 1.7A note on versions
  3. 2Hello, WebAssembly
    1. 2.1The smallest module
    2. 2.2A function that returns a number
    3. 2.3Turning text into a binary
    4. 2.4Running a module from JavaScript
    5. 2.5Saying hello
    6. 2.6The book's host
    7. 2.7What you have seen
  4. 3A Stack Machine
    1. 3.1Pushing and popping
    2. 3.2Watching the stack
    3. 3.3The folded form
    4. 3.4Locals
    5. 3.5drop and select
    6. 3.6The stack is checked before anything runs
    7. 3.7The stack does not exist
    8. 3.8What you have seen
  5. 4Numbers
    1. 4.1Integers have no sign
    2. 4.2Wrapping and trapping
    3. 4.3Small integers
    4. 4.464-bit integers
    5. 4.5Floating point
    6. 4.6Conversions
    7. 4.7What you have seen
  6. 5Functions
    1. 5.1Signatures and calls
    2. 5.2Recursion
    3. 5.3Multiple results
    4. 5.4Types and the type section
    5. 5.5The function index space
    6. 5.6The invisible call stack
    7. 5.7Stack overflow
    8. 5.8What you have seen
  7. Part IIStructure

    Native code jumps wherever it likes. WebAssembly code cannot: its control flow is nested blocks and loops, and a branch can only leave a block or restart a loop. The first two chapters of this part show how that works and how a compiler fits a language's control flow into it, including the control flow that does not fit naturally. The third steps back to look at a module as a whole: what it can contain, how its parts are numbered, and how it connects to the outside world.

  8. 6Structured Control Flow
    1. 6.1block and br
    2. 6.2loop
    3. 6.3if
    4. 6.4Blocks with results
    5. 6.5br_table
    6. 6.6Compiling loops
    7. 6.7Short-circuit operators
    8. 6.8How validation handles control flow
    9. 6.9What you have seen
  9. 7Control Flow Graphs
    1. 7.1Why there is no goto
    2. 7.2You may not need a graph
    3. 7.3Reducible graphs
    4. 7.4From a graph to blocks
    5. 7.5The Relooper
    6. 7.6Irreducible control flow
    7. 7.7What you have seen
  10. 8Modules and Instances
    1. 8.1What a module holds
    2. 8.2Imports and exports
    3. 8.3Modules and instances
    4. 8.4Linking modules together
    5. 8.5Instantiation, step by step
    6. 8.6The start function
    7. 8.7What you have seen
  11. Part IIIMemory and Indirection

    A WebAssembly function can keep values in locals and on the operand stack, but anything with an address lives in linear memory: one flat array of bytes per memory, bounds-checked on every access. This part covers the instructions that use it, how a compiler lays out a language's data and its stack inside it, and the two other kinds of state a module has: globals, and the tables that make indirect calls possible.

  12. 9Linear Memory
    1. 9.1Declaring a memory
    2. 9.2Loads and stores
    3. 9.3Offsets and alignment
    4. 9.4Out of bounds
    5. 9.5Growing memory
    6. 9.6Data segments
    7. 9.7Filling and copying
    8. 9.8Memory from JavaScript
    9. 9.9Multiple memories and 64-bit memories
    10. 9.10What you have seen
  13. 10Laying Out Memory
    1. 10.1A memory map
    2. 10.2The shadow stack
    3. 10.3Struct layout
    4. 10.4Passing and returning aggregates
    5. 10.5A bump allocator
    6. 10.6Null pointers
    7. 10.7Strings, arrays and slices
    8. 10.8What you have seen
  14. 11Globals and Tables
    1. 11.1Globals
    2. 11.2Globals shared with the host
    3. 11.3Tables and indirect calls
    4. 11.4Virtual calls
    5. 11.5Closures
    6. 11.6Tables as runtime state
    7. 11.7Host references
    8. 11.8What you have seen
  15. Part IVThe Binary Format

    Everything so far has been written in the text format and turned into bytes by an assembler. A compiler usually skips the text and writes the bytes itself, which is simpler than it sounds: the binary format was designed to be easy to produce and fast to decode. This part goes through it from the first byte to the last, writes a module in hex by hand, builds a small encoder, and covers the optional sections that carry names and debugging information.

  16. 12Bytes on the Wire
    1. 12.1The preamble
    2. 12.2LEB128
    3. 12.3Vectors and names
    4. 12.4Sections
    5. 12.5Types
    6. 12.6Imports, functions and exports
    7. 12.7A module by hand
    8. 12.8Letting a tool count for you
    9. 12.9What you have seen
  17. 13Encoding Instructions
    1. 13.1Function bodies
    2. 13.2Opcodes and immediates
    3. 13.3Block types
    4. 13.4Memory immediates
    5. 13.5Prefixed opcodes
    6. 13.6An encoder
    7. 13.7Things a real encoder adds
    8. 13.8What you have seen
  18. 14Custom Sections and Debugging
    1. 14.1The format
    2. 14.2The name section
    3. 14.3Names in stack traces
    4. 14.4Producers and features
    5. 14.5Source maps and DWARF
    6. 14.6Debugging in the browser
    7. 14.7What you have seen
  19. Part VThe Host

    A WebAssembly module on its own can compute, but it cannot see or do anything: every effect goes through its host. In a browser, the host is the JavaScript engine. This part covers the JavaScript API for compiling and instantiating modules, the rules for converting values at the boundary, the patterns for moving strings, arrays and callbacks across it efficiently, and what happens when a module traps.

  20. 15The JavaScript API
    1. 15.1Compiling and instantiating
    2. 15.2Looking inside a module
    3. 15.3Exported functions
    4. 15.4Imported functions
    5. 15.5Converting values
    6. 15.6Memory, Table and Global objects
    7. 15.7Errors
    8. 15.8A minimal loader
    9. 15.9What you have seen
  21. 16Across the Boundary
    1. 16.1Strings out
    2. 16.2Strings in
    3. 16.3Bulk data without copying
    4. 16.4Batching: the command buffer
    5. 16.5Calling back into WebAssembly
    6. 16.6Designing the interface
    7. 16.7What you have seen
  22. 17Traps
    1. 17.1Everything that traps
    2. 17.2After a trap
    3. 17.3Traps and the language you are compiling
    4. 17.4What you have seen
  23. Part VIBeyond the First Version

    WebAssembly 1.0 was deliberately minimal. Everything in this part came later: SIMD for numeric code, exception handling, tail calls, garbage collected structs and arrays, and shared-memory threads. Each one changes what a compiler can emit, and some change how a whole language should be mapped onto WebAssembly. The part starts with how to find out which of them the engine you are running on supports.

  24. 18Versions and Feature Detection
    1. 18.1The versions
    2. 18.2Support in browsers
    3. 18.3Why feature detection matters
    4. 18.4Detecting features
    5. 18.5Telling tools which features you use
    6. 18.6What you have seen
  25. 19SIMD
    1. 19.1One type, many shapes
    2. 19.2The instructions
    3. 19.3Vector maths
    4. 19.4Structure of arrays
    5. 19.5SIMD and the boundary
    6. 19.6Relaxed SIMD
    7. 19.7What you have seen
  26. 20Exceptions
    1. 20.1Tags and throw
    2. 20.2try_table and catch
    3. 20.3exnref and rethrowing
    4. 20.4Exceptions and JavaScript
    5. 20.5Compiling try and catch
    6. 20.6The legacy instructions
    7. 20.7What you have seen
  27. 21Tail Calls
    1. 21.1return_call
    2. 21.2The rules
    3. 21.3What tail calls make possible
    4. 21.4What you have seen
  28. 22Garbage Collection
    1. 22.1Struct types
    2. 22.2Nullable and non-null references
    3. 22.3Arrays
    4. 22.4The type hierarchy
    5. 22.5Subtyping and casts
    6. 22.6Recursive types
    7. 22.7i31ref
    8. 22.8GC objects and JavaScript
    9. 22.9Should your language use it?
    10. 22.10What you have seen
  29. 23Threads and Atomics
    1. 23.1Shared memory
    2. 23.2Atomic instructions
    3. 23.3Threads counting together
    4. 23.4Waiting and waking
    5. 23.5What is per thread
    6. 23.6What you have seen
  30. Part VIICompiling to WebAssembly

    The earlier parts described the machine one feature at a time. This part looks at it from the compiler's side. The first chapter goes through the decisions involved in mapping a language onto WebAssembly, from scalar types to calling conventions to coroutines, with a worked example. The second explains how engines turn a module into machine code, and what that means for the code your compiler should emit.

  31. 24Mapping a Language onto WebAssembly
    1. 24.1The decisions
    2. 24.2Scalars
    3. 24.3Aggregates and unions
    4. 24.4Calling convention
    5. 24.5Generics
    6. 24.6Control flow and the call stack
    7. 24.7Runtime
    8. 24.8A worked example
    9. 24.9What you have seen
  32. 25Performance
    1. 25.1How engines compile
    2. 25.2What the engine does for you
    3. 25.3What it does not
    4. 25.4Binaryen's optimiser
    5. 25.5Code size and start-up
    6. 25.6The cost of the boundary
    7. 25.7Memory64 and other costs
    8. 25.8Practical rules
    9. 25.9What you have seen
  33. Part VIIITesting

    A compiler that emits WebAssembly has more ways to be wrong than most programs: it can write malformed bytes, invalid code, valid code that computes the wrong thing, or correct code that one engine runs differently from another. The good news is that the WebAssembly ecosystem has excellent tools for catching each kind of mistake. This part covers the specification's own test format, then builds a layered testing strategy for a compiler on top of existing tools.

  34. 26The Spec's Test Format
    1. 26.1A first script
    2. 26.2The commands
    3. 26.3Running scripts
    4. 26.4How the book's runner works
    5. 26.5Using WAST in a compiler's tests
    6. 26.6What you have seen
  35. 27Testing a Compiler
    1. 27.1The layers
    2. 27.2Layer 1: validate everything
    3. 27.3Layer 2: golden output
    4. 27.4Layer 3: execution tests
    5. 27.5Layer 4: differential testing
    6. 27.6Layer 5: fuzzing
    7. 27.7Layer 6: the browser
    8. 27.8Performance tests
    9. 27.9A case study: this book
    10. 27.10What you have seen
  36. AInstruction Reference
    1. A.1Control
    2. A.2Parametric and variables
    3. A.3Tables
    4. A.4Memory
    5. A.5Constants
    6. A.6i32 operations
    7. A.7i64 operations
    8. A.8f32 operations
    9. A.9f64 operations
    10. A.10Conversions
    11. A.11References
    12. A.12Garbage collection
    13. A.13SIMD (a selection)
    14. A.14Threads (a selection)
  37. BTools
    1. B.1wabt
    2. B.2Binaryen
    3. B.3wasm-tools
    4. B.4Wasmtime
    5. B.5Node and V8
    6. B.6This book's scripts
  38. CFurther Reading
    1. C.1The specification
    2. C.2Papers
    3. C.3Compilers to read
    4. C.4Books

The diagrams, text and examples are original. The fonts are Lora, JetBrains Mono and Patrick Hand, under the SIL Open Font License; their licences are in fonts/.