Skip to content

feat(rv32-promo): measured no-grow profitability for local promotion (#472, #601, epic #242) - #626

Merged
avrabe merged 1 commit into
mainfrom
fix/rv-local-promo-profitability
Jul 8, 2026
Merged

feat(rv32-promo): measured no-grow profitability for local promotion (#472, #601, epic #242)#626
avrabe merged 1 commit into
mainfrom
fix/rv-local-promo-profitability

Conversation

@avrabe

@avrabe avrabe commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

RV32 local-promotion profitability: MEASURED no-grow decision (#472, #601, epic #242)

The SYNTH_RV_LOCAL_PROMO flip was HELD out of the RV32 lever flip-wave (#601) because the v1 profitability model ("≥2 accesses repay the save/restore") under-charged: it priced neither the per-RETURN epilogue restore (preserve_callee_saved duplicates the lw s_i restores into EVERY ret) nor the WAR-snapshot mvs. Small functions grew net: war_set 56→64 B, war_tee 60→68 B, control_step_decide +4 B.

The fix — measure, don't model

select_inner now lowers the function unpromoted (the baseline — byte-identical to flag-off), then over every non-empty subset of the candidate locals (≤3 → ≤7 attempts), and keeps a promoted lowering only when emitted_byte_size(promoted) <= emitted_byte_size(baseline) — strict no-grow, priced on the ACTUAL emitted sequence, so per-return restores, WAR mvs, zero-inits and frame addi interactions are all charged by construction. Subsets matter: one local's win must not smuggle another local's loss past the gate. emitted_byte_size is mirror-pinned to assemble_function's pass-1 sizing by emitted_byte_size_matches_assembled_text (the #511 estimator↔encoder drift lesson).

Salvage provenance

Recovered from an interrupted session (two predecessor agents died mid-work); the salvage commit was found complete in the worktree, then independently re-verified end-to-end below. Branch is based on current main (v0.30.2).

RED → GREEN

The new corpus gate rv32_local_promo_no_grow_corpus_472 (synth-cli tests) was re-run against the OLD model (both backend files reverted to origin/main, same test file):

no function may grow under RV32 local promotion: control_step_decide flag-off=484B flag-on=488B (control_step.wasm)
test result: FAILED

With the measured model: test result: ok.

Execution differentials — all 11 scripts/repro/*riscv*_differential.py, flag ON, fresh binary

harness result
const_addr_fold PASS (−56 B, 14 folds)
control_step PASS (correct + ABI-compliant)
controller_step PASS (correct + ABI-compliant)
filter_axis PASS (correct + ABI-compliant)
i64_divs_317 PASS
if_else_result_343 PASS
rv32_cmp_select_472 PASS (182 vectors, 0 failures)
rv32_local_promotion_472 PASS
shift_fold PASS (21/21)
signed_div_const PASS
u64_unpack PASS

Per-function byte deltas — ENTIRE repro corpus (67 RV32-compiling fixtures × flag-off/on, ELF .symtab sizes)

fixture function off on Δ
controller_step.wasm/.wat controller_step_decide 332 308 −24
flight_seam.wasm/.wat controller_step 416 412 −4
flight_seam_flat.wasm/.wat controller_step 416 412 −4
flight_seam_flat.wasm/.wat flight_algo 756 724 −32
rv32_cmp_select_472.wat clamp 104 100 −4
rv32_local_promotion_472.wat accum 264 196 −68

10 functions shrink, 0 grow, function sets unchanged across all 67 fixtures. Every other function is byte-neutral (the measured gate declines, falling back to the exact baseline lowering — war_set/war_tee/rbw/control_step_decide included).

Frozen anchors

Flag stays opt-in in this PR — flag-off is byte-identical (single baseline run). frozen_codegen_bytes (ARM + RV32 gates + both escape hatches): untouched, green. Bonus, verified for the follow-up flip: the two pinned RV32 goldens (control_step 6ac5d7f9…/484, signed_div_const 15fa429d…/88) are byte-identical even flag-ON.

Gate (all green)

  • cargo test -p synth-backend-riscv (210) · cargo test -p synth-cli (all suites)
  • cargo test --workspace — 104 suites, 0 failures
  • cargo fmt --check · cargo clippy --workspace --all-targets -- -D warnings

Follow-up: the default-on flip (#601 refreeze ritual) rides in the next PR.

🤖 Generated with Claude Code

…472, #601, epic #242)

Salvaged from an interrupted session (299 tool uses): select_inner now
MEASURES promotion profitability — lowers the unpromoted baseline plus
every candidate subset (<=3 locals -> <=7 attempts) and keeps a promoted
lowering only when emitted_byte_size <= baseline. Prices per-return
epilogue restores and WAR-snapshot mvs by construction (the #601 flip
blocker). emitted_byte_size mirror-pinned to assemble_function pass-1
sizing (#511 lesson). Corpus no-grow gate: rv32_local_promo_no_grow_corpus_472.
Flag SYNTH_RV_LOCAL_PROMO stays opt-in.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@codecov

codecov Bot commented Jul 8, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.19626% with 6 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
crates/synth-backend-riscv/src/selector.rs 96.70% 6 Missing ⚠️

📢 Thoughts on this report? Let us know!

@avrabe
avrabe merged commit 2145252 into main Jul 8, 2026
29 checks passed
@avrabe
avrabe deleted the fix/rv-local-promo-profitability branch July 8, 2026 00:28
avrabe added a commit that referenced this pull request Jul 8, 2026
… RV promo flip + z3-free default (#621/#623/#624/#626/#627) (#628)

Pin sweep 0.30.2 -> 0.31.0 + CHANGELOG.

Co-authored-by: Claude Fable 5 <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

Development

Successfully merging this pull request may close these issues.

1 participant