Skip to content

fix(arm): gale-wasm linkability — internal-call relocations + Thumb BL encoding (#167) + graceful skip (#168)#169

Merged
avrabe merged 2 commits into
mainfrom
feat/gale-arm-linkability
May 29, 2026
Merged

fix(arm): gale-wasm linkability — internal-call relocations + Thumb BL encoding (#167) + graceful skip (#168)#169
avrabe merged 2 commits into
mainfrom
feat/gale-arm-linkability

Conversation

@avrabe
Copy link
Copy Markdown
Contributor

@avrabe avrabe commented May 29, 2026

Fixes the two serious gale-wasm compilation blockers filed against v0.11.0.

#167 — non-linkable ELF (regression from v0.3.0)

Every synth ARM object containing a function call was non-linkable. Three chained defects:

  1. Dropped relocations. arm_backend.rs recorded a relocation only for BL labels starting with __meld_ (import dispatch). Internal BL func_N got none → emitted as bare bl #0 with nothing to patch. → record a relocation for every BL.
  2. No target symbol + wrong reloc type. build_relocatable_elf defined only export-name symbols, but the selector emits BL func_{wasm_index}. → define a func_{index} symbol per function and emit R_ARM_THM_CALL (Thumb) instead of R_ARM_CALL (ARM-mode).
  3. Garbage BL encoding. The Thumb BL placeholder used second halfword 0xD000 — with J1=J2=0, S=0 that decodes to I1=I2=1, baking in a bogus ~+0x600000 addend. That is the original garbage bl c0000c target and the linker relocation truncated to fit. A true zero-offset Thumb BL is 0xF800 (J1=J2=1). Fixed.

Verified on the issue's minimal 2-function repro: the .o now carries R_ARM_THM_CALL → func_0, links with arm-none-eabi-ld with zero errors (was "truncated to fit"), and the call resolves to the callee.

#168 — register exhaustion aborts whole module

compiler_builtins::float::div exhausts the i64 consecutive-pair allocator; under --all-exports (gale's --whole-archive) this killed the entire module. Now compile_all_exports skips the un-compilable function with a diagnostic, continues, and reports the skipped set; a caller that references it gets a clean undefined symbol func_N link error (via the #167 relocation path). Erroring only if every function fails. (The allocator limitation itself — spill / pair-coalescing — remains a follow-up.)

Tests

Falsification

This is wrong if: a module with internal calls still produces a .o that fails to link (no R_ARM_THM_CALL for the call, or a bogus addend), or --all-exports still aborts on a single un-compilable function.

Follow-ups (filed-worthy, not blocking)

🤖 Generated with Claude Code

avrabe and others added 2 commits May 29, 2026 22:37
…coding (#167)

Three defects made every synth ARM object with a function call non-linkable
(regression from v0.3.0):

1. arm_backend.rs recorded a relocation ONLY for BL labels starting with
   `__meld_` (import dispatch stubs). Internal calls (`BL func_N`) got no
   relocation, so they were emitted as bare `bl #0` with nothing to patch
   them. Now records a relocation for every BL.

2. build_relocatable_elf only defined export-name symbols; the selector
   emits `BL func_{wasm_index}`, so internal-call relocations had no target
   symbol. Now defines a `func_{index}` symbol per function (alongside the
   export name) and emits R_ARM_THM_CALL (Thumb) — not R_ARM_CALL (ARM mode).

3. The Thumb BL placeholder encoded the second halfword as 0xD000, which
   (J1=J2=0, S=0 ⟹ I1=I2=1) bakes in a bogus ~+0x600000 addend — the source
   of the garbage `bl c0000c` target AND the linker "relocation truncated to
   fit". A true zero-offset Thumb BL is 0xF800 (J1=J2=1). Fixed.

Verified on the #167 minimal repro (2 internal-call functions): the .o now
carries an R_ARM_THM_CALL against func_0, links with arm-none-eabi-ld with
zero errors (was "truncated to fit"), and the call resolves to the callee.
All 180+ synth-backend tests pass.

Remaining (follow-up): standalone --cortex-m direct call resolution; $t
mapping symbols for Thumb disassembly/debug display.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… aborting (#168)

A single function that the backend cannot compile (e.g.
compiler_builtins::float::div, which exhausts the i64 register-pair
allocator) previously aborted the entire --all-exports module compile.
For the gale --whole-archive route this means one piece of dead weight
kills 190+ otherwise-fine functions.

Now compile_all_exports records the failure (with a per-function
'warning: skipping function ...' diagnostic), continues, and reports
the full skipped set at the end. A caller that actually references a
skipped function gets a clean 'undefined symbol func_N' link error
(it becomes an undefined external via the #167 relocation path) —
far more actionable than a whole-module failure. If EVERY function is
skipped, we still error (nothing to emit).

The underlying allocator limitation (no consecutive free pair for i64)
is unchanged; this is the graceful-degradation mitigation the issue
requested. Spilling / pair-coalescing remains a follow-up.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@codecov
Copy link
Copy Markdown

codecov Bot commented May 29, 2026

Codecov Report

❌ Patch coverage is 71.69811% with 15 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
crates/synth-cli/src/main.rs 70.58% 15 Missing ⚠️

📢 Thoughts on this report? Let us know!

avrabe added a commit that referenced this pull request May 30, 2026
Bumps workspace 0.11.0 → 0.11.1, sweeps the intra-workspace path-dep
pins + MODULE.bazel, promotes [Unreleased] → [0.11.1] with the
falsification statement.

PR #169 (closes #167, #168): internal-call relocations + correct Thumb
BL encoding (0xF800) make synth ARM objects with function calls linkable
again (v0.3.0 regression); --all-exports skips un-compilable functions
instead of aborting. Backend bugfix only — no proof-suite changes.
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