Skip to content

feat(#851): aarch64 direct call lowering (bl + R_AARCH64_CALL26)#859

Open
avrabe wants to merge 4 commits into
mainfrom
feat/aarch64-calls-851
Open

feat(#851): aarch64 direct call lowering (bl + R_AARCH64_CALL26)#859
avrabe wants to merge 4 commits into
mainfrom
feat/aarch64-calls-851

Conversation

@avrabe

@avrabe avrabe commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Lane L3 of the v0.51 hub. Lowers WASM direct call on the -b aarch64 backend (previously a loud-decline). Ref #851.

What was lowered

Direct call — args marshalled into x0..x7 per AAPCS64, bl func_N to the callee (linkable via a new R_AARCH64_CALL26 .rela.text relocation), x0 result pushed. A function that emits a call becomes NON-LEAF: it saves/restores FP+LR (stp x29,x30,[sp,#-16]! / ldp x29,x30,[sp],#16) around the body. Leaf functions stay byte-identical (the frame is gated on "body has a lowered call").

The multi-function object now places every reachable local function with a func_N symbol (+ export alias) so a call to a non-exported helper resolves.

Honest frontier (loud-declined, never wrong code)

  • calling an import (no import-dispatch ABI yet)
  • > 8 integer args; multi-result; float-result callees (returned in v0/d0, not x0 — this guard prevents a silent miscompile)
  • a caller that reads its own incoming params across a call (param-homing is a later increment; a callee freely reads its params — leaf)
  • any live value beneath the args at the call site
  • call_indirect (the named residual)

Changes

area change
encoder bl (imm26), stp_fp_lr_pre16, ldp_fp_lr_post16 — clang-ground-truth-verified
selector select_typed_cf_calls threads num_imports / arg / result / float-return; marshals + emits bl + records call sites; non-leaf LR frame
backend func_result_counts + func_ret_float threaded; relocations produced; compile_module places all locals
elf .rela.text (ELF64 r_info sym in high word, type 283); multi-symbol-per-function
core RelocKind::AArch64Call26; DecodedModule::func_result_counts; CompileConfig::func_result_counts
cli thread func_result_counts (single + WAST-merged paths); fix build_multi_func_aarch64_elf to emit func_N + forward relocations

Gate evidence (RED → GREEN)

RED (origin/main): unsupported wasm op for aarch64 subset: Call(1) — the exported caller is skipped, absent from the object.

GREEN: scripts/repro/aarch64_calls_851.py (native arm64 MAP_JIT, resolves the CALL26 relocations itself) — 5 call modules bit-identical vs wasmtime (const args, computed args, void call, i64 call, result-consumed). Asserts every expected symbol is present, so a silently-declined caller fails loudly (never hangs on an unrelocated bl #0). Wired into the aarch64-native-matrix CI job.

  • 10 new unit tests — 8 selector + 1 elf .rela.text + 1 encoder (76 backend tests pass)
  • Frozen ARM anchors 10/10 untouched (aarch64 is a separate backend)
  • Standing aarch64_matrix.sh unchanged (32 ops / 86 checks PASS — calls aren't in its op-set)
  • fmt clean; clippy -D warnings clean on aarch64/core/cli; --features riscv builds

🤖 Generated with Claude Code

https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L

avrabe and others added 4 commits July 23, 2026 21:27
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
@codecov

codecov Bot commented Jul 23, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 82.13457% with 77 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
crates/synth-backend-aarch64/src/backend.rs 53.76% 43 Missing ⚠️
crates/synth-backend-aarch64/src/selector.rs 90.72% 18 Missing ⚠️
crates/synth-cli/src/main.rs 37.50% 15 Missing ⚠️
crates/synth-backend-aarch64/src/elf.rs 98.93% 1 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