commit 1e545a0552f6789f537d69d3ffde862b64dfd00e
parent 5925ba0f1130826e2d1ea1f8ca31454d3b911588
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Tue, 16 Jun 2026 22:43:29 -0700
doc(ARM32): record Phase 2b — memory/frame/ABI correctness pass
§1 unified memory layer (correct-by-construction), -O1 scratch, tail calls,
frame alignment + variadic epilogue, varargs -O1, intrinsics (IMM + bswap.W +
memmove), TLS-as-static, atomics-addr. Toy corpus 355/425 -> ~418/425. Residual
reds + remaining (full inline asm, §3 self-built rt + kit reset stub +
.ARM.attributes) enumerated.
Diffstat:
| M | doc/plan/ARM32.md | | | 67 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------ |
1 file changed, 61 insertions(+), 6 deletions(-)
diff --git a/doc/plan/ARM32.md b/doc/plan/ARM32.md
@@ -1,13 +1,68 @@
# Plan: 32-bit ARM (`arm-none-eabi`, ARMv7-M / ARMv7E-M, Thumb-2)
-## Status — 2026-06-16 — Phase 2 (full backend) — core landed; checklist below
+## Status — 2026-06-16 — Phase 2b — memory/frame/ABI correctness landed
The arm32 backend is a working -O0/-O1 Cortex-M C toolchain: it compiles, links
-(both `kit ld` and `ld.lld`), and runs the full `test-smoke-arm32` set and most
-of the cross corpus. The core inventory below is implemented and qemu-validated.
-It is **not yet a complete, correctly-designed backend across the board** — the
-"Remaining work" checklist records what is left, and (this is the point) those
-items must be **designed and implemented correctly, not patched in piecemeal**:
+(both `kit ld` and `ld.lld`), and runs the full `test-smoke-arm32` set and almost
+all of the cross corpus. **The Phase-2b correctness pass (below) redesigned the
+memory layer correct-by-construction and fixed frame/ABI/varargs across the
+board; the toy corpus went from 355/425 to ~418/425.**
+
+### Phase 2b — landed (the memory / frame / ABI correctness pass)
+- **§1 unified memory-access layer** — one `arm_resolve_mem` address resolver
+ (`{frame|reg|global} × offset × width`) with ONE asserted scratch invariant
+ (`arm_pick_scratch`), retiring the ad-hoc lr/ip rescue paths. Fixes the
+ i64/wide store-to-global miscompile, initialized-global array/record/tuple
+ reads at a non-zero element offset, and the switch jump-table crashes.
+ Globals apply a non-zero byte offset as a separate add (ARM REL MOVW/MOVT
+ addends are carry-lossy), matching `elf_arm_reloc_field_addend`'s contract.
+- **-O1 native-emit scratch** — `NativeAllocClassInfo.opt_scratch` gives the
+ optimizer two scratch (IP+LR) without the NDT taking LR; SREM/UREM no longer
+ need a hard LR temp. Fixes the "no scratch register" -O1 panics.
+- **Tail/sibling calls** — full AAPCS lowering (direct/indirect/void/sret/
+ variadic/stack-args), frame-size-independent epilogue, in-window stack args.
+- **Frame correctness** — sp kept 8-byte aligned at calls (odd-word saved block
+ padded); the variadic prologue's r0..r3 GP-save is reclaimed in the epilogue.
+- **Varargs at -O1** — GP-save spill in the known-frame prologue, va_start
+ anchored at `saved_block_bytes`, wide-result dst via `arm_loc_addr`, va_arg
+ cursor distinct from dst.
+- **Intrinsics** — IMM args materialized (clz/ctz/bswap/popcount/expect/
+ overflow); bswap uses the 32-bit REV.W (the 16-bit form truncates high regs);
+ wide @expect moves both lanes; **@memmove** (overlap-safe, direction-aware).
+- **TLS-as-static** — `.tdata`/`.tbss` placed + seeded by the bare reset stub.
+- **Atomics** — CAS/RMW use the pointer's own register (an LR-staged operand no
+ longer aliases the base).
+
+### Residual reds (toy DEPTH=full) — see "Remaining work"
+- **112_many_function_type_params/O0**, **123_spec_demo/O0** — register-starved
+ -O0 edge: an indirect call with a large outgoing stack window needs the callee
+ held across both stack-arg staging (IP) and the address resolver (LR); fix is
+ to spill the callee to a frame slot and reload it just before BLX. (-O1 works.)
+- **124_atomic_word_ops/O1** — the -O1 atomic CAS is register-hungry (base + 4
+ operands + STREX status r4); needs an explicit operand-aware scratch budget.
+- **140_fp_callee_save_bottom_frame/O1** — soft-float f64 callee-save across a
+ call at -O1 (no d-regs on soft-float; an integer-pair-spill detail).
+- **154_frame_return_address/O1** — `@frame_address`/`@return_address` walk the
+ r7 chain, but the -O1 prologue's single STMDB does not anchor r7 at a
+ `{caller_r7, lr}` pair (callee-saves interleave). Needs the fp-at-pair prologue
+ (split the callee-save push from the {r7,lr} push), which also fixes backtraces.
+- **20_cg_api_inline_asm_full** — still on the minimal template runner; route
+ `asm_block` through the descriptor-driven `asm.c` (Remaining work §2).
+
+### Still TODO (Remaining work, below)
+- §2 full inline asm via the assembler.
+- §3 grow `kit as` to self-build the **full** rt (AEABI alias + coro) with no
+ exclusions, and the **kit-compiled** Cortex-M reset stub; `.ARM.attributes`.
+
+(Minor: the disassembler renders shifted-MOV forms — LSR/ASR `mov.w rd,rs` — as a
+plain `mov`, losing the shift in listings. Cosmetic; the encodings are correct.)
+
+---
+
+The original Phase-2 inventory below remains the implementation map. It is **not
+yet a complete, correctly-designed backend across the board** — the "Remaining
+work" checklist records what is left, and (this is the point) those items must be
+**designed and implemented correctly, not patched in piecemeal**:
- **-O1 known-frame path** (`func_begin_known_frame`, callee-save PUSH/POP, slim
leaf tier) + large frames/offsets staged through IP. The cross lane runs at