Control Flow

WebAssembly has structured control flow.

Unlike native CPU instructions which are unrestricted in their jumps, control flow in WebAssembly is always expressed in function calls and blocks inside a function.

nop and unreachable

The unreachable instruction aborts, or traps, the execution of a WASM module. When unreachable instruction is executed, the host of the WASM environments gets something similar to an exception in OOP-style languages or panic!() in Rust:

unreachable    ;; bye WASM world, the host must handle this.

Handling this situation is up to the host environment and is outside of the WASM spec. The host environment can report the failure to the user; if this is a breakpoint, it can patch back the real instruction and resume execution; etc.

Helpfully, unreachable is encoded as 0x00 in binary, so any zero-initialized memory is full of unreachable when (mis)executed.

Another occasionally useful instruction is nop. It does not do anything and can be used as padding to a nicer address or a placeholder for other commands written in its place later.

nop    ;; just blank space, nothing to see here, go ahead
nop
;; actually useful instructions

blocks and branching

Jumps (i.e. non-sequential execution of instructions) inside a function are called branching in WASM and always happen to (maybe nested) blocks. Branching instruction names usually start with br.

Unconditional branching is done with the br <blockidx> instruction.

The simplest kind of block is introduced by block and end WASM instructions (indentation is not significant here):

block
  br 0         ;; jump to the end of the innermost, "0th" block
  unreachable  ;; any instructions until `end` are unreachable
end
;; execution continues from here

A more high-level, folded representation uses parenthesis and omits end:

(block
  br 0
  unreachable)
;; the jump destination is at the end of the `block`

nested blocks

When nested, blocks are indexed by their distance from the current instruction: the innermost current block has index 0, its outer block has index 1, the next outer block is indexed 2, and so on and so forth.

(block      ;; 1st
  (block    ;; 0th
    (br 1)  ;; skip one level of blocks, jump to the end
    ;; unreachable
    )
    ;; also unreachable
    (br 0)  ;; would jump out one level up
  )
;; jumps here

Blocks can be given aliases that can be used by branching instructions. For example:

(block $outer       ;; blockidx 1
  (block $inner     ;; blockidx 0
    (br $outer)
    unreachable)
  unreachable)
;; to here

block results

If a block leaves something on the stack, this must be declared as a (result <type>) just after block:

;; stack before: [...]
(block (result i32)
  i32.const 42
  br 0
  unreachable)
;; stack after: [... 42]

TODO: more than one result

TODO: stack validation

conditional branching

The only thing that the previous example can do is to introduce dead code which cannot be executed. Let's do something useful, e.g. TODO

TODO: if/else/end, (if ... (then ...) (else ...)), select

TODO: br_if

loops

Another kind of blocks is loop. Unlike block, jump destination is at the start of the loop:

;; this is an infinite loop
(loop
  (br 0)        ;; continue
  unreachable)

TODO: a simple counter loop example

TODO: while loop example

TODO: break and continue example

TODO: call and return

TODO: tables and call_indirect

TODO: the tailcalls extension