← All reports

[JSC] Implement wasm memory64 with multi-memory

Component: JSC WebAssembly | 0bbbac7

Source/JavaScriptCore/wasm/WasmSectionParser.cpp

- (removed check rejecting memory64 + multi-memory combinations)

JSTests/wasm/stress/memory64-multi-memory.js

+// An i32 address on the IPInt stack may hold garbage in its upper half, because i32.wrap_i64 is a
+// no-op there. Accessing a memory32 in a module whose memory 0 is memory64 must still ignore those
+// bits rather than folding them into the address.
+await test(`
+(module
+ (memory i64 1)
+ (memory 1)
+ (func (export "storeWrapped") (param i64 i32)
+ (i32.wrap_i64 (local.get 0)) (local.get 1) (i32.store 1))
+ (func (export "loadWrapped") (param i64) (result i32)

IPInt (In-Place Interpreter) is JSC's baseline Wasm interpreter, executing bytecode directly without JIT compilation. Wasm's memory64 proposal allows a linear memory to use 64-bit addressing instead of the default 32-bit; multi-memory allows a module to declare several independent memory instances.

Previously IPInt derived the address width for every memory access from memory 0, so mixing widths across memories was unsafe and rejected at parse time by WasmSectionParser. This patch removes that restriction and makes IPInt track and apply the correct address width per memory index, which requires precise handling of address truncation and wrapping when memory32 accesses coexist with memory64 accesses in the same module.

Wasm modules can now legally combine 32-bit and 64-bit memories in one instance, changing how address calculations are validated and dispatched per-access. The lifted restriction was itself a safety measure; the new per-memory width tracking has to carry the weight it used to.

The forward-facing pattern is a value whose upper bits are semantically undefined flowing into an address computation that may or may not mask them, depending on which memory index it targets. Narrow: the test comments flag it directly — an i32 address on the IPInt stack may hold garbage in its upper half because i32.wrap_i64 is a no-op there, and that garbage must be masked when routed to a memory32 access but preserved in full for memory64. Review InPlaceInterpreter64.asm for how the address width is selected per memory index, and JSWebAssemblyInstance for how per-memory bounds and width metadata are stored and looked up, especially under memory index reordering (memory64 declared first versus memory32 first). Wider: the same shape exists at every other tier that lowers Wasm memory accesses — BBQ and OMG bounds-check emission, and the Wasm-to-JS boundary — since each must independently derive width per memory rather than from memory 0; the match tell is any address computation reading a width or bound from a fixed index or a cached "the" memory. Widest: a removed parser restriction is always worth treating as a new attack surface enumeration exercise: list every downstream consumer that was previously guaranteed the rejected combination could not occur, since each one encoded that guarantee implicitly and none of them were changed by the removal.