The Stack Machine Model
WebAssembly is a stack machine. There are no named general-purpose registers like eax or r0. Instead, every instruction operates on an implicit operand stack: some instructions push values onto it, others pop values off and push a result. The validator can check the entire stack type at compile time, which is why Wasm is both safe and fast to compile to native register-based code.
Instructions Are Stack Operations
Section titled “Instructions Are Stack Operations”Every Wasm instruction falls into one of three roles:
- Producers — push a value onto the stack (e.g.
i32.const 7,local.get $x) - Consumers — pop one or more values and push a result (e.g.
i32.add,i32.mul) - Side-effectful — pop values and do something without pushing (e.g.
local.set $x,drop)
At the end of a function body, exactly the values declared in (result ...) must remain on the stack. The validator enforces this statically — if the types do not match, the module is rejected before a single byte executes.
Walking Through i32.const 3 + i32.const 4 + i32.add
Section titled “Walking Through i32.const 3 + i32.const 4 + i32.add”The simplest demonstration is addition. The WAT for “return 3 + 4” looks like this:
(module (func (export "addThreeAndFour") (result i32) i32.const 3 i32.const 4 i32.add))Here is what happens to the operand stack at each step:
flowchart TD A["Stack: []\n> i32.const 3"] --> B["Stack: [3]\n> i32.const 4"] B --> C["Stack: [3, 4]\n> i32.add"] C --> D["Stack: [7]\n<- return value"]
Step by step:
i32.const 3— pushes the 32-bit integer3. Stack:[3]i32.const 4— pushes the 32-bit integer4. Stack:[3, 4]i32.add— pops4and3, adds them, pushes7. Stack:[7]- End of function — the validator sees one
i32on the stack, matching(result i32). The value7becomes the return value.
Types on the Stack
Section titled “Types on the Stack”Wasm’s type system tracks the type of every slot on the stack. The four numeric types are:
| Type | Width | Description |
|---|---|---|
i32 | 32-bit | Integer — used for booleans, pointers, and most counts |
i64 | 64-bit | Integer — large numbers and 64-bit addresses |
f32 | 32-bit | IEEE 754 single-precision float |
f64 | 64-bit | IEEE 754 double-precision float |
An instruction like i32.add requires both operands to be i32. Attempting to add an i32 and an f64 is a validation error — rejected at load time, not at runtime.
Try It Live
Section titled “Try It Live”The runner below executes an add function built exactly from the stack operations described above.