Data Types and Stack Machine

a quickstart example

Every computation in WASM happens on the stack. For examples of stack-based languages see Forth. JVM or CLR are another examples of abstract stack machines.

As an example, subtracting two i32s (32-bit signed integers) looks like this:

;; the run-time stack: [...]
i32.const 3
;; [... 3], constant value 3 of type i32 is pushed on the stack
i32.const 2
;; [... 3 2]
i32.sub
;; [... 1] - the result 1 is left on the stack

This can also be writen in a more structured, folded form:

(i32.sub
  (i32.const 3)
  (i32.const 2))

Both formats translate into the same binary encoding:

41 03     ;; i32.const 3
41 02     ;; i32.const 2
6b        ;; i32.sub

Stacks are local to a function call.

(func $sub (result i32)
    (i32.sub (i32.const 3) (i32.const 2))
    ;; always returns 1 
)  

Here:

  • $sub gives this function an optional human-readable name, which is an alias to the number (index, idx) of this function in its module. (func (result i32) ...) is also valid.
  • if a function takes any parameters, each of them is listed after its name as (param $opt-arg-name i32) where i32 (or any other type) is the argument type. $opt-arg-name is again an optional human-readable name and an alias to the parameter index inside the function.
  • if a function returns a value, it should be specified as (result i32) (i32 or any other type).
  • the VM instructions follow

running a module

In order to be runnable from outside, functions must be wrapped in a module and export-ed from it:

(module 
  (func (export "sub") (result i32)
    (i32.sub (i32.const 3) (i32.const 2))
  ))

You can run this example with a WASM runtime like wasmtime, wasmer, wasm3 and many others. NodeJS/Firefox/Chrome also contain a built-in WASM runtime accessible via a Javascript API:

$ wasmtime run --invoke sub sub.wat
1

.wat files can be translated into binary modules using e.g. wasm-as from the binaryen utilities suite:

$ wasm-as sub.wat -o sub.wasm
$ xxd sub.wasm
00000000: 0061 736d 0100 0000 0105 0160 0001 7f03  .asm.......`....
00000010: 0201 0007 0701 0373 7562 0000 0a09 0107  .......sub......
00000020: 0041 0341 026b 0b                        .A.A.k.

or disassembled back into wat using wasm-dis:

$ wasm-dis sub.wasm
(module
 (type $0 (func (result i32)))
 (export "sub" (func $0))
 (func $0 (result i32)
  (i32.sub
   (i32.const 3)
   (i32.const 2)
  )
 )
)

We will look into the details of the binary encoding later.

The produced sub.wasm can also be transfered and executed somewhere, e.g. in NodeJS:

#!/usr/bin/env node

const fs = require('fs');

async function main(wasmPath, wasmFunc, ...args) {
    const wasmBuffer = fs.readFileSync(wasmPath);
    let wasmModule = await WebAssembly.instantiate(wasmBuffer);
    let func = wasmModule.instance.exports[wasmFunc];
    let result = await func(...args);
    console.log(result);
}

const process = require('process');
main(...process.argv.slice(2)).catch(e => {
    console.error(e);
    process.exit(1);
})
$ ./wasmrun.js sub.wasm sub
1

module validation

There's an invisible step that happens before running a module: module validation. WASM specification has a rich set of invariants that are statically verified for a module, e.g. all of the following fails validation:

  • setting a variable of one type to a value of another type without explicit casting.
  • referring to a variable that does not exist
  • trying to call a value as a function (unless it's a funcref)
  • having more than one value on the stack of a function when returning one value from it
  • stack underflow is statically impossible
  • ...

data types and operations

In the example above, we have already seen the data type i32 and instructions that work on it, like i32.const, i32.sub. What else is there?

TODO: numeric types

TODO: conversions between numerics types

TODO: funcref

TODO: externref

dropping values

If something produces a value that you don't need (e.g. a function called for its side effects, which also produces a value on the stack which can be ignored), it can be discarded from the stack with drop:

i32.const 42    ;; [ 42 ]
i32.const -1    ;; [ 42 -1 ]
drop            ;; [ 42 ]