Skip to content

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.

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"]
Operand stack state for i32.const 3 · i32.const 4 · i32.add

Step by step:

  1. i32.const 3 — pushes the 32-bit integer 3. Stack: [3]
  2. i32.const 4 — pushes the 32-bit integer 4. Stack: [3, 4]
  3. i32.add — pops 4 and 3, adds them, pushes 7. Stack: [7]
  4. End of function — the validator sees one i32 on the stack, matching (result i32). The value 7 becomes the return value.

Wasm’s type system tracks the type of every slot on the stack. The four numeric types are:

TypeWidthDescription
i3232-bitInteger — used for booleans, pointers, and most counts
i6464-bitInteger — large numbers and 64-bit addresses
f3232-bitIEEE 754 single-precision float
f6464-bitIEEE 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.

The runner below executes an add function built exactly from the stack operations described above.

WebAssembly
What does `i32.add` do on the Wasm operand stack?
After executing `i32.const 3` followed by `i32.const 4`, how many values are on the operand stack?
What happens if the types on the stack do not match the instruction signature?