feat(vcr-sel): cmp→select → IT-block predication fusion, flag-off (VCR-SEL-004, #242, #428) - #444
Merged
Merged
Conversation
…R-SEL-004, #242, #428) The selector lowers a `select` whose condition is a comparison to a *materialize-then-re-test* sequence: `cmp a,b; SetCond D,c; cmp D,#0; movne dst,v1; moveq dst,v2`. The `SetCond` and the select's `cmp D,#0` are pure overhead — the comparison already set NZCV, and each `SelectMove` is an independent flag-preserving `IT;MOV`. This adds a LATE peephole `fuse_cmp_select` that collapses the pair onto the comparison's own flags (delete `SetCond` + `cmp D,#0`, retarget the moves to `c` / `invert(c)`), yielding the textbook predicated clamp `cmp a,b; movc dst,v1; mov{!c} dst,v2` — −2 instructions per fused select. gale #428 re-ranked this the #1 hot-path size/cycle lever on the gust_mix clamp chain. Sound by construction (each guard has a negative unit test): - flags reused only when nothing clobbers them in the `SetCond`→`cmp` window (`clobbers_flags`, a tight load/store/push/pop/nop allowlist); - the boolean deleted only when provably dead — a downstream redefinition before any read / unmodeled op / branch / end (`reg_dead_by_redef`, so an R0 return value is never mistaken for dead); - `[i-1]` must be the flag-setting compare; `dst != D`. Also adds `Condition::invert()` (exhaustive over all 10 conditions). Wired into arm_backend.rs AFTER range-realloc (final register identities for the dead-D proof) and before encode, BEHIND `SYNTH_CMP_SELECT_FUSE=1`. Default-off ⇒ a literal no-op ⇒ the three frozen differentials (control_step 0x00210A55 / flat+inlined flight_algo 0x07FDF307 / divseam) stay bit-identical, un-rerun. The default-on flip is the held byte-changing step, gated on gale's `gust_codegen_bench` (G474RE) + the VCR-ORACLE-001 execution differential — DEFERRED, not done here. Validation: 8 liveness unit tests (positive incl. the gust_mix ×2 in-place clamp; 5 negative guards), 3 encoder byte-tests (all 10 IT conditions), rewrite-count>0 on real fixtures (control_step 3, flight_algo/controller 5-6), and an arm-none-eabi-objdump diff confirming `cmp r0,#19; movcc r5,r6` replaces the 5-insn materialize+re-test while unsafe sites correctly decline. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
This was referenced Jun 23, 2026
avrabe
added a commit
that referenced
this pull request
Jun 23, 2026
…use gale's clamp-#2 decline (VCR-SEL-004, #428, #242) (#448) gale's gust_codegen_bench (qemu -icount) confirmed cmp→select (#444) is correctness-identical over [0,2047] and a monotonic win (1.05→0.95 ticks, 2.63×→2.375×, 132→124 B), but surfaced two observations: the fusion fired one gust_mix clamp and declined its sibling, and the two-move moveq→mov{invert(c)} arm never executed end-to-end. Investigation (objdump of the synthetic two-move fixture) found a SINGLE root cause — not gale's "narrow dst-match" guess: reg_dead_by_redef requires an explicit downstream redefinition of the boolean before deleting the SetCond. The real selector ABANDONS the boolean temp after the select (used once, not live-out, never rewritten), so the guard conservatively declines. This one mechanism unifies BOTH the clamp-#2 decline AND the two-move-arm-never-fires gap: the two-move arm is effectively unreachable through the real selector today. Adds: - scripts/repro/cmp_select_two_move.wat — forces the selector's two-move form (val2 a live param); compiles, and (verified by objdump) currently declines. - cmp_select_two_move_coverage.rs — characterization test: the two-move fixture fuses 0 sites while in-place control_step fuses >0 (non-vacuous). Flips to >=1 when the deadness guard is fixed. - VCR-SEL-004 note: the finding, the unified follow-on (teach reg_dead_by_redef that "abandoned + not-live-out" is dead — closes clamp #2 AND makes the two-move arm reachable), and the two flip preconditions (two-move exercised end-to-end + gale G474RE DWT no-regression). Frozen-safe: test + fixture + docs only, zero production change. fmt + clippy + rivet (0 non-xref) clean. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
avrabe
added a commit
that referenced
this pull request
Jun 23, 2026
…wo-move arm reachable (VCR-SEL-004 #7, #428, #242) #444's deadness guard deleted the SetCond only when the boolean was explicitly REDEFINED downstream. The real selector abandons the boolean temp after the select (used once, not live-out, never rewritten), so the guard declined — both gale's gust_mix clamp #2 and EVERY two-move shape, leaving the two-move moveq→mov{invert(c)} arm unreachable through the real selector. Fix: reg_dead_by_redef now recognizes the function RETURN terminator (`bx lr`, or `pop {…,pc}`). At a return the only live-out registers are the ABI result regs {R0,R1} (i32→R0, i64→R0:R1; R2/R3 are i64 operand inputs, never results), so an abandoned boolean in R2..R8 is proven dead and the SetCond can be deleted. Soundness: the forward scan still bails (declines) on every Label and branch — reg_effect returns None for them — so it never walks past a join point where the boolean could be live on another edge. The `d ∉ {R0,R1}` guard is load-bearing. Locked by unit tests: positive (abandoned boolean before pop/bx-lr ⇒ fuse, with movlt/movge — the invert arm) and negatives (boolean in a result reg, a branch in the tail, and bx-to-non-LR all still decline). Verified end-to-end: scripts/repro/cmp_select_two_move.wat now lowers to `cmp r0,r1; movlt r4,r0; movge r4,r2` — the mov{invert(c)} arm firing for the first time, semantically identical to the select. Characterization test flipped to assert reachability (fuses 1 / in-place control_step fuses >0). Frozen-safe: reg_dead_by_redef is reached ONLY via fuse_cmp_select, which runs only behind SYNTH_CMP_SELECT_FUSE (off by default) — so the shipped default path is byte-identical; frozen byte gates #445/#446 stay GREEN flag-off (verified). NOT claimed (owed by the flip, the separate gated step): the two-move arm is reachable + objdump/IR-correct but NOT execution-validated — gale's next gust_codegen_bench (flag-on) exercises it for the first time. "Closes clamp #2" is by the same mechanism but unverified locally (no gust_mix.wasm); gale's bench confirms. No flip, no re-freeze, no tag. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
avrabe
added a commit
that referenced
this pull request
Jun 23, 2026
…wo-move arm reachable (VCR-SEL-004 #7, #428, #242) #444's deadness guard deleted the SetCond only when the boolean was explicitly REDEFINED downstream. The real selector abandons the boolean temp after the select (used once, not live-out, never rewritten), so the guard declined — both gale's gust_mix clamp #2 and EVERY two-move shape, leaving the two-move moveq→mov{invert(c)} arm unreachable through the real selector. Fix: reg_dead_by_redef now recognizes the function RETURN terminator (`bx lr`, or `pop {…,pc}`). At a return the only live-out registers are the ABI result regs {R0,R1} (i32→R0, i64→R0:R1; R2/R3 are i64 operand inputs, never results), so an abandoned boolean in R2..R8 is proven dead and the SetCond can be deleted. Soundness: the forward scan still bails (declines) on every Label and branch — reg_effect returns None for them — so it never walks past a join point where the boolean could be live on another edge. The `d ∉ {R0,R1}` guard is load-bearing. Locked by unit tests: positive (abandoned boolean before pop/bx-lr ⇒ fuse, with movlt/movge — the invert arm) and negatives (boolean in a result reg, a branch in the tail, and bx-to-non-LR all still decline). Verified end-to-end: scripts/repro/cmp_select_two_move.wat now lowers to `cmp r0,r1; movlt r4,r0; movge r4,r2` — the mov{invert(c)} arm firing for the first time, semantically identical to the select. Characterization test flipped to assert reachability (fuses 1 / in-place control_step fuses >0). Frozen-safe: reg_dead_by_redef is reached ONLY via fuse_cmp_select, which runs only behind SYNTH_CMP_SELECT_FUSE (off by default) — so the shipped default path is byte-identical; frozen byte gates #445/#446 stay GREEN flag-off (verified). NOT claimed (owed by the flip, the separate gated step): the two-move arm is reachable + objdump/IR-correct but NOT execution-validated — gale's next gust_codegen_bench (flag-on) exercises it for the first time. "Closes clamp #2" is by the same mechanism but unverified locally (no gust_mix.wasm); gale's bench confirms. No flip, no re-freeze, no tag. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
avrabe
added a commit
that referenced
this pull request
Jun 23, 2026
…wo-move arm reachable (VCR-SEL-004 #7, #428, #242) (#449) #444's deadness guard deleted the SetCond only when the boolean was explicitly REDEFINED downstream. The real selector abandons the boolean temp after the select (used once, not live-out, never rewritten), so the guard declined — both gale's gust_mix clamp #2 and EVERY two-move shape, leaving the two-move moveq→mov{invert(c)} arm unreachable through the real selector. Fix: reg_dead_by_redef now recognizes the function RETURN terminator (`bx lr`, or `pop {…,pc}`). At a return the only live-out registers are the ABI result regs {R0,R1} (i32→R0, i64→R0:R1; R2/R3 are i64 operand inputs, never results), so an abandoned boolean in R2..R8 is proven dead and the SetCond can be deleted. Soundness: the forward scan still bails (declines) on every Label and branch — reg_effect returns None for them — so it never walks past a join point where the boolean could be live on another edge. The `d ∉ {R0,R1}` guard is load-bearing. Locked by unit tests: positive (abandoned boolean before pop/bx-lr ⇒ fuse, with movlt/movge — the invert arm) and negatives (boolean in a result reg, a branch in the tail, and bx-to-non-LR all still decline). Verified end-to-end: scripts/repro/cmp_select_two_move.wat now lowers to `cmp r0,r1; movlt r4,r0; movge r4,r2` — the mov{invert(c)} arm firing for the first time, semantically identical to the select. Characterization test flipped to assert reachability (fuses 1 / in-place control_step fuses >0). Frozen-safe: reg_dead_by_redef is reached ONLY via fuse_cmp_select, which runs only behind SYNTH_CMP_SELECT_FUSE (off by default) — so the shipped default path is byte-identical; frozen byte gates #445/#446 stay GREEN flag-off (verified). NOT claimed (owed by the flip, the separate gated step): the two-move arm is reachable + objdump/IR-correct but NOT execution-validated — gale's next gust_codegen_bench (flag-on) exercises it for the first time. "Closes clamp #2" is by the same mechanism but unverified locally (no gust_mix.wasm); gale's bench confirms. No flip, no re-freeze, no tag. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
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.
What
Adds the late peephole
fuse_cmp_select(VCR-SEL-004, epic #242) — the cmp→select half of gale's #1 hot-path size/cycle lever (#428).The selector lowers a
selectwhose condition is a comparison to a materialize-then-re-test sequence:The
SetCondand the select'scmp D,#0are pure overhead — the comparison already set NZCV, and eachSelectMoveis an independent flag-preservingIT;MOV. The peephole collapses the pair onto the comparison's own flags (deleteSetCond+cmp D,#0, retarget the moves toc/invert(c)), yielding the textbook predicated clamp:Real-fixture objdump (control_step), flag-on:
Scope — select half only
The artifact title is "select/br_if". This PR lands the
select→movCChalf. Thebr_if→predicated-branch half is not done (branch consumers are unmodeled byreg_effectand never match) — deferred follow-up. Artifact note scoped accordingly.Sound by construction (each guard has a negative unit test)
SetCond→cmpwindow (clobbers_flags— tight ldr/str/push/pop/nop allowlist);reg_dead_by_redef, so an R0 return value is never mistaken for dead);[i-1]must be the flag-setting compare;dst != D.Plus
Condition::invert()(exhaustive over all 10 conditions).Frozen-safe / gated
Wired into
arm_backend.rsafter range-realloc (final register identities for the dead-D proof) and before encode, behindSYNTH_CMP_SELECT_FUSE=1. Default-off ⇒ a literal no-op ⇒ the three frozen differentials (control_step0x00210A55/ flat+inlined flight_algo0x07FDF307/ divseam) stay bit-identical, un-rerun.Validation (this step)
IT <cond>; MOVconditions, exact bytes for the LT/GE + LO/HS pairs fusion newly produces;Deliberately deferred (owed by the default-on flip step — NOT done here)
gust_codegen_benchon G474RE (≤1.3× fn-only) — the agreed on-silicon kill-criterion. Only then does the flag flip default-on and the tag cut.moveq→mov{invert(c)}path (whereinvert()matters at runtime) has run in unit/encoder tests but never selector→fusion→encoder→exec on a real function. The flip's differential must include a fixture forcing a non-in-place select (e.g.val2a live param). (wasm-synth z_impl: sem->count not incremented on give (stays 0, limit=1) on hardware — u64-packed new_count miscompiled; binary sem never signals (v0.11.12) #204/arm32 encoder drops the register index on indexed loads/stores ([r11] instead of [r11, rN]) #206 IR-right/composition-wrong lesson.)br_if→predicated-branch half (separate follow-up).Refs #242, #428. Baseline v0.12.0.
🤖 Generated with Claude Code