Understanding Globals in Wasm
This page answers one question: compiled WebAssembly modules contain globals — __stack_pointer, __heap_base, odd-looking exported values — and the
WebAssembly JavaScript API has a WebAssembly.Global type. You want to understand what globals are, how they differ from static variables in linear memory, how
compilers use them, and when your own code should use them.
Prerequisites
- [ ] Basic WAT reading skills.
- [ ] A compiled module to inspect (
wasm-tools printorwasm-objdump -x). - [ ] Familiarity with the JavaScript WebAssembly API.
Globals are typed values outside memory
A WebAssembly global holds a single value of a value type — i32, i64, f32, f64, v128 or a reference type — and is declared either immutable
((global $g i32 (i32.const 42))) or mutable ((global $g (mut i32) (i32.const 0))). Code reads it with global.get and, if mutable, writes it with
global.set. Globals do not live in linear memory: they have no address, cannot be read through pointers, and cannot be corrupted by out-of-bounds memory
writes. Engines usually keep them in a per-instance area and often in registers in hot code.
That distinguishes them from what C and Rust call “global variables”. A static mut COUNTER: u32 in Rust or a file-scope int counter; in C becomes a location
in linear memory (in the data or BSS region), accessed through loads and stores, because it can have its address taken. Wasm globals are used by compilers for a
few special values, and by hand-written modules for small amounts of state.
How compilers use globals
The most important compiler-generated global is __stack_pointer, a mutable i32 holding the current top of the shadow stack in linear memory. Functions that
need stack space subtract from it on entry and restore it on exit. Because it is a global — not a memory location — a buffer overflow in linear memory cannot
overwrite it directly. Linkers also define globals such as __heap_base, __data_end and __memory_base/__table_base (for position-independent code in
dynamic linking), and thread-local storage uses a global (__tls_base) per instance. Most of these are immutable or set once at startup.
(global $__stack_pointer (mut i32) (i32.const 1048576))
(func $uses_stack
(local $sp i32)
(local.set $sp (i32.sub (global.get $__stack_pointer) (i32.const 64)))
(global.set $__stack_pointer (local.get $sp))
;; ... use 64 bytes at $sp ...
(global.set $__stack_pointer (i32.add (local.get $sp) (i32.const 64))))
Initialisation with constant expressions
A global’s initial value is a constant expression: a constant, a global.get of an imported immutable global, a ref.null or ref.func, and — in newer
versions of the spec (extended constant expressions) — simple arithmetic such as i32.add of constants and imported globals. That last addition lets dynamic
linking compute addresses like __memory_base + offset at instantiation without running code.
Importing and exporting globals
Globals can be imported and exported, which makes them a way to share configuration between JavaScript and a module:
const debugLevel = new WebAssembly.Global({ value: "i32", mutable: true }, 1);
const { instance } = await WebAssembly.instantiate(bytes, { env: { debug_level: debugLevel } });
debugLevel.value = 3; // the module sees 3 on its next global.get
console.log(instance.exports.items_processed.value); // read an exported global
(import "env" "debug_level" (global $debug (mut i32)))
(global $items (export "items_processed") (mut i32) (i32.const 0))
Mutable imported and exported globals (the “mutable globals” feature, long since standard) are shared by reference: JavaScript and every instance that imports the
same WebAssembly.Global see the same value. Immutable globals are copied at instantiation.
When to use globals yourself
In hand-written WAT and small modules, globals are ideal for a handful of scalar values: a counter, a configuration flag, a pointer to a buffer. In compiled languages, you rarely create Wasm globals directly — the compiler decides — but you meet them when reading output, configuring linkers, or exporting values for JavaScript. For anything larger than a few scalars, or anything that needs an address, use linear memory.
Threads and globals
Each instance has its own globals, even when instances share a memory. In threaded programs, that is what makes __stack_pointer per-thread: every thread’s
instance has its own stack pointer pointing to its own stack region. It also means a global is not a way to share state between threads — use shared memory
and atomics for that.
Globals in dynamic linking
Position-independent code uses globals to find its data and functions. A side module compiled with -fPIC does not know where its static data or table
entries will end up, so it imports __memory_base and __table_base as immutable i32 globals and computes addresses relative to them; references to data
in other modules go through imported globals in a “global offset table” (GOT.mem and GOT.func imports), which the dynamic loader fills in at instantiation.
Reading a side module’s WAT, you therefore see many imported globals and address arithmetic such as global.get $__memory_base followed by i32.add. Extended
constant expressions let some of these computations happen in global initialisers rather than in code, which reduces startup work in dynamically linked programs.
Exported globals for diagnostics
Exporting a few mutable globals is a cheap way to expose diagnostics without function calls: a counter of processed items, the current phase of a long
operation, the last error code. JavaScript can read them at any time through WebAssembly.Global.value — even while a worker is busy, if the global is
shared appropriately — and a debug panel can poll them. Because reading a global requires no call into the module, it does not disturb the module’s execution
or allocate. For values that must be consistent with each other, though, read them together after a call returns, since there is no atomic snapshot of several
globals.
Tooling
wasm-objdump -x -j Global lists globals with types, mutability and initial values; wasm-tools print shows them in WAT. When a module fails to link with
“imported global type mismatch”, compare the declared type and mutability with what JavaScript provides — a mutable import requires a mutable
WebAssembly.Global, and plain numbers can only satisfy immutable imports.
A quick experiment
Write a three-line WAT module with a mutable exported global and a function that increments it, instantiate it in the browser console, call the function and
read exports.counter.value. Then import a WebAssembly.Global created in JavaScript instead, change its value from JavaScript and watch the module read it.
Five minutes of this makes the sharing semantics concrete.
Expected output
You can read (global $__stack_pointer (mut i32) ...) and explain its role, distinguish Wasm globals from static variables in linear memory, import and export
globals with WebAssembly.Global, explain why a memory overflow cannot directly overwrite the stack pointer, and choose globals for small scalar state in
hand-written modules.
Gotchas
- Expecting C globals to be Wasm globals. They live in linear memory. Look for loads and stores.
- Sharing state between threads with globals. Each instance has its own. Use shared memory.
- Mutating an immutable imported global. Validation fails. Declare
muton both sides. - Initialising with non-constant code. Only constant expressions are allowed.
- Assuming exported immutable globals update. They are fixed at instantiation.
- Passing a plain number for a mutable global import. Linking fails. Provide a mutable
WebAssembly.Global.
Performance note
global.get and global.set compile to a load or store from the instance’s globals area, and engines often keep hot globals in registers within a function;
accessing a static in linear memory costs a similar load but cannot be cached as aggressively across stores to memory, because any store might alias it.
Frequently Asked Questions
Can a global hold a reference?
Yes — externref and funcref globals hold references.
Can I take the address of a global? No — globals have no address. Copy the value into memory if you need a pointer.
Are globals visible in DevTools? DevTools shows module globals in the scope view when paused in Wasm code.
Do globals count against memory? No — they are separate from linear memory.
Why do side modules import so many globals?
Position-independent code finds its data and functions through __memory_base, __table_base and GOT globals filled in by the loader.
Can JavaScript read exported globals while the module runs?
Yes, through .value; read related globals together after a call for a consistent view.
What does “imported global type mismatch” mean? The type or mutability of the provided value does not match the module’s declaration.
How do I list a module’s globals?
wasm-objdump -x -j Global shows types, mutability and initial values.
Are extended constant expressions widely supported? They are in current engines and used by toolchains for dynamic linking; check your oldest supported engine if you rely on them.
Related
- Using global imports and exports — the JavaScript API.
- Understanding the shadow stack in linear memory — the stack pointer’s job.
- Exporting memory and globals from WAT — hand-written exports.
- Laying out a Wasm module’s memory map — linker globals.
← Back to Stack vs Heap Execution Model