Skip to content

feat(#242): VCR-RA-003 phase 2 — across-CALL + across-JOIN allocation validation (whole-function)#817

Merged
avrabe merged 6 commits into
mainfrom
feat/49-vcr-ra-003-ph2
Jul 17, 2026
Merged

feat(#242): VCR-RA-003 phase 2 — across-CALL + across-JOIN allocation validation (whole-function)#817
avrabe merged 6 commits into
mainfrom
feat/49-vcr-ra-003-ph2

Conversation

@avrabe

@avrabe avrabe commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

VCR-RA-003 phase 2 (epic #242) — make the register-allocation validator WHOLE-FUNCTION

v0.48 (#808) shipped validate_final_allocation — an UNCONDITIONAL per-compilation register-allocation validator (runs on every ARM compile in the default --features riscv build, hard-errors on violation) — but BOUNDED to straight-line segments + spill/reload + callee-saved discipline. This lane extends it past straight-line to control-flow joins and calls.

Two new invariants (each anchored on a reference OUTSIDE inferred value-identity — the v0.48 vacuity lesson)

3. Caller-saved preservation across a CALL. A value in R2/R3/R12 defined before a bl/blx/call and read after it (no intervening redef) crossed a boundary the AAPCS is contractually allowed to clobber → CallerSavedLiveAcrossCall. The reference is the ABI contract at the call, not an inferred redefinition. R0/R1 are excluded (a call DEFINES them: the return value) — a documented false-negative boundary (FN < FP). Reasoned locally; does not modify the shared reg_effect (whose None-on-call meaning the phase-1 callee-saved fail-safe + eliminate_unread_frame_stores both depend on).

4. Value availability across a JOIN. A register live-in to a CF join (≥2 preds) must be AVAILABLE — must-defined on every incoming path, or an entry live-in → else JoinValueNotAvailable. Forward MUST (intersection) availability fixpoint (universe-init, not empty — it is a must-analysis) over a dedicated join CFG that admits bx lr returns (sinks) and calls (fall-through with a caller-saved def-set), so branchy functions with calls/returns stay analyzable. Entry seed = params R0-R3, SP, LR, and the RESERVED registers R9/R10/R11/R12 (contracts::regalloc::RESERVED_REGS — R11 = linmem base, resident by construction).

RaFinalVerdict gains NotAttempted { reason } — a loud honest decline when the join CFG can't be built (numeric branch, BrTable, computed Bx, dup label). NON-FATAL at the arm_backend call site; only Violation hard-errors. Decline>guess applied to the checker itself.

Red-first evidence (non-vacuity proven BEFORE trusting)

cargo test -p synth-synthesis --lib ra003_ — 17 tests. New phase-2:

  • RED across-CALL: ra003_red_caller_saved_live_across_call_is_caught — R2 defined, bl, R2 read → asserts the SPECIFIC CallerSavedLiveAcrossCall (never Violation | *).
  • RED across-JOIN: ra003_red_across_join_clobber_is_caught — R4 on then-path, R5 (not R4) on else-path, join reads R4 → asserts the SPECIFIC JoinValueNotAvailable{R4}.
  • GREEN: return-value-after-call, callee-saved-across-call, both-paths-define, dominator-defined-at-join.
  • ra003_join_declines_on_unmodeled_control_flowBrTableNotAttempted (never a silent Consistent).

Silent on real codegen + vacuity-in-practice

  • Invariant 4 FIRES on real branchy codegen (traced: if_else_result_343 / cf_shapes_500 / block_brif_483 joins all Consistent, incl. a real R11 availability check) — not unit-tested-but-dead-in-shipping.
  • Found + fixed a real false positive: block_brif_483's nested — a str [r11,#off] at a join, R11 (linmem base) defined nowhere in the stream → fixed by seeding the reserved registers.
  • Sweep: 130 fixtures on --relocatable + control_step & 7 branchy × {relocatable, default} = 16 pairs → 0 false positives.

Path scope (honest)

Invariant 4 fires on the label-form / --relocatable path (analyzable CFG). On the default optimized path branches are pre-resolved to NUMERIC offsets before the validator runs, so invariant 4 declines to NotAttempted there (invariants 1-3 still run on both paths — control_step, the differential's own input, compiles CLEAN on the default path). The NotAttempted decline is common on the default path (~41/130 fixtures), so its diagnostic eprintln is gated behind SYNTH_RA003_VERBOSE (production stays quiet, verdict observable on demand). Numeric-branch CFG = named follow-up.

Frozen / gates

  • Frozen 10/10 byte-identical (control_step 0x00210A55, flight_algo 0x07FDF307) — the validator emits nothing.
  • Full cargo test --workspace green; clippy -D warnings clean; cargo fmt --check clean.
  • claim_check 25/25. rivet: 52 errors / 102 warnings identical to origin/main (pre-existing coverage backlog; this lane adds zero new errors).
  • CI: appended a third step to vcr-ra-003-alloc-validator-gate running scripts/repro/vcr_ra_003_phase2_join_call.py over branchy/mem/call fixtures on BOTH paths (existing red-first + frozen steps untouched).

Still loud-flags-unattempted (named follow-ups)

Merged origin/main (VCR-SEL-005 #816 + #761 R9/globals base separation); re-verified all gates post-merge.

🤖 Generated with Claude Code

https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L

avrabe and others added 6 commits July 17, 2026 16:17
… validation

Extend the unconditional per-compilation register-allocation validator past
straight-line to WHOLE-FUNCTION control flow:

- Invariant 3 (across-CALL): a caller-saved reg R2/R3/R12 live across a
  bl/blx/call (defined before, read after, no intervening redef) is flagged —
  the AAPCS boundary destroys it. Anchored on the ABI contract at the call, not
  inferred value-identity (the v0.48 vacuity lesson). R0/R1 excluded (return
  value). Does NOT touch the shared reg_effect (whose None-on-call meaning the
  phase-1 fail-safe depends on).

- Invariant 4 (across-JOIN): a reg live-in to a CF join (>=2 preds) must be
  available (must-defined on every incoming path, or an entry live-in). Forward
  MUST/intersection availability fixpoint (UNIVERSE-init, not empty) over a
  dedicated join CFG that admits bx-lr returns (sink) and calls (fall-through
  with a caller-saved def-set). Entry seed = R0-R3, SP, LR, and the RESERVED
  regs R9/R10/R11/R12 (linmem base etc. — resident by construction).

- RaFinalVerdict gains NotAttempted { reason } (loud honest decline when the
  join CFG can't be built: numeric branch, BrTable, computed Bx, dup label).
  NON-FATAL at the arm_backend call site; only Violation hard-errors.

Vacuity-in-practice verified: invariant 4 fires on real branchy codegen
(if_else_result_343, cf_shapes_500, block_brif_483 joins all Consistent).
Caught + fixed a real false positive (block_brif nested: R11 linmem base
defined nowhere in-stream) by seeding the reserved regs. 130-fixture sweep:
0 false positives. Frozen 10/10 byte-identical.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
…gate

Six new ra003_ tests prove the phase-2 checks are NON-VACUOUS:
- ra003_red_caller_saved_live_across_call_is_caught: R2 defined, bl, R2 read →
  CallerSavedLiveAcrossCall (asserts the SPECIFIC violation, not Violation|*).
- ra003_green_return_value_used_after_call: bl defines R0, R0 read → Consistent
  (the "use the return value" pattern is not a clobber).
- ra003_green_callee_saved_value_across_call: R4 (callee-saved) across bl → clean.
- ra003_red_across_join_clobber_is_caught: R4 defined on then-path, R5 (not R4)
  on else-path, join reads R4 → JoinValueNotAvailable{R4} (specific).
- ra003_green_across_join_both_paths_define / _dominator_defined: correct diamonds
  stay Consistent (no false positive on both-paths-define or dominator-def).
- ra003_join_declines_on_unmodeled_control_flow: BrTable → NotAttempted (loud
  honest decline, never a silent Consistent).

Full workspace green (synth-synthesis lib 641), clippy -D clean, fmt clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
- ci.yml: append a third step to vcr-ra-003-alloc-validator-gate running
  scripts/repro/vcr_ra_003_phase2_join_call.py over branchy/mem/call fixtures
  (silent-on-real-branchy-codegen; the R11-linmem-base false-positive guard).
  Existing red-first (ra003_) + frozen steps untouched.
- scripts/repro/vcr_ra_003_phase2_join_call.py: compiles 7 CF-join / memory /
  call fixtures through the unconditional validator, asserts 0 false positives.
- artifacts/verified-codegen-roadmap.yaml: VCR-RA-003 phase-2 note (invariants
  3+4, the NotAttempted honest decline, the reserved-reg seed fix, vacuity-in-
  practice evidence). Zero new rivet errors (52/102 identical to origin/main).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
…e eprintln

Advisor caught: all phase-2 verification was --relocatable only; the DEFAULT
optimized path (what the control_step differential actually compiles) had never
run through invariants 3/4.

- Verified the default path: control_step + 7 branchy fixtures × {relocatable,
  default} = 16 pairs, 0 false-positive Violations; full 130-fixture default-path
  sweep also 0 violations. No blocking issue.
- HONEST SCOPE: on the default path branches are pre-resolved to NUMERIC offsets,
  so invariant 4 declines to NotAttempted there (invariants 1-3 still run). It
  FIRES on the label-form/relocatable path. Numeric-branch CFG = named follow-up.
- Gate the NotAttempted eprintln behind SYNTH_RA003_VERBOSE: the decline is the
  COMMON default-path case (~41/130 fixtures) — unconditional it would spam every
  branchy production compile (noise phase 1 never produced). Verdict stays
  observable on demand; the repro greps FAILED so it's unaffected.
- repro now sweeps BOTH paths + control_step.wasm; roadmap states the path scope.

Frozen 10/10 byte-identical, clippy 0, control_step default-path clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
…verse-init

Advisor caught: universe-init (the load-bearing MUST/intersection fixpoint
correction) had zero CONFIRMED exercise as a Consistent-via-fixpoint result on a
real back-edge — all prior evidence was forward diamonds. loop_param_bound_663
traces Consistent on the relocatable path, but the sweep conflated
Consistent/NotAttempted.

ra003_green_loop_back_edge_preserves_availability: a value (R4) defined before a
loop and read in the body; the loop header is a join of preheader + back-edge.
Verified NON-VACUOUS: flipping avail_out init from universe to empty makes this
test FAIL (a false positive — the back-edge starts empty and the intersection
drops the dominator-defined R4), so it durably guards the universe-init choice.

18 ra003_ tests pass; frozen 10/10 (test-only, byte-identical); clippy 0; fmt clean.
Confirmed ci.yml line-75 PyYAML/actionlint parse note is PRE-EXISTING on
origin/main (a `space::` scalar), not this lane's merge resolution.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
@avrabe
avrabe merged commit 17f6751 into main Jul 17, 2026
4 checks passed
@avrabe
avrabe deleted the feat/49-vcr-ra-003-ph2 branch July 17, 2026 14:51
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