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"