chore(release): v0.51.0 — aarch64 runs real WASM modules (#851) + oracle hardening (#850)#860
Conversation
Lower the three op groups that previously loud-declined on -b aarch64: - Integer div/rem (i32+i64): SDIV/UDIV, rem via MSUB (a-(a/b)*b). A64 div is TOTAL where WASM is PARTIAL, so emit explicit WASM trap guards: divisor==0 -> brk (all four forms, full-width cbnz for i64) and, for SIGNED DIV only, INT_MIN/-1 overflow -> brk. rem_s(INT_MIN,-1)==0 falls out of MSUB naturally (no overflow guard). Localized divrem closure; temps allocated while operands still reserved (the #776 aliasing rule) so MSUB's simultaneous a/b/q reads never clobber. - popcnt (i32+i64): SIMD fmov->CNT.8b->ADDV->fmov (A64 has no scalar popcnt). i32 uses fmov s (zero-fills upper lanes), i64 uses fmov d. - f64<->i64 reinterpret (GI-FPU-001): fmov d,x / fmov x,d bit-casts. New encoders (llvm-mc ground-truth byte tests): sdiv/udiv/msub (+64), cnt_8b, addv_8b, cbnz64. All clang/llvm-mc verified. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
if/else, loop back-edge, return execution differential — native arm64 (MAP_JIT fork) + unicorn vs wasmtime. RED before lowering: all 7 functions decline (If unsupported, loop declined by name). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
Extends the void-block-only #538 control flow (block/br/br_if) with: - if/else: kind-tagged frame; cbz cond -> else/end, unconditional b over the else arm, join at end. Void (0,0) only — value-if loud-declines. - loop: backward-branch target; br/br_if to a loop frame resolves a NEGATIVE offset eagerly (loop header known at push). Void (0,0) only. - return: early epilogue (funnel top -> x0/d0, restore SP, ret). Branch dispatch is on the TARGET frame's kind (Block/If = forward pending, patched at End; Loop = backward, resolved now), so loop back-edges and forward block exits coexist. patch_branch() centralizes b/cbnz/cbz re-encode so the new cbz can't be mis-patched as cbnz. Reachability flag: code after br/return/unreachable is stack-polymorphic (WASM); truncate the value stack unconditionally but gate the fall-through height debug_assert on reachability. block_arity ordinal now consumed by Loop and If (one entry each), keeping the arity-table lookup aligned. br_table still loud-declines (catch-all). Execution gate: aarch64_ctrlflow_851_differential.py — 25 cases, both branch edges + loop trip counts, native arm64 (MAP_JIT fork) + unicorn vs wasmtime, all GREEN (was RED: all 7 fns declined). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
…al (#851) - Map F64ReinterpretI64/I64ReinterpretF64 in convert_operator (they had WasmOp variants but were never decoded, so globally declined). Now reaches the aarch64 fmov d,x / fmov x,d arms. Verified all backends either emit or LOUD-decline (never silent-drop): aarch64 emits; cortex-m7dp ARM emits i64_reinterpret_f64 / loud-declines the f64 return (GI-FPU-002 ABI class); no-FPU ARM + RV32 loud-decline. Cross- backend op-parity gate green. - Fix div_s INT_MIN/-1 overflow-guard branch offset (+3 -> +4: the two mov_imm halfwords make the skip span 3 insns, so b.ne targets +4 words). Caught red by the native matrix (div_s(7,2) spuriously TRAPped). - New aarch64_divrem_851 execution differential: 152 wasmtime cases (34 trap cases) verified native (SIGTRAP) + unicorn (brk). Proves div/rem by-zero (all forms, both widths) and div_s INT_MIN/-1 TRAP exactly where wasmtime traps; rem_s(INT_MIN,-1)==0 (no trap). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
Backend unit tests for the new lowerings: void-if cbz skip, void-if/else both-edge patch, loop back-edge negative offset, early-return epilogue. Update the old decline test to br_table-and-typed-loop/if only (loop/if now lowered for the void shape). Fix the countdown fixture to copy the param into a non-param local (param writes loud-decline). 70/70 backend tests pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
…851) - Add the #851 div/rem trap-and-value execution oracle to the aarch64-oracle CI job. - Remove div/rem/popcnt from the decline-honesty probe (they now lower and are execution-verified) and refresh the job comment. The probe now asserts only rounding + i64<->float conversions still loud-decline (3/3 green). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
Address advisor tightening: - check_imm26/check_imm19 guard every branch displacement (eager backward Br/BrIf-to-loop + patched forward b/cbz/cbnz) against the A64 field width; over-range LOUD-DECLINES instead of wrapping silently (No silent miscompile). patch_branch is now fallible and propagated at all three call sites. - do_while_count fixture: br_if BACK to the loop header exercises the backward CONDITIONAL branch (cbnz-to-header) that br 0 + br_if 1 loops never hit. Differential now 28 cases, all GREEN. fmt clean, workspace clippy -D warnings clean, 70/70 backend tests. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
…acle Extend the #851 differential beyond div/rem: i32/i64 popcnt (both widths, including the i64 full-8-lane ADDV path) and the f64<->i64 reinterpret round-trip (bit-identity) now execute vs wasmtime too — native SIGTRAP + unicorn. 152 -> 179 cases. Gives the popcnt/reinterpret deliverables the same execution bar as div/rem (previously byte-tested only). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
Lower WASM direct `call` on the -b aarch64 backend: - encoder: bl (imm26), stp/ldp FP+LR non-leaf frame primitives - selector: select_typed_cf_calls threads num_imports/arg/result counts; marshals args to x0..x7 (AAPCS64), bl func_N, pushes x0 result; non-leaf prologue saves LR. Loud-declines imports, >8 args, multi/float results, param-across-call, and any live stack below the args. - backend: compile_module places ALL local functions (func_N symbols + export aliases); emits R_AARCH64_CALL26 relocations - elf: .rela.text section (ELF64 r_info sym in high word, type 283) - core: DecodedModule.func_result_counts (0/1 result distinction); RelocKind::AArch64Call26 Leaf functions byte-identical. 7 new selector tests + 2 elf tests. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
…+ differential
Integration + soundness follow-up to the call lowering:
- The CLI drives a per-function compile loop (not backend::compile_module), so
thread call metadata via CompileConfig: add func_result_counts (0/1 result),
populated in both the single-module and WAST-merged decode paths.
- Fix build_multi_func_aarch64_elf / build_aarch64_elf for the new
A64ElfFunction {symbols, code, relocations}: emit func_N for EVERY function
(the CALL26 target) + export alias, and FORWARD relocations (were dropped).
- SOUNDNESS: loud-decline a callee that returns f32/f64 — AAPCS64 returns
floats in v0/d0, not x0; pushing x0 would be a silent miscompile. Threads
func_ret_float into the selector. New selector test.
- Differential (scripts/repro/aarch64_calls_851.py): native arm64 MAP_JIT,
resolves R_AARCH64_CALL26 itself, 5 call modules (const/computed args, void,
i64, result-consumed) bit-identical vs wasmtime. Asserts every expected
symbol is present (a silently-declined caller fails loudly, never hangs on an
unrelocated bl #0).
- CHANGELOG [Unreleased] entry.
RED (origin/main): "unsupported wasm op for aarch64 subset: Call" + caller
skipped. GREEN: 5/5 bit-identical. Frozen ARM anchors 10/10 untouched;
aarch64_matrix.sh unchanged (32 ops/86 checks PASS).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
Add scripts/repro/aarch64_calls_851.py as a step in the aarch64-native-matrix job (macos-latest arm64 — has clang + wasm-tools + wasmtime). The standing matrix is single-function and cannot exercise calls; this harness JITs linked multi-function blobs and diffs vs wasmtime, failing loudly if any expected symbol is missing (a silently-declined caller). Static strings only. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
…lf + 1 encoder) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
…into release/v0.51.0
…nt/reinterpret) Assemble the three W2 aarch64 lanes onto release/v0.51.0. Selector conflict (L3 call_sites + L4 Kind-enum CF-state; L3's is_non_leaf epilogue param vs L4's branch-displacement helpers) resolved keeping BOTH: L4's Kind enum + branch helpers AND L3's call_sites + 4-arg epilogue. CHANGELOG: all lane entries kept. Build + differentials verify next.
…ing + CHANGELOG - Version 0.50.1 → 0.51.0 (all path-dep pins + MODULE.bazel + npm + status.json). - Regen status.json + FEATURE_MATRIX (--emit-status): aarch64_selector_ops 132 → 148 after the L3/L4/L5 aarch64 ops; claim_check 25/25. - Wire L2 locals + L4 control-flow differentials into the aarch64-oracle CI job (mem/divrem/calls/matrix already wired by their lanes). - CHANGELOG [0.51.0] headline: 'aarch64 runs real WASM modules' — gating four (memory/locals/calls/control-flow) + div/rem/popcnt/reinterpret, gale frontier empty. Deferred: allocator widen (v0.52), ordeal un-pin (ordeal#101). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
Automated review for PR #860pulseengine/synth: Verdict: 💬 Comment Summary: The changes proposed in this pull request are consistent with the project's release process and maintain the integrity of the codebase. The version number has been updated to reflect the latest release, and all dependencies have been updated accordingly. Findings: 0 mechanical (rivet) · 4 from local AI model. Findings (4):
Generated by a local AI model and post-validated against a strict JSON contract. Each finding includes the verbatim line being criticised — verify by reading the file at the cited location. Reviewed at |
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
…review fix) Cold review caught internally-contradictory generated/shipped docs: FEATURE_MATRIX template + selector module-doc still listed div/rem, popcnt, and calls as 'declined'/'not yet' — but L3/L4/L5 shipped them (differentials prove it). Doc-only, no code/soundness change: - feature_matrix.md.tmpl: aarch64 row now advertises div/rem (trap-guarded), popcnt, reinterpret, full control flow, direct calls; declines list corrected to call_indirect/imports/value-blocks/br_table/>8-args/float-result. Regenerated FEATURE_MATRIX.md (0 'no div/rem/popcnt yet' hits; claim_check 25/25). - selector.rs module doc: moved div/rem/popcnt/memory/calls/CF from 'declined' to 'shipped since #851'; declined list now the real residual frontier. - CHANGELOG div/rem case count 152 -> 179 (the 34 trap count was already correct). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
v0.51.0 — "aarch64 runs real WASM modules"
Assembles the v0.51 hub. The
-b aarch64backend now runs actual programs — the gating four all lower + execute, bit-identical vs wasmtime, gated by gale's acceptance matrix (frontier now empty).Merged lanes (all #851 unless noted)
aarch64_matrix.shgate.calllowering (bl + R_AARCH64_CALL26) #859) — AAPCS64 +R_AARCH64_CALL26, leaf functions byte-identical.L3/L4/L5 assembled here (selector fan-in resolved: L4's CF
Kindenum + branch helpers and L3'scall_sites+ 4-arg epilogue coexist).Verified locally (combined binary)
5 aarch64 differentials all PASS — memory 38/38, locals 17/17, control-flow 28, div/rem/popcnt/reinterpret 179 (34 trap cases), calls 5/5 native. Frozen ARM anchors 10/10 untouched. claim_check 25/25. fmt + clippy -D clean. Version 0.50.1→0.51.0; status.json aarch64_selector_ops 132→148.
Deferred by design
bvuremregression (ordeal#101); synth stays pinnedordeal =0.9.1.Honest aarch64 frontier (loud-decline, never wrong):
call_indirect, imports, value-blocks/loops,br_table, >8 args, float-result callees.🤖 Generated with Claude Code