RQ-60-VFPPRESSURE increment 1: AEABI-routed i64-f32 conversions on single-precision FPU targets - Refs #1069, Refs #869 - #1073
Merged
Conversation
… family through AEABI builtins on single-precision FPU targets A function whose WASM signature contains NO f64 anywhere (e.g. `(param i64) (result f32)` + f32.convert_i64_u) was declined on cortex-m4f/m7 for "needing f64", because the #869 inline lowering of the i64<->float conversions runs on double-precision machinery. That pinned every conversion-carrying function to cortex-m7dp — the reach failure jess #1069 measured (the falcon estimator cannot be built for the M4 core it is architecturally assigned to). The route is the one #869's acceptance comment specified: the AEABI runtime helpers (__aeabi_l2f/ul2f for the converts, __aeabi_f2lz/f2ulz for the truncations) — base-AAPCS core-register calls, zero VFP pressure. Active ONLY on a single-precision FPU target under --relocatable (the host linker resolves the symbols from the embedder's runtime: libgcc / compiler-rt / kiln builtins); the m7dp inline path is untouched and byte-identical (pre/post cmp on the minimal repro), and non-relocatable single-precision keeps the loud decline, now naming the closable gap. Soundness fences (the helpers are MORE-TOTAL than WASM, #633/#666/#709/#665/#642 class): - trapping truncs: two-bound f32 domain guard (exact f32 bounds, 2 UDF sites) BEFORE the call; the unsigned (-1,0)->+0 residue is clamped so the C-style helper never sees undefined input; - trunc_sat: NaN/saturation results selected inline; only in-range values reach the helper; no UDF (never traps); - converts: total, no fence. The six f64-TYPED family members (f64.convert_i64_*, i64.trunc[_sat]_f64_*) stay declined by name on single-precision — their WASM types carry f64. Red-first: tests/aeabi_i64_float_1069.rs fails on pre-change sources (every routed row declined) and pins the named builtin per op, the Udf guard geometry, the f64-machinery-free property, the non-relocatable and f64-typed declines, and the m7dp no-__aeabi invariant. Ratchet: selector_lines_code 18288 -> 18907 (+619), waived with reason (new reach where the family had no lowering; the frozen m7dp path could not be deleted in exchange); selector_lines_total 28929 -> 29553. Refs #1069, refs #869. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
… AEABI route, CI-wired scripts/repro/aeabi_i64_float_1069_differential.py — the m4f execution oracle for the routed family: 24,270 checks (19,139 stubbed builtin calls) vs wasmtime under unicorn, boundary rows (0/±1/2^53/2^63/2^64 edges, the #869 double-rounding killer 0x8000008000000001, largest-f32- below-bound rows, NaN payloads, ±inf, -0.0) + 4,000 fixed-seed fuzz patterns per direction, all trap rows EXECUTED on both sides (wasmtime trap must be an ARM UDF stop). Trust boundary stated in the script: the __aeabi_* builtins are the embedder's link obligation, provided here as spec-exact stubs (integer-arithmetic RNE for l2f/ul2f, truncation for f2lz/f2ulz); every stub ASSERTS its C-defined input domain, so a guard hole that lets NaN/out-of-range reach the helper reddens the run even when the numeric answer coincides. What the oracle verifies is everything synth EMITS: marshalling, caller-saved preservation, the f32 domain guard, the unsigned (-1,0)->+0 clamp, inline saturation/NaN selection, result capture. Negative control executed: an off-by-one mutation in the f2ulz stub reddens the run with 4,088 failures. Wiring per #890/#910: ci-status wired + `emulations >= 24000` floor; new oracle_run step in ci.yml; --min-emulation-floor 298754 -> 322754 moved together on all four surfaces (ci.yml, claims.yaml pins, ORACLE_WIRING.md, feature_matrix template) with the count-min floors bumped (151/147/160). Refs #1069, refs #869. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
…e-derive ratchet pins (+3 comment lines) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
…e-precision targets Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
avrabe
force-pushed
the
feat/aeabi-i64-float-builtins-1069
branch
from
August 26, 2026 17:15
051124f to
23aed2c
Compare
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
avrabe
added a commit
that referenced
this pull request
Aug 26, 2026
#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
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
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>
avrabe
added a commit
that referenced
this pull request
Aug 27, 2026
…cknowledge #1073; RQ-60-FLIPCOUPLE is implemented - RQ-60-CANARY / RQ-60-A64IMPORT / RQ-60-CFOBLIG / RQ-60-FLIPCOUPLE carry in-repo signatures (contains: predicates on their delivered/planned surfaces); CFOBLIG's (WasmInstructions.v gains BrIf) is deliberately FALSE today and flips the moment the model extension lands. - RQ-60-RACOST / RQ-60-ARTIFACTSPLIT / RQ-60-WCETKEY / RQ-60-VFPPRESSURE are honestly manual: their definitions of done are measured verdicts or external runs with no single in-repo signature — for these, Direction-A protection rests on the delivery-commit floor, and any future implemented flip must carry a written verified-by basis. - RQ-60-VFPPRESSURE gains landed: '#1073' — the machine-readable statement 'increment 1 landed, the stated outcome (5-of-5 cascade stages) does not yet hold', which is the distinction the seven misses collapsed. - RQ-60-FLIPCOUPLE proposed -> implemented, with the delivered mechanism, its replay result, and its stated residuals recorded in the description. rivet validate: error set byte-identical to baseline (40, all foreign-prefix cross-repo); rivet list: 473 artifacts, unchanged — floor untouched. Refs #1064 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
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 1 — the conversion routing only (the S-register pool extension is increment 2, a separate lane). Refs #1069, refs #869.
What this does
A function whose WASM signature contains no f64 anywhere was refused on cortex-m4f/m7 for "needing f64", because the #869 inline lowering of the i64↔float conversions runs on double-precision machinery. That pinned every conversion-carrying function to
cortex-m7dp— the reach failure #1069 measured on the falcon cascade.This routes exactly the six i64/f32-typed family members through the AEABI runtime helpers — the route #869's acceptance comment specified: base-AAPCS core-register calls, zero VFP pressure:
f32.convert_i64_s/_ubl __aeabi_l2f/__aeabi_ul2fi64.trunc_f32_s/_ubl __aeabi_f2lz/__aeabi_f2ulz(-1,0)→+0clamp so the C-style helper never sees undefined inputi64.trunc_sat_f32_s/_ublThe fences matter because the AEABI helpers are more-total than WASM (the #633/#666/#709/#665/#642 class) — the exact soundness constraint from #869's acceptance comment, honored here on the builtin route.
Red first (literal output)
The eight-line repro on current main:
The new test suite against pre-change sources (
git stashof the impl, test kept):After (this branch):
Design decisions
1. Unconditional or pressure-heuristic? Neither, exactly: the builtin route applies only where inline is impossible — single-precision FPU targets, where the family previously had no lowering at all. On m7dp the #869 inline path is untouched. Rationale: predictable, and it moves zero shipping bytes (see below). What would make this the wrong call: if
position#tick's m7dp D-exhaustion must be fixed on m7dp — that would need a pressure heuristic choosing builtin-over-inline there, and it belongs with the pressure work, stated as a named follow-up, not drifted into here. (Measured mitigation that exists today: on cortex-m7 — single-precision, the actual RT1176 core for an f32-only cascade — the route makesposition#tickcompile fully; see below.)2. Which conversions. Measured on main: all 12 family members decline on m4f. Routed: the six whose WASM types are i64/f32 only (their f64 was an artifact of the lowering, not the program). Left declined by name:
f64.convert_i64_s/u,i64.trunc_f64_s/u,i64.trunc_sat_f64_s/u— their WASM types carry f64, which a single-precision target genuinely cannot represent (any producer/consumer of that f64 value declines anyway). Routing them would be reach theater.3. m7dp emitted bytes: none moved. The route is gated on
FPUPrecision::Single, so m7dp never enters it. Verified three ways: pre/postcmpon the compiled minimal repro object (byte-identical), all frozen anchors green (cargo test --workspace, 151 suites, 0 failures — includes the frozen-fixture differentials), and the new test pins that no__aeabi_*call ever appears on m7dp. The pre-existing #869 (96,296 checks) and #782 (192,686 checks) oracles re-run green.Gating on
--relocatable: thebl __aeabi_*gets the sameR_ARM_THM_CALL-against-undefined-symbol shape as every #197 external call, so the host linker resolves it. A self-contained image has no linker — non-relocatable single-precision keeps the loud decline, whose message now names the closable gap (compile with --relocatable to route ... through the AEABI builtins). This is a distinct mechanism from the existing--builtins <kiln-builtins.o>CLI flag (which links an object into--linkmode); reusing that flag name for selection-time routing would conflate "what to link" with "how to lower", so the route keys on target capability + relocatable mode instead, and the flag keeps its one meaning.The runtime-library dependency, stated
The emitted object now imports up to four symbols the embedder must provide at link time:
__aeabi_l2f,__aeabi_ul2f,__aeabi_f2lz,__aeabi_f2ulz. Any of libgcc (-lgcc), compiler-rt builtins, or kiln's builtins archive satisfies them (jess links a kiln bridge already). Verified end-to-end: the m4f object links cleanly against realarm-none-eabilibgcc (link-exit=0), and the linked ELF — real libgcc code, no stubs — executes 88/88 boundary checks bit-exact vs wasmtime under unicorn, including the trap rows and the0x8000008000000001double-rounding killer.Execution differential (CI-wired)
scripts/repro/aeabi_i64_float_1069_differential.py— 24,270 checks, 0 failures (19,139 stubbed builtin calls) vs wasmtime under unicorn on the m4f-compiled object:0, ±1, 2^24±, 2^53±, 2^63±, 2^64±, u64::MAX, i64::MIN/MAX, the arm backend: the entire 64-bit integer<->float conversion family is unlowered (6 ops) — blocks 3 of 5 falcon cascade stages at their entry points; source-side workaround verified exhausted #869 double-rounding killer0x8000008000000001, largest-f32-below-each-bound, next-f32-below-2^63,-1.0,-0.0,(-1,0)residue, NaN payloads, ±inf;Wired per #890/#910:
ci-status: wired,emulations >= 24000, neworacle_runstep;--min-emulation-floor 298754 → 322754moved together on all four surfaces (ci.yml, claims.yaml pins, ORACLE_WIRING.md, feature-matrix template).Real falcon components (the #1069 gate, run locally)
Pulled the published
falcon1.134.1 components from ghcr (the issue's public repro) and compiled with-t cortex-m4f --relocatable --all-exports --embedder-data-init --embedder-global-init:raterate@0.7.0#tickskipped (GI-FPU-002 phase 2), exit non-zero#tickexported, sole external__aeabi_ul2fpositionposition@0.7.0#tickskipped#tickexported, externals__aeabi_ul2f,__aeabi_f2lz,__aeabi_l2fpositionon cortex-m7#tickskipped (m7dp-only pinning → D0..D7 exhausted)So of #1069's three named blockers,
position#tickis now compilable on the M7 (as-t cortex-m7; on m7dp the inline path is deliberately unchanged and still D-exhausts).attitude#tick/ekf#estimateare phase-1 S-register exhaustion — increment 2's scope, untouched here.Gates
cargo fmt --allclean,cargo clippy --workspace --all-targets -- -D warningsclean,cargo test --workspace151 suites / 0 failures.python3 scripts/claim_check.py claims.yamlexit 0 (51/51), incl. the ratchet:selector_lines_code18288 → 18910 with a same-commit waiver (to: 18910) — growth, not substitution, by design: the m7dp path is byte-frozen so nothing could be deleted in exchange; this is new reach on targets where the family had no lowering at all.selector_lines_total28929 → 29556. Wildcard pins unmoved (55/90).Known residuals (named, not hidden)
position#tickon m7dp): unchanged by design; fixing it there means builtin-vs-inline under pressure — a follow-up decision, not this increment.--cortex-m) single-precision still declines the family: no linker to satisfy__aeabi_*. Embedding a builtins blob in the image is a possible follow-up.vfp_op_demandfor the routed ops still carries the inline path's conservative 4-D-pair figure; on the Single route it only over-spills under the GI-FPU-002 + RA tail is now the ONLY gate between the falcon cascade and the M7 — 5 entry-point symbols; phase-2 D-register pressure is new in v0.52 (inline-f64 #869 lowering) #881 retry rung (sound, never wrong bytes).bl __aeabi_*is an external call → the existingcallloud-decline, honest and unchanged.🤖 Generated with Claude Code
https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L