Locals and Globals
function parameters and local variables
The quickstart example is rather useless, because it always returns 1. Let's do an actual computation by passing arguments to a function:
# compute (x - y)^2:
$ wasmtime run --invoke sqrsub sqrsub.wat 8 5
9
Every WASM function has a list of locals. Their list starts with param
s passed to the function, with (local <name>? <type>)
definitions after that.
Locals are referred to by their statically known index (e.g. 0
, 1
) or index alias (e.g. $x
, $y
). Locals can be loaded on the stack with local.get <idx>
and set to a value from stack with local.set <idx>
(this value is popped from the stack). The type of the value on the stack must match the type of the local, otherwise the module will be rejected during validation; i.e. (local $x i64) (local.set $x (f32.const 42))
does not pass module validation.
(module
;; compute (x-y)^2
(func (export "sqrsub") (param $x f64) (param $y f64) (result f64)
;; declare a local variable $diff of type f64
(local $diff f64)
;; assign a local:
(local.set $diff
(f64.sub (local.get $x) (local.get $y)))
;; return a value computed using a local:
(return (f64.mul (local.get $diff) (local.get $diff)))
)
)
The same module in a more low-level form reads as
(module
(func ;; (func 0)
(param f64) ;; local 0, $x above
(param f64) ;; local 1, $y above
(result f64)
(local f64) ;; local 2, $diff above
;; the stack: []
local.get 0 ;; [ $x ]
local.get 1 ;; [ $x $y ]
f64.sub ;; [ ($x-$y) ]
local.set 2 ;; []
local.get 2 ;; [ $diff ]
local.get 2 ;; [ $diff $diff ]
f64.mul ;; [ ($diff*$diff) ], one f64 return value
)
(export "sqrsub" (func 0))
)
Note that the WASM VM does not have a dup
opcode, an intermediate value must be saved into a local if it is used twice.
The locals of sqrsub
are:
- two function parameters with
<localidx>
0 and 1 (aliased to$x
and$y
in the first example) - one local variable with
<localidx>
2 (aliased to$diff
).
Functions themselves are indexed within a module, e.g. (func 0)
refers to the first function in the module.
This example can be optimized using local.tee
and reusing one of parameters as a variable:
(func ;; (func 0)
(param f64) ;; local 0, $x above
(param f64) ;; local 1, $y above
(result f64)
;; the stack: []
local.get 0 ;; [ $0 ]
local.get 1 ;; [ $0 $1 ]
f64.sub ;; [ ($0-$1) ]
local.tee 0 ;; set `local 0` to the value, but also keep it on the stack
local.get 0 ;; [ $0 $0 ]
f64.mul ;; [ ($0*$0) ], one f64 return value
)
globals
Modules can have their own top-level constants, globals. Globals have their own index namespace (i.e. globalidx=0, localidx=0, funcidx=0 all refer to different things).
(module
(global ;; a declaration of a global
$the-answer ;; an alias for globalidx=0
i32 ;; its (constant) type
(i32.const 42) ;; its initializer expression
)
(func (export "the_answer") (result i32)
(global.get $the-answer)) ;; push its value on stack
)
Values of globals can be pushed on stack using global.get <idx>
.
$the-answer
is a constant: it's initialized once and cannot change its value. Only a restricted set of instructions for constant expressions is allowed in an initializer context.
mutable globals
Globals can be mutable globals when their type is specified with (mut <type>)
. Their value can be popped from stack and set with global.set <idx>
.
For example:
(module
(global $call-times (mut i32)
(i32.const 0))
;; increment the counter on every call
(func (export ("call_me"))
(global.set $call-times
(i32.add
(i32.const 1)
(global.get $call-times))))
;; get the counter
(func (export "stats") (return i32)
(global.get $call-times))
)
TODO: more about initialization
stack validation
All WASM instructions have statically known effects on the stack. This means that:
- instructions always pop a known number of values
- instructions always push a known number of values
- it's possible to know the depth of the stack at each instruction
- it's possible to know the maximum depth of the stack during a function call
- stacks for then/else branches of
if
must be balanced. - loops cannot grow stack dynamically; linear memory must be used for that instead.
TODO: param/result notation
stack tricks
Some neat stack-based tricks apply.
swapping two values
local.get $a ;; [ $a ]
local.get $b ;; [ $a $b ]
local.set $a ;; [ $a ]
local.set $b ;; [ ]