← 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)는 JSC의 baseline Wasm interpreter로, JIT compilation 없이 bytecode를 직접 실행합니다. Wasm의 memory64 proposal은 linear memory가 기본값인 32비트 addressing 대신 64비트 addressing을 쓸 수 있게 해주며, multi-memory는 하나의 모듈이 여러 개의 독립적인 memory instance를 선언할 수 있게 해줍니다.

이전까지 IPInt는 모든 memory access의 address width를 memory 0에서 가져왔습니다. 그래서 width가 서로 다른 memory를 섞어 쓰는 것은 안전하지 않다고 보고, WasmSectionParser가 parse 시점에 이를 거부했습니다. 이번 patch는 이 제약을 제거하고, IPInt가 memory index별로 올바른 address width를 추적하고 적용하도록 변경합니다. 이를 위해서는 memory32 access와 memory64 access가 같은 모듈 안에 공존할 때 발생하는 address truncation과 wrapping을 정밀하게 처리해야 합니다.

이제 Wasm 모듈은 하나의 instance 안에서 32비트 memory와 64비트 memory를 합법적으로 함께 사용할 수 있습니다. 그 결과 access마다 address 계산이 검증되고 dispatch되는 방식 자체가 달라집니다. 애초에 제거된 제약 자체가 하나의 안전장치였던 만큼, 새로 도입된 memory별 width 추적 로직이 그 무게를 그대로 짊어져야 합니다.

여기서 주목할 forward-facing 패턴은, 상위 비트의 의미가 정의되지 않은 값이 address 계산으로 흘러 들어가는데, 이 값이 마스킹되는지 여부가 대상 memory index에 따라 갈린다는 점입니다. 좁게 보면, 테스트 코드의 주석이 이 문제를 직접 지적하고 있습니다. IPInt stack 위의 i32 address는 상위 절반에 garbage를 담고 있을 수 있는데, 이는 i32.wrap_i64가 그 위치에서 no-op이기 때문입니다. 이 garbage는 memory32 access로 routing될 때는 마스킹되어야 하지만, memory64에서는 그대로 온전히 보존되어야 합니다. InPlaceInterpreter64.asm에서 memory index별로 address width가 어떻게 선택되는지 확인해야 하고, JSWebAssemblyInstance에서 memory별 bounds와 width metadata가 어떻게 저장되고 조회되는지도 살펴봐야 합니다. 특히 memory index 순서가 뒤바뀌는 경우, 즉 memory64가 먼저 선언되는 경우와 memory32가 먼저 선언되는 경우를 모두 점검할 필요가 있습니다. 조금 더 넓게 보면, Wasm memory access를 lowering하는 다른 모든 tier에서도 동일한 형태의 문제가 나타날 수 있습니다. BBQ와 OMG의 bounds-check emission, 그리고 Wasm-to-JS boundary가 여기에 해당하는데, 이들 각각도 width를 memory 0이 아니라 memory별로 독립적으로 도출해야 하기 때문입니다. 이런 문제를 알아보는 단서는, 고정된 index나 캐시된 "the" memory로부터 width나 bound를 읽어오는 address 계산이 있는지 찾아보는 것입니다. 가장 넓게 보면, parser의 제약이 제거되었다는 사실 자체를 새로운 attack surface 목록을 다시 만들어야 하는 계기로 삼을 필요가 있습니다. 이전에는 거부되었던 조합이 발생할 수 없다는 전제 하에 동작하던 downstream consumer를 모두 나열해 봐야 합니다. 이 전제는 각 consumer 코드에 암묵적으로 녹아 있었을 뿐이고, 이번 제약 제거로 인해 그 코드들이 함께 수정된 것은 아니기 때문입니다.