kit

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

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:
Msrc/opt/pass_combine.c | 35+++++++++++++++++++++++++++++++++++
Mtest/opt/o1p_combine.sh | 68++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
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"