commit 0711b27c9cd911ac8d8794d313ea5cf03a88bc6f
parent 51681b4548f8b4046ecb863e175c4ab64179aa5f
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Wed, 17 Jun 2026 13:12:09 -0700
doc/ARM32: record parse-corpus wrong-result closure (12 reds → 0)
All 12 wrong-runtime-result parse reds are resolved: 3 arm32 codegen fixes
(struct-param ABI, signed-negative switch SUB-imm truncation, many-int variadic
spill aliasing), 2 shared-corpus cases made data-model-agnostic (far_slot_large_
frame → long long, builtin_generic_overflow → __LONG_MAX__), and 2 data-model
skips (6_5_64_unsigned_size_division, rv64_atomic_widths_orders, matching rv32).
Parse lane now 928 pass / 5 fail / 62 skip; the 5 remaining fails are all the
pre-existing emit/codegen gaps (asm_01_grammar, builtin_31_readcyclecounter,
gnu_labels_as_values_threaded), not wrong results.
Diffstat:
| M | doc/plan/ARM32.md | | | 79 | +++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------- |
1 file changed, 51 insertions(+), 28 deletions(-)
diff --git a/doc/plan/ARM32.md b/doc/plan/ARM32.md
@@ -33,40 +33,63 @@ the qemu gate). See the "Follow-on variants / polish" checklist below for status
(and MVN shift suffix) matching llvm-objdump.
Verification note: `make test-cross TARGET=freestanding-arm32 DEPTH=full` toy lane
-is **423/0/2**; the broad parse lane is **921 pass / 17 fail / 58 skip**. The 58
-skips are the i128 + binary128-long-double cases (unsupported by design, now
-`.arm32.skip`-sidecar'd to match rv32). The remaining 17 fails are a genuine
-follow-on triage list — see "Broad parse-corpus triage" below. The fix that took
-the parse lane from 33 to 17 fails: the O1 frameless-leaf tier returned through a
-bare `BX lr` while the backend uses `lr` as an emit scratch (global-address
-staging, overflow/popcount/bitfield/byte-copy), so any such leaf clobbered its
-return address (the 16-case O1 globals/statics/attributes `rc 134` cluster). The
-slim tier now saves `lr` (`PUSH {lr}`/`POP {pc}`).
-
-### Broad parse-corpus triage — outstanding (NOT core; freestanding-arm32 parse lane)
-
-17 reds remain in `make test-cross TARGET=freestanding-arm32 DEPTH=full` (parse
-lane). None are in the closed core checklist; each needs separate triage:
-- **Emit/codegen gaps (5):**
+is **423/0/2**; the broad parse lane is **928 pass / 5 fail / 62 skip** (the run
+also showed one transient `6_5_41_ptr_sub_ptr/O1/E` qemu timeout under parallel
+load — `got 124` = GNU `timeout`'s exit code at 22 s vs the usual 140 ms; it
+passes 3/3 isolated, an infrastructure flake, not a red). The 62 skips are the
+i128 + binary128-long-double cases plus the two data-model-only cases
+(`6_5_64_unsigned_size_division`, `rv64_atomic_widths_orders`), all
+`.arm32.skip`-sidecar'd to match rv32. The remaining **5 fails are all
+emit/codegen gaps** (`asm_01_grammar`, `builtin_31_readcyclecounter`,
+`gnu_labels_as_values_threaded`) — see "Broad parse-corpus triage" below; the 12
+wrong-runtime-result reds are CLOSED (2026-06-17). The earlier fix that took the
+lane from 33 to 17 fails: the O1 frameless-leaf tier returned through a bare
+`BX lr` while the backend uses `lr` as an emit scratch (global-address staging,
+overflow/popcount/bitfield/byte-copy), so any such leaf clobbered its return
+address (the 16-case O1 globals/statics/attributes `rc 134` cluster). The slim
+tier now saves `lr` (`PUSH {lr}`/`POP {pc}`).
+
+### Broad parse-corpus triage — wrong-results CLOSED 2026-06-17
+
+The 12 **wrong-runtime-result** reds are all resolved (3 codegen fixes, 2
+data-model-agnostic test rewrites, 2 data-model skips). Only the 5 emit/codegen
+gaps remain on the parse lane:
+- **Emit/codegen gaps (5) — outstanding:**
- [ ] `asm_01_grammar` O0+O1 — `arm32 inline asm: optimizer asm input not in a
register` (inline-asm operand binding at the parse-corpus's harder cases).
- [ ] `builtin_31_readcyclecounter` O0+O1 — `arm32` does not support the
`readcyclecounter` intrinsic (needs a DWT/CYCCNT or unsupported gate).
- [ ] `gnu_labels_as_values_threaded` O1 — `opt native emit: no scratch
register` (computed-goto under -O1 register pressure).
-- **Wrong runtime result (12):**
- - [ ] `6_5_2_2_06_struct_param_mixed_fp_int` O0 (got 229) + O1 (got 5) — mixed
- fp/int struct-param ABI lowering.
- - [ ] `6_5_64_unsigned_size_division` O0+O1 (got 10) — unsigned size division.
- - [ ] `6_8_29_switch_signed_negative` O1 (got 42, want 100) — signed-negative
- switch at O1.
- - [ ] `builtin_generic_overflow` O0+O1 (got 6, want 7) — `__builtin_*_overflow`
- generic-selection edge.
- - [ ] `far_slot_large_frame` O0+O1 (got 4, want 7) — large-frame far slot
- addressing.
- - [ ] `rv64_atomic_widths_orders` O0+O1 (got 9, want 42) — atomics widths/orders
- (the case is rv64-named but runs in the arm32 lane).
- - [ ] `variadic_02_many_ints` O1 (got 0) — many-int varargs at O1.
+- **Wrong runtime result (12) — DONE:**
+ - [x] `6_5_2_2_06_struct_param_mixed_fp_int` O0+O1 — AAPCS32 8-byte-aligned
+ aggregate (struct{double;long;}) was mis-split between core regs and stack;
+ caller/callee disagreed. Fixed: surface the record alignment on parts[0],
+ one `arm_arg_needs_even` predicate everywhere, round-to-even + naive
+ straddle (drop the no-straddle override). (commit 8d022009)
+ - [x] `6_8_29_switch_signed_negative` O1 — the jump-table `idx = sel - vmin`
+ with vmin=-3 emitted `subw #4093` (the −3 constant truncated to 12 bits);
+ legality accepted the negate-trick but emit never implemented it. Fixed:
+ shared `arm_resolve_addsub_imm` for legality + emit. (commit e98cc403)
+ - [x] `variadic_02_many_ints` O1 — under pressure the spilled constant call args
+ rematerialized into the round-robin opt-scratch (ip/lr), aliasing the
+ simultaneously-live register args. Fixed: exclude call-arg values from
+ remat (keep their spill store) + store register-resident stack args
+ directly. (commit 51681b45)
+ - [x] `far_slot_large_frame` — was a data-model artifact (`long` is 32-bit on
+ arm32, so `x`'s 8-byte check failed and the 8-byte far slot wasn't even
+ exercised). Made the shared case `long long` → tests the 8-byte far slot
+ on every arch and passes (arm32 + LP64). (commit 8d022009)
+ - [x] `builtin_generic_overflow` — LP64-only by construction; the one
+ data-model-dependent line used a magic 0x7fff…ffffL. Switched to
+ `(long)__LONG_MAX__` (overflows `long` under both ILP32 and LP64) → now
+ data-model-agnostic, passes on arm32 + LP64. (commit 8d022009)
+ - [x] `6_5_64_unsigned_size_division` — 64-bit-size_t overflow idiom; on a
+ 32-bit target size_t IS 32-bit and the clamp legitimately fires (clang
+ returns 10 too). `.arm32.skip` (matches the rv32 skip). (commit 8d022009)
+ - [x] `rv64_atomic_widths_orders` — exercises rv64's 64-bit A-extension atomics
+ through `long`; 64-bit atomics are out of ILP32 scope (32-bit atomics
+ covered by builtin_06..25). `.arm32.skip` (matches rv32). (commit 8d022009)
## Core checklist — remaining items (single source of truth)