Skip to content

feat(#869): lower the ARM 64-bit integer<->float conversion family — self-contained expansions, trap-guarded truncs, round-to-odd-exact f32 converts - #873

Merged
avrabe merged 7 commits into
mainfrom
feat/arm-i64-float-conv-869
Jul 29, 2026
Merged

feat(#869): lower the ARM 64-bit integer<->float conversion family — self-contained expansions, trap-guarded truncs, round-to-odd-exact f32 converts#873
avrabe merged 7 commits into
mainfrom
feat/arm-i64-float-conv-869

Conversation

@avrabe

@avrabe avrabe commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Closes #869 (and the #756 i64.trunc_f64 selector-lowering gap).

What now lowers (cortex-m7dp, both --relocatable and self-contained)

op lowering
f64.convert_i64_{s,u} exact two-word build: VCVT each half, ×2^32 (exact), one VADD.F64 = the single correct RNE rounding
f32.convert_i64_{s,u} same build + Fast2Sum exact residual + branch-free round-to-odd fixup + one VCVT.F32.F64 demote (double-rounding-exact, Boldo–Melquiond)
i64.trunc_f32_{s,u} TRAP-GUARDED: promote (exact) → #709-class i64 domain guard → #782 word-decompose (provably never saturates in-domain)
i64.trunc_f64_{s,u} #709-class i64 domain guard → #782 word-decompose

Loud-declined by name (no silent holes): every family member on single-precision targets (m4f/m7 — the lowerings run on f64 machinery, undefined encodings on FPv4-SP; the pre-existing i64.trunc_sat_f32 promote path is now caught by the same earlier gate) and on no-FPU targets; select_default (register-blind fallback, not a shipping path) keeps typed declines. aarch64/RV32 unchanged (wildcard declines; parity universe updated).

Inline expansion, not __aeabi builtins — why

gale's issue offered both routes. Chosen: self-contained expansion — (1) no runtime link dependency (--relocatable consumers like meld would inherit an __aeabi_* obligation kiln doesn't ship today), (2) no hard/soft-float ABI variance to verify per target, (3) unicorn-executes directly so the trap rows are executed in CI rather than trusted, (4) it is the #782 trunc_sat precedent — the trapping trunc is literally guard + the shipped decompose. The AEABI builtins also do NOT trap, so they'd still need the exact same guards; the call would buy nothing but a dependency.

Soundness (the load-bearing part)

Found & contained: a range-realloc soundness hazard (#872)

The first cut of the fixup used the I64Add pseudo-op; the default-on SYNTH_RANGE_REALLOC pass treats reg_effect=None ops as segment barriers whose register READS are invisible, reused the live-in pair for intermediates, and its own segment validator accepted the wrong rewrite. Caught by this PR's differential at land time; contained here by emitting modeled Adds/Adc (uses visible, exit ranges pinned — unit-pinned so it can't regress to I64Add); the pass-level hazard is filed with full evidence as #872.

Gates (all run locally, green)

  1. Red-first repro scripts/repro/i64_float_conv_869.wat — RED on v0.51 (six exports absent from nm), GREEN now (all 12 → T), gale's exact flags.
  2. Execution differential scripts/repro/i64_float_conv_869_differential.py (CI-wired into the ARM oracle job): 96,296 checks vs wasmtime under unicorn — full boundary tables with trap rows executed on both sides (NaN, ±inf, 2^63, −2^63, 2^64, largest-below-bound values, (-1,0) no-trap window), double-rounding killers, RNE tie rows at 24- and 53-bit granularity, ≥12k fixed-seed fuzz per direction; m4f decline-honesty; falcon-flags symbol presence.
  3. Frozen anchors: frozen_codegen_bytes 10/10 byte-identical (byte-changing only for modules using these ops).
  4. cargo test --workspace green, fmt clean, clippy -D clean (final run in CI).
  5. Unit contracts (i64_float_conv_869.rs): 8/8 lower on m7dp, truncs exactly 2 UDFs, converts 0, sat twins 0, Adds/Adc pinned, m4f/m3 named declines. Existing trunc_sat_782 suite untouched-green.

falcon evidence: the three blocked stages (rate#tick, position#tick, ekf#estimate) are blocked at their entry points by F32ConvertI64U per the issue; gale's standing jess gate covers the real fused components on this branch.

🤖 Generated with Claude Code

https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L

avrabe and others added 6 commits July 29, 2026 06:25
… ops)

- decode-un-drop F32ConvertI64S/U, F64ConvertI64S/U, I64TruncF64S/U; add the
  missing WasmOp variants I64TruncF32S/U
- f64.convert_i64_{s,u}: exact two-word build (VCVT hi/lo + 2^32 scale + one
  correctly-rounding VADD)
- f32.convert_i64_{s,u}: same build + Fast2Sum residual + branch-free
  round-to-odd fixup + single VCVT.F32.F64 demote (double-rounding-exact)
- i64.trunc_f32/f64_{s,u}: #709-class i64 domain guard (TRAP on
  NaN/out-of-range, WASM 4.3.3) before the #782 word-decompose
- DP capability gate: all f64-machinery members (incl. the pre-existing
  i64.trunc_sat_f32 pair, latent m4f hole) listed in is_scope_f64_op ->
  single-precision targets loud-decline instead of emitting UNDEFINED
  VCVT.F64 encodings
- select_default keeps register-blind honest declines by name

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
…g killers+fuzz) vs wasmtime; realloc-sound Adds/Adc fixup

The round-to-odd fixup's 64-bit increment is emitted as MODELED Adds/Adc
instead of the I64Add pseudo-op: reg_effect=None ops are range-realloc
segment barriers whose register READS are invisible to the segment-local
liveness, so the colorer reused slo/shi for intermediates and the pair
reaching the final VMOV was garbage (caught by this differential at land
time; the segment validator shares the last-use assumption and accepted
the wrong rewrite — filed as a follow-up allocator issue).

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
… CI wiring + CHANGELOG

- synth-synthesis/tests/i64_float_conv_869.rs: m7dp lowers all 8 (converts
  guard-free, truncs exactly 2 UDFs), trunc_sat twins stay guard-free,
  Adds/Adc (not I64Add) pinned, m4f/m3 loud-decline by name
- cross_backend_op_parity: new variants placed StructurallyExcluded(FLOAT)
- trap.rs trunc_op maps I64TruncF32S/U; wasm_semantics 64-bit-typed arms;
  is_trap_gated_op comment names the symbolic-wiring residual (#756-class,
  execution-gated by the #869 differential meanwhile)
- ci.yml: i64<->float family oracle wired into the ARM oracle job
- allocator segment-liveness hazard filed as #872

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
…mbolic trap-VC wiring stays the named residual

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

avrabe commented Jul 29, 2026

Copy link
Copy Markdown
Contributor Author

Final local gate, all green at 471a33f:

  • cargo test --workspace: 129 suites, 0 failures (includes the decoder-test update — its 'still-dropped' probe was i64.trunc_f64_s, which this PR legitimately un-drops; the flag-never-drop honesty contract for the remaining undecoded float surface stays pinned by the float-global test)
  • cargo clippy --workspace --all-targets -- -D warnings: clean
  • cargo fmt --check: clean
  • frozen anchors: 10/10 byte-identical
  • i64_float_conv_869_differential.py: 96,296 checks PASS (default env, range-realloc ON)
  • regression: trunc_sat_782 (32,686 checks), f64_369 (339), f32_mem_trunc_708_709 (48), float_select_return_782 — all PASS with this branch's binary
  • claim_check.py: 25/25 hold

Allocator hazard found during this work is filed with executable evidence as #872.

@codecov

codecov Bot commented Jul 29, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 94.06780% with 28 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
crates/synth-synthesis/src/instruction_selector.rs 96.69% 15 Missing ⚠️
crates/synth-core/src/wasm_decoder.rs 36.36% 7 Missing ⚠️
crates/synth-verify/src/wasm_semantics.rs 0.00% 4 Missing ⚠️
crates/synth-verify/src/trap.rs 0.00% 2 Missing ⚠️

📢 Thoughts on this report? Let us know!

@avrabe

avrabe commented Jul 29, 2026

Copy link
Copy Markdown
Contributor Author

Rebased onto origin/main (merged df1a8ad, the #867/#870 ISA-model-adequacy lane) and re-verified from scratch — PR is MERGEABLE.

What conflicted: only CHANGELOG.md (both lanes wrote [Unreleased]). Resolved keeping both sides: my #869 bullet plus main's two #867 bullets under one ### Added, then my ### Fixed bullet. ci.yml and claims.yaml auto-merged with both lanes' content intact. No generated files touchedartifacts/status.json, docs/status/FEATURE_MATRIX.md, artifacts/model-coverage.json, coq/STATUS.md are main's versions verbatim (zero drift in my diff; coordinator regenerates at assembly). No code conflicts: main's commit was docs/ledger/CI-only, so liveness.rs and the RISC-V backend had zero interaction.

Re-verified post-merge (fresh binary, worktree-local target on the internal SSD):

Remote CI on the merged head: 27 SUCCESS, 0 FAILURE (remainder queued on runner availability).

@avrabe
avrabe merged commit f6204f0 into main Jul 29, 2026
47 checks passed
@avrabe
avrabe deleted the feat/arm-i64-float-conv-869 branch July 29, 2026 12:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant