You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(arm): home i64 params per AAPCS register-pairs — close#518 silent miscompile (#518, #242) (#531)
An i64 binop reading an i64 PARAM silently miscompiled on BOTH ARM selectors: an
i64 param occupies an AAPCS register PAIR (param0 = R0:R1), but both selectors
treated a param as a single i32-width register. Found by footgun/adversarial
differential testing; flip-independent, pre-existing.
Root cause + fix, by selector:
- DIRECT (select_with_stack): `infer_i64_locals` learns i64-ness only from
LocalSet/Tee, so a read-only i64 param stayed is_i64=false → its implicit hi
register was unreserved and a following i64.const was allocated INTO it
(movw r1,#K clobbered R1 = hi of an i64 param in R0:R1). Fixes: (a) seed
`i64_locals` from `self.params_i64`; (b) reserve the hi half of a live i64
param in the #193 reservation (live_param_regs); (c) map params to registers
via the new `aapcs_param_regs` (even-aligned pairs — (i32,i64) -> R0,R2:R3,
not the sequential R1:R2 the old index_to_reg used). All-i32 signatures map
identically to index_to_reg, so non-i64-param functions are byte-identical.
- OPTIMIZED (ir_to_arm): the I64Load param-home guard `num_params >= 2/4`
conflated register-count with param-count and dropped the param (read from a
fresh R4:R5 pair). Since ir_to_arm lacks per-param TYPES it cannot compute
AAPCS pairing — decline any function that reads an i64 param to the direct
selector (the #359/#188/#507 honest-degradation pattern).
Two i64-param sub-cases are declined LOUDLY (Ok-or-Err, never silent wrong-code),
their correct lowering tracked as follow-ups:
- an i64 param AAPCS-passed PAST R3 (stack) — the open #503-i64 case;
- an i64 param in a FRAME-BACKING function (has a call, or the pair-exhaustion
retry) — the param_slots path sizes an i64 param's slot from a width set that
excludes params, dropping the high half.
So no silent wrong-code remains: every i64-param function is either correctly
compiled (leaf, register-resident, the common case) or loud-skipped.
Gate: scripts/repro/i64_param_518_differential.py flipped to the correctness gate
(EXPECT_MISCOMPILE=False) — 11 leaf cases match wasmtime on BOTH paths across the
full AAPCS matrix (single i64, (i32,i64), (i64,i64), second-i64-param), PLUS a
decline-contract check (i64_param_518_decline.wat): d_past_r3 + d_call loud-skip,
d_leaf emitted + executes correctly. Frozen anchors 3/3 byte-identical
(control_step 0x00210A55 / flight_algo 0x07FDF307 / divseam — no i64 params).
Updated the #94 hi32-extract byte-size test to source its i64 from a sign-extended
i32 param (a non-param i64 that stays on the optimized path) — the old i64-param
shape was itself exercising the now-declined miscompile, masked by a size-only
assertion.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
0 commit comments