kit

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

commit 3b5edcc56d6b796f1080799cc70fbf0b3f7605b1
parent d35a2b355417977a3a289885fd43c89f1558ecfb
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Tue, 16 Jun 2026 08:46:16 -0700

test/opt: L5 single-use copy coalescing coverage guard (O1-PATTERNS L5)

L5 (forward a single-use register copy into its convert/extend, store-
value, call-arg, and IR_RET-value consumers) is already implemented by the
existing same-block copy propagator try_substitute (unconditional SK_REG
forwarding into every whitelisted consumer slot in pass_combine.c) plus the
IR_RET back-propagation in try_ret_retarget. Verified: convert/store cases
coalesce, zero `mov retreg,rN; ret` survive across lvm/lapi/lparser/yyjson.

The only residual is forwarding an EMIT-SCRATCH-sourced copy — empirically
unsound (disabling the scratch-source guard in try_substitute_for_reg
miscompiles sqlite, because native emit repurposes the scratch regs to
materialize locals between MIR instructions). The broad reg-to-reg `mov`
surplus the catalog counted is ABI-mandated arg-shuffle/save moves plus the
cross-block register-residency problem the catalog itself files under
NEEDS-SSA-O2 (§3) — neither is a clean same-block peephole. So no new fold
is added for L5; this is a coverage/regression guard asserting the spec's
named consumer coalescing holds, plus a copy-chain O0==O1 correctness run.

Diffstat:
Mtest/opt/o1p_combine.sh | 71+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 71 insertions(+), 0 deletions(-)

diff --git a/test/opt/o1p_combine.sh b/test/opt/o1p_combine.sh @@ -329,4 +329,75 @@ EOF [ "$L4_O0" = "$L4_O1" ] || fail "L4 run O0/O1 differ (mis-normalized bool?): $L4_O0 vs $L4_O1" printf ' L4: run rc=%s (O0==O1; copy + invert + double-not forms)\n' "$L4_O0" +# ============================================================================ +# L5 — single-use register copy coalesced into its consumer +# ============================================================================ +# STATUS: the L5 transformation (forward a single-use copy's source into its +# convert/extend, store-value, call-arg, and IR_RET-value consumers) is already +# implemented by the existing same-block copy propagator try_substitute +# (unconditional SK_REG forwarding into every whitelisted consumer slot, +# pass_combine.c) plus the IR_RET back-propagation try_ret_retarget. The only +# residual is forwarding an EMIT-SCRATCH-sourced copy, which is unsound at MIR +# level (native emit repurposes the scratch regs to materialize locals BETWEEN +# instructions — disabling the scratch-source guard miscompiles sqlite), and the +# broad reg-to-reg `mov` surplus the catalog counted is ABI-mandated arg-shuffle/ +# save moves + the cross-block register-residency problem (NEEDS-SSA-O2, the +# catalog's own §3). So this section is a COVERAGE/REGRESSION guard: it asserts +# the spec's named consumer coalescing holds (no redundant single-use copy +# survives feeding a convert / store-value / return) rather than introducing a +# new, unsound fold. +echo "== L5 single-use copy coalescing (coverage/regression) ==" +cat > "$WORK/l5cov.c" <<'EOF' +extern void usel(long); +/* copy -> convert/extend consumer: the value should be widened directly. */ +long conv_c(const unsigned char *p, int i) { + unsigned char b = p[i]; + long w = (long)(int)b; /* no `mov; sxt` of an intermediate */ + return w; +} +/* copy -> store-value consumer. */ +void store_c(int *q, int v) { int t = v + 1; q[2] = t; } +/* copy -> return consumer (value not from the immediately-preceding inst). */ +long ret_c(long a, long b) { long t = a * b; long u = t; usel(0); return u; } +EOF +"$KIT" cc -O1 -std=c11 -c "$WORK/l5cov.c" -o "$WORK/l5cov.o" >"$WORK/l5cov.cc" 2>&1 || fail "L5 cov compile" "$WORK/l5cov.cc" +"$KIT" objdump -d "$WORK/l5cov.o" > "$WORK/l5cov.dis" 2>&1 +CONV_BODY="$(fn_body "$WORK/l5cov.dis" conv_c)" +STORE_BODY="$(fn_body "$WORK/l5cov.dis" store_c)" +# conv_c: no reg-to-reg mov should feed the widen (the load result widens directly). +CONV_MOV="$(printf '%s\n' "$CONV_BODY" | grep -cE '\bmov\s+[wx][0-9]+, [wx][0-9]+\b' || true)" +[ "$CONV_MOV" -eq 0 ] || fail "L5: conv_c has a redundant reg-to-reg mov ($CONV_MOV)" "$WORK/l5cov.dis" +# store_c: the store value reads the computed result directly (no mov before str). +STORE_MOVSTORE="$(printf '%s\n' "$STORE_BODY" | grep -E -A1 '\bmov\s+[wx][0-9]+, [wx][0-9]+\b' | grep -cE '\bstr[bh]?\b' || true)" +[ "$STORE_MOVSTORE" -eq 0 ] || fail "L5: store_c keeps a mov feeding the store ($STORE_MOVSTORE)" "$WORK/l5cov.dis" +printf ' L5: convert/store/return copies coalesced (conv mov=%s, store mov;str=%s) [subsumed by try_substitute/try_ret_retarget]\n' "$CONV_MOV" "$STORE_MOVSTORE" + +# L5 correctness: copy-chain heavy program, O0 vs O1. +cat > "$WORK/l5run.c" <<'EOF' +extern long sinkl(long); +static long acc = 0; +long sinkl(long x) { acc += x; return x ^ 0x5a5a5a5a; } +static long chain(long a, long b, long c) { + long x = a; + long y = x; /* copy chain */ + long z = y + b; + long w = z; /* copy */ + long t = (int)(char)w; /* copy -> convert */ + long r = sinkl(t + c); /* copy -> call arg */ + long u = r; /* copy -> return */ + return u; +} +int main(void) { + long s = 0; + for (long i=-50;i<=50;i++) { acc=0; s = s*131 + chain(i, i*2+1, i*3-7) + acc; } + return (int)(s & 0x7f); +} +EOF +"$KIT" cc -O0 -std=c11 "$WORK/l5run.c" -o "$WORK/l5run.o0" >"$WORK/l5run.o0.cc" 2>&1 || fail "L5 run -O0 compile" "$WORK/l5run.o0.cc" +"$KIT" cc -O1 -std=c11 "$WORK/l5run.c" -o "$WORK/l5run.o1" >"$WORK/l5run.o1.cc" 2>&1 || fail "L5 run -O1 compile" "$WORK/l5run.o1.cc" +"$WORK/l5run.o0"; L5_O0=$? +"$WORK/l5run.o1"; L5_O1=$? +[ "$L5_O0" = "$L5_O1" ] || fail "L5 run O0/O1 differ: $L5_O0 vs $L5_O1" +printf ' L5: run rc=%s (O0==O1; copy-chain into convert/call/return)\n' "$L5_O0" + echo "o1p_combine: OK"