Skip to content

chore(release): v0.51.0 — aarch64 runs real WASM modules (#851) + oracle hardening (#850)#860

Merged
avrabe merged 19 commits into
mainfrom
release/v0.51.0
Jul 23, 2026
Merged

chore(release): v0.51.0 — aarch64 runs real WASM modules (#851) + oracle hardening (#850)#860
avrabe merged 19 commits into
mainfrom
release/v0.51.0

Conversation

@avrabe

@avrabe avrabe commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

v0.51.0 — "aarch64 runs real WASM modules"

Assembles the v0.51 hub. The -b aarch64 backend 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)

L3/L4/L5 assembled here (selector fan-in resolved: L4's CF Kind enum + branch helpers and L3's call_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

  • Graph-colouring allocator widen → v0.52 (multi-release arc; WIP preserved).
  • ordeal 0.16 un-pin → blocked on upstream unsigned-bvurem regression (ordeal#101); synth stays pinned ordeal =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

avrabe and others added 18 commits July 23, 2026 20:46
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
…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
…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
@temper-pulseengine

Copy link
Copy Markdown
Contributor

Automated review for PR #860

pulseengine/synth:release/v0.51.0 → pulseengine/synth:main

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):

  1. MODULE.bazel:7

    version = "0.51.0"
    

    The version number has been updated to reflect the latest release.

  2. Cargo.toml:28

    version = "0.51.0"
    

    The version number has been updated to reflect the latest release.

  3. npm/package.json:1

    "version": "0.51.0"
    

    The version number has been updated to reflect the latest release.

  4. artifacts/status.json:1

    "version": "0.51.0"
    

    The version number has been updated to reflect the latest release.


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 12e800d

@codecov

codecov Bot commented Jul 23, 2026

Copy link
Copy Markdown

…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
@avrabe
avrabe merged commit 710e167 into main Jul 23, 2026
48 checks passed
@avrabe
avrabe deleted the release/v0.51.0 branch July 23, 2026 22:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant