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: 196608

Method 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);