kit

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

o1p_combine.sh (19989B)


      1 #!/usr/bin/env bash
      2 # Structural + correctness guards for the five -O1 same-block MIR peepholes from
      3 # doc/plan/O1-PATTERNS.md §2 (L1, L6, L2, L4, L5), all implemented in
      4 # src/opt/pass_combine.c on top of the existing per-block CombineCtx machinery.
      5 #
      6 # Each L-item has its own clearly-labeled section below. Every section is a
      7 # RED-GREEN check: the same fixture is compiled with the candidate kit ($KIT)
      8 # AND with a saved baseline ($KIT_BASE, default build/kit_base). The guard
      9 # asserts the target idiom is PRESENT in the baseline ("red") and REDUCED /
     10 # GONE in the candidate ("green"), and that the program still computes the
     11 # identical, correct result at -O0 and -O1.
     12 #
     13 # Items L1 and L6 use the exact ecosystem files the patterns were mined from
     14 # (lvm.c for L1's spill-reload churn, yyjson.c for L6's sxtb-after-ldrsb); if
     15 # those sources are not provisioned in the kit ecosystem cache the structural
     16 # half of that item is SKIPPED (the harness prints SKIP) while the synthetic
     17 # correctness half still runs. L2/L4/L5 use self-contained synthetic fixtures.
     18 set -uo pipefail
     19 
     20 ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
     21 KIT="${KIT:-$ROOT/build/kit}"
     22 KIT_BASE="${KIT_BASE:-$ROOT/build/kit_base}"
     23 WORK="$ROOT/build/test/opt/o1p_combine"
     24 mkdir -p "$WORK"
     25 SYS="$(xcrun --sdk macosx --show-sdk-path 2>/dev/null || echo)"
     26 
     27 fail() {
     28   printf 'o1p_combine FAILED: %s\n' "$1" >&2
     29   shift || true
     30   for f in "$@"; do
     31     printf '  --- %s ---\n' "$f" >&2
     32     sed 's/^/  | /' "$f" >&2
     33   done
     34   exit 1
     35 }
     36 
     37 have_base=1
     38 [ -x "$KIT_BASE" ] || { have_base=0; printf 'o1p_combine: note: no %s — red-green checks degrade to green-only\n' "$KIT_BASE" >&2; }
     39 
     40 fn_body() { # $1 = file, $2 = symbol -> stdout body
     41   awk -v want="$2" '
     42     $0 ~ ("^[0-9a-f]+ <_?" want ">:") { in_fn = 1; next }
     43     /^[0-9a-f]+ </ { in_fn = 0 }
     44     in_fn { print }
     45   ' "$1"
     46 }
     47 
     48 eco_src() { "$ROOT/scripts/ecosystem.sh" srcdir "$1" 2>/dev/null; }
     49 
     50 # ============================================================================
     51 # L1 — store->load forwarding across a register mismatch
     52 # ============================================================================
     53 # `str rX,[slot]; ldr rY,[slot]` (rX != rY, same slot+size) reloads a value rX
     54 # still holds. opt_combine_compact_block now rewrites the reload to `copy rY,rX`
     55 # (copy-prop + W8 DSE + mir_dce then retire it). The signature shape lives in
     56 # lvm.c's luaV_execute; we count adjacent same-slot diff-register str;ldr pairs.
     57 echo "== L1 store->load forwarding =="
     58 count_strldr() { # $1 = disasm file -> stdout count of adjacent diff-reg str;ldr
     59 python3 - "$1" <<'PY'
     60 import re, sys
     61 lines = open(sys.argv[1]).read().splitlines()
     62 srx = re.compile(r'\b(str)\s+([wx]\d+),\s*\[(x29|sp),\s*(#[0-9]+|#-?0x[0-9a-f]+)?\]')
     63 lrx = re.compile(r'\b(ldr)\s+([wx]\d+),\s*\[(x29|sp),\s*(#[0-9]+|#-?0x[0-9a-f]+)?\]')
     64 def p(line, rx):
     65     m = rx.search(line)
     66     return (m.group(2), m.group(3), m.group(4) or '#0') if m else None
     67 n = 0
     68 for i in range(len(lines)-1):
     69     a, b = p(lines[i], srx), p(lines[i+1], lrx)
     70     if a and b and a[1] == b[1] and a[2] == b[2] and a[0] != b[0]:
     71         n += 1
     72 print(n)
     73 PY
     74 }
     75 LVM_SRC="$(eco_src lua)"
     76 if [ -n "$LVM_SRC" ] && [ -f "$LVM_SRC/lvm.c" ] && [ -n "$SYS" ]; then
     77   "$KIT" cc -O1 -I"$LVM_SRC" --sysroot "$SYS" -c "$LVM_SRC/lvm.c" -o "$WORK/lvm.cand.o" \
     78     > "$WORK/lvm.cand.cc" 2>&1 || fail "L1 lvm candidate compile failed" "$WORK/lvm.cand.cc"
     79   "$KIT" objdump -d "$WORK/lvm.cand.o" > "$WORK/lvm.cand.dis" 2>&1
     80   CAND_N="$(count_strldr "$WORK/lvm.cand.dis")"
     81   if [ "$have_base" = 1 ]; then
     82     "$KIT_BASE" cc -O1 -I"$LVM_SRC" --sysroot "$SYS" -c "$LVM_SRC/lvm.c" -o "$WORK/lvm.base.o" \
     83       > "$WORK/lvm.base.cc" 2>&1 || fail "L1 lvm baseline compile failed" "$WORK/lvm.base.cc"
     84     "$KIT_BASE" objdump -d "$WORK/lvm.base.o" > "$WORK/lvm.base.dis" 2>&1
     85     BASE_N="$(count_strldr "$WORK/lvm.base.dis")"
     86     [ "$BASE_N" -ge 100 ] || fail "L1 red precondition: expected baseline lvm to have many str;ldr-diff-reg (got $BASE_N)"
     87     # Green: candidate must eliminate the large majority of them.
     88     [ "$CAND_N" -le $((BASE_N / 4)) ] || \
     89       fail "L1: candidate did not forward enough str;ldr (base=$BASE_N cand=$CAND_N)"
     90     printf '  L1: lvm str;ldr-diff-reg base=%s -> cand=%s (RED->GREEN)\n' "$BASE_N" "$CAND_N"
     91   else
     92     [ "$CAND_N" -le 100 ] || fail "L1: candidate lvm still has $CAND_N str;ldr-diff-reg"
     93     printf '  L1: lvm str;ldr-diff-reg cand=%s (green-only)\n' "$CAND_N"
     94   fi
     95 else
     96   printf '  L1: SKIP structural (lua/lvm.c not provisioned or no SDK)\n'
     97 fi
     98 
     99 # L1 correctness: spill-heavy program, identical result -O0 vs -O1.
    100 cat > "$WORK/l1run.c" <<'EOF'
    101 extern long ext(long);
    102 static long acc = 0;
    103 long ext(long x) { acc += x; return (x * 2654435761u) ^ (x >> 3); }
    104 /* Many simultaneously-live values forced through the frame, each re-read after
    105  * the homing stores — the L1 forwarding shape. */
    106 static long spilly(long a, long b, long c, long d, long e, long f, long g, long h) {
    107   long v0=a*3+1, v1=b*5+2, v2=c*7+3, v3=d*11+4;
    108   long v4=e*13+5, v5=f*17+6, v6=g*19+7, v7=h*23+8;
    109   long s = ext(v0 + v1);
    110   s += ext(v2 + v3); s += ext(v4 + v5); s += ext(v6 + v7);
    111   return s + v0 + v1 + v2 + v3 + v4 + v5 + v6 + v7;
    112 }
    113 int main(void) {
    114   long r = 0;
    115   for (long i = 0; i < 37; i++) {
    116     acc = 0;
    117     r ^= spilly(i, i+1, i+2, i+3, i+4, i+5, i+6, i+7);
    118     r ^= acc;
    119   }
    120   return (int)(r & 0x7f);
    121 }
    122 EOF
    123 "$KIT" cc -O0 -std=c11 "$WORK/l1run.c" -o "$WORK/l1run.o0" >"$WORK/l1run.o0.cc" 2>&1 || fail "L1 run -O0 compile" "$WORK/l1run.o0.cc"
    124 "$KIT" cc -O1 -std=c11 "$WORK/l1run.c" -o "$WORK/l1run.o1" >"$WORK/l1run.o1.cc" 2>&1 || fail "L1 run -O1 compile" "$WORK/l1run.o1.cc"
    125 "$WORK/l1run.o0"; L1_O0=$?
    126 "$WORK/l1run.o1"; L1_O1=$?
    127 [ "$L1_O0" = "$L1_O1" ] || fail "L1 run O0/O1 differ: $L1_O0 vs $L1_O1"
    128 printf '  L1: run rc=%s (O0==O1)\n' "$L1_O0"
    129 
    130 # ============================================================================
    131 # L6 — drop redundant sxtb/sxth/sxtw after a same-width-or-wider extending load
    132 # ============================================================================
    133 # combine_exts now drops a SEXT whose source's most-recent same-block def is a
    134 # sign-extending load of <= width, OR a zero-extending narrow load whose value
    135 # cannot have the sign bit set (mem.size < sb). Signature lives in yyjson.c
    136 # (`ldrsb x; sxtb w`). We count `sxtb` (yyjson has no sxth and its sxtw aren't
    137 # load-rooted).
    138 echo "== L6 drop redundant sxt after extending load =="
    139 YY_SRC="$(eco_src yyjson)"
    140 if [ -n "$YY_SRC" ] && [ -f "$YY_SRC/yyjson.c" ] && [ -n "$SYS" ]; then
    141   "$KIT" cc -O1 -I"$YY_SRC" --sysroot "$SYS" -c "$YY_SRC/yyjson.c" -o "$WORK/yy.cand.o" \
    142     > "$WORK/yy.cand.cc" 2>&1 || fail "L6 yyjson candidate compile failed" "$WORK/yy.cand.cc"
    143   "$KIT" objdump -d "$WORK/yy.cand.o" > "$WORK/yy.cand.dis" 2>&1
    144   CAND_SXTB="$(grep -cE '\bsxtb\b' "$WORK/yy.cand.dis" || true)"
    145   if [ "$have_base" = 1 ]; then
    146     "$KIT_BASE" cc -O1 -I"$YY_SRC" --sysroot "$SYS" -c "$YY_SRC/yyjson.c" -o "$WORK/yy.base.o" \
    147       > "$WORK/yy.base.cc" 2>&1 || fail "L6 yyjson baseline compile failed" "$WORK/yy.base.cc"
    148     "$KIT_BASE" objdump -d "$WORK/yy.base.o" > "$WORK/yy.base.dis" 2>&1
    149     BASE_SXTB="$(grep -cE '\bsxtb\b' "$WORK/yy.base.dis" || true)"
    150     [ "$BASE_SXTB" -ge 50 ] || fail "L6 red precondition: expected baseline yyjson many sxtb (got $BASE_SXTB)"
    151     [ "$CAND_SXTB" -lt "$BASE_SXTB" ] || fail "L6: candidate did not drop any sxtb (base=$BASE_SXTB cand=$CAND_SXTB)"
    152     printf '  L6: yyjson sxtb base=%s -> cand=%s (RED->GREEN)\n' "$BASE_SXTB" "$CAND_SXTB"
    153   else
    154     printf '  L6: yyjson sxtb cand=%s (green-only)\n' "$CAND_SXTB"
    155   fi
    156 else
    157   printf '  L6: SKIP structural (yyjson.c not provisioned or no SDK)\n'
    158 fi
    159 
    160 # L6 correctness: signed-char + zero-extending-byte widening, O0 vs O1.
    161 cat > "$WORK/l6run.c" <<'EOF'
    162 /* Exercises both L6 shapes: signed-byte loads widened (ldrsb;sxtb / sxtw) and
    163  * unsigned-byte loads widened to long (ldrb;sxtw, sign bit provably 0). */
    164 static long sgn(const signed char *s, int n) {
    165   long acc = 0;
    166   for (int i = 0; i < n; i++) {
    167     signed char c = s[i];      /* sign-extending load */
    168     int e = (int)c;            /* sxtb -> no-op */
    169     long w = (long)c;          /* sxtw of the sign-extended byte -> no-op */
    170     acc += (long)e + w + (c < 0 ? 1 : 0);
    171   }
    172   return acc;
    173 }
    174 static long usn(const unsigned char *s, int n) {
    175   long acc = 0;
    176   for (int i = 0; i < n; i++) {
    177     unsigned char c = s[i];    /* zero-extending load */
    178     long w = (long)(int)c;     /* widen a 0..255 value: sign bit provably 0 */
    179     acc += w * 3;
    180   }
    181   return acc;
    182 }
    183 int main(void) {
    184   signed char sc[256];
    185   unsigned char uc[256];
    186   for (int i = 0; i < 256; i++) { sc[i] = (signed char)(i - 128); uc[i] = (unsigned char)i; }
    187   long r = sgn(sc, 256) ^ usn(uc, 256);
    188   return (int)(r & 0x7f);
    189 }
    190 EOF
    191 "$KIT" cc -O0 -std=c11 "$WORK/l6run.c" -o "$WORK/l6run.o0" >"$WORK/l6run.o0.cc" 2>&1 || fail "L6 run -O0 compile" "$WORK/l6run.o0.cc"
    192 "$KIT" cc -O1 -std=c11 "$WORK/l6run.c" -o "$WORK/l6run.o1" >"$WORK/l6run.o1.cc" 2>&1 || fail "L6 run -O1 compile" "$WORK/l6run.o1.cc"
    193 "$WORK/l6run.o0"; L6_O0=$?
    194 "$WORK/l6run.o1"; L6_O1=$?
    195 [ "$L6_O0" = "$L6_O1" ] || fail "L6 run O0/O1 differ: $L6_O0 vs $L6_O1"
    196 printf '  L6: run rc=%s (O0==O1)\n' "$L6_O0"
    197 
    198 # ============================================================================
    199 # L2 — fuse cmp rD; cmp_branch(NE/EQ, rD, #0) -> cmp_branch (boolean-into-branch)
    200 # ============================================================================
    201 # When a relational's 0/1 bool was materialized into a register (`cset rD`) and
    202 # then re-tested by the terminator (`cbnz/cbz rD`), and the cmp + branch land in
    203 # the SAME MIR block (single-use cmp), try_fuse_cmp_branch rewrites the branch to
    204 # test the cmp's own relation and NOPs the cmp. (Many `if`s split the cmp and
    205 # branch into different blocks at no-SSA O1 — those are out of reach for a
    206 # same-block peephole; L2 captures the co-located subset, e.g. inlined
    207 # predicates in yyjson.) Structural signal: fewer `cset` on yyjson.
    208 echo "== L2 cmp;cset;cbnz/cbz -> cmp_branch fusion =="
    209 if [ -n "${YY_SRC:-}" ] && [ -f "$YY_SRC/yyjson.c" ] && [ -n "$SYS" ]; then
    210   # candidate yy.cand.dis was produced by the L6 section above.
    211   CAND_CSET="$(grep -cE '\bcset\b' "$WORK/yy.cand.dis" || true)"
    212   if [ "$have_base" = 1 ]; then
    213     BASE_CSET="$(grep -cE '\bcset\b' "$WORK/yy.base.dis" || true)"
    214     [ "$BASE_CSET" -ge 100 ] || fail "L2 red precondition: expected baseline yyjson many cset (got $BASE_CSET)"
    215     [ "$CAND_CSET" -lt "$BASE_CSET" ] || fail "L2: candidate did not reduce cset (base=$BASE_CSET cand=$CAND_CSET)"
    216     printf '  L2: yyjson cset base=%s -> cand=%s (RED->GREEN; L1/L6/L2 cumulative)\n' "$BASE_CSET" "$CAND_CSET"
    217   else
    218     printf '  L2: yyjson cset cand=%s (green-only)\n' "$CAND_CSET"
    219   fi
    220 else
    221   printf '  L2: SKIP structural (yyjson.c not provisioned or no SDK)\n'
    222 fi
    223 
    224 # L2 correctness — the critical trap is mis-inverting the condition. Exercise
    225 # every relation in BOTH branch polarities (if(t) and if(!t)) plus FP NaN/inf,
    226 # forcing materialization, and require kit -O0 == kit -O1.
    227 cat > "$WORK/l2run.c" <<'EOF'
    228 static int hits = 0;
    229 static void use(int x) { hits += x; }
    230 static long f(int a, int b, double x, double y) {
    231   int lt=(a<b);   if(lt)use(1);    if(!lt)use(2);
    232   int le=(a<=b);  if(le)use(4);    if(!le)use(8);
    233   int gt=(a>b);   if(gt)use(16);   if(!gt)use(32);
    234   int ge=(a>=b);  if(ge)use(64);   if(!ge)use(128);
    235   int eq=(a==b);  if(eq)use(256);  if(!eq)use(512);
    236   int ne=(a!=b);  if(ne)use(1024); if(!ne)use(2048);
    237   unsigned ua=(unsigned)a, ub=(unsigned)b;
    238   int ult=(ua<ub);  if(ult)use(4096);   if(!ult)use(8192);
    239   int uge=(ua>=ub); if(uge)use(16384);  if(!uge)use(32768);
    240   int feq=(x==y);   if(feq)use(1<<16);  if(!feq)use(1<<17);
    241   int flt=(x<y);    if(flt)use(1<<18);  if(!flt)use(1<<19);
    242   return hits;
    243 }
    244 int main(void) {
    245   long acc = 0;
    246   double ds[5] = {-1.0, 0.0, 1.0, 2.0, 3.0/(double)(0.0==1.0 ? 1.0 : 1e308*1e308)};
    247   for (int a=-3;a<=3;a++) for (int b=-3;b<=3;b++)
    248     for (int i=0;i<5;i++) for (int j=0;j<5;j++) { hits=0; acc=acc*131+f(a,b,ds[i],ds[j]); }
    249   return (int)(acc & 0x7f);
    250 }
    251 EOF
    252 "$KIT" cc -O0 -std=c11 "$WORK/l2run.c" -o "$WORK/l2run.o0" >"$WORK/l2run.o0.cc" 2>&1 || fail "L2 run -O0 compile" "$WORK/l2run.o0.cc"
    253 "$KIT" cc -O1 -std=c11 "$WORK/l2run.c" -o "$WORK/l2run.o1" >"$WORK/l2run.o1.cc" 2>&1 || fail "L2 run -O1 compile" "$WORK/l2run.o1.cc"
    254 "$WORK/l2run.o0"; L2_O0=$?
    255 "$WORK/l2run.o1"; L2_O1=$?
    256 [ "$L2_O0" = "$L2_O1" ] || fail "L2 run O0/O1 differ (mis-inverted condition?): $L2_O0 vs $L2_O1"
    257 printf '  L2: run rc=%s (O0==O1; all relations x both polarities x FP)\n' "$L2_O0"
    258 
    259 # ============================================================================
    260 # L4 — drop the double-cset re-normalize (cset rD; cmp rD,#0|#1; cset rD)
    261 # ============================================================================
    262 # An IR_CMP `rOuter = (rInner <eq/ne> 0|1)` whose rInner is itself an IR_CMP
    263 # (a proven 0/1 bool) re-normalizes an already-0/1 value. try_drop_double_cmp
    264 # rewrites the outer to a COPY of rInner (t!=0 / t==1) or to the inner relation
    265 # INVERTED on the inner's own operands (t==0 / t!=1; skipped when an inner
    266 # operand aliases the inner dst). Signature: yyjson's 573 `cset;cmp#0/1;cset`
    267 # triples.
    268 echo "== L4 drop double-cset re-normalize =="
    269 count_triple() { # $1 = disasm file -> stdout count of cset;cmp#0|#1;cset triples
    270 python3 - "$1" <<'PY'
    271 import re, sys
    272 L = open(sys.argv[1]).read().splitlines()
    273 cset = re.compile(r'\bcset\s+([wx]\d+),')
    274 cmp01 = re.compile(r'\bcmp\s+([wx]\d+),\s*#[01]\b')
    275 n = 0
    276 for i in range(len(L)-2):
    277     if cset.search(L[i]) and cmp01.search(L[i+1]) and cset.search(L[i+2]):
    278         n += 1
    279 print(n)
    280 PY
    281 }
    282 if [ -n "${YY_SRC:-}" ] && [ -f "$YY_SRC/yyjson.c" ] && [ -n "$SYS" ]; then
    283   CAND_TRIP="$(count_triple "$WORK/yy.cand.dis")"
    284   if [ "$have_base" = 1 ]; then
    285     BASE_TRIP="$(count_triple "$WORK/yy.base.dis")"
    286     [ "$BASE_TRIP" -ge 100 ] || fail "L4 red precondition: expected baseline yyjson many double-cset triples (got $BASE_TRIP)"
    287     [ "$CAND_TRIP" -le $((BASE_TRIP / 2)) ] || fail "L4: candidate did not drop enough triples (base=$BASE_TRIP cand=$CAND_TRIP)"
    288     printf '  L4: yyjson cset;cmp#0/1;cset triples base=%s -> cand=%s (RED->GREEN)\n' "$BASE_TRIP" "$CAND_TRIP"
    289   else
    290     [ "$CAND_TRIP" -le 100 ] || fail "L4: candidate yyjson still has $CAND_TRIP triples"
    291     printf '  L4: yyjson triples cand=%s (green-only)\n' "$CAND_TRIP"
    292   fi
    293 else
    294   printf '  L4: SKIP structural (yyjson.c not provisioned or no SDK)\n'
    295 fi
    296 
    297 # L4 correctness: double/triple bool normalization in both directions, O0==O1.
    298 cat > "$WORK/l4run.c" <<'EOF'
    299 static int use_count = 0;
    300 static void use(int x) { use_count += x; }
    301 /* Force the cset;cmp;cset re-normalize: a relational bool fed into further
    302  * !=0 / ==0 / double-negation boolean contexts. */
    303 static long f(int a, int b) {
    304   int t = (a < b);
    305   int u = (t != 0);          /* copy form */
    306   int v = !(a == b);         /* invert form */
    307   int w = !!(a > b);         /* double-not */
    308   int x = ((a <= b) == 0);   /* invert via ==0 */
    309   if (u) use(1);
    310   if (v) use(2);
    311   if (w) use(4);
    312   if (x) use(8);
    313   if (((a >= b) != 1)) use(16);
    314   unsigned ua=(unsigned)a, ub=(unsigned)b;
    315   int y = !(ua < ub);
    316   if (y) use(32);
    317   return use_count + u + v + w + x + y;
    318 }
    319 int main(void) {
    320   long acc = 0;
    321   for (int a=-4;a<=4;a++) for (int b=-4;b<=4;b++) { use_count=0; acc=acc*131+f(a,b); }
    322   return (int)(acc & 0x7f);
    323 }
    324 EOF
    325 "$KIT" cc -O0 -std=c11 "$WORK/l4run.c" -o "$WORK/l4run.o0" >"$WORK/l4run.o0.cc" 2>&1 || fail "L4 run -O0 compile" "$WORK/l4run.o0.cc"
    326 "$KIT" cc -O1 -std=c11 "$WORK/l4run.c" -o "$WORK/l4run.o1" >"$WORK/l4run.o1.cc" 2>&1 || fail "L4 run -O1 compile" "$WORK/l4run.o1.cc"
    327 "$WORK/l4run.o0"; L4_O0=$?
    328 "$WORK/l4run.o1"; L4_O1=$?
    329 [ "$L4_O0" = "$L4_O1" ] || fail "L4 run O0/O1 differ (mis-normalized bool?): $L4_O0 vs $L4_O1"
    330 printf '  L4: run rc=%s (O0==O1; copy + invert + double-not forms)\n' "$L4_O0"
    331 
    332 # ============================================================================
    333 # L5 — single-use register copy coalesced into its consumer
    334 # ============================================================================
    335 # STATUS: the L5 transformation (forward a single-use copy's source into its
    336 # convert/extend, store-value, call-arg, and IR_RET-value consumers) is already
    337 # implemented by the existing same-block copy propagator try_substitute
    338 # (unconditional SK_REG forwarding into every whitelisted consumer slot,
    339 # pass_combine.c) plus the IR_RET back-propagation try_ret_retarget. The only
    340 # former emitter-temp source exception no longer exists: location MIR contains
    341 # one canonical program home, while emitter temps are instruction-scoped leases
    342 # that the MIR verifier rejects as persistent operands. The broad reg-to-reg
    343 # `mov` surplus the catalog counted is ABI-mandated arg-shuffle/save moves plus
    344 # the cross-block register-residency problem (NEEDS-SSA-O2, the catalog's own
    345 # §3). So this section is a COVERAGE/REGRESSION guard: it asserts the spec's
    346 # named consumer coalescing holds (no redundant single-use copy survives
    347 # feeding a convert / store-value / return) rather than introducing a new,
    348 # unsound fold.
    349 echo "== L5 single-use copy coalescing (coverage/regression) =="
    350 cat > "$WORK/l5cov.c" <<'EOF'
    351 extern void usel(long);
    352 /* copy -> convert/extend consumer: the value should be widened directly. */
    353 long conv_c(const unsigned char *p, int i) {
    354   unsigned char b = p[i];
    355   long w = (long)(int)b;   /* no `mov; sxt` of an intermediate */
    356   return w;
    357 }
    358 /* copy -> store-value consumer. */
    359 void store_c(int *q, int v) { int t = v + 1; q[2] = t; }
    360 /* copy -> return consumer (value not from the immediately-preceding inst). */
    361 long ret_c(long a, long b) { long t = a * b; long u = t; usel(0); return u; }
    362 EOF
    363 "$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"
    364 "$KIT" objdump -d "$WORK/l5cov.o" > "$WORK/l5cov.dis" 2>&1
    365 CONV_BODY="$(fn_body "$WORK/l5cov.dis" conv_c)"
    366 STORE_BODY="$(fn_body "$WORK/l5cov.dis" store_c)"
    367 # conv_c: no reg-to-reg mov should feed the widen (the load result widens directly).
    368 CONV_MOV="$(printf '%s\n' "$CONV_BODY" | grep -cE '\bmov\s+[wx][0-9]+, [wx][0-9]+\b' || true)"
    369 [ "$CONV_MOV" -eq 0 ] || fail "L5: conv_c has a redundant reg-to-reg mov ($CONV_MOV)" "$WORK/l5cov.dis"
    370 # store_c: the store value reads the computed result directly (no mov before str).
    371 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)"
    372 [ "$STORE_MOVSTORE" -eq 0 ] || fail "L5: store_c keeps a mov feeding the store ($STORE_MOVSTORE)" "$WORK/l5cov.dis"
    373 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"
    374 
    375 # L5 correctness: copy-chain heavy program, O0 vs O1.
    376 cat > "$WORK/l5run.c" <<'EOF'
    377 extern long sinkl(long);
    378 static long acc = 0;
    379 long sinkl(long x) { acc += x; return x ^ 0x5a5a5a5a; }
    380 static long chain(long a, long b, long c) {
    381   long x = a;
    382   long y = x;          /* copy chain */
    383   long z = y + b;
    384   long w = z;          /* copy */
    385   long t = (int)(char)w; /* copy -> convert */
    386   long r = sinkl(t + c); /* copy -> call arg */
    387   long u = r;          /* copy -> return */
    388   return u;
    389 }
    390 int main(void) {
    391   long s = 0;
    392   for (long i=-50;i<=50;i++) { acc=0; s = s*131 + chain(i, i*2+1, i*3-7) + acc; }
    393   return (int)(s & 0x7f);
    394 }
    395 EOF
    396 "$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"
    397 "$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"
    398 "$WORK/l5run.o0"; L5_O0=$?
    399 "$WORK/l5run.o1"; L5_O1=$?
    400 [ "$L5_O0" = "$L5_O1" ] || fail "L5 run O0/O1 differ: $L5_O0 vs $L5_O1"
    401 printf '  L5: run rc=%s (O0==O1; copy-chain into convert/call/return)\n' "$L5_O0"
    402 
    403 echo "o1p_combine: OK"