kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

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:
Mdoc/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