Managing WebAssembly Linear Memory for Host-to-Module Data Exchange
Learn how to manage WebAssembly linear memory to exchange strings and complex data between JavaScript and Wasm modules using allocation pointers and byte buffers.
17 Oct 2025, 00:30 UTC

The Memory Gap Between JavaScript and WebAssembly
WebAssembly (Wasm) cannot directly access JavaScript objects, strings, or arrays. It operates exclusively on Linear Memory—a contiguous, growable array of raw bytes. To pass a string or a complex data structure from JavaScript to a Wasm module, you must manually write that data into this byte array and pass the starting memory address (an integer offset) to the Wasm function.
The primary challenge is memory ownership. If JavaScript writes data to a random offset in the Wasm memory, it may overwrite the module's stack or heap, causing a runtime trap (crash). The solution is to export an allocation function from the Wasm module that tells the host exactly where it is safe to write.
Prerequisites
- A Wasm module compiled from a language like C, C++, or Rust.
- The module must export its
WebAssembly.Memoryobject. - The module must export an allocation function (e.g.,
malloc) to reserve space. - A JavaScript environment (Browser or Node.js) supporting the
TextEncoderandTextDecoderAPIs.
Implementing the Data Exchange Workflow
This process involves four distinct steps: allocating space in the guest, encoding the data, executing the function, and decoding the result.
1. Allocate Memory in the Guest
Before sending data, call the Wasm module's allocator. This ensures the guest language's memory manager tracks the block and prevents collisions.
2. Write Data from the Host
Use a Uint8Array view of the Wasm memory buffer to write the encoded bytes starting at the returned pointer.
3. Execute and Retrieve
Pass the pointer and the length of the data to the Wasm function. If the function returns a pointer to a new string, use that pointer to read the bytes back from memory.
Concrete Implementation Example
Assume a Wasm module provides malloc(size) and a function process_string(ptr, len) that returns a pointer to a greeting string.
// JavaScript Host Implementation
async function runWasmExample(wasmInstance) {
const { memory, malloc, process_string } = wasmInstance.exports;
const inputString = "Hello Wasm!";
// 1. Encode string to UTF-8 bytes
const encoder = new TextEncoder();
const encodedString = encoder.encode(inputString);
// 2. Allocate memory in Wasm for the input
// Run this in the JS main thread; requires permissions to call exported Wasm functions
const ptr = malloc(encodedString.length);
if (ptr === 0) throw new Error("Memory allocation failed");
// 3. Write bytes into the Wasm linear memory buffer
const memView = new Uint8Array(memory.buffer);
memView.set(encodedString, ptr);
// 4. Call the Wasm function
const resultPtr = process_string(ptr, encodedString.length);
// 5. Read the result (assuming the result is null-terminated)
let resultBytes = [];
let i = resultPtr;
while (memView[i] !== 0) {
resultBytes.push(memView[i]);
i++;
}
const decoder = new TextDecoder();
console.log("Result from Wasm:", decoder.decode(new Uint8Array(resultBytes)));
}
Diagnostic Decision: When to Use SharedArrayBuffer
Depending on your performance needs, you must choose between standard WebAssembly.Memory and SharedArrayBuffer:
| Scenario | Recommended Memory Type | Trade-off |
|---|---|---|
| Simple request-response data exchange | Standard Linear Memory | Low complexity; data is copied. |
| High-frequency updates (e.g., 60fps physics) | SharedArrayBuffer | Requires COOP/COEP headers; allows atomic operations. |
| Large datasets (>100MB) | Standard Linear Memory (Growable) | Memory is growable but cannot be shrunk. |
Verification and Limitations
To verify the implementation, use the browser's Developer Tools (Memory tab) to monitor the WebAssembly.Memory object size. If the memory size increases linearly without plateauing, you have a memory leak in the host (failing to call a corresponding free() function in Wasm).
Limitations:
- Encoding Overhead: Converting JS strings to UTF-8 bytes is an $O(n)$ operation. For massive datasets, this can become a bottleneck.
- Memory Fragmentation: Frequent small allocations via
malloccan fragment the linear memory, potentially leading to allocation failures even if total free memory is sufficient. - Buffer Detachment: If the Wasm module grows its memory (using
memory.grow), the existingArrayBufferis detached. Any previousUint8Arrayviews become invalid and must be re-created frommemory.buffer.
Rollback and Cleanup
Because Wasm linear memory is managed manually, you must implement a cleanup phase to avoid leaking memory in the guest heap:
- Call the exported
free(ptr)function for everymalloc(size)call made by the host. - Nullify the JavaScript pointer variable to prevent accidental reuse of a freed memory address.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.