Repository navigation
feat(#782a): implement the WASM trunc_sat family — nontrapping saturating float→int (i32 forms on ARM32, all 8 on aarch64) - #803
Merged
Conversation
…owerings The 8 nontrapping saturating float->int ops (0xFC prefix, WASM §4.3.2) now decode. ARM32: i32-target forms lower as the bare saturating VCVT (round- toward-zero VCVT saturates + NaN->0 = exactly trunc_sat; the #709 guard is for the TRAPPING forms and stays untouched); i64-target forms LOUD-decline (no i64 register-pair conversion path). aarch64: all 8 via FCVTZS/FCVTZU (w and clang-verified x forms). RV32 loud-declines via the existing catch-all. falcon (#782) carries 7x i32.trunc_sat_f64_s + 1x i32.trunc_sat_f32_s on cortex-m7dp --relocatable. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
…or trunc_sat - scripts/repro/trunc_sat_782_differential.py + .wat: NaN/±inf/exact INT_MIN-INT_MAX boundaries/±0.5/in-range vs wasmtime, executed under unicorn (Thumb-2 m7dp + m4f, aarch64) AND natively (arm64 host, forked child) — 536 checks, no traps anywhere; falcon's exact flags (-t cortex-m7dp --relocatable) compile-gated: i32 forms must not skip, i64 forms must decline loudly by name. - FIX the differential caught: the OPTIMIZED path silently dropped trunc_sat to a 2-byte bx-lr stub (the #615 silent-NOP class) — is_unsupported_float_op now bails all 8 forms to select_with_stack. - Rust gates: ARM32 sat=bare-VCVT/no-Udf + trapping twins KEEP the #709 Udf guard + i64 loud declines + FPU honest-rejects (synth-synthesis); aarch64 one-bare-fcvtz w/x forms; RV32 Unsupported; decoder un-drop. - Existing trap differentials stay green (f32 708/709: 48/48, f64 369: 339/339, aarch64 m4: 167 cases); frozen anchors bit-identical (10/10). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
# Conflicts: # .github/workflows/ci.yml
avrabe
added a commit
that referenced
this pull request
Jul 17, 2026
…at ops (#805) #803 (trunc_sat, aarch64_selector_ops 99→107) and #804 (the generated artifacts) landed in sequence; #804's committed status.json/FEATURE_MATRIX snapshotted the pre-#803 op count and merged textually-clean on top of #803, so the freshness gate went red on main (working as designed — it caught a genuinely stale generated doc). Regenerated with `--emit-status`; 25/25 claims hold. No behavior change; generated-artifact refresh only. Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
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.
Part a of #782 (v0.48 wave 1, lane 1): the 0xFC-prefixed saturating-float-to-int family (
i32/i64.trunc_sat_f32/f64_s/u, WASM Core §4.3.2) was never implemented — the decoder dropped all 8 ops and every containing function loud-skipped (falcon'sskipping 'ts32'/'ts64'class; falcon-flight-v1.123 carries 7×i32.trunc_sat_f64_s+ 1×i32.trunc_sat_f32_s).Semantics (§4.3.2 — TOTAL, never traps)
NaN → 0; below INT_MIN → INT_MIN; above INT_MAX → INT_MAX (unsigned: negatives → 0, above UINT_MAX → UINT_MAX); else truncate toward zero.
What lands where
i32.trunc_sat_f32_s/uVCVT.{S32,U32}.F32(m4f/m7/m7dp)FCVTZS/FCVTZU wdi32.trunc_sat_f64_s/uVCVT.{S32,U32}.F64(m7dp)FCVTZS/FCVTZU wd, dni64.trunc_sat_*(4)FCVTZS/FCVTZU xd(clang-verified sf=1 encodings)The key insight both ways: ARM RZ
VCVT/ A64FCVTZsaturate out-of-range and give 0 for NaN — the exact "more-total-than-WASM" behavior the #709 domain guard exists to keep away from the trappingtruncforms is the required semantics fortrunc_sat, so the correct lowering is the bare convert with no guard. Both selector paths covered (with-stack = falcon's--relocatablepath, andselect_defaultparity); the with-stack lowerings reuse the trapping twins' pinned-home copy dance.Bug the gate caught before it shipped
The optimized path silently compiled
trunc_satfunctions to 2-bytebx lrstubs (the #615 silent-NOP class):is_unsupported_float_opdidn't list the new ops, so they fell through the IR bridge as no-ops. Fixed by bailing all 8 forms toselect_with_stack; the boundary differential is what caught it.Gates (mechanical, non-vacuous)
scripts/repro/trunc_sat_782_differential.py(CI-wired intrap-semantics-oracle): 536 checks vs wasmtime — NaN, ±inf, exact INT_MIN/INT_MAX boundaries (incl. the f32-can't-represent-2^31−1 and f64 −2147483648.999 edges), ±0.5 fractions, in-range values — executed under unicorn (Thumb-2 m7dp and m4f, aarch64) and natively on an arm64 host (forked child so a spuriousbrkis observable). No case may trap anywhere. Falcon's exact flags (-t cortex-m7dp --relocatable) are compile-gated: the i32 forms must not skip; the ARM32 i64 forms must decline loudly by name.Udf+ the trapping twins keep their 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 #709Udfguard + i64 loud-declines + no-FPU/single-precision honest-rejects (synth-synthesis/tests/trunc_sat_782.rs); aarch64 one-bare-fcvtz w/x-form streams + clang-pinned x encodings; RV32Unsupported; decoder un-drop test.Decline-honesty
No existing gate asserted "trunc_sat unsupported" (the drop was the decoder
_ => None); the still-declining subset (ARM32 i64 targets, RV32 everything) now has named loud-decline gates in both the Rust tests and the differential.falcon relevance (#782)
Addresses the
GI-FPU-001 unsupported operatorclass fori32.trunc_sat_f32_s/i32.trunc_sat_f64_s— 2 of the 3 functions in that skip class (the third isf64.reinterpret_i64, a separate lane). The dominant 12× "integer popped f32" class and the rest of the #782 breakdown are out of scope for part a.🤖 Generated with Claude Code
https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L