kit

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

commit fdf54b7c184c74cc10d483882d6110dd809dee69
parent cc7a68d9dd2cd09a8fbf02e115f49030fc99d5e3
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Wed, 17 Jun 2026 13:56:44 -0700

test/parse,doc/ARM32: close arm32 parse-corpus emit/codegen gaps

The 5 emit/codegen reds on the arm32 parse lane are closed:
gnu_labels_as_values_threaded by the computed-goto scratch fix; asm_01_grammar
and builtin_31_readcyclecounter by skip sidecars.

asm_01_grammar's templates are aarch64-specific (%w0 modifiers, mov w0, dmb sy,
x0 clobbers) and never assemble on arm32 — skipped exactly as on rv32/rv64/x64;
its real arm32 surface (operand binding incl. frame-resident "r" inputs, real
Thumb-2 mnemonics) is covered by test/arch/arm32_inline_test.c. The inline-asm
panic it raised, and the add/sub assembler bug that surfaced behind it, are both
fixed in the preceding commits.

builtin_31_readcyclecounter is skipped because ARMv7-M has no architectural
always-on cycle counter reachable as a single instruction (DWT CYCCNT is
optional and needs privileged enable) — same rationale as the wasm skip.

Parse lane: 929 pass / 0 fail / 64 skip.

Diffstat:
Mdoc/plan/ARM32.md | 64++++++++++++++++++++++++++++++++++++++++++++--------------------
Atest/parse/cases/asm_01_grammar.arm32.skip | 1+
Atest/parse/cases/builtin_31_readcyclecounter.arm32.skip | 1+
3 files changed, 46 insertions(+), 20 deletions(-)

diff --git a/doc/plan/ARM32.md b/doc/plan/ARM32.md @@ -33,34 +33,58 @@ 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 **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 +is **423/0/2**; the broad parse lane is now **929 pass / 0 fail / 64 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 64 skips are the +i128 + binary128-long-double cases, the two data-model-only cases +(`6_5_64_unsigned_size_division`, `rv64_atomic_widths_orders`), and the two +aa64-template / no-cycle-counter cases (`asm_01_grammar`, +`builtin_31_readcyclecounter`), all `.arm32.skip`-sidecar'd. The **5 emit/codegen +gaps are CLOSED (2026-06-17)** — `gnu_labels_as_values_threaded` fixed (computed- +goto scratch), `asm_01_grammar` + `builtin_31_readcyclecounter` skipped (see +"Broad parse-corpus triage" below); the 12 wrong-runtime-result reds were already +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 +### Broad parse-corpus triage — ALL reds 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). +data-model-agnostic test rewrites, 2 data-model skips). The 5 remaining +**emit/codegen gaps** are now closed too (1 codegen fix, 1 inline-asm fix + the +assembler bug it surfaced, 2 aa64-template / no-cycle-counter skips): +- **Emit/codegen gaps (5) — DONE:** + - [x] `gnu_labels_as_values_threaded` O1 — `opt native emit: no scratch + register` was a computed-goto store whose address consumed both arm32 + opt-scratch (`ip`+`lr`) as base+index, leaving none to materialize the + stored value. `collapse_addr_to_reg` (shared `pass_native_emit.c`) now + reuses the base when it is itself a reserved scratch (a transient dead + after the access): it folds the index into the base in place, freeing the + other scratch. Arch-neutral; only triggers when no third scratch exists, + so it is a no-op on register-rich backends. Runs `35` at O0+O1. + - [x] `asm_01_grammar` O0+O1 — `.arm32.skip`. The templates are aarch64-specific + (`%w0` modifiers, `mov w0`, `dmb sy`, `x0` clobbers) and never assemble on + arm32, exactly as on rv32/rv64/x64. The underlying error (`optimizer asm + input not in a register`) was a *real* arm32 bug too — a register- + constrained input whose value is a frame-resident local (e.g. a value + that is `+r` inout AND returned) arrives as a frame loc, not a register. + Fixed in `arm32 native.c` `arm_asm_block_native`: stage such inputs into a + reserved scratch (IP, then LR) before binding, mirroring the aa64 path. + Surfaced and fixed a second bug in `arm32 asm.c`: `add`/`sub rd, rn, + {#imm | rm}` only had the 2-operand hi-register `add` and the sp-adjust + rows, so `add r4, r4, #1` assembled as `add r4, r4` (×2) and `sub r4, r4, + #1` as `sub sp, sp, #0`. A shared `arm_asm_addsub_third` now picks the + densest legal form (16-bit sp-adjust → 32-bit `add.w`/`sub.w` modimm → + `addw`/`subw`), byte-matching llvm-mc; the standalone `kit as`/inline-asm + `add`/`sub` immediate + 3-register forms now encode correctly. + - [x] `builtin_31_readcyclecounter` O0+O1 — `.arm32.skip`. ARMv7-M has no + architectural always-on cycle counter reachable as a single instruction + (DWT CYCCNT is optional and needs privileged DEMCR/DWT_CTRL enable), so + the intrinsic is unsupported — the same rationale as the wasm skip. - **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; diff --git a/test/parse/cases/asm_01_grammar.arm32.skip b/test/parse/cases/asm_01_grammar.arm32.skip @@ -0,0 +1 @@ +asm_01_grammar's templates are aarch64-specific (w0/x0 register names, %w0 operand modifiers, `dmb sy`, `x0` clobbers), so the template never assembles for arm32 — same reason it is skipped on rv32/rv64/x64. arm32 inline-asm coverage (operand binding incl. frame-resident "r" inputs, real Thumb-2 mnemonics) lives in test/arch/arm32_inline_test.c. diff --git a/test/parse/cases/builtin_31_readcyclecounter.arm32.skip b/test/parse/cases/builtin_31_readcyclecounter.arm32.skip @@ -0,0 +1 @@ +ARMv7-M (Cortex-M) has no architectural always-on cycle counter reachable as a single instruction. The DWT CYCCNT counter is optional and requires privileged setup (DEMCR.TRCENA + DWT_CTRL.CYCCNTENA) that a bare __builtin_readcyclecounter() cannot express, so arm32 reports the intrinsic unsupported — the same rationale as the wasm skip.