RQ-60-VFPPRESSURE increment 2: frame-homed overflow VFP locals — the 13->14 homed-local wall falls, 5/5 falcon cascade stages on cortex-m7dp (Refs #1069) - #1075
Open
avrabe wants to merge 5 commits into
Conversation
…austed, 60-deep stack fine jess's discriminating measurement (fixture credit: jess): the 13->14 wall is HOME residence, not register-file capacity — the #881 rung rescues 60 simultaneously-live f32 on the operand stack but never spills pinned f32/f64 local homes. live13 is the negative control (must keep compiling, base path); live14/live16 are the phase-1 S-file red line (attitude@0.7.0#tick / ekf@0.7.0#estimate shape); live24 forces slot-pool composition with the #587 grow retry; live8d is the D-file twin on cortex-m7dp. Red at this commit (cortex-m7dp --relocatable): live13: 384 bytes; live14/live16/live24: GI-FPU-002 S-file exhausted even after the #881 rung; live8d: D-file exhausted. Refs #1069 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
…g — the 13->14 homed-local wall falls The wall was HOME residence, not register-file capacity: a home S-register is pinned for the function's extent and spill_deepest_vfp explicitly skips homes, so the #881 rung rescued 60-deep operand stacks while 14 homed locals declined. Design (rung-only, byte-identical base path by construction): * Residence at birth, not eviction: a fresh non-param f32/f64 local whose register grant would land above the S7/D3 cap (or fails) lives in the frame from its first def — local.set stores to a permanent [SP,#slot], local.get loads into a fresh temp. The store IS the wasm def, so load-over-store dominance is inherited from wasm's own def-use semantics; a mid-function eviction's transfer store could sit on one arm of a branch, which is the same dominance hole the stack guard's cf_floor exists to avoid. * Victim choice: none — the cap is positional (no pinned home above S7/D3), keeping S8..S15 reclaimable so the worst transient op demand (4 aligned D-pairs, the #869 conversion family) stays satisfiable. First-def order decides who keeps a register; a furthest-next-use pre-scan is a named quality follow-up, not a soundness need. * Ladder: rides the existing vfp_spill_on_exhaustion rung. The permanent slots can exhaust the shared 8-slot pool — the new exhaustion message is a pub const the backend matches (single source, no drifting mirror), and the grow retry now sizes the pool from operand-stack depth + distinct local.set targets. The substring-is-control-flow coupling is pinned red-first by the live24 test. Fixture (jess's, + negative control): live13 ok on the BASE path, live14/16/24 green via the rung, live8d the f64 twin. Execution differential (vfp_local_pressure_1069_differential.py, CI-wired): 70/70 emulations bit-identical to wasmtime incl. ±0/NaN/inf/denormal underflow — every local's round-tripped value feeds the product, so a wrong-slot reload flips result bits. The #881 oracle re-runs green (109/109). Refs #1069 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
…rung functions keep yesterday's bytes Found on the real falcon components before this restructure: rate@0.7.0#tick compiles TODAY via the plain #881 rung with f64 local homes ABOVE the D3 cap, so gating the residence cap on the rung flag itself moved rate.o's bytes. The hard requirement is byte-identity for every currently-compiling function — including ones that compile via the rung — so the cap now lives behind a separate selector lever (set_vfp_frame_home_locals) that the backend tries ONLY after the plain rung (and its pool-grow composition) also failed. Yesterday's three escapes are re-run verbatim first; the frame stage is reached exclusively by functions that failed all of them. Evidence: falcon rate.o and mixer.o now bit-identical main vs branch (cmp exit 0), all five cascade stages still reach nm -> T on cortex-m7dp, and plain_rung_stays_yesterdays_path_for_shapes_it_already_rescues pins the class red-first: base path red, plain rung green by itself, and the frame lever provably produces different instructions for it — the reason the ordering is load-bearing. Ratchet: selector_lines_code 18910 -> 19192 with a same-commit waiver (growth, not substitution: every existing path is byte-frozen so nothing could be deleted in exchange); selector_lines_total 29556 -> 29838; wildcard pins unmoved (the guard override is wildcard-free by construction). Corpus sweep: the new fixture's live8d (f64) declines by precision on the m4f sweep target — named in EXPECTED_DECLINES; the f32 columns compile there. Refs #1069 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
…ijacked them), fix stale references, defensively key the spill-area reservation on the frame lever The inserted #1069 const block landed between try_lower_f32's doc comment + too_many_arguments allow and the fn item, silently re-attaching both to VFP_HOME_CAP_S (and explaining why clippy re-flagged the fn). Also: the frame-map comment updated for the last-resort restructure, the const doc now names the real live24 test, and compute_local_layout's force_spill_area condition includes vfp_frame_home_locals so an API caller setting the lever alone can never be handed slots aliasing the #204 param homes. Ratchet re-derived after the doc moves: selector_lines_code 19192 -> 19199 (waiver to: updated in the same commit), selector_lines_total 29838 -> 29845. Refs #1069 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
avrabe
added a commit
that referenced
this pull request
Aug 26, 2026
I flipped it mechanically on "#1073 merged" without reading the artifact's own DEFINITION OF DONE: those three symbols reaching `nm -> T` — 5 of 5 stages exported Increment 1 did not achieve that. It cleared `position#tick` on single-precision cortex-m7; on **m7dp — jess's actual flight core — all three symbols still fail on main**. Increment 2 (#1075, open) is what delivers 5/5. The flip belongs there. This is the MIRROR of the orphaned-flip class I have been chasing all release. Six times I caught a status left `proposed` over shipped code; here I set one to `implemented` over code that had not shipped the thing the artifact promises. Both directions under-report the truth, and RQ-60-FLIPCOUPLE should derive status from EVIDENCE precisely so neither is a judgement call — "the PR merged" is not the same claim as "the artifact's stated outcome holds". ALSO: increment 2's written plan is marked RETIRED BY MEASUREMENT rather than deleted, so it is not re-proposed. The plan was "extend the S pool into callee-saved S16..S31". A discriminating variant — same pressure, different RESIDENCE — showed the wall is not capacity: 60 simultaneously-live f32 on the OPERAND STACK compile while 14 in HOMED LOCALS do not. Pool extension moves the wall 13 -> ~29: a bigger number and the same wall further out, paid with prologue/epilogue push/pop. Recorded because a retired plan that leaves no trace gets proposed again. v0.60 reads 2/8; VFPPRESSURE lands with #1075. CI-filter OURS=0, measured 473 = floor 473, claim_check exit 0. Refs #1069, Refs #1064 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
…tooling, not an oracle CI red: the #890 oracle-wiring gate flagged `scripts/repro/gen_vfp_local_1069.py` UNDECLARED. Correct catch — "an oracle nothing runs must SAY so". The obvious fix would have been wrong. Declaring it `manual` would consume a slot on a ceiling that is a REAL BUDGET: `count-max: 8`, currently at 8, and scripts/repro/ORACLE_WIRING.md says in as many words that a ninth "needs a conversation, not a commit". Spending that conversation on a fixture generator would devalue a limit that exists to bound the un-run surface. It is not an oracle. Its own docstring: "The checked-in .wat is the fixture of record; this generator documents its shape." It asserts nothing, so there is nothing for CI to fail on. Moved to scripts/, following the explicit precedent in scripts/tier_census_1021.py: # NOTE on home: this lives in scripts/ ... not scripts/repro/ (defect oracles). and carrying the same marker so the next person does not re-file it under repro/. Both path references updated. Verified: oracle_wiring exit 0 ("all repro scripts declare a CI status"), claim_check exit 0, manual count UNCHANGED at 8, and the generator still reproduces the committed fixture BYTE-FOR-BYTE (`diff -q` clean) — a moved generator that silently stopped matching its fixture would be worse than the red it fixed. Refs #1069 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
avrabe
added a commit
that referenced
this pull request
Aug 27, 2026
…d 5th orphaned flips, plus a floor left slack (#1074) * chore(rivet): RQ-60-CANARY + RQ-60-A64IMPORT implemented — the 4th and 5th orphaned flips RQ-60-FLIPCOUPLE is now demonstrating itself. That artifact was filed BECAUSE three status flips were orphaned in v0.59; here are two more, in the release that scopes the fix: RQ-60-CANARY its work SHIPPED IN v0.59.0 (#1061, tag-verified) — the artifact was written describing already-delivered work and landed as `proposed` anyway RQ-60-A64IMPORT #1071 merged minutes ago and did not flip its own status Verified ON MAIN rather than inferred from the merge list: expansion_canary_gate_1021.py present, 1 CI invocation, `git tag --contains` reports v0.59.0 aarch64_import_dispatch_1017_differential.py present, 1 CI invocation ALSO TIGHTENED THE FLOOR 472 -> 473. #1071 added SWVER-025 without raising it. A floor only fails BELOW itself, so 473-measured against 472-pinned would have passed QUIETLY — slack, not red. The gate's own rule is that a PR adding artifacts raises the floor in that PR, and leaving it loose is the first crack in the discipline the gate exists to create. That is the same failure shape twice in one commit: a number that drifts in the direction that still looks green. Both are exactly what RQ-60-FLIPCOUPLE is scoped to make mechanical instead of vigilant. Verified BY ID: both resolve `[implemented]`; CI-filter OURS=0; measured 473 = floor 473; claim_check exit 0. Refs #1064, Refs #1017, Refs #1021 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L * chore(rivet): RQ-60-VFPPRESSURE implemented — the SIXTH orphaned flip #1073 merged as e6a3b27 and did not flip its own status. That is three in v0.59 and now three in v0.60, in the release whose RQ-60-FLIPCOUPLE artifact exists to fix exactly this. Verified ON MAIN, not inferred: 8 `__aeabi_ul2f`/`__aeabi_f2lz` references in crates/synth-synthesis/src/, and scripts/repro/aeabi_i64_float_1069_differential.py present. Six for six, every one caught by querying AFTER the merge rather than by any mechanism. That is the argument for RQ-60-FLIPCOUPLE stated as data rather than as a worry: a discipline that depends on somebody remembering fails on the tick they do not. Verified BY ID: CANARY / A64IMPORT / VFPPRESSURE all `[implemented]`; CI-filter OURS=0; measured 473 = floor 473; claim_check exit 0. Refs #1064, Refs #1069 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L * fix(rivet): RQ-60-VFPPRESSURE back to `proposed` — my flip was PREMATURE I flipped it mechanically on "#1073 merged" without reading the artifact's own DEFINITION OF DONE: those three symbols reaching `nm -> T` — 5 of 5 stages exported Increment 1 did not achieve that. It cleared `position#tick` on single-precision cortex-m7; on **m7dp — jess's actual flight core — all three symbols still fail on main**. Increment 2 (#1075, open) is what delivers 5/5. The flip belongs there. This is the MIRROR of the orphaned-flip class I have been chasing all release. Six times I caught a status left `proposed` over shipped code; here I set one to `implemented` over code that had not shipped the thing the artifact promises. Both directions under-report the truth, and RQ-60-FLIPCOUPLE should derive status from EVIDENCE precisely so neither is a judgement call — "the PR merged" is not the same claim as "the artifact's stated outcome holds". ALSO: increment 2's written plan is marked RETIRED BY MEASUREMENT rather than deleted, so it is not re-proposed. The plan was "extend the S pool into callee-saved S16..S31". A discriminating variant — same pressure, different RESIDENCE — showed the wall is not capacity: 60 simultaneously-live f32 on the OPERAND STACK compile while 14 in HOMED LOCALS do not. Pool extension moves the wall 13 -> ~29: a bigger number and the same wall further out, paid with prologue/epilogue push/pop. Recorded because a retired plan that leaves no trace gets proposed again. v0.60 reads 2/8; VFPPRESSURE lands with #1075. CI-filter OURS=0, measured 473 = floor 473, claim_check exit 0. Refs #1069, Refs #1064 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
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.
RQ-60-VFPPRESSURE increment 2 — VFP pressure from homed locals. Refs #1069.
The measurement this is built on (and why the roadmap's fix was wrong)
jess's fixture plus a discriminating variant — same pressure, different residence:
Sixty simultaneously-live f32 on the operand stack compile; fourteen in homed locals do not. The 13→14 wall is home residence, not register-file capacity: a home S-register is pinned for the function's extent, and
spill_deepest_vfp(correctly) never victims a home. Extending the pool into callee-saved S16..S31 would move the same wall from ~13 to ~29 locals at prologue/epilogue cost on every affected function — re-derived, agreed with the brief, not implemented.Red first (literal output, commit 7f658f3)
After (this branch): all 5 compile;
RESULT: PASS, 70/70 emulations bit-identical to wasmtime.The three design decisions
1. Spill the local, or de-home it? — De-home at birth, never evict. A mid-function eviction must emit a transfer store that dominates every later read; on one arm of a branch it does not (the same dominance hole the stack guard's
cf_floorexists to avoid), and the single-pass selector has no dominator analysis. Frame-homing from the first def sidesteps it:local.setis the store,local.getloads — dominance is inherited from wasm's own def-use semantics, since the store sits exactly where wasm writes the local.2. Choosing the victim — a positional cap, not a next-use heuristic. No pinned local home may sit above S7/D3; a fresh local whose lowest-free grant lands above the cap (or fails) is frame-homed. First-def order decides who keeps a register — honest reason: a single forward pass has no next-use information without a pre-scan, and the cap is what guarantees the invariant that matters for soundness of the rest of the rung: S8..S15 (4 aligned D-pairs) stay reclaimable, which is exactly the worst transient demand in
vfp_op_demand(the #869 conversion family, 4 pairs). A furthest-next-use / access-count pre-scan is a named quality follow-up, not a soundness need.3. Ladder — a separate LAST-resort stage, not a change to the #881 rung. This one was corrected by measurement mid-lane: the first draft gated the cap on
vfp_spill_on_exhaustionitself, and the real falconrate.omoved bytes —rate@0.7.0#tickcompiles today via the plain rung with f64 homes above D3. So the cap lives behind a new selector lever (set_vfp_frame_home_locals), and the backend tries it only after the plain #881 rung and its pool-grow composition failed:plain_rung_stays_yesterdays_path_for_shapes_it_already_rescuespins the class red-first: base red, plain rung green by itself, and the frame lever provably produces different instructions for it — the ordering is load-bearing.The two traps, handled
pub const(VFP_FRAME_HOME_SLOT_EXHAUSTION) thatarm_backend.rsmatches directly — one source, no drifting copy.live24(24 locals > the 8-slot pool) is the test that goes red if the message and the matcher ever separate: it compiles only if the retry fires.depth + distinct local.set/tee targets + 4. The plain rung's grow keeps yesterday'sdepth + 4exactly.Byte-identity evidence (currently-compiling functions)
live13(jess's negative control) compiles on the base path — pinned by test.cargo test --workspacegreen (includes the frozen-fixture byte gates).rate.oandmixer.o(the two stages that fully compile on main) —cmpbit-identical main vs branch. rate is the strong case: it compiles via the plain rung, and it is the module that caught the first draft.deep_local, the homes-are-never-victims pin).Execution differential (CI-wired)
scripts/repro/vfp_local_pressure_1069_differential.py— neworacle_runCI step,emulations >= 40, measured 70:-t cortex-m7dp --relocatable, linked witharm-none-eabi-ld(any diagnostic hard-fails), executed under unicorn vs wasmtime bit-exact (NaN==NaN per §4.3.3) across ±0, ±1, denormal-underfow, 1e±30, ±inf, NaN;#973ARM corpus sweep re-run PASS (154/167 compiled, 2502/2502 vectors; the new fixture's f32 columns compile on the m4f sweep target,live8ddeclines by f64 precision there, named inEXPECTED_DECLINES).The falcon cascade, by symbol name, on cortex-m7dp (the target jess ships)
Pulled the published
falcon1.134.1 components from ghcr (the issue's public repro), compiled-t cortex-m7dp --relocatable --all-exports --embedder-data-init --embedder-global-init:pulseengine:falcon-cascade/attitude@0.7.0#tickpulseengine:falcon-cascade/ekf@0.7.0#estimatepulseengine:falcon-cascade/position@0.7.0#tickpulseengine:falcon-cascade/rate@0.7.0#tickpulseengine:falcon-cascade/mixer@0.7.0#mix5/5 cascade stages — #1069's definition of done, on the per-stage components.
position#tickwas not in this increment's assigned scope (phase-2 D-file) but the same mechanism closes it: its D-pressure is inline-conversion temps plus pinned D homes, and capping the homes gives the #869 machinery its 4 pairs back. Bonus: all five also compile on single-precision cortex-m7 (where increment 1 had already cleared position only).Gates
cargo fmt --allclean;cargo clippy --workspace --all-targets -- -D warningsclean;cargo test --workspaceexit 0, 152 suites, 0 failures (incl. the 8 new tests and the frozen-anchor byte gates), gated on cargo's own exit code, not a pipeline tail's.python3 scripts/claim_check.py claims.yamlexit 0:selector_lines_code18910 → 19199 with a same-commit waiver (to: 19199) — growth, not substitution: every existing path is byte-frozen so nothing could be deleted in exchange.selector_lines_total29556 → 29845. Wildcard pins unmoved (the guard override is written wildcard-free).artifacts/status.jsonregenerated.Honest residuals (named, not hidden)
fuse --memory shared --pack-rebase(issue used 0.52.0). The per-stage objects are exactly what the issue's step-1 pulls; jess's standing offer in GI-FPU-002: three named cascade entry points are the entire remaining gap to a complete falcon M7 image (attitude#tick, ekf#estimate, position#tick) — everything else in the chain now works #1069 is the right loop for the fused RT1176 image, and the delta above is stated per symbol so it can be checked against it directly.#tick/#estimateneeds the component ABI/memory environment (jess's on-target loop). The mechanism (frame round-trip under pressure) is what the 70-row differential proves bit-exact, on the same code path.local.get; first-def order (not next-use) decides who keeps a register; a pre-scan ranking locals is a named follow-up. Functions rescued by this stage previously did not compile at all.🤖 Generated with Claude Code
https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L