Skip to content

feat(#851): aarch64 linear-memory load/store lowering + gale's acceptance gate#855

Merged
avrabe merged 5 commits into
mainfrom
feat/aarch64-memory-851
Jul 23, 2026
Merged

feat(#851): aarch64 linear-memory load/store lowering + gale's acceptance gate#855
avrabe merged 5 commits into
mainfrom
feat/aarch64-memory-851

Conversation

@avrabe

@avrabe avrabe commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

aarch64 linear-memory load/store lowering (#851, the biggest blocker) + gale's acceptance gate

Lowers WASM linear-memory ops in the host-native A64 backend (-b aarch64) and
wires gale's execution-differential as the standing acceptance gate.

What landed

  • Memory lowering (synth-backend-aarch64): i32.load/store, i64.load/store, and every sub-word form — i32.load8_{s,u}/16_{s,u}, i32.store8/16, i64.load8_{s,u}/16_{s,u}/32_{s,u}, i64.store8/16/32. Effective address = x28 (linear-memory base) + uxtw(i32 addr) + memarg.offset; offsets fold into the size-scaled imm12 when they fit, else materialize-and-add.
  • Encoders: LDR/STR (w/x), LDRB/STRB, LDRH/STRH, LDRSB/LDRSH (w+x sign forms), LDRSW, and an add …, uxtw address form — all clang-ground-truth-verified.
  • Base convention: x28 = __linear_memory_base on entry, mirroring ARM R11 / RV32 s11. Chosen outside the temp pool (x9–x15), the AAPCS64 arg/result registers, and platform/frame regs. The lowering only reads it.
  • Honest frontier kept: MultiMemory and everything uncovered still loud-decline (no silent miscompile).

Gate evidence (frontier before → after)

  • Vendored gale's matrix to scripts/repro/aarch64_matrix.sh; it reproduces the stated baseline exactly: 32 ops / 86 native checks PASS, frontier popcnt div_s rem_s (memory ops aren't in gale's op-set, so the matrix is unchanged — as required).
  • New memory differential (aarch64_mem_851_differential.py): 38/38 store/load round-trips bit-identical vs wasmtime under unicorn with a real linear-memory region + x28 set. Covers i32/i64, all sub-word sign/zero forms, the big-offset materialize path, and a cross-function wr/rd pair proving the shared-base convention (not sp-scratch).
  • Red-first: before this change every memory op is skipped from the .text symtab (warning: skipping function), so the differential's symbol missing → fail path is RED; after, GREEN. (Structurally identical to how div_s declines today.)

Verification scope (honest)

  • Memory load/store is execution-verified under unicorn (host-independent), not on native silicon: gale's macos native-matrix job uses a fixed fn(int32,int32) runner that cannot set the x28 base, so it exercises the ALU/float/control ops on real arm64 but not memory. The unicorn differential sets x28 + a real linear-memory region and diffs vs wasmtime.

CI

  • Memory differential runs in the existing host-independent aarch64-oracle (unicorn) job.
  • New aarch64-native-matrix job on macos-latest (arm64) runs gale's native MAP_JIT matrix as the real-silicon acceptance gate — with a non-vacuity guard (points OBJCOPY at brew llvm, asserts accepted ops >= 32) so a broken objcopy/objdump can't green the gate having verified nothing.

Documented residual frontier (honest, not silent)

Per the selector comment + differential docstring:

  • No bounds check — WASM OOB-trap is not emitted (host-native subset has no memory-size convention yet); all differential inputs are in-bounds.
  • x28 is not yet established by emitted code — there is no aarch64 startup/linker script (unlike RV32's startup.rs). The lowering is correct given the x28 = base precondition, which the differential supplies; wiring the ABI/startup that establishes it in a real program is a follow-on.
  • data-segment init and memory.{size,grow} are follow-ons.

Refs #851

🤖 Generated with Claude Code

https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L

avrabe and others added 5 commits July 23, 2026 20:20
…word)

Lower WASM linear-memory ops in the host-native A64 backend against a
dedicated base register (x28 = __linear_memory_base on entry), mirroring
the ARM R11 / RV32 s11 convention. Adds LDR/STR (w/x) + LDRB/STRB +
LDRH/STRH + LDRSB/LDRSH (w and x sign forms) + LDRSW encoders, all
clang-ground-truth-verified, and an add-extended (uxtw) address form.

Effective address = x28 + uxtw(i32 addr) + memarg.offset. Offsets that
fit the size-scaled imm12 fold into the load/store immediate; larger
offsets materialize-and-add. Covers I32/I64 load/store and every
sub-word i32/i64 variant; MultiMemory and everything else still
loud-decline (no silent miscompile).

Honest frontier: computes+dereferences the effective address but does
NOT bounds-check — WASM OOB-trap, data-segment init, and
memory.{size,grow} are documented follow-ons (host-native subset has no
memory-size convention yet). In-bounds load/store is execution-verified
vs wasmtime.

Refs #851

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
…orn gates

Vendor gale's self-contained aarch64 execution-differential acceptance
gate to scripts/repro/aarch64_matrix.sh (reproduces the stated baseline:
32 ops / 86 native checks PASS, frontier popcnt/div_s/rem_s). Exits
non-zero ONLY on a miscompile so each op auto-flips declined->verified
as its lowering lands.

Add a memory execution-differential (aarch64_mem_851_differential.py +
.wat): 38 store/load round-trips run under unicorn with a real
linear-memory region and x28 set to the base, diffed bit-exact vs
wasmtime. Covers i32/i64 + all sub-word sign/zero forms, the big-offset
materialize path, and a cross-function wr/rd pair that proves the
shared-base convention (not sp-scratch). Red-first: before the lowering
every memory op is skipped from the symtab (symbol-missing => fail).

CI: run the memory differential in the existing host-independent
aarch64-oracle (unicorn) job; add an aarch64-native-matrix job on
macos-latest (arm64) that installs wasm-tools+wasmtime and runs gale's
native MAP_JIT matrix as the real-silicon acceptance gate.

Refs #851

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
…tablishment frontier

- macos native-matrix job: point OBJCOPY at brew llvm (not on default
  PATH) and assert accepted-op count >= 32. The matrix counts an op as
  DECLINED (=PASS) when .text extraction yields empty hex, so a missing
  objcopy/objdump would green the gate having verified nothing — the
  vacuous-gate class. The assertion makes that a hard failure.
- Selector: document that the backend does NOT yet EMIT code that
  establishes x28 (no aarch64 startup/linker script, unlike RV32 s11) —
  the lowering is correct given the x28=base precondition the #851
  differential supplies; wiring the ABI/startup is a documented follow-on.

Refs #851

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
… warnings)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
…native-matrix rc check

- Feature-matrix template: aarch64 row now states linear-memory load/store
  is handled (in-bounds, x28 base, #851) instead of 'memory ... declines';
  regenerated FEATURE_MATRIX.md + status.json (aarch64 selector ops 111->130,
  the claim-check count gate that this change moved).
- claims.yaml VCR-VER-003 note (d): aarch64 is N/A for the static-data
  addressing validator because it has load/store but no data segments yet
  (not because it has no memory ops) — accurate post-#851.
- ci.yml native-matrix: capture the matrix exit code explicitly and fail on
  it (the miscompile signal), rather than leaning on set -e propagating
  through $(...); keep the accepted-op non-vacuity assert.
- CHANGELOG [Unreleased]: the #851 memory lowering + gate.

Refs #851

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
@avrabe
avrabe force-pushed the feat/aarch64-memory-851 branch from 82f057a to c070878 Compare July 23, 2026 18:23
@avrabe
avrabe merged commit 06bb473 into main Jul 23, 2026
45 checks passed
@avrabe
avrabe deleted the feat/aarch64-memory-851 branch July 23, 2026 18:25
@codecov

codecov Bot commented Jul 23, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 47.79412% with 71 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
crates/synth-backend-aarch64/src/selector.rs 4.05% 71 Missing ⚠️

📢 Thoughts on this report? Let us know!

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