commit 06bbc026b294d80cc9ed27fba745d9ed3220d922
parent 0338a73ebf9c9a2c0f64360b649af9310cf26490
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Tue, 16 Jun 2026 08:14:18 -0700
opt: drop redundant sxtb/sxth/sxtw after extending load at O1 (O1-PATTERNS L6)
The signed mirror of the landed ZEXT-of-load fold in combine_exts. A
narrow integer load fills the whole destination register: a flagged
sign-extending load (MF_SEXT_LOAD, ldrsb/ldrsh) sign-extends to register
width and a plain load (ldrb/ldrh) zero-extends to register width. So a
following SXTB/SXTH/SXTW is a no-op in two shapes -> drop to IR_COPY:
(a) SEXT of a sign-extending load of <= width (the load already
replicated the sign bit); (b) SEXT of a zero-extending load with
mem.size < sb STRICT (the sign bit the SEXT would replicate is provably
0). Pure drop, no load-with-extend rider needed.
yyjson.c -O1: sxtb 210 -> 75, __text 166296 -> 162424 (-2.3%).
Battery (signed/unsigned char) + cjson/yyjson/sqlite/lua -O1 run-correct;
existing redundant_copy_ext guard stays green.
Adds the L6 red-green section to test/opt/o1p_combine.sh (yyjson sxtb
count, skipped if unprovisioned) + signed/unsigned-char O0==O1 run.
Diffstat:
2 files changed, 103 insertions(+), 0 deletions(-)
diff --git a/src/opt/pass_combine.c b/src/opt/pass_combine.c
@@ -1315,6 +1315,41 @@ static int try_combine_exts(CombineCtx* ctx, Inst* in, i32 i) {
return 1;
}
+ /* L6 (O1-PATTERNS): the SIGNED mirror of the ZEXT-of-load fold above. A
+ * narrow integer load fills the ENTIRE destination register on every target
+ * (native_direct_target.c:1700): a flagged sign-extending load (MF_SEXT_LOAD,
+ * `ldrsb`/`ldrsh`) sign-extends to register width, and a plain load
+ * (`ldrb`/`ldrh`) zero-extends to register width. A subsequent SXTB/SXTH/SXTW
+ * convert is then a no-op in two shapes — drop it to an IR_COPY (copy-prop +
+ * DCE retire it; worst case a same-cost register move):
+ *
+ * (a) SEXT of a sign-extending load of equal-or-smaller width
+ * (`mem.size <= sb`): the load already replicated the loaded sign bit
+ * across the whole register, so re-sign-extending from `sb >= mem.size`
+ * bytes re-reads bits that are already that sign extension -> identity.
+ * (`ldrsb x; sxtb w` / `ldrsb x; sxtw x` — yyjson's signature.)
+ *
+ * (b) SEXT of a ZERO-extending load whose value cannot have the sign bit set
+ * in the widened position (`mem.size < sb`, STRICT): the loaded value
+ * occupies only the low `mem.size` bytes and bit `8*sb-1` is in the
+ * zeroed region, so the sign bit the SEXT replicates is guaranteed 0 ->
+ * the SEXT equals the ZEXT the load already produced -> identity. Strict
+ * `<` is required: at `mem.size == sb` the load's top byte may set the
+ * sign bit, and SEXT would then differ from the zero-extended value.
+ * (`ldrb w; sxtw x` of a 0..255 byte — lvm/sqlite.) */
+ if (outer_sign && src_cls == RC_INT && (IROp)prod->op == IR_LOAD &&
+ prod->extra.mem.size && prod->nopnds >= 1 &&
+ same_reg_operand(&prod->opnds[0], &in->opnds[1])) {
+ int sext_load = (prod->extra.mem.flags & MF_SEXT_LOAD) != 0;
+ if ((sext_load && prod->extra.mem.size <= sb) ||
+ (!sext_load && prod->extra.mem.size < sb)) {
+ in->op = IR_COPY;
+ in->nopnds = 2; /* opnds[0]=dst, opnds[1]=src already in place */
+ ctx->block_change_p = 1;
+ return 1;
+ }
+ }
+
u32 isb, idb;
int inner_sign;
if (!ext_params(prod, &isb, &idb, &inner_sign)) return 0;
diff --git a/test/opt/o1p_combine.sh b/test/opt/o1p_combine.sh
@@ -127,4 +127,72 @@ EOF
[ "$L1_O0" = "$L1_O1" ] || fail "L1 run O0/O1 differ: $L1_O0 vs $L1_O1"
printf ' L1: run rc=%s (O0==O1)\n' "$L1_O0"
+# ============================================================================
+# L6 — drop redundant sxtb/sxth/sxtw after a same-width-or-wider extending load
+# ============================================================================
+# combine_exts now drops a SEXT whose source's most-recent same-block def is a
+# sign-extending load of <= width, OR a zero-extending narrow load whose value
+# cannot have the sign bit set (mem.size < sb). Signature lives in yyjson.c
+# (`ldrsb x; sxtb w`). We count `sxtb` (yyjson has no sxth and its sxtw aren't
+# load-rooted).
+echo "== L6 drop redundant sxt after extending load =="
+YY_SRC="$(eco_src yyjson)"
+if [ -n "$YY_SRC" ] && [ -f "$YY_SRC/yyjson.c" ] && [ -n "$SYS" ]; then
+ "$KIT" cc -O1 -I"$YY_SRC" --sysroot "$SYS" -c "$YY_SRC/yyjson.c" -o "$WORK/yy.cand.o" \
+ > "$WORK/yy.cand.cc" 2>&1 || fail "L6 yyjson candidate compile failed" "$WORK/yy.cand.cc"
+ "$KIT" objdump -d "$WORK/yy.cand.o" > "$WORK/yy.cand.dis" 2>&1
+ CAND_SXTB="$(grep -cE '\bsxtb\b' "$WORK/yy.cand.dis" || true)"
+ if [ "$have_base" = 1 ]; then
+ "$KIT_BASE" cc -O1 -I"$YY_SRC" --sysroot "$SYS" -c "$YY_SRC/yyjson.c" -o "$WORK/yy.base.o" \
+ > "$WORK/yy.base.cc" 2>&1 || fail "L6 yyjson baseline compile failed" "$WORK/yy.base.cc"
+ "$KIT_BASE" objdump -d "$WORK/yy.base.o" > "$WORK/yy.base.dis" 2>&1
+ BASE_SXTB="$(grep -cE '\bsxtb\b' "$WORK/yy.base.dis" || true)"
+ [ "$BASE_SXTB" -ge 50 ] || fail "L6 red precondition: expected baseline yyjson many sxtb (got $BASE_SXTB)"
+ [ "$CAND_SXTB" -lt "$BASE_SXTB" ] || fail "L6: candidate did not drop any sxtb (base=$BASE_SXTB cand=$CAND_SXTB)"
+ printf ' L6: yyjson sxtb base=%s -> cand=%s (RED->GREEN)\n' "$BASE_SXTB" "$CAND_SXTB"
+ else
+ printf ' L6: yyjson sxtb cand=%s (green-only)\n' "$CAND_SXTB"
+ fi
+else
+ printf ' L6: SKIP structural (yyjson.c not provisioned or no SDK)\n'
+fi
+
+# L6 correctness: signed-char + zero-extending-byte widening, O0 vs O1.
+cat > "$WORK/l6run.c" <<'EOF'
+/* Exercises both L6 shapes: signed-byte loads widened (ldrsb;sxtb / sxtw) and
+ * unsigned-byte loads widened to long (ldrb;sxtw, sign bit provably 0). */
+static long sgn(const signed char *s, int n) {
+ long acc = 0;
+ for (int i = 0; i < n; i++) {
+ signed char c = s[i]; /* sign-extending load */
+ int e = (int)c; /* sxtb -> no-op */
+ long w = (long)c; /* sxtw of the sign-extended byte -> no-op */
+ acc += (long)e + w + (c < 0 ? 1 : 0);
+ }
+ return acc;
+}
+static long usn(const unsigned char *s, int n) {
+ long acc = 0;
+ for (int i = 0; i < n; i++) {
+ unsigned char c = s[i]; /* zero-extending load */
+ long w = (long)(int)c; /* widen a 0..255 value: sign bit provably 0 */
+ acc += w * 3;
+ }
+ return acc;
+}
+int main(void) {
+ signed char sc[256];
+ unsigned char uc[256];
+ for (int i = 0; i < 256; i++) { sc[i] = (signed char)(i - 128); uc[i] = (unsigned char)i; }
+ long r = sgn(sc, 256) ^ usn(uc, 256);
+ return (int)(r & 0x7f);
+}
+EOF
+"$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"
+"$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"
+"$WORK/l6run.o0"; L6_O0=$?
+"$WORK/l6run.o1"; L6_O1=$?
+[ "$L6_O0" = "$L6_O1" ] || fail "L6 run O0/O1 differ: $L6_O0 vs $L6_O1"
+printf ' L6: run rc=%s (O0==O1)\n' "$L6_O0"
+
echo "o1p_combine: OK"