[Wasm] Use a lazy restore frame when returning from tail calls
5223ee5
JSTests/wasm/stress/tail-call-cross-instance-gc.js
+// Test that GC during a cross-instance tail call chain doesn't corrupt the
+// RestoreInstanceCallee thunk frame. The thunk's Callee slot holds the
+// RestoreInstanceCallee singleton and CodeBlock holds a wasmInstance pointer;
+// both must survive GC scanning.
+ function triggerGC() { $vm.gc(); }
+ assert.eq(instPing.exports.start(1), 255);
+ assert.eq(instPing.exports.start(11), 255);
+ for (let i = 0; i < wasmTestLoopCount; i++) {
+ assert.eq(instPing.exports.start(1), 255);
+ assert.eq(instPing.exports.start(11), 255);
+ }
Each WebAssembly instance pins instance-specific data — memory base, memory bounds, and the instance pointer — in dedicated registers. When a tail call crosses instance boundaries (e.g., via return_call_indirect to a different module), the callee's instance clobbers those registers, and if the chain eventually returns to the original non-tail caller, those registers must be restored before it resumes.
This commit replaces the previous compile-time transitive tail call clobbering analysis with a runtime "restore frame" mechanism. When a Wasm tail call crosses instance boundaries for the first time, a 32-byte frame is lazily inserted just above the caller's argument area, capturing the caller's instance pointer and original return address; the rest of the frame is shifted down 32 bytes. A dedicated thunk (_wasm_restore_frame_return) reloads instance and memory registers on return. The patch removes callCanClobberInstance/computeTransitiveTailCalls and their inlining restriction, and adds ARM64E PAC re-signing plus a JIT cage gate thunk. A reuse check — comparing the current return PC against the thunk address — prevents restore-frame accumulation on repeated cross-instance hops, which the spec requires.
After (runtime restore frame, lazy insertion):
Caller frame (inst A)
arg area
[32-byte restore frame inserted above args, first cross-inst call only]
slot 0: original CallerFrameAndPC
slot 1: saved instance A
slot 2: RestoreFrameCallee
callee-saves <- entire frame shifted down 32 bytes
return -> new cfr -> _wasm_restore_frame_return -> reload inst A regs -> jump to original retPC
Reuse check: if retPC == thunk addr, skip insertion (no accumulation)
Significance
Cross-instance tail call chains can now inline freely, but ABI correctness now depends on a new runtime frame type that the GC, stack unwinder, sampling profiler, and three Wasm backends (BBQ, OMG, IPInt) must all handle consistently — including ARM64E PAC re-signing of the saved return PC. The dedicated GC stress test signals that the authors knew correctness here was a non-trivial risk.
Audit directions
a Aaaaaaa Aaaaa Aaaaaaaa Aaaa Aaaaaaa Aaa Aaaaaaa Aaaaa Aa Aaaaaaaa Aa Aaaaaaaaaa Aaaaaaaa Aaa Aaaaaaaa Aaaaaa Aaaaa Aaaaaaaaaaaaa Aaaaaaaaa Aaaa Aa Aaaaaa Aaa Aaaa Aaaa Aa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaaa a Aaaaaaaa Aaaa Aaaaaaaaaaaaa Aaaaaaa Aaaaa Aaa Aaaaa Aa Aaa a Aaaaaaaaaaa Aaaaaaaaa Aaaa Aaa Aaaaaaa Aaaaaaaaaa Aa Aaa Aaaa Aaaaa Aa Aaaaaaaaa Aaaaa Aaaaaaaa Aaaaaaa Aaaaaaaaaaa Aaaaaaaaa Aa Aaaaaaaaa Aaaaaaaa Aaaa Aaaaaaa Aaaaaaaaaa a Aaaaa Aaaaaaaaaaaa
a Aaaaaaaa Aaa Aaaaaaaaaaaaa Aaa Aaaaaaaa Aaaaaa Aa Aaaa Aa Aaaaaaaa Aaaa Aaa Aaaaaa Aaaaaaa Aaa Aaaaaaa Aaaaaaa Aa Aaa Aaaaaaa Aaaaaaa Aaa Aaaaaaaa Aaa Aaaaaa Aaaaaaaaaa Aaaaaaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aa a Aaaaaaa Aaaaa a Aaaaaaa Aa Aaaaa Aaaaaaa Aa Aaa Aa Aaaaa Aa a Aaaaaa Aaaa Aaaa Aa Aaa Aaaaaaaaaaaaaa Aaaaa Aaaaa Aaaaaaa a Aaaaaaaaa Aaaaaa Aaaaaaa Aa a Aaa Aaaaaaa
a Aaaaaaaaaaaaa Aaaaaaaaaaa Aaaaaa Aaaaaaaaaaa Aaa Aaa Aaaaaaaaaaaaa Aaaaaaaaa Aaaaaaa Aaaaaaaa Aa Aaa Aaaaaaaaa Aa Aaaaaaaaaaaaaa Aaaaaa Aaaa Aaaa Aaaaa Aaaaaa Aaaa Aaaa Aaaaaa Aaa Aaa Aaa Aaaa Aaaa Aaaaaa Aa a Aaaaaaaaaa Aaaaaa Aaaaaaa Aaaaaaaa Aaaa Aaa Aaaaa Aaaaaaaa Aa Aaa Aaaa Aaaaaaa Aaaaaaa Aaaaaaa Aaaa Aaaa Aaa Aaaaa Aaaaaaaa Aaaaaaaa Aaa Aaaaaaa Aaaaa Aa Aaaaaa Aaaaaaa Aaaaaa Aaaaaaaa Aaaaaaaaa Aa Aaaaaaaa Aaaaaaaaaaa Aaaaaaaaaaaaaaa Aaaaa Aaaaaaaa
a Aaaa Aaa Aaaaaaaa Aaaaaaaaaaaaaa Aaa Aaaaaaa Aaaaa Aa a Aaa Aaaaa Aaaaa Aaa Aa Aaaa Aaaa Aaa Aaaaaaaa Aaaaaaaaaaaaaaaaaaaaaaaa Aaaaaaaaaa Aaa Aaaaaaaaaaa Aaaaaaaaaaaaaaa Aaaaaaaa Aaaaaa Aaa Aaaaa Aaaaaaaa Aaa Aaaaaaaa Aaaaaaaa Aaaa Aaaaaaaaa Aaa Aaaaaaaa Aaa Aaaaaaa Aa Aaa Aa Aaaaa a Aaaa Aaaaaaaaaaaa Aaaaa Aa Aaaaaaaa a Aaa Aaaaa Aaaaaaaaaaaaaa Aa Aaaaaaaaa Aaaaaaaa Aaaaaaaaaaaaaa
a Aaaaa Aaaa Aaaa Aaaaa Aaaaaaaaaaaaaa Aaa Aaaaaaaaaaaaaaaaaa Aaaa Aaaaa Aaaa Aaaaaaaaa Aaaaa Aaaaaaaaaaaaaa Aaaaaa Aaaa Aaa Aaaaaaaaaa Aaaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaaaa Aa a Aaaaaaaa Aaaaaaaa Aaaaaaaaaaaaa Aaaa Aaaaa Aa Aaaaaaaaa Aa Aaaaaaa Aaa Aaaaaaaaaa
🔒New runtime Wasm stack frame mechanism with cross-backend ABI and pointer-auth implications — several security audit directions included.
Subscribe to read more