How to Grow WebAssembly Memory Dynamically
WebAssembly (Wasm) operates on a linear memory model that can be resized during runtime to accommodate growing data needs. This article explains how to dynamically increase the memory allocated to a WebAssembly instance using both the JavaScript host API and internal WebAssembly instructions, while addressing key development considerations like buffer invalidation.
Understanding Wasm Memory Pages
Before growing memory, it is important to understand how WebAssembly measures memory. Wasm memory is allocated in structured units called pages. One page is exactly 64 Kibibytes (KiB), or 65,536 bytes. When you grow memory, you must request additional pages rather than individual bytes.
Method 1: Growing Memory via JavaScript (Host)
If you control the WebAssembly instance from a JavaScript host
environment, you can grow the memory dynamically using the
WebAssembly.Memory.prototype.grow() method. This method
accepts the number of pages you want to add as an argument.
// Create Wasm memory with 1 initial page (64 KiB) and a maximum of 10 pages
const memory = new WebAssembly.Memory({ initial: 1, maximum: 10 });
// Grow the memory by 2 pages (128 KiB)
memory.grow(2);
// The memory is now 3 pages total (192 KiB)
console.log(memory.buffer.byteLength); // Outputs: 196608Method 2: Growing Memory within Wasm (Guest)
WebAssembly instructions can also trigger memory growth from inside
the compiled binary. The memory.grow instruction takes the
number of pages to add from the stack and returns the previous memory
size (in pages), or -1 if the allocation fails (for
example, if it exceeds the specified maximum limit).
In WebAssembly Text Format (WAT), the instruction is implemented like this:
(module
(memory 1)
(func $grow_memory (param $pages_to_add i32) (result i32)
local.get $pages_to_add
memory.grow
)
(export "growMemory" (func $grow_memory))
)
Compilers for high-level languages like Rust, C, and C++ abstract
this instruction. When you call memory allocation functions like
malloc() in C, or resize vectors in Rust, the languageās
allocator automatically executes the memory.grow
instruction under the hood when the existing heap is exhausted.
Crucial Caveat: ArrayBuffer Invalidation
When WebAssembly memory grows, the underlying JavaScript
ArrayBuffer is internally detached and a new, larger
ArrayBuffer is created in a new memory address space.
If you have reference views to the Wasm memory in your JavaScript
code (such as Uint8Array or Float32Array),
those references become invalid after a growth event. Attempting to use
them will throw a TypeError. You must re-create these views
whenever memory is grown:
let view = new Uint8Array(memory.buffer);
// Memory grows, invalidating the 'view' variable
memory.grow(1);
// You must re-assign the view to the new buffer
view = new Uint8Array(memory.buffer);