JavaScript Views on WebAssembly Linear Memory
This article explores how JavaScript interacts with WebAssembly
linear memory using TypedArray views and DataView wrappers.
WebAssembly operates within an isolated, contiguous block of raw memory
that JavaScript can inspect, manipulate, and share via standard
ArrayBuffer references without incurring data copying
overhead.
The Structure of WebAssembly Memory
WebAssembly memory is exposed to both the host environment
(JavaScript) and the WebAssembly runtime as an instance of
WebAssembly.Memory. Under the hood, this object wraps an
underlying ArrayBuffer, which represents an expandable,
contiguous block of raw bytes. WebAssembly code references memory
locations using integer offsets, commonly referred to as pointers.
Accessing Memory via
memory.buffer
JavaScript cannot directly read raw pointers, but it can access the
memory’s backing store through the
WebAssembly.Memory.prototype.buffer property. This property
exposes the underlying ArrayBuffer.
To read or write specific data types at specific offsets, JavaScript
creates TypedArray views (such as Uint8Array,
Int32Array, or Float64Array) or a
DataView over that buffer:
// Assume wasmInstance exports a memory object
const wasmMemory = wasmInstance.exports.memory;
// Create a view over the entire linear memory as bytes
const byteView = new Uint8Array(wasmMemory.buffer);
// Create a view for a 32-bit integer array located at a specific pointer
const pointer = wasmInstance.exports.get_data_pointer();
const length = 10;
const int32View = new Int32Array(wasmMemory.buffer, pointer, length);Because these views point directly to the shared
ArrayBuffer, modifications made by JavaScript are
immediately visible to WebAssembly, and vice versa. This enables
zero-copy data sharing between the two execution environments.
Resolving Pointers and Structs
When a WebAssembly function returns a pointer to an allocated data structure, string, or array, it is returning a byte offset relative to the start of its linear memory:
- Strings (UTF-8): JavaScript creates a
Uint8Arrayview starting at the string’s pointer offset for the specified byte length, then decodes it usingTextDecoder. - Homogeneous Arrays: JavaScript wraps the memory
range in the corresponding TypedArray (e.g.,
Float32Arrayfor floating-point vectors). - Complex Structs: JavaScript uses a
DataViewat the base pointer offset to read heterogeneous fields with methods like.getInt32(offset, littleEndian)and.getFloat64(offset, littleEndian).
Buffer Detachment and Memory Growth
A critical behavior occurs when WebAssembly memory grows via the
memory.grow() instruction (or the JavaScript
WebAssembly.Memory.prototype.grow() method).
Growing memory allocates new WebAssembly pages (each page is 64 KiB)
and reallocates the underlying ArrayBuffer. Consequently,
the previous ArrayBuffer becomes detached (neutered),
rendering all existing TypedArray and DataView instances
invalid:
let view = new Uint8Array(wasmMemory.buffer);
console.log(view.byteLength); // Valid size
// WebAssembly grows memory internally or explicitly via JS
wasmMemory.grow(1);
console.log(view.byteLength); // 0 (detached buffer)To prevent runtime errors caused by detached buffers, JavaScript memory views should either be short-lived—created on demand right before reading or writing—or refreshed whenever a memory growth event occurs.