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