RQ-60-A64IMPORT (VCR-REACH-002 inc. 1): aarch64 import dispatch — SHN_UNDEF externals, the ARM #197 contract ported. Refs #1017, Refs #242 - #1071
Merged
Conversation
…_UNDEF externals, the ARM #173/#197 contract ported The top-ranked real-world blocker for aarch64 (~121 of 805 modules, 88 of 101 components): a module whose code calls an imported function, or whose funcref table holds one, loud-declined wholesale. This is synth's own ARM --relocatable design ported, not new policy: - selector: a call to an import lowers through the SAME bl+CALL26 path as a local call (all marshalling guards apply identically); the now-unused num_imports parameter is DELETED, not kept vestigially. - substrate: a table slot holding an import emits its trampoline `b <field>` against the import's wasm field name (JUMP26) instead of declining; an unnamed import still declines (never fabricate a symbol). - elf: build_relocatable_object_full gains a driver-supplied undefined- externals ALLOWLIST — a reloc against a listed name emits a GLOBAL STT_FUNC SHN_UNDEF symbol (ARM add_undefined_symbol shape). NOT a policy change: a symbol neither placed nor listed keeps the #1013 clean refusal (httparse still exits 1 naming func_0), so a declined local callee can never silently become a link-time external. - backend + CLI: import func_N relocs are rewritten to the import FIELD name (ARM build_relocatable_elf #173 rewrite, ported); field names come from ONE shared derivation (substrate::import_func_symbols). - CLI single-function path: ported the #871 sibling guard — this wrapper ships no .rela.text, so a function carrying relocations now declines loudly instead of shipping dead `bl #0` sites (latent for local calls before this change). Oracle evidence (red -> green on scripts/repro/aarch64_import_{call,table}_1017.wat): - pre-change both exit 1 (import dispatch declines); post-change exit 0. - pyelftools symtab: host_add / ext_inc are STB_GLOBAL STT_FUNC SHN_UNDEF; relocs 283 (R_AARCH64_CALL26) at the bl site and 282 (R_AARCH64_JUMP26) at the table trampoline bind to them. - ld.lld links both objects against an assembled definition of the imports; unicorn executes the linked images: run(37)=42 via the import, table dispatch slot0 (import) 41->42 and slot1 (local) 21->42. - import-free modules byte-identical (unit-tested: empty allowlist and unreferenced externals are invisible); 148 aarch64 tests + full workspace green; claim_check 51/51. Refs #1017, Refs #242 (VCR-REACH-002 increment 1) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
…m12 range — a latent release-mode miscompile the census reached Found by re-running the #1017 acceptance census after import dispatch landed: six kiln modules (wasip3 filesystem tests, yolo_inference) went rc=101. Root cause is NOT the import work — the prologue's `sub sp, sp, #frame` encoder (`sub_imm64`) only debug_asserts its unsigned 12-bit immediate, so a function with >= 512 slot-resident locals panics in a debug build and, in RELEASE, encodes the overflow bits into neighbouring instruction fields — a WRONG SP adjustment, the silent-miscompile class (#180/#185's A64 sibling). Latent since the prologue landed; unreachable until #1017 removed the module-level import declines that had always aborted those modules first. Fix: the selector loud-declines a frame > 4095 bytes before emitting anything, naming the limit. Regression test pins both directions (4096-byte frame declines, 4080-byte frame still lowers); all six census crashers now exit 1 with the reason. Refs #1017, Refs #242 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
…to the aarch64 CI job scripts/repro/aarch64_import_dispatch_1017_differential.py (ci-status: wired, emulations >= 6): symtab contract via pyelftools (field name is GLOBAL/STT_FUNC/SHN_UNDEF; CALL26 resp. JUMP26 binds to it), then the harness acts as the host linker — places .text, appends its own A64 definitions of the imports, resolves every relocation — and executes under unicorn against wasmtime-FIRST expected values, including the still-must-TRAP out-of-range table cases (import dispatch must not loosen the §4.4.8 guards). Decline-honesty leg: a loud-declined LOCAL callee still refuses exit-1 naming the symbol — the externals allowlist is imports-only. CI step asserts non-zero counts in BOTH directions plus the verdict line, set -euo pipefail (#890). oracle_wiring_check green; claim_check 51/51. Refs #1017, Refs #242 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
… — the measured census delta and the oracle Verifies VCR-REACH-002 increment 1 (aarch64 import dispatch): symtab contract + linked-execution differential (CI-wired), decline-honesty in both directions, frozen-safety, the found-by-census latent frame defect, and the MEASURED acceptance delta on the org-repo corpus (307 unique modules, the same corpus as #1051's census; the 805-module gale corpus is not local): 60/307 (19.5%) -> 67/307 (21.8%), 0 regressions. Also records the overlap finding: 80/89 data-segment and 60/64 multi-memory declines ALSO carry function imports, so first-blocker attribution understates this increment — it pays out again when those two land. Strict-duplicate-key YAML parse + rivet validate (errors unchanged at main's 40, warnings 233 -> 232) + verification-evidence oracle green. Refs #1017, Refs #242 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
… claim — the capability that closed it is verified working The decline-matrix honesty oracle (aarch64_m2_decline_538.py) went red on this branch EXACTLY as designed: its 'table slot holding an IMPORTED function' entry asserted a loud decline that this PR's increment 1 closed (commit b81aa8e: the slot's trampoline emits `b <field>` against the import's GLOBAL/STT_FUNC/SHN_UNDEF field-name symbol, R_AARCH64_JUMP26 — the ARM #173/#197 contract ported). Per the oracle's own header, an entry whose gap has closed is a stale claim and must be deleted in the same change that closes it — v0.58's "delete the thing you replaced", applied to a decline claim. Deleted because the replacing capability is VERIFIED WORKING, not merely because the decline stopped happening: - aarch64_import_dispatch_1017_differential.py (CI-wired in the SAME aarch64-oracle job) executes the import-slot dispatch under unicorn against wasmtime-first values — slot 0 (import) 41 -> 42, slot 1 (local) 21 -> 42, OOB indices still TRAP where wasmtime traps — after asserting the symtab contract via pyelftools; at authoring the object was also linked with ld.lld against a real definition and executed. - The narrower residual stays pinned: an import the module does not NAME still declines (substrate.rs table_slot_holding_an_unnamed_import_still_declines_1017). The declared non-vacuity floor moves 14 -> 13 in the same commit — deliberate, this oracle is DESIGNED to shrink as capability lands (same shape as the v0.54/v0.55 entries that moved off this list). Sibling gate checked, not assumed: a64_extended_surface (VCR-SEL-005) scopes itself OP-LEVEL ONLY (module/shape-level refusals incl. import calls explicitly out of scope) and is green on this branch. Re-run through the CI runner: 13/13 declined ops loud-declined (10 with their machine reason asserted) RESULT: PASS — decline matrix honest ORACLE-EVIDENCE ... mode=stdout floor=13 measured=13 compiles=13 exit=0 oracle_wiring_check green (stdout floor total 460 -> 459); claim_check 51/51. Refs #1017, Refs #242 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
avrabe
added a commit
that referenced
this pull request
Aug 26, 2026
…d 5th orphaned flips RQ-60-FLIPCOUPLE is now demonstrating itself. That artifact was filed BECAUSE three status flips were orphaned in v0.59; here are two more, in the release that scopes the fix: RQ-60-CANARY its work SHIPPED IN v0.59.0 (#1061, tag-verified) — the artifact was written describing already-delivered work and landed as `proposed` anyway RQ-60-A64IMPORT #1071 merged minutes ago and did not flip its own status Verified ON MAIN rather than inferred from the merge list: expansion_canary_gate_1021.py present, 1 CI invocation, `git tag --contains` reports v0.59.0 aarch64_import_dispatch_1017_differential.py present, 1 CI invocation ALSO TIGHTENED THE FLOOR 472 -> 473. #1071 added SWVER-025 without raising it. A floor only fails BELOW itself, so 473-measured against 472-pinned would have passed QUIETLY — slack, not red. The gate's own rule is that a PR adding artifacts raises the floor in that PR, and leaving it loose is the first crack in the discipline the gate exists to create. That is the same failure shape twice in one commit: a number that drifts in the direction that still looks green. Both are exactly what RQ-60-FLIPCOUPLE is scoped to make mechanical instead of vigilant. Verified BY ID: both resolve `[implemented]`; CI-filter OURS=0; measured 473 = floor 473; claim_check exit 0. Refs #1064, Refs #1017, Refs #1021 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
The top-ranked real-world aarch64 blocker (#1017: ~121 of 805 modules, 88 of 101 components): a module that calls an imported function, or whose funcref table holds one, loud-declined wholesale. This ports synth's own ARM
--relocatabledesign (#173/#197 — the wasm2c/Wasker undefined-symbol pattern), inventing no new policy:bl+CALL26 path as a local call; every marshalling guard (≤8 args, ≤1 result, no float result/args, exact-height stack) applies to imports identically. The now-meaninglessnum_importsparameter is deleted, not kept vestigially.b <field>(JUMP26) against the import's wasm field name; an unnamed import still declines — never fabricate a symbol.build_relocatable_object_fullgains an undefined-externals allowlist (the module's import field names). A reloc against a listed name emits a GLOBALSTT_FUNCSHN_UNDEFsymbol. Not a policy change: a symbol neither placed nor listed keeps the aarch64 ELF builder PANICS (exit 101) when a retained function relocates against a declined one — arm and riscv exit 1 cleanly on the identical module #1013 clean refusal — httparse still exits 1 namingfunc_0(verified), so a loud-declined local callee can never silently become a link-time external.func_Nrelocs rewritten to field names via ONE shared derivation (substrate::import_func_symbols)..rela.text, so a function carrying relocations now declines loudly instead of shipping deadbl #0sites (latent for local calls before this PR).Red-first
On main, both repros exit 1:
After: both exit 0, and the pyelftools symtab (never disasm text) shows:
Linked and executed, not merely produced
ld.lldlinks both objects against a clang-assembled definition of the imports; unicorn executes the linked images:run(37)=42through the import call; table dispatch slot 0 (import)41→42, slot 1 (local)21→42; out-of-range indices still TRAP exactly where wasmtime traps. The CI-wired differential (scripts/repro/aarch64_import_dispatch_1017_differential.py,emulations >= 6) repeats all of this on every run with wasmtime-first expected values plus a decline-honesty probe;oracle_wiring_checkgreen.The measured number
The 805-module gale corpus is not available locally (1.5 GB, gale-side). Measured instead on the org-repo corpus: 307 unique modules / 489 MB / 124 components — the same corpus RQ-59-PARTIALCENSUS (#1051) used — with a main-built baseline binary vs this branch, same harness, same command (
-b aarch64 --all-exports --relocatable):+7, 0 regressions. The flips include real component-shaped modules (meld
consumer*.wasmcomposites, wasi-testsuite AssemblyScriptproc_exit-*, loom repro-219, synth i2c_thin_882).Honest framing of the delta: this corpus is NOT the gale corpus — its baseline is 19.5%, not 1.6%, and its top blockers are active data segments (89) and multi-memory (64), not import dispatch, because org-repo modules are toolchain/meld artifacts rather than wasm.directory components. The 13/805 → N/805 number can only be measured by gale. What this corpus does show is the overlap finding the artifact asked for: of the 89 data-segment declines, 80 also carry function imports; of the 64 multi-memory declines, 60 do. First-blocker attribution therefore understates this increment — import dispatch is a necessary co-requisite that pays out again when the data-segment and multi-memory increments land.
Found by the census: a latent release-mode miscompile
Re-measuring exposed 6 rc=101 panics — not in the import code: the prologue's
sub sp, sp, #imm12encoder onlydebug_asserted its 12-bit range, so a ≥512-slot local frame panicked in debug and would encode overflow bits into neighbouring instruction fields in release — a wrong SP adjustment, the silent-miscompile class (#180/#185's A64 sibling). Latent since the prologue landed; unreachable until this PR removed the module-level import declines that had always aborted those modules first. Fixed as a loud selector decline with a two-direction regression test; all six modules now exit 1 cleanly, and the final census has 0 crashes.Deliberately out of scope
What did not port cleanly
Nothing structural. One deviation from ARM worth naming: ARM turns any unresolved reloc into an undefined external; aarch64 deliberately keeps the stricter #1013 refusal for non-import symbols (declined local callees), using an imports-only allowlist — the sibling behaviour would have erased a v0.59 guarantee.
Gates
cargo fmt --check·cargo clippy --workspace --all-targets -- -D warnings·cargo test --workspace(150 suites) ·python3 scripts/claim_check.py claims.yaml(51/51) ·scripts/oracle_wiring_check.py·rivet validate(errors unchanged at main's 40; warnings 233→232) — all green, gated on exit codes. ARM/RV32 frozen anchors untouched (import-free aarch64 output byte-identical, unit-tested).Refs #1017, Refs #242 (VCR-REACH-002 increment 1)
🤖 Generated with Claude Code
https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L