feat(#869): lower the ARM 64-bit integer<->float conversion family — self-contained expansions, trap-guarded truncs, round-to-odd-exact f32 converts - #873
Conversation
…irst) Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
… 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
…clippy unused-mut Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
|
Final local gate, all green at 471a33f:
Allocator hazard found during this work is filed with executable evidence as #872. |
…nv-869 # Conflicts: # CHANGELOG.md
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
|
Rebased onto What conflicted: only 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). |
Closes #869 (and the #756
i64.trunc_f64selector-lowering gap).What now lowers (cortex-m7dp, both
--relocatableand self-contained)f64.convert_i64_{s,u}VCVTeach half, ×2^32 (exact), oneVADD.F64= the single correct RNE roundingf32.convert_i64_{s,u}VCVT.F32.F64demote (double-rounding-exact, Boldo–Melquiond)i64.trunc_f32_{s,u}i64.trunc_f64_{s,u}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_f32promote 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
__aeabibuiltins — whygale's issue offered both routes. Chosen: self-contained expansion — (1) no runtime link dependency (
--relocatableconsumers 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)
sattruncs to trap on NaN/out-of-range; the decompose (like__aeabi_f2lz) saturates — the thumb-2: i64.div_s(INT64_MIN, -1) silently returns INT64_MIN instead of trapping (overflow guard missing on i64 path; i32 path has it) #633/rv32: i32.rem_s(INT_MIN,-1) spuriously traps — div_s overflow ebreak guard wrongly copied into rem_s (irem_s must return 0) #666/thumb-2 f32: i32.trunc_f32_s/u silently saturate instead of trapping on NaN/Inf/out-of-range (bare VCVT, no guard) — WASM Core §4.3.3 soundness bug #709/synth compiles WASM unreachable to a no-op on both thumb-2 and rv32 — falls through instead of trapping (WASM 4.4.5) #665/thumb-2: call_indirect emits no bounds-check and no type-check — OOB/wrong-type index does an uncontrolled indirect branch instead of trapping (WASM 4.4.8) #642 class. The guard bounds are exact on f32/f64 (-2^63 ≤ x < 2^63signed — no representable value sits in(-2^63-1, -2^63);-1 < x < 2^64unsigned), ordered compares so NaN falls to the firstUDF.trunc_sattwins verified still guard-free (no trap leak; unit-pinned).f32.convert_i64double-rounds (killer row0x8000_0080_0000_0001in the differential); the round-to-odd fixup makes 64→53→24 ≡ 64→24 RNE.Found & contained: a range-realloc soundness hazard (#872)
The first cut of the fixup used the
I64Addpseudo-op; the default-onSYNTH_RANGE_REALLOCpass treatsreg_effect=Noneops 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 modeledAdds/Adc(uses visible, exit ranges pinned — unit-pinned so it can't regress toI64Add); the pass-level hazard is filed with full evidence as #872.Gates (all run locally, green)
scripts/repro/i64_float_conv_869.wat— RED on v0.51 (six exports absent fromnm), GREEN now (all 12 →T), gale's exact flags.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.frozen_codegen_bytes10/10 byte-identical (byte-changing only for modules using these ops).cargo test --workspacegreen, fmt clean, clippy -D clean (final run in CI).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. Existingtrunc_sat_782suite untouched-green.falcon evidence: the three blocked stages (
rate#tick,position#tick,ekf#estimate) are blocked at their entry points byF32ConvertI64Uper 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