Tables, Globals and Indirect Calls

9.1 Tables and call_indirect

Tables store references (not raw addresses), enabling function pointers safely. call_indirect checks the signature at runtime.

A wrong signature or an out-of-range index traps. C/C++ function pointers and virtual methods compile to call_indirect.

Table instructions: table.get, table.set, table.size, table.grow, table.fill, table.copy, table.init, elem.drop.

(module
  (type $op (func (param i32 i32) (result i32)))
  (table $ops 3 funcref)
  (elem (table $ops) (i32.const 0) func $add $sub $mul)   ;; active element segment

  (func $add (type $op) (i32.add (local.get 0) (local.get 1)))
  (func $sub (type $op) (i32.sub (local.get 0) (local.get 1)))
  (func $mul (type $op) (i32.mul (local.get 0) (local.get 1)))

  (func (export "apply") (param $index i32) (param $a i32) (param $b i32) (result i32)
    (call_indirect $ops (type $op)
      (local.get $a) (local.get $b) (local.get $index))))

9.2 Globals

Compilers use a mutable global as the stack pointer (__stack_pointer) for the shadow stack in linear memory.

(module
  (global $pi f64 (f64.const 3.14159))                 ;; immutable
  (global $calls (export "calls") (mut i32) (i32.const 0))
  (import "env" "seed" (global $seed i32))
  (func (export "tick") (result i32)
    (global.set $calls (i32.add (global.get $calls) (i32.const 1)))
    (global.get $calls)))
const g = new WebAssembly.Global({ value: "i32", mutable: false }, 42);
const { instance } = await WebAssembly.instantiate(bytes, { env: { seed: g } });
instance.exports.tick();
console.log(instance.exports.calls.value); // 1