Skip to content

Locals and the Stack

WebAssembly is a stack machine. There is no register file like in x86 or ARM — instead, instructions communicate by pushing and popping values on an implicit value stack. Understanding this model is the key to reading WAT fluently.

Every instruction operates on the top of the stack. An instruction pops its inputs and pushes its output. For example, i32.add pops two i32 values and pushes one i32 result.

flowchart LR
  A["Stack: []\ni32.const 10"] --> B["Stack: [10]\ni32.const 3"]
  B --> C["Stack: [10, 3]\ni32.add"]
  C --> D["Stack: [13]"]
Value stack during i32.const 10 / i32.const 3 / i32.add

At the end of a function, whatever is left on the stack must exactly match the declared result types. Leave too much or too little and the module fails validation.

Besides the stack, functions can declare local variables using (local $name type). These are mutable slots that live for the duration of the function call.

(module
(func (export "example") (param $x i32) (result i32)
(local $tmp i32)
local.get $x ;; push $x
i32.const 1
i32.add ;; pop two, push sum
local.set $tmp ;; pop and store in $tmp
local.get $tmp)) ;; push $tmp back

Parameters are also locals — you can read them with local.get just like declared locals, but they are initialized with the call arguments.

local.tee is like local.set but it leaves a copy of the value on the stack after storing. It is useful when you need to both save a value and immediately use it:

(module
(func (export "storeAndDouble") (param $n i32) (result i32)
(local $saved i32)
local.get $n
local.tee $saved ;; stores $n in $saved AND leaves the value on stack
i32.const 2
i32.mul)) ;; doubles the value that tee left behind

Without local.tee you would need local.set followed by local.get, which is two instructions instead of one.

WebAssembly
What does `local.tee $x` do?
In a stack machine, what happens when `i32.add` executes?
Where must `(local $x i32)` appear in a function?