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:
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"