[6] Wasm OMG Tail Call Scratch Register Corruption on ARM
Severity: High | Component: JSC Wasm OMG JIT — tail call implementation | 4572dd4
Rated High because the observable effect is deterministic register corruption during tail call setup on ARM platforms (iOS, Apple Silicon macOS), and the projected escalation to controlled argument substitution is assessed with confidence 0.82 — the exact implicit scratch usage by ARM macro assembler instructions is well-known but not directly provable from the diff.
The OMG tailcall patchpoint uses the scratch register. Currently, the scratch is not clobbered early because on x64 we exhaust all registers if we do so. Because of that, prepareTailCallImpl has special handling for saving and restoring the scratch if it happens to alias one of the inputs. This special save and restore has issues on ARM as the stack pointer arithmetic itself may use the scratch, which complicates the restoring. This PR makes the tail call patchpoint code architecture-specific to confine the save/restore complexity to x64.
Source/JavaScriptCore/wasm/WasmOMGIRGenerator.cpp
AllowMacroScratchRegisterUsage allowScratch(jit);
auto tmp = jit.scratchRegister();
+#if CPU(X86_64)
+ // On x64, the scratch register may alias one of the inputs and needs special saving.
+ //
+ // Be careful not to clobber this below.
+ // We also need to make sure that we preserve this if it is used by the patchpoint body.
bool tmpNeedsSaving = false;
int tmpSpillOffsetRelativeToOriginalSP = 0;
...
jit.storePtr(tmp, CCallHelpers::Address(MacroAssembler::stackPointerRegister, tmpSpillOffsetRelativeToOriginalSP));
}
+#else
+ constexpr bool tmpNeedsSaving = false;
+ constexpr int tmpSpillOffsetRelativeToOriginalSP = 0;
+
+ // Set up a valid frame so that we can clobber this one.
+ jit.emitRestore(calleeSaves);
+
+#if ASSERT_ENABLED
+ for (unsigned i = 0; i < params.size(); ++i) {
+ auto arg = params[i];
+ if (arg.isGPR()) {
+ ASSERT(!calleeSaves.find(arg.gpr()));
+ ASSERT(arg.gpr() != tmp);
+ continue;
+ }
+ ...
+ }
+ ASSERT(!calleeSaves.find(tmp));
+#endif // ASSERT_ENABLED
+#endif // CPU(X86_64)
...
- // Nothing after restoring tmp can use the scratch register since it might clobber an input.
{
+#if CPU(X86_64)
+ // On x64, nothing after restoring tmp can use the scratch register since it might clobber an input.
DisallowMacroScratchRegisterUsage disallowScratch(jit);
+#endif
jit.addPtr(MacroAssembler::TrustedImm32(newSPAtPrologueOffsetFromSP), MacroAssembler::stackPointerRegister);
Scratch register aliasing with patchpoint inputs during tail call setup, where platform-specific implicit scratch usage in stack pointer arithmetic corrupts the aliased input.
Patch Details
The fix makes prepareForTailCallImpl architecture-specific. On non-x64 (ARM), the scratch register is now clobbered early (callee saves are restored before argument shuffling), so it can never alias a patchpoint input. The DisallowMacroScratchRegisterUsage guard after the scratch restore is also made x64-only. On the non-x64 path, ASSERT_ENABLED guards verify the invariant: no patchpoint input aliases the scratch register, and no callee save register conflicts with the scratch. The x64 path retains the existing save/restore logic because x64 cannot afford to clobber the scratch early (it would exhaust available GPRs).
Background
WebAssembly tail calls (return_call / return_call_indirect) allow a function to transfer control to another function by reusing the current stack frame rather than pushing a new one. During tail call preparation, the JIT must temporarily spill live argument values to scratch stack space before rearranging them into the callee's expected locations — because source and destination slots may overlap (a permutation problem).
The OMG tier implements tail calls via patchpoints — late-bound code generation callbacks that emit platform-specific machine code after register allocation. prepareForTailCallImpl shuffles arguments from the current frame into the caller's frame positions, adjusting the stack pointer. A patchpoint's params list contains the register assignments chosen by the B3 register allocator for the patchpoint's inputs.
On ARM, the macro assembler may implicitly use a designated scratch register (typically ip0/ip1) for address materialization and arithmetic that does not fit in a single instruction. This implicit usage is invisible at the JIT emission level — a jit.addPtr(TrustedImm32(largeValue), sp) may internally use the scratch to materialize the immediate, corrupting whatever value the scratch held.
Analysis
The root cause is that prepareForTailCallImpl used a single codepath for all architectures to handle the scratch register during tail call setup. The scratch register could alias one of the tail call's input arguments (assigned by the B3 register allocator). On x64, this was handled by special save/restore logic — spilling the scratch's value before clobbering it and restoring it afterward. On ARM, this save/restore approach was insufficient because ARM's stack pointer arithmetic itself may use the scratch register internally via the macro assembler. When the scratch aliased an input argument, the stack pointer adjustment (addPtr with a large immediate to SP) would corrupt the scratch, and subsequent code that tried to use the original input value from it would read a corrupted value.
The fix correctly splits the implementation: ARM clobbers the scratch early by restoring callee saves before argument shuffling, which guarantees that the register allocator cannot have assigned the scratch to any patchpoint input (since callee saves and the scratch are now restored before the shuffling code runs). The assertion guards on the non-x64 path (ASSERT(arg.gpr() != tmp)) make the invariant machine-checkable.
Aa Aaaaaaaa Aaaaa Aaaa Aa Aaaaa a Aaaa Aaaaaa Aaaa Aaaaaaaa Aaaaaaaaaaaaa Aaaaaaaa Aaaaaa Aaaaaaaaaaaa Aaaa Aaaaaa Aaaaa Aaaaaaaa Aaaaaaaa Aaaaaaaa Aaaaaaaa Aaaaaaaaaaa Aaa Aaaa Aaaa Aa a Aaaaa Aaaaaaaaaaaaaa Aaaa Aaaaaa Aaaa Aaaaaaa Aaaaaaa Aaa Aaaa Aaaaaa Aaaaaaaaaaaaaa Aa Aaaa Aaaaaa Aaaaaa a Aa Aaaaaaaaa Aaaa Aaaaaaaa Aaaaaaaa Aaaa Aaaa Aaaaaaaa Aaaaaaa Aaa Aaaaaaaa Aaaaaaaaaa Aa Aaaaaaa Aaa Aaaaaaaa Aaaaaaaa Aaaaaaaaaa Aaaa Aaaaaa Aaaaaaaaaaaaa Aaaaaaaaa Aaaa Aaa Aaaaaaaa Aaaaaaaaa Aa Aaaa Aaa Aaaaaaaaaa Aa Aaaaaaaaaaaaaa
Aa Aaa Aaaaaaaaa Aaaaa Aa a Aaaaaaa Aaaaaaaa Aaaaaaaa Aa Aaa Aaaaaa Aaaaaa a Aaaaaaaaa Aaaa Aa a Aaaaa Aaaa Aa a Aaaaaa Aaaaaaa Aaaa Aaaaa Aaaaa Aa Aaaaaaaaaaaaa Aaaaaa Aaaaaa Aa Aaaa Aaaaaaaaa Aa Aaa Aaaaaaa Aaa Aaaaaaaaa Aaaaa Aaaaa Aa Aaaaaaa Aa Aaa Aaaaa Aaaaa Aaaa Aaaaaa Aa Aaaaa Aaaa Aaa Aaaaaaaa Aa Aaaa Aaaaa Aaaaa Aa Aaaaaaaaaaaaaaaaaaa Aaa Aaaaaaaa Aaaaaaaaa Aaaaaaaaaaa Aaaa Aa Aaaaaaaaaaaaa Aaaaaaaaa Aaa Aaa Aaaaa Aaaaaaa Aaaaa Aaaaaaaaaa
Aaaa Aaaaaaaaaaaaa Aaaaaaa Aaaaaa Aaaaaa Aa Aaa Aaaaaaaaaa Aaaaaaaa Aaaaaaaa Aaa Aaaa Aaaa Aaaaaa Aaaaaaa Aaaa Aaaaaaaa Aaaaaaaaa Aaa Aaaaaa Aaaaaaaaa Aaaaaa Aaaa Aaaaaa Aaaaaa Aaa Aaaa Aa Aaa Aaaaaaaaaa Aaa Aaaaaaa Aaaaaaaa Aaaaaaaaaa Aaaaa Aaaaa Aaaaa Aaaaaa Aa Aa Aaaaaaa Aa Aaa Aaaaa Aaaaa Aa Aaaaaa Aa Aaaaaaaaa Aa Aaa Aaaaaaaaaaa Aaaaaaaaa Aaaaaaaaaaa Aaaaaaaaa Aaaa a Aaaaaaaaaaa Aaa Aaaa Aaaaaa Aaaaaaaaaaa
Aaa Aaaaaaaaaaa Aaaaaaa Aa Aaaa Aaa Aaa Aaa Aaa Aaaaaaaaa Aa Aaaaaaa Aaa Aaaaaaa Aaaaaa Aaaaa Aaaaa Aaaaa Aaaaaaaaa Aaaaaaaaaa Aaaa Aaa Aaaaaaa Aa Aaaa Aaaa Aaaa Aaaaaaaaaaaa Aaaaaaaaaaaa a a Aaaaaaaaa Aaaaaaa Aa Aaaaaaaaaaaaaa Aaaaa
Audit directions
a Aaaaaaaa Aaa Aaaaaaaaaa Aaaa Aaaa Aaaaaaa Aaa Aaaaaaaa Aaaaaaa Aaaaaaaa Aa Aaaa Aa Aaa Aaaaaaa Aaaaaaaaaaa Aaaaaaaa Aaaaaaaaa Aaaaaaaaaaaa Aa Aaaa Aaa Aaaaaaaaaa Aaaa Aaaa Aaaaaaaaaaaaaaaaaaa Aaa Aaaa Aaaaa Aaaa Aaaaaaaa Aaa Aaaaaa Aa Aaaaaaaaaaa Aa Aaa Aaaaaaa Aaaaaa Aaaaaaaa Aa Aaaaaaaaa Aaaaaa Aaaaa Aaaaa Aaaaaaaaaa Aaaaaaaaaa Aa Aaaaaaaaaaaaaaaaaaaaaa Aaaaaaa Aaa Aaaaaaaaaaaaaaaaaaa Aaaaa Aaaaaa Aaaaaaaaaaaaaaaaaaa Aaaaaaaa Aa Aaaaaa Aaaa Aaaaaa Aaaaaaa Aaa Aaaaaaa Aa Aaaaaa Aaaaaaaa Aaaaaaaaaa
a Aaaaaaaa Aaaaaaaaaaaaaa Aaa Aaaa Aaaa Aaaa Aaaaaaaaaaaa Aaaaaa Aaaa Aaaaaa Aaaaaaaaaaaa Aaaaaaaa Aaaaaaaaaa Aaaa Aa Aaaaa Aaaaaaaaaaaaaa Aaaa Aaaaaaaaaaaaaaaaaaaaaa Aaa Aaaaaaaaaaaaaa Aaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa a Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaaaaa Aaa Aaaaaa Aaaa Aaa Aaaaaaa Aaaaaaaa Aaaaaaaa Aaaaaaaaaaa Aaa Aaaaaaa Aa Aaa a Aaaaaaaaaaaa Aaaaaa Aaaaa Aaaaaaa Aaaaaaaaaaaaa Aaaaaaaaa Aa Aaa Aaaaaaaa Aaaa Aaaa
a Aaaaa Aaa Aaa Aaa Aaaa Aaaa Aaaaaaaaaaaaaa Aaa Aaa Aaaa Aaaaaaa Aaaaaaaa Aaaaaa Aaa Aaaaaaaa Aaaaa Aaaaaaaa Aaa Aa Aaaaaaaaaaaaaa Aaaaaaaa Aaa Aaa Aaaa Aaaa Aaaaaaa Aaaaaaaaaaaaaaaaa Aaaaaaa Aaaaa Aaaaaaa Aaaaa Aaaa Aaaa Aaaa Aaaa Aaaa Aaaaaaa Aaaaaaaaaaaaa Aaaaaaaa Aaaaaaaaa Aa Aaaa
a Aaaaaaaa Aaaa Aaaa Aaaaaaaa Aaaaaaaaa Aaaa Aaaaaaaa Aa Aaaa Aaaaaaaa Aaaaa Aaaaaaa Aaaaaaaaa Aaaa Aaaaaaaa Aaaaaaaaa Aaaa Aa Aa Aaaaa Aaaaaaa Aaaaaaaaaaa Aaaaaa Aaa Aaaaaaaaaaa Aaaaaaaaaa Aaa Aaaaaaaa Aaaaaaaa Aaa Aa Aaa Aaaaaaaaa Aaaaaaaaaaaa Aaaaaaaaaa Aaa Aaaaaaaa Aaaaaaa Aaaaaaaaa Aaaaaaa Aaaaa Aaaaa Aaaa Aaaa Aaaaaaaaaaaaaaa Aa Aaa Aaaaaaaaaaa Aaaa Aaaaa Aa Aaaaaaaa Aaa Aaaaaaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaaaaa Aa Aaaa