WebAssembly Global Variables and How to Mutate Them
WebAssembly (Wasm) global variables are accessible across different functions within a Wasm module and can even be shared between the module and its host environment, such as JavaScript. This article explores what Wasm global variables are, how they are defined, and the specific mechanisms required to mutate (change) their values both inside WebAssembly and from the host environment.
What Are WebAssembly Global Variables?
In WebAssembly, a global variable is a storage location that holds a
single numeric value of a specific primitive type: i32,
i64, f32, or f64. Global
variables are declared at the module level, meaning they exist outside
the scope of individual functions.
Wasm globals serve two primary purposes: 1. Internal State: Keeping track of module-wide state that multiple functions need to read or write. 2. Host Sharing: Sharing configuration, pointers, or state between the WebAssembly module and the host environment (like a web browser or Node.js).
By default, global variables in WebAssembly are immutable (read-only constants). To make a global variable changeable, you must explicitly declare it as mutable.
Defining Globals in WebAssembly Text Format (WAT)
To understand how globals work, it is helpful to look at how they are defined in WebAssembly Text Format (WAT).
Immutable Global
An immutable global is defined with a type and an initializing expression:
(global $MAX_CONNECTIONS i32 (i32.const 100))
Mutable Global
A mutable global wraps the type in a mut keyword:
(global $visitor_count (mut i32) (i32.const 0))
How to Mutate Wasm Globals Inside WebAssembly
Within a WebAssembly module, global variables are manipulated using
two specific instructions: global.get and
global.set.
global.get: Pushes the current value of the specified global variable onto the stack.global.set: Pops the top value off the stack and writes it to the specified global variable.
Example: Mutating a Global in WAT
Below is a simple Wasm function that increments a mutable global
variable named $counter by 1:
(module
;; Define a mutable global variable
(global $counter (mut i32) (i32.const 0))
;; Function to increment the global
(func (export "increment")
;; Load the current value of $counter onto the stack
global.get $counter
;; Load the constant 1 onto the stack
i32.const 1
;; Add the two values
i32.add
;; Pop the result and write it back to $counter
global.set $counter
)
)
If you attempt to use global.set on an immutable global
(one declared without mut), the WebAssembly module will
fail to validate and compile.
How to Mutate Wasm Globals from JavaScript
WebAssembly globals can be exported to the JavaScript host
environment or imported from it. To interact with Wasm globals in
JavaScript, the WebAssembly.Global API is used.
Creating and Mutating Globals in JavaScript
You can create a mutable global in JavaScript and pass it to a WebAssembly module during instantiation:
// Create a mutable i32 global initialized to 10
const jsGlobal = new WebAssembly.Global({ value: 'i32', mutable: true }, 10);
// Mutate the global variable from JavaScript
jsGlobal.value = 20;
console.log(jsGlobal.value); // Outputs: 20Mutating Exported Globals
If a WebAssembly module exports a mutable global, JavaScript can
access and mutate it directly through the module’s exports
object:
const importObject = {};
WebAssembly.instantiateStreaming(fetch('module.wasm'), importObject)
.then(obj => {
// Access the exported mutable global
const counter = obj.instance.exports.counter;
// Read the value
console.log(counter.value);
// Mutate the value from JavaScript
counter.value = 42;
// Call a Wasm function that uses this updated global
obj.instance.exports.printCounter();
});If the exported global is immutable (not declared with
mut in the Wasm code), attempting to assign a new value to
global.value in JavaScript will throw a
TypeError.