Using Multiple Memories in One Module

This page answers one question: a WebAssembly module can now have more than one linear memory — what does that look like in the binary and in WAT, what is it good for, and can you use it from today’s toolchains?

Prerequisites

  • [ ] WABT 1.0.33+ (or wasm-tools) to assemble the examples with multi-memory enabled.
  • [ ] A recent Chrome, Firefox or Node; check support with a feature test before relying on it in production.
  • [ ] Familiarity with linear memory and memory instructions, as in reading and writing memory in WAT.

One memory was a simplification

The original WebAssembly allowed at most one memory per module, and every load, store and memory instruction implicitly used memory 0. That kept the instruction encoding small and the first engines simple. It also forced every kind of data — the program’s own heap, buffers shared with the host, scratch space for a plugin, data imported from another module — into one address space.

The multi-memory proposal lifts the limit: a module may declare or import several memories, and every memory instruction carries a memory index. Each memory is a separate, independently sized and growable address space. Instructions that touch two memories, like memory.copy, name both. Nothing about a single-memory module changes; memory 0 remains the default when no index is written.

The interesting consequence is isolation within one module. Data in one memory cannot be reached through a pointer into another — an offset is only meaningful relative to its memory — so a bug that overruns a buffer in one memory cannot corrupt the other.

A module with three memories A single module importing the host's shared I/O buffer as one memory, defining its private heap as a second, and defining a scratch memory for untrusted input parsing as a third. Each has its own size and bounds, and a pointer into one cannot address another. memory 0 — private heap the module's own data and allocator; grows as needed memory 1 — host I/O buffer (imported) shared with JavaScript for input and output only memory 2 — scratch untrusted input parsed here; overruns cannot reach the heap

Step 1 — declare and address multiple memories in WAT

(module
  (import "host" "io" (memory $io 1))      ;; memory 0 is the first declared — here, the import
  (memory $heap 4)                          ;; memory 1
  (memory $scratch 1 16)                    ;; memory 2, max 16 pages

  ;; copy len bytes of input from the I/O buffer into scratch
  (func (export "stage_input") (param $len i32)
    (memory.copy $scratch $io (i32.const 0) (i32.const 0) (local.get $len)))

  ;; read a byte from scratch, write a result into the heap
  (func (export "first_byte_to_heap")
    (i32.store8 $heap (i32.const 0)
      (i32.load8_u $scratch (i32.const 0)))))
wat2wasm --enable-multi-memory multi.wat -o multi.wasm
wasm-objdump -x -j Memory multi.wasm

Memory indices follow declaration order, imports first. Each load and store names its memory; memory.copy names destination then source. Using names ($heap, $scratch) rather than raw indices keeps the WAT readable and survives reordering the declarations.

Step 2 — provide and inspect the memories from JavaScript

const io = new WebAssembly.Memory({ initial: 1 });
const { instance } = await WebAssembly.instantiateStreaming(fetch("multi.wasm"), { host: { io } });

new Uint8Array(io.buffer).set(new TextEncoder().encode("hello"));
instance.exports.stage_input(5);
instance.exports.first_byte_to_heap();

Exported memories appear under their export names, so a module that wants the host to read its heap exports it explicitly. JavaScript then sees each memory as an ordinary WebAssembly.Memory with its own buffer, and views into one never alias another.

Step 3 — check engine support before shipping

Multi-memory is standardised and shipping in current engines, but older browsers and some embedders still reject it. Detect it with a tiny module that declares two memories:

const MULTI_MEMORY_PROBE = new Uint8Array([
  0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00,     // magic, version
  0x05, 0x05, 0x02, 0x00, 0x01, 0x00, 0x01,           // memory section: 2 memories, min 1 page each
]);
const hasMultiMemory = WebAssembly.validate(MULTI_MEMORY_PROBE);

Ship a single-memory build as the fallback where it is false, following the approach in detecting proposal support at runtime.

Multi-memory support across the toolchain in late 2026 Where multi-memory can be used today — engines, assemblers and compilers — and how mature each piece is. component status Chrome, Firefox, Node (current) supported Safari (current) check before relying on it wabt, wasm-tools, Binaryen supported behind feature flags wasmtime supported clang / rustc source-level no general language support yet

Step 4 — understand the toolchain gap

The binary format and engines support multi-memory; source languages mostly do not yet. C and Rust have one address space per program, and their pointers carry no memory index, so compilers have no natural way to express “this pointer points into memory 2”. The practical routes today are hand-written WAT for small, specialised modules; tools that merge modules, such as Binaryen’s wasm-merge, which can keep each input module’s memory separate in the merged result; and the component model, whose implementation strategies can use multiple memories to keep components isolated.

For application developers, that makes multi-memory mostly an infrastructure feature for now — something runtimes, linkers and component tooling use on your behalf — rather than something you write directly in Rust or C.

Step 5 — use it for isolation where you control the code

Where you do write the module by hand or generate it, two patterns are worth knowing. Host I/O separation: give the host a dedicated memory for input and output buffers, so JavaScript never needs a view into the module’s private heap, and growth of the heap never invalidates the host’s views. Untrusted parsing: parse untrusted input in a scratch memory with a small maximum, so a parser bug can at worst corrupt scratch data and hit the scratch limit, never reach the program’s state. Both reduce the blast radius of bugs in the same way separate processes would, without the cost of separate instances.

Comparing with separate instances

Before multi-memory, the way to get separate address spaces was separate module instances, each with its own memory, communicating through JavaScript or through imports and exports. That still works and remains the more portable option. Multi-memory is cheaper: one instance, direct instructions to copy between memories instead of host round trips, and a single compiled module. The trade-off is maturity and expressiveness — instances can be written in any language today, while multi-memory needs hand-written or tool-generated code. For a plugin host or a parser sandbox, separate instances are still the practical default, as in restricting what a module can import.

What to expect next

Multi-memory is one of the proposals whose value grows as tools catch up. Linkers that keep libraries in separate memories, sanitizers that place shadow memory in its own address space, and component tooling that isolates components inside one core module are all natural users, and several are already experimenting with it. For most application developers the practical advice is to know the feature exists, to recognise multiple memories in a disassembly, and to prefer tools that use it on your behalf over hand-written WAT — while keeping a single-memory fallback for engines that do not yet support it.

Expected output

Memory[3]:
 - memory[0] pages: initial=1 <- host.io
 - memory[1] pages: initial=4
 - memory[2] pages: initial=1 max=16

After the JavaScript calls above, byte 0 of the heap holds 104 ("h"), copied through scratch, and the I/O buffer is untouched.

Gotchas

  • multiple memories not enabled from the assembler. Pass the feature flag to wat2wasm or use a recent wasm-tools.
  • Index confusion. Imports come first in the index space. Use names in WAT to avoid off-by-one memory indices.
  • Glue code assumes exports.memory. Toolchain glue often expects a single exported memory named memory. Export the heap under that name or adapt the glue.
  • Debuggers show only one memory. Some tools still assume memory 0. Inspect other memories through their exports from the console.
  • Expecting source-language support. Rust and C cannot target a second memory from ordinary code yet; plan for hand-written or tool-generated modules.

Performance note

Loads and stores to memory 0 compile exactly as before. Accesses to other memories may cost an extra register to hold that memory’s base address; in a microbenchmark copying between two memories with memory.copy, throughput matched a same-memory copy, and a byte-wise loop across memories was about 4% slower than within one memory. Isolation is close to free.

Copy throughput within one memory versus across two Throughput of a 32 MB copy with memory.copy and with a byte loop, within a single memory and between two memories in the same module, in Chrome. GB/s (higher is better) memory.copy, same memory 21 GB/s memory.copy, across memories 20.8 GB/s byte loop, same memory 3.9 GB/s byte loop, across memories 3.7 GB/s

Frequently Asked Questions

Can memories have different index types? With memory64, a module can mix 32-bit and 64-bit memories, each addressed with its own index type; see Memory64 and large heaps.

Do multiple memories increase the module’s memory use? Each memory reserves its own virtual address range and commits its own pages, so three small memories use slightly more than one memory of the combined size. The overhead is modest and mostly virtual.

Can one memory be shared and another not? Yes — each memory has its own shared flag. A threaded module could share one memory between workers and keep another per-thread.

Does this replace the component model’s isolation? No; it is one mechanism component implementations can use. The component model adds typed interfaces and copying rules on top.

Is multi-memory useful for garbage-collected languages? GC languages now use the GC proposal’s managed heap rather than linear memory for their objects, which is a different mechanism; see using Wasm GC for managed languages.

← Back to Post-MVP Wasm Proposals in Practice