Introduction
WebAssembly is an instruction set for a machine that does not exist. No chip runs it. Browsers and other hosts take it in, check it, and translate it into the native code of whatever processor they are running on. What makes it unusual is how much of that process the format was designed around. A module can be validated in one pass. It can be compiled while it is still downloading. Running it cannot corrupt the host, and nearly everything it does takes exactly one meaning on every engine.
If you are writing a compiler, that is a remarkable target. You get one
output format that runs in every browser at close to native speed, inside a
sandbox you do not have to build, with no garbage collector in the way
unless you ask for one. In exchange you give up some things a native target
lets you do. There is no goto. You cannot see the call stack. You cannot
take the address of a local variable. Memory is a single array of bytes
whose index 0 is a perfectly good address. Most of this book is about what
those constraints are, why they are there, and how a compiler works with
them.
#Who this book is for1.1
This book is for programmers who want to generate WebAssembly: people writing a compiler, a JIT, a code generator for a game's scripting language, or a backend for an existing language. It assumes you can read C, Rust or JavaScript, that you know roughly what a compiler does, and that you have at least a passing familiarity with how a native program uses registers, a stack and a heap.
It does not assume you have written a line of WebAssembly. It starts from an empty module and builds up, one idea at a time, to the parts that matter most to a compiler writer: how control flow must be shaped, how a language's data is laid out in linear memory, how the binary encoding works byte by byte, how a module talks to the JavaScript engine around it, and how to test that what your compiler emits is right.
The running motivation throughout is a compiler whose output runs inside a game engine in the browser. That is where WebAssembly's strengths matter most. A game needs predictable frame times, so it wants no pauses for garbage collection. It wants tight numeric code, so it wants SIMD. It wants to call into the browser's graphics and audio APIs many times a frame, so it cares about what crossing the boundary costs. None of the book is a tutorial for a particular engine, though, and nothing in it depends on one.
#What WebAssembly is, and is not1.2
It is easy to get the wrong picture of WebAssembly from its name, so here is a short list of what it actually is.
It is a virtual instruction set, not a language. You can write it by hand in the text format, and this book does a great deal of that, but it was designed to be produced by compilers. There are no variables beyond numbered locals, no expressions beyond what fits on a stack, and no types beyond a few numbers and references.
It is a stack machine on paper and a register machine in practice. Instructions take their operands from an implicit stack and push results back onto it. That makes the encoding small. But the stack's shape at every instruction is known before the code runs, so engines turn it into registers when they compile. Nothing is pushed or popped at runtime.
It is structured. Control flow is made of nested blocks, loops and
ifs, with branches that can only leave a block or restart a loop. That
is what makes single-pass validation and compilation possible, and it is
the biggest single difference from targeting a native ISA.
It is sandboxed. A module can touch only its own memories, tables and globals, and whatever its host chose to give it. Every memory access is checked against the memory's size. Every indirect call is checked against the expected signature. Code is never stored in the memory a program can write, so a program cannot read or rewrite its own code.
It is portable and nearly deterministic. Integer arithmetic wraps the same way everywhere. Floating point is IEEE 754 with round-to-nearest. The few places where engines may differ, such as the bit patterns of NaNs and the results of the "relaxed" SIMD instructions, are listed and narrow.
It is not a JavaScript replacement and not a VM with a runtime. There is no standard library, no I/O, no threads beyond what the host gives you, no exceptions beyond what you declare, and (unless you use the garbage collection feature) no objects. Anything a module does to the outside world goes through a function the host imported into it.
#How the book is organised1.3
The book has eight parts. They are meant to be read in order the first time, since each one leans on the ones before it. Later, the chapters work as a reference.
Part I, Getting Started, writes and runs the first modules and covers the core machine: the operand stack, the four number types, and functions.
Part II, Structure, covers structured control flow, how to compile the control flow of a real language into it (including the control flow WebAssembly does not have), and the module as a whole: its imports, exports and index spaces.
Part III, Memory and Indirection, covers linear memory, how a compiler lays out a language's data inside it, the shadow stack, and the tables and globals that make function pointers and virtual calls possible.
Part IV, The Binary Format, takes the format apart byte by byte. By the end you will have written a module in hex, and a small encoder that emits modules that run.
Part V, The Host, is about the JavaScript side: the WebAssembly
object, how values cross the boundary, how to pass strings and arrays and
callbacks, and what happens when a module traps.
Part VI, Beyond the First Version, covers the features added since WebAssembly 1.0 that matter to a compiler: SIMD, exception handling, tail calls, garbage-collected structs and arrays, and threads.
Part VII, Compiling to WebAssembly, pulls it together into the design questions you face when mapping a language onto WebAssembly, and how engines turn your output into machine code, which tells you what is fast.
Part VIII, Testing, covers the spec's own test format, and builds up a strategy for testing a compiler: validation, execution tests, golden files, differential testing across engines, and fuzzing, all on top of existing tools.
Three appendices summarise the instructions, the tools, and where to read further.
#The tools1.4
Everything in this book runs on freely available tools. You will meet each of them properly when it first becomes useful.
| Tool | Where it comes from | Used for |
|---|---|---|
| Node.js 22 | nodejs.org | running modules in V8, the engine inside Chrome |
| wabt | npm install wabt |
wat2wasm, wasm2wat, wasm-objdump, wasm-interp, wasm-validate |
| Binaryen | npm install binaryen |
wasm-opt, the optimiser most toolchains use |
| Wasmtime | pip install wasmtime |
a second, independent engine, and the wat crate's assembler |
| wasm-tools | cargo install wasm-tools |
the Bytecode Alliance's Swiss army knife (optional) |
The npm packages are wabt and Binaryen compiled to WebAssembly themselves, so they need nothing but Node. If you prefer native builds, every tool here also has release binaries on GitHub, and the commands are the same.
To follow along, install Node 22 or later, then in an empty directory:
npm install wabt binaryen
pip install wasmtime
and put node_modules/.bin on your PATH.
#How the examples are checked1.5
Every example in this book is run when the book is built, and the build fails if any of them stops matching what the book says it does. A module shown with output below it was assembled, validated, run in Node, and produced exactly that output. A module shown with an error below it really was rejected with that message. A command shown with output really printed it.
The modules are run by a small host, about eighty lines of JavaScript, that gives each module a few functions to print with. Chapter 2 shows exactly what it does, so that nothing in the examples is magic, and Chapter 27 explains how the checking itself works. That checking is a miniature version of the test harness Part VIII recommends for a compiler.
#Notes in the margin1.6
Some paragraphs are set off in boxes like this one:
#A note on versions1.7
WebAssembly is versioned by the specification. Version 1.0 (2017–2019) is often called the MVP, the minimum viable product. Version 2.0 (2022) added features that had shipped in browsers since, such as bulk memory operations, multiple results, reference types and SIMD. Version 3.0 (2025) added garbage collection, typed function references, exception handling, tail calls, 64-bit memories, multiple memories and relaxed SIMD.
Everything in Parts I to V is in WebAssembly 1.0 or 2.0 and runs in every current browser. Part VI covers the 3.0 features, and Chapter 18 says how to detect, at runtime, which ones the engine you are running on supports. Every example in the book runs in Node 22, which uses the same V8 engine as Chrome.