feat(fuzz): cargo-fuzz harnesses for ARM instruction selection (#82)#100
Open
avrabe wants to merge 2 commits into
Open
feat(fuzz): cargo-fuzz harnesses for ARM instruction selection (#82)#100avrabe wants to merge 2 commits into
avrabe wants to merge 2 commits into
Conversation
This was referenced May 11, 2026
bacc0e0 to
9c339bb
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
6 tasks
9c339bb to
781bfab
Compare
5 tasks
avrabe
added a commit
that referenced
this pull request
May 12, 2026
Closes #103. Audited 24 i64 ops that previously fell through to `select_default` which hardcoded R0..R3 — all 24 now allocate destination via `alloc_consecutive_pair` / `alloc_temp_safe` with the popped halves in `extra_avoid`. Categories: 10 comparisons (Eq/Ne/Lt/Le/Gt/Ge × Signed/Unsigned), 5 arithmetic (Mul/Div/Rem × Signed/Unsigned), 2 rotations (Rotl/Rotr), 3 unary bit (Clz/Ctz/Popcnt), 3 sign-extend (Extend8/16/32 Signed), 1 conversion (I32WrapI64). Same class as v0.1.1 #86 (I64Const) — found by PR #100's `i64_lowering_doesnt_clobber_params` fuzz harness. +3 regression tests.
781bfab to
31376ca
Compare
6 tasks
d033349 to
cfd9efc
Compare
4 tasks
Adds a workspace-excluded fuzz crate under `fuzz/` with four targeted libfuzzer harnesses for the WASM → IR → ARM lowering pipeline. Each harness inverts a specific bug class that has actually shipped (or nearly shipped) into a fuzz-detectable invariant: * wasm_ops_lower_or_error — panic / unencodable-instr class * wasm_to_ir_roundtrip_op_coverage — silent op-drop class (issue #93) * i64_lowering_doesnt_clobber_params — AAPCS-clobber class (#85, #86) * encoder_no_panic — encoder panic on valid ArmOp Layout: `fuzz/` is excluded from the main workspace via `[workspace] members = []` so the libfuzzer-sys ASan toolchain requirement does not leak into stable workspace builds. `fuzz/src/common.rs` defines a curated Arbitrary-derived `FuzzOp` mirror of `synth_core::WasmOp` (i32/i64 arithmetic + comparison + conversions + locals) that lowers deterministically into `WasmOp`, so any libfuzzer crash carries a replayable WASM op sequence. `wasm_to_ir_roundtrip_op_coverage` would have caught issue #93 by itself: it asserts that every value-producing wasm op leaves at least one IR instruction after `optimize_full` (with all optimizations disabled). The three issue-#93 conversion ops (`I64ExtendI32U/S`, `I32WrapI64`) are *skipped* until PR #97 lands; the skip block is inline-documented and removing it after #97 restores full coverage and re-arms the regression check. CI smoke job (`.github/workflows/fuzz-smoke.yml`) runs each harness for 60 seconds on every PR, on the same self-hosted Linux pool as the rest of synth CI, with crash artifacts uploaded on failure. Long-budget nightly runs are out of scope for this PR. Trace: NFR-002 Trace: issue-82 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
PR #100's i64_lowering_doesnt_clobber_params harness was over-broadly flagging any write to R{p} before LocalGet(p) — including legitimate writes the wasm program explicitly requested via LocalSet(p)/LocalTee(p). This produced false-positive crashes that masked the real I32WrapI64 intermediate-op-pin bug (now fixed in #111). The carve-out only suppresses writes whose source wasm op is exactly LocalSet(p) or LocalTee(p) writing param p — every other write before LocalGet(p) is still flagged. The harness retains its full ability to catch genuine compiler-emitted AAPCS clobbers.
cfd9efc to
2051f63
Compare
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.
Closes #82 (scaffolds the fuzz directory and the smoke job; long-budget
nightly campaigns and capstone-differential are left as follow-ups on
the same issue).
Why
synth's V&V coverage matrix has property-tests, Rocq proofs, and Z3
translation validation, but no cargo-fuzz. ARM-codegen edges — encoding
corner cases, instruction-mix edge cases, odd register allocations — are
exactly where silent mis-compilations live and where fuzzing pays the
most.
Concrete evidence: issue #93 (silicon-blocking) escaped every existing
check because the bug —
optimizer_bridge::wasm_to_irsilently mappingthree i32↔i64 conversion ops to
Opcode::Nopvia a_ =>fallback —left an IR that looked perfectly well-formed to type-based checkers. A
single-op fuzz harness would have flagged it in seconds.
What
Adds
fuzz/(workspace-excluded), with libfuzzer-sys + arbitrary, andfour targeted harnesses. Each harness inverts a bug class — not a
single bug instance — into a fuzz-detectable invariant.
wasm_ops_lower_or_erroroptimize_full+ir_to_armandselect_with_stackeach returnOk(_)/Err(_); emittedArmOps round-trip throughArmEncoder::encode.wasm_to_ir_roundtrip_op_coveragei64_lowering_doesnt_clobber_paramsp < num_params, no ARM instr emitted from a wasm op precedingLocalGet(p)writesR{p}.encoder_no_panicArmEncoder::encodereturnsOk(_)/Err(_)on randomly-parametrised but well-typedArmOpacross ARM32 / Thumb-2 / Thumb-2+VFP.Each
FuzzOplowers deterministically to onesynth_core::WasmOp, solibfuzzer crashes carry replayable WASM op sequences — drop the saved
artifact into a unit test and the regression is pinned.
CI smoke gate
.github/workflows/fuzz-smoke.ymlruns each harness for 60 s on everyPR + push to main, on the same self-hosted Linux pool as the rest of
synth CI (
[self-hosted, linux, x64, rust-cpu]). Matrix:wasm_ops_lower_or_errorwasm_to_ir_roundtrip_op_coveragei64_lowering_doesnt_clobber_paramsencoder_no_panicCrash artifacts (corpus + minimised input) are uploaded on failure with
30-day retention.
Verification
cargo build --workspaceclean (synth-fuzz is workspace-excluded; main workspace unaffected).cd fuzz && cargo buildclean — all four harnesses compile under stable Rust.cd fuzz && cargo clippy --all-targets -- -D warningsclean.cd fuzz && cargo fmt --checkclean.cargo +nightly fuzz run X -- -max_total_time=60— runtime validation is left to the CI smoke job (cargo-fuzz needs nightly + libfuzzer ASan, which is not available in the dev sandbox this PR was authored in).Notes
optimizer_bridge.rs(PR fix(opt): i64 lowering miscompiles memset-style loop counter on Cortex-M (#93) #97 in flight),arm_backend.rs(feat(opt): direct hi/lo extraction for u64-packed values (#94) #98),instruction_selector.rs(feat(opt): fold constant address into base+offset for memory ops (#95) #96) per task spec.How to run locally
rustup toolchain install nightly cargo install cargo-fuzz cd fuzz cargo +nightly fuzz run wasm_to_ir_roundtrip_op_coverage -- -max_total_time=300Trace: NFR-002
Refs: #82, #93
🤖 Generated with Claude Code