Skip to content

feat(vcr-sel-riscv): RV32 cmp→select fusion peephole, flag-off (#472)#568

Merged
avrabe merged 1 commit into
mainfrom
feat/472-rv32-cmp-select
Jul 2, 2026
Merged

feat(vcr-sel-riscv): RV32 cmp→select fusion peephole, flag-off (#472)#568
avrabe merged 1 commit into
mainfrom
feat/472-rv32-cmp-select

Conversation

@avrabe

@avrabe avrabe commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Ports the ARM cmp→select lever (VCR-SEL-004) to the RV32 backend for #472: an i32 comparison whose boolean DIRECTLY feeds a select fuses into the select's branch — the boolean materialization (slt/sltu/xor+sltiu/xori) is deleted and the branch tests the comparison itself (blt a, b instead of slt t, a, b; bne t, zero), saving 1–2 instructions per select. RV32's B-type branch comparators (beq/bne/blt/bge/bltu/bgeu) play the role ARM's IT-predicated flags do.

Flag-off by default: SYNTH_RV_CMP_SELECT. With the flag unset, record_pending_cmp is a no-op so pending_cmp is never populated and lower_select emits the exact baseline bne cond, zero — unchanged by construction.

Safety of the fusion

  • lower_one take()s the pending record at the start of EVERY op — it only survives from a comparison to the immediately following op.
  • lower_select additionally requires the popped condition register to be exactly the recorded bool_reg AND out.len() unchanged since the comparison (also declines if a pop emitted a reload), so a stale record can never smuggle through.
  • Deleting the boolean tail is sound: bool_reg is a fresh temp pushed exactly once and popped by the select — nothing else observes it; rs1/rs2 are unchanged because no instruction was emitted in between. The fused branch is emitted before any mv, so dst aliasing a comparison operand is harmless.

Gates (all foreground, exit-code 0)

  • Flag-off byte-identity: cargo test -p synth-cli --test frozen_codegen_bytes3/3 including the RV32 anchor (frozen_fixtures_rv32_text_is_bit_identical_oracle_001).
  • Differential oracle (scripts/repro/rv32_cmp_select_472_riscv_differential.py, unicorn RV32 vs wasmtime, symbols via ELF .symtab): all 11 fusible comparison kinds + i32-cmp-selecting-i64 + i64-cmp (out of scope, baseline path) + back-to-back selects + clamp, 13 vector pairs incl. sign boundaries (0x80000000 / 0xFFFFFFF0):
    • flag OFF: 182 vectors, 0 failures — PASS
    • flag ON: 182 vectors, 0 failures — PASS
  • Size delta (repro module, flag-on): .text 1176 → 1096 bytes (−80 B, −6.8%).
  • Unit tests: cargo test -p synth-backend-riscv — 206 passed, incl. 8 new _472 tests (flag-off ≡ default path, per-kind fused sequences for lt_s/eq/ge_u/eqz, i64-operand select, adjacency invalidation via local.tee, i64-comparison exclusion).
  • cargo fmt --check exit 0; cargo clippy -p synth-backend-riscv --all-targets -- -D warnings exit 0.

Closes #472.

🤖 Generated with Claude Code

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@codecov

codecov Bot commented Jul 2, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@avrabe avrabe merged commit f60639b into main Jul 2, 2026
24 checks passed
@avrabe avrabe deleted the feat/472-rv32-cmp-select branch July 2, 2026 09:20
avrabe added a commit that referenced this pull request Jul 2, 2026
…ag-off) + gimli 0.34 (#571)

Cuts the accumulated increment: the last RV32 lever port (#568), the
VCR-RA-001 spill spike with the CI-locked flat_flight Belady target (#569),
and the gimli 0.34 bump (#535). VCR-RA-001 stays `implemented` (NOT
strengthened to verified — the spike verified its increment, not the full
allocator claim) and is re-scoped to v0.23.0. Pin sweep + lock + CHANGELOG.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant