fix(opt): i64 ops hardcoding R0..R3 cause AAPCS param clobber (#103)#106
Merged
Conversation
The cargo-fuzz harness `i64_lowering_doesnt_clobber_params` (PR #100) caught I64SetCond emitting `rd: R0, rn_lo: R0, rn_hi: R1, rm_lo: R2, rm_hi: R3` before LocalGet(0) reads R0 — clobbering an AAPCS param register. Root cause: in `instruction_selector::select_with_stack` (the `--no-optimize` lowering path), the wildcard fallthrough handed every unhandled i64 op off to `select_default`, which hardcodes R0:R1 / R2:R3 for operand pairs and R0 for the result. Same class as PR #86's i64-const fix in `optimizer_bridge`, just for the no-optimize path. Audited every i64 op in `select_with_stack` and added explicit handlers for those that previously fell through: - Comparisons: I64Eq, I64Ne, I64LtS, I64LtU, I64LeS, I64LeU, I64GtS, I64GtU, I64GeS, I64GeU - Arithmetic: I64Mul, I64DivS, I64DivU, I64RemS, I64RemU - Rotations: I64Rotl, I64Rotr - Unary bit: I64Clz, I64Ctz, I64Popcnt - Sign-extends: I64Extend8S, I64Extend16S, I64Extend32S - Conversion: I32WrapI64 Each pops the actual operand register pair(s) from the wasm stack and allocates the destination via `alloc_temp_safe` (i32 result) or `alloc_consecutive_pair` (i64 result), with the popped halves passed in `extra_avoid` to keep them live until the encoded sequence reads them. Regression test `issue_103_i64_aapcs.rs` covers the exact #103 repro plus all 24 audited ops at num_params=1..4. Fails on main with the documented clobber message, passes with this fix. Test count delta: +3 tests in synth-synthesis. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #103.
Summary
The cargo-fuzz harness
i64_lowering_doesnt_clobber_params(PR #100) caughtI64SetCondemittingrd: R0, rn_lo: R0, rn_hi: R1, rm_lo: R2, rm_hi: R3beforeLocalGet(0)reads R0 — clobbering an AAPCS param register. The fix is the same class as PR #86's i64-const fix inoptimizer_bridge, just applied to the--no-optimizepath.Root cause
In
instruction_selector::select_with_stack(the--no-optimizelowering path), the wildcard fallthrough handed every unhandled i64 op off toselect_default, which hardcodes:rn_lo: R0, rn_hi: R1(first operand)rm_lo: R2, rm_hi: R3(second operand)rd: R0(result)These are the AAPCS param registers — clobbered before
LocalGet(0..3)has a chance to read them.Audit
Every i64 op in
select_with_stackaudited. The following previously fell through toselect_defaultand have now received explicit handlers (24 ops in total):I64Eq,I64Ne,I64LtS,I64LtU,I64LeS,I64LeU,I64GtS,I64GtU,I64GeS,I64GeUI64Mul,I64DivS,I64DivU,I64RemS,I64RemUI64Rotl,I64RotrI64Clz,I64Ctz,I64PopcntI64Extend8S,I64Extend16S,I64Extend32SI32WrapI64Already-handled i64 ops (
I64Add,I64Sub,I64And/Or/Xor,I64Shl/ShrU/ShrS,I64Const,I64ExtendI32S/U,I64Load*,I64Store*,I64Eqz) were already correct.Each new handler:
alloc_temp_safe(i32 result) oralloc_consecutive_pair(i64 result), passing the popped halves inextra_avoidso the destination doesn't overlap any operand half before the encoded sequence reads it.ArmOpwith stack-tracked register references.Regression test
New file
crates/synth-synthesis/tests/issue_103_i64_aapcs.rs(385 lines, 3 tests):issue_103_i64_lt_s_does_not_clobber_r0— the exact I64SetCond hardcodes R0..R3, clobbers param registers (found by #100 fuzz harness) #103 repro (i64 LtS before LocalGet(0)). On main this panics with:AAPCS clobber: ARM instr at wasm line 2 writes param reg R0 before LocalGet(0) at line 4. Op: I64SetCond { rd: R0, rn_lo: R0, rn_hi: R1, rm_lo: R2, rm_hi: R3, cond: LT }. Passes with this fix.issue_103_all_i64_ops_preserve_params— class-level audit covering all 24 ops atnum_params∈ {1, 2, 3, 4}.i64_eqz_still_preserves_params— sanity check that the already-handledI64Eqzpath is not regressed.The test framework rebuilds the same AAPCS invariant PR #100's fuzz harness asserts: no ARM instruction may write a param register before that param's earliest
LocalGet.Test plan
cargo test -p synth-synthesis --test issue_103_i64_aapcs— 3/3 pass with fix, 2/3 fail (the two new tests) on main.cargo test --workspace --exclude synth-verify— clean (synth-verifyexcluded because of unrelated z3-sys download failure on the dev host).cargo clippy --workspace --all-targets --exclude synth-verify -- -D warnings— clean.cargo fmt --check— clean.cd fuzz && cargo fuzz run i64_lowering_doesnt_clobber_params -- -max_total_time=120— fuzz infra not yet on main (PR feat(fuzz): cargo-fuzz harnesses for ARM instruction selection (#82) #100 still in CI); the static regression test in this PR encodes the same invariant.Test count delta
+3 testsinsynth-synthesis.Files referenced as out-of-scope
Per task instructions, did not touch:
crates/synth-cli/tests/wast_compile.rs::compile_i32_memory(i32.load/i32.store wasm_to_ir produces unmapped vreg (latent #93-class bug) #104 territory)🤖 Generated with Claude Code