Skip to content

#369 marked closed, but the real falcon-flight-v1.123 fused core still skips 26/156 on --relocatable cortex-m7dp (incl. run-stabilization); trunc_sat unsupported + a pressure-dependent 'integer popped f32' class #782

Description

@avrabe

Thanks for the big float-completion push (v0.41–v0.45) — the synthetic differentials (156/156 f32, 126/126 f64) are great. But verifying against the real shipped falcon core (relay invited this on jess#144) shows #369 is not complete for falcon yet. Filing precisely, evidence-first.

Real-build measurement

  • Input: falcon-flight-v1.123.wasm (relay release asset, sha e4622a12…) → meld v0.41.1 fuse --reproducible → the fused Core module.
  • Compiler: synth v0.45.1, compile -t cortex-m7dp --relocatable --native-pointer-abi --shadow-stack-size 4096.
  • Result: 26 of 156 functions skipped. (Plain --relocatable without --native-pointer-abi: 24/183.) Identical on the pre-loom fused module, so it is not a loom/meld artifact.

Breakdown of the 26

count class note
12 GI-FPU-002: an integer operation popped an f32 (VFP) stack value — invalid wasm or an unlowered float op reached the integer path includes run-stabilization (the flight-control step). See caveat below.
3 GI-FPU-001: unsupported operator i32.trunc_sat_f32_s, i32.trunc_sat_f64_s, f64.reinterpret_i64
3 GI-FPU-002: VFP register file exhausted (S0–S15 / D0–D7 all live, no VFP spilling) deep f32/f64 expressions
2 #503 (i64/f64 stack arg)
2 #518 (i64/f64 param in a frame-backing fn)
2 #345 (LdrSym literal pool out of range, imm12 > 4095 for __synth_globals)
2 i64 spill-slot pool / no free callee-saved

Confirmed minimal repro (GI-FPU-001, trunc_sat)

(module
  (func (export "ts32") (param f32) (result i32) (i32.trunc_sat_f32_s (local.get 0)))
  (func (export "ts64") (param f64) (result i32) (i32.trunc_sat_f64_s (local.get 0))))
synth compile ts.wasm -t cortex-m7dp --relocatable
→ skipping 'ts32': unsupported operator (I32TruncSatF32S) … (GI-FPU-001, #369)
→ skipping 'ts64': unsupported operator (I32TruncSatF64S) … (GI-FPU-001, #369)

The trapping trunc_f32_s variants lower, but the saturating trunc_sat_* variants don't. Rust emits trunc_sat for as casts (default since the saturating-float-to-int stabilization), so falcon hits these (8 occurrences in v1.123: 7× i32.trunc_sat_f64_s, 1× i32.trunc_sat_f32_s). On VFP this is a direct VCVT (Arm VCVT.S32.F32 already saturates), so it should be cheap to add.

Honest caveat on the dominant class

I could not minimize the 12× "integer popped f32" yet: the obvious candidate (i32.reinterpret_f32, 97 uses in the module) lowers fine in isolation:

(func (export "f2i") (param f32) (result i32) (i32.add (i32.reinterpret_f32 (local.get 0)) (i32.const 1)))  ;; compiles OK

So it's pattern/register-pressure dependent, not one operator. Happy to bisect with you against the fused module — tell me the most useful next probe (e.g. a --dump of the offending function's op stream).

jess impact

run-stabilization is the falcon flight-control step; while it doesn't lower, falcon cannot reach the M7 on-target (REQ-PIX-001 / DD-018 --relocatable+TCB-link path). Not urgent to you if it's fast to close, but it's the gate for our on-target bring-up. #369 is currently marked closed — this is the real-fused-core residual against it.

Repro is fully deterministic (public falcon asset + meld). Glad to help minimize or test a fix.

Activity

  1. avrabe commented on Jul 17, 2026

    @avrabe
    ContributorAuthor

    Shipped in v0.48.0. Both parts closed:

    Remaining (honest residual, NOT a regression): ARM32 i64 trunc_sat forms LOUD-DECLINE by name (the i32 forms and all aarch64 forms are complete). Tracked for v0.49 L1 (falcon-skip-to-zero). If falcon still skips a specific op, please drop the shape and it becomes the next lane.

  2. added 2 commits that reference this issue on Jul 17, 2026
  3. avrabe commented on Jul 21, 2026

    @avrabe
    ContributorAuthor

    Re-verified against the real build on synth v0.49.0 (meld v0.41.3, falcon-flight-v1.124 sha c8db2dfd…, -t cortex-m7dp --relocatable --native-pointer-abi): 26 → 16 skips, and run-stabilization now lowers. The f32-select + float-return fixes cleared the whole "integer popped f32" class — that's the flight-control step compiling on the M7. Big step, thank you. Dropping the exact residual shapes for the falcon-skip-to-zero lane:

    Float (6)

    • 4× GI-FPU-002 VFP register-file exhaustion — 2× f32 (S0–S15 all live), 2× f64 (D0–D7 all live). "no VFP spilling yet" — deep float expressions. Biggest remaining class.
    • 2× GI-FPU-001 i64.reinterpret_f64 / f64.reinterpret_i64 unsupported (the f64↔i64 bitcast; VMOV D-reg ↔ GPR pair). trunc_sat is fixed; this reinterpret pair remains.

    i64/f64 ABI + regalloc (7)

    Addressing (3)

    The RA gate catching JoinValueNotAvailable on the real bytes is exactly the red-first value you built it for. Same offer as before — happy to hand you any specific function's op stream. Repro is deterministic (public falcon-v1.124 asset + meld v0.41.3).

  4. avrabe commented on Jul 22, 2026

    @avrabe
    ContributorAuthor

    Minimal repro for the #345 LdrSym literal-pool-out-of-range shape (3 of the remaining 14 falcon skips). It's a function-size effect, not a global-count one — a large function places the literal pool holding the __synth_globals address >4095 B from the ldr, so the imm12 can't reach it:

    (module
      (global $g (mut i32) (i32.const 7))
      (func (export "big") (result i32) (local $x i32)
        (local.set $x (global.get $g))   ;; the __synth_globals literal load, near the start
        ;; ~3000 straight-line ops to push the pool past 4 KB of machine code:
        (local.set $x (i32.add (local.get $x) (i32.const 1)))
        ;; ... repeat ~3000x ...
        (local.get $x)))
    synth compile big.wasm -t cortex-m7dp --relocatable
    → skipping 'big': LdrSym literal pool out of range (#345): imm12=12012 > 4095 for symbol __synth_globals
    

    (Confirmed on v0.49.0. Generator: 3000 (i32.add … (i32.const i)) between the global.get and the return.)

    Why falcon hits it: the 3 affected functions are its large ones (the fused control/estimator bodies), where a single monolithic literal pool at the function end is out of imm12 range from an early symbol load.

    Fix directions: (a) emit __synth_globals (and other far symbols) as a movw/movt immediate pair instead of a pc-relative ldr — no pool, no range limit; or (b) constant-island placement — flush the literal pool mid-function whenever the pending ldr→pool distance approaches the 4095 limit (the standard LLVM/GAS large-function approach). (a) is simpler and pool-free.

    Deterministic + tiny; happy to adjust the shape if you want a specific size boundary.

  5. added 5 commits that reference this issue on Jul 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions