commit b40b8fb7be7f3af9feab1fcdb703b8715f854e37
parent fb361a2c5e14ae76e5020dc11420bca9d19d4a83
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Tue, 16 Jun 2026 19:50:47 -0700
doc(ARM32): remaining-work checklist — design correctly, do not patch
Reframe the residual work as a design-focused checklist. The headline: the
recurring Phase-2 scratch/offset bugs (incl. the now-known i64/wide-store-to-
global miscompile) are all symptoms of an un-designed memory-access/addressing/
scratch layer, so §1 calls for a single correct-by-construction memory lowering
rather than another spot patch. Honestly downgrades the status from 'all
implemented' and records the i64-global-store correctness gap.
Diffstat:
| M | doc/plan/ARM32.md | | | 103 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------- |
1 file changed, 86 insertions(+), 17 deletions(-)
diff --git a/doc/plan/ARM32.md b/doc/plan/ARM32.md
@@ -1,9 +1,13 @@
# Plan: 32-bit ARM (`arm-none-eabi`, ARMv7-M / ARMv7E-M, Thumb-2)
-## Status — 2026-06-16 — Phase 2 (full backend) LANDED
+## Status — 2026-06-16 — Phase 2 (full backend) — core landed; checklist below
-The arm32 backend is now a complete -O0/-O1 Cortex-M C toolchain. All of the
-Phase-2 inventory below is implemented and qemu-validated:
+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**:
- **-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
@@ -12,7 +16,10 @@ Phase-2 inventory below is implemented and qemu-validated:
`_MOVT_ABS` (scaled-index folded into `load_addr`); `load_const`/`load_label_addr`.
- **i64 + soft-double (wide8)** — AAPCS even-register pair alignment, per-lane
call/return passing, 8-byte `load_const`; mul/div/shift + all `double`/`float`
- via the kit-built runtime's libgcc helpers (`__udivdi3`/`__adddf3`/…).
+ via the kit-built runtime's libgcc helpers (`__udivdi3`/`__adddf3`/…). Correct
+ for locals, args, returns, and i64 global *reads*; **i64/wide stores to a
+ global/static address (and an i64 struct field at a non-zero offset) are still
+ miscompiled** — Remaining work §1.
- **Aggregates / varargs / atomics / inline asm** — `copy_bytes`/`set_bytes`,
INDIRECT struct-by-value params/args/returns, AAPCS32 `va_*` (8-byte-aligned),
LDREX/STREX + DMB ≤4-byte atomics (8-byte → spinlock libcall), a minimal
@@ -35,19 +42,81 @@ Phase-2 inventory below is implemented and qemu-validated:
`sym = .` after a glob) landed alongside.
`make test-cross TARGET=freestanding-arm32 DEPTH=full` (toy X) runs 355/425
-green; the ~70 reds are left RED on purpose (not skipped), and break down as:
-tail calls (~14 cases — `arm_emit_call` panics on `CG_CALL_TAIL`), wide-type
-(i64) hint intrinsics (`@expect`/scalar-intrinsic preserve-type), the
-single-thread TLS limitation below (threadlocal mutate/multi), `@memmove`, full
-inline-asm, and a couple of arch-specific cases (e.g. the aa64 privileged hints
-cross-compiled onto arm).
-
-Remaining follow-ons (intentional, documented): **tail calls**; the **AEABI alias
-layer + coroutine asm** in the kit-self-built rt (needs broader kit-as coverage);
-`@memmove` + the full inline-asm assembler; **wide (i64) hint intrinsics**;
-hard-float (FPv4-SP); DSP/ARMv7E-M; ARMv6-M/Thumb-1; full `-mcpu`/`-mfpu`
-parsing; `.ARM.attributes`; a real **multi-threaded TLS** path; `-O0` code
-density.
+green; the ~70 reds are left RED on purpose (not skipped) and map onto the
+checklist below.
+
+## Remaining work — design these correctly; do NOT keep patching
+
+Phase 2 surfaced a clear lesson. Several separate correctness bugs — a branch
+placeholder addend, two scratch-register clobbers (`str lr,[lr]`;
+ip-resident-base clobber), and an incomplete wide-access split — all lived in the
+**memory-access / addressing / scratch layer** and were each fixed cell-by-cell.
+The matrix that layer must cover —
+
+ {base: frame | reg | global} × {offset: in-range | out-of-range}
+ × {width: 1/2/4/8} × {scratch aliases neither the transfer reg nor a live base}
+
+— was never designed as a whole, so each new combination found a new hole (the
+i64/wide-store-to-global bug in §1 is the latest). **The items below must be done
+as coherent, correct-by-construction designs, not as more spot patches.** Each
+names the design, not a workaround.
+
+### 1. Correctness — the wide / addressing memory layer (highest priority)
+- [ ] **i64/wide stores to global/static addresses, and an i64 struct field at a
+ non-zero offset, are miscompiled** (i64 global *reads* work; *writes* do
+ not). Located: the 8-byte split's high lane + the GLOBAL-base
+ address/offset + scratch selection are not uniform across base kinds.
+- [ ] **Design:** one memory-access lowering, correct by construction —
+ (a) a single address-resolver that maps any `NativeAddr` × byte offset ×
+ width to a legal `(base, in-range-offset)`, or materializes the full
+ effective address once into a *reserved* scratch, choosing a scratch that
+ provably aliases neither the transfer register `rt` nor `base`;
+ (b) a uniform wide-N decomposition over the register pair whose high-lane
+ address is computed correctly for frame / reg / **global**;
+ (c) the scratch-reservation invariant stated and `assert`-enforced in one
+ place. This retires the ad-hoc `lr`/`ip` rescue paths added during Phase 2.
+
+### 2. Functional features — real implementations, not stubs
+- [ ] **Tail / sibling calls** (`arm_emit_call` panics on `CG_CALL_TAIL`) —
+ AAPCS tail-call lowering (epilogue-before-jump, in-place arg shuffle,
+ `BX`/`B.W` to the callee) covering musttail / sret / variadic forms.
+- [ ] **`@memmove`** — overlap-safe, direction-aware copy (compare dst vs src),
+ sharing the granule engine with `copy_bytes`.
+- [ ] **Varargs edge cases** — mixed int/fp/i64, stack-arg + result-on-stack:
+ complete `va_arg` over the unified arg window, validated against the
+ GP-save-area layout (no per-width special cases).
+- [ ] **Wide (i64) hint intrinsics** — `@expect`/`assume_aligned` on 8-byte
+ values currently move only the low lane; fold them through the wide path.
+- [ ] **Full inline asm** — route `asm_block` through the real descriptor-driven
+ assembler instead of the minimal template runner.
+
+### 3. Toolchain / runtime completeness
+- [ ] **kit-self-built rt with no exclusions** — grow the descriptor-driven
+ assembler to cover the M-profile system + hand-written-asm idioms, then
+ re-enable the AEABI alias layer + coroutine register-switch asm (`AEABI`/
+ `CORO` in `mk/rt.mk` + the driver's `kRtSrcArm32`), which are clang-only now.
+- [ ] **Multi-threaded TLS** — replace single-thread TLS-as-static with a real
+ variant-I tp-relative model (software thread pointer + reset-stub seed).
+
+### 4. Profiles / ABIs — new variants via the clean `variant` seam, not forks
+- [ ] **Hard-float** (FPv4-SP, `arm-none-eabihf`): VFP codegen + the softfp/hard
+ float-abi axis (`double` stays soft on FPv4-SP).
+- [ ] **DSP / ARMv7E-M** saturating + packed-SIMD behind the `dsp` feature.
+- [ ] **ARMv6-M / Thumb-1** (no IT / MOVW-MOVT / hardware divide / 32-bit Thumb).
+- [ ] Full **`-mcpu`/`-march`/`-mfpu`/`-mfloat-abi`** parsing + the complete
+ `__ARM_FEATURE_*` predefined-macro set.
+
+### 5. Polish (correctness-neutral, do last)
+- [ ] `.ARM.attributes` (Tag_ABI build attributes) for external-toolchain interop.
+- [ ] 16-bit-encoding density + shifted-register operands (deferred peephole);
+ `-O0` code density toward tcc.
+- [ ] The kit-compiled Cortex-M reset stub (needs kit-`as` for MSR/MRS/BKPT).
+
+### Exit criterion
+`make test-cross TARGET=freestanding-arm32 DEPTH=full` is green with no red
+except cases genuinely inapplicable to the target (each carrying a `.arm32.skip`
+sidecar with a reason), and `make rt-arm-eabi-thumb2` builds the **full** runtime
+(AEABI + coro included) with kit's own assembler.
## Status — 2026-06-16 — Phase 1 (walking skeleton) LANDED