O1 code-quality patterns: the post-W1 disassembly catalog
A curated catalog of the residual -O1 vs clang -O1 code-quality gaps, from a
per-file disassembly audit of the ecosystem corpus after the entire O1.md §3
worklist (W1–W10) landed (2026-06-16, aggregate __TEXT ≈ 1.09× clang). This
is the successor to that worklist: it identifies what remains, ranks the
linear/no-SSA wins still on the table, and cleanly separates them from the gaps
that genuinely require the parked O2 SSA mid-end.
Update (2026-06-16): all of §2 (L1–L10) is landed. Implemented in parallel (worktree-isolated agents) and merged gated. Aggregate
__TEXT1.09× → 1.059× clang (lvm 2.86× → 2.57× via L1; yyjson 0.86× → 0.80× via L6/L4/L2; sqlite 1.11× → 1.09×; lparser 1.07× → 1.04× via L9; tinyexpr via L10). Per-item: L1 store→load forwarding (pass_combine.c, lvm −9.95%), L2 cset→cmp_branch fusion (same-block subset; cross-block stays O2), L3 widenaa_set_byteszero-init (aa64/native.c, cjson −5.4%), L4 double-cset drop, L5 confirmed already-subsumed bytry_substitute/try_ret_retarget(no unsound new fold; the scratch-source residual is a miscompile, left out), L6 sxt-after-load drop (yyjson −5.45%), L7 shift-into-ALU + L8 sxtw-index-into-addressing (Operand riders, aa64-only; x64/rv64 capability-gated off), L9 const div/mul fold (pass_simplify.c, lparser udiv 22→0), L10 frameless-leaf elision. L9 also surfaced + got fixed a latent volatile-load miscompile (a volatile load of a register-resident local was served from the value-residency fast path with no observable memory access emitted; fixed insrc/cg/memory.c). Gates: test-opt (18 guards) + test-toy 1392/0 + ecosystem 28/0 + smoke-x64/rv64 3/0 + test-cg-api 289/289. The §3 (SSA/O2) and §4 (not-a-deficiency) findings below stand.
Read O1.md first — especially §3 (the landed worklist), §4 (the SSA/O2 out-of-scope list), and §5 (already-landed). Read OPT.md §3 for the O1 pipeline and pass inventory. Every "linear" claim here is checked against those.
1. Methodology
Harness. scripts/o1_quality.sh builds the per-file kit (.k.o) and clang
(.c.o) objects into build/o1_quality/; disassemble with
./build/release/kit objdump -d NAME.k.o. Per-file ratio = kit __TEXT ÷ clang
__TEXT (size -m). Instruction counts are real instruction lines (objdump
format is addr:\t bytes \t mnemonic operands — the mnemonic is after the
second tab; a naive grep ':\t\w+' matches the byte column and is wrong).
Corpus and current standings (kit / clang __TEXT, all aarch64/Darwin -O1,
release kit):
| file | kit/clang | note |
|---|---|---|
| lz4 | 0.38× | clang unrolls/inlines → clang bigger (NOT a kit gap) |
| yyjson | 0.86× | kit smaller (clang inlines far more) |
| lapi | 0.83× | kit smaller |
| tinyexpr | 0.96× | ~par |
| lparser | 1.07× | ~par; the SIZE_MAX/udiv guard pushes it over |
| cjson | 1.31× | aggregate/array zero-init dominates |
| miniz | 1.67× | csel + frame traffic in the deflate loops |
| sqlite | 1.11× | frame traffic + bit-tests + addr folds |
| lvm | 2.86× | the lua interpreter loop — still the worst |
W1.1 is confirmed landed in these objects: sub xN,x29,#k spill-address
recomputes are 0 in lvm (was 8,774) and sqlite (was 35,360); every spill slot
is a one-instruction positive ldr/str [x29,#k]. So the residual gap is no longer
spill addressing — it is the spilling itself (frame round-trips that a real
RA would not emit) plus a family of local peepholes the no-SSA O1 still misses.
Rubric. Each pattern is graded LINEAR (a same-block / per-instruction forward
peephole, no SSA, no superlinear axis) · NEEDS-SSA-O2 (wants GVN/DSE/LICM/IV or a
cross-block register allocator) · NOT-A-DEFICIENCY (clang bigger via inlining/
unrolling, or kit already smaller). Linearity verdicts and host-pass claims were
spot-checked against the live passes (pass_combine.c:1914 compact rule,
aa_set_bytes at aa64/native.c:2577, aa_add_lsl/aa_ldst_regoff emitters).
Spot-checks performed (all reproduced against the current build/o1_quality/
objects): lvm store→reload-different-register (1495 adjacent + 1962 within-4),
mov wN,wM 330 vs clang 5; cjson strb 338 with a 64-long consecutive run;
yyjson double-cset 573 triples and cset→cbnz 438. No high-impact pattern was
dropped on spot-check (one was downgraded — see §4).
2. Linear / n-log-n O1 opportunities
The actionable shortlist, ranked by (impact × confidence × cross-file breadth). This is the section that matters.
L1 — Store-to-load forwarding across a register mismatch ★ highest leverage
The gap. After a spilled def is homed (str rX,[slot]) the O1 reload at the
next use targets a fresh rotating scratch (ldr rY,[slot], rY != rX), so the
value round-trips through memory even though rX still holds it. The existing
adjacent compaction (opt_combine_compact_block, pass_combine.c:1934) collapses
the str;ldr pair only when same_reg_operand(store.src, load.dst) (rX==rY);
the dominant different-register case falls straight through to bl->insts[w++]
and survives.
kit (lvm _luaV_execute): clang keeps the bytecode word live:
str w10, [x29, #512] lsr x8, x28, #7
ldr w9, [x29, #512] ; w9 != w10 add x8, x21, x8, lsl #4
and w10, w9, #0xff ubfx w9, w28, #7, #8
str w10, [x29, #512] ldr x10, [x9]
ldr w9, [x29, #512] ; again (no stack round-trip at all)
Where / frequency. lvm: 1495 adjacent str;ldr same-slot different-reg
(vs 79 same-reg already handled), 1962 within a 4-insn window; concentrated in
luaV_execute. lz4: 191 intra-block store-then-adjacent-reload pairs (the two
giant funcs). sqlite: ~8,500 store-then-reload-within-4. miniz: 1103 near same-slot
store→reload. The single biggest cross-file contributor to the ldr/str excess
(kit lvm ldr 4709 vs clang 731, str 3813 vs 214).
Impact. Each forwarded reload becomes a mov rY,rX, which the existing copy
substitution then folds into rY's uses (often deleting the mov); when rX is dead
and the slot has no other reader, the store is dead too (→ W8 stack-DSE + mir_dce).
Conservatively 1400 insns in lvm alone (9% of the object), with real wins in lz4
(~380) / sqlite (intra-block subset) / miniz.
Host pass + sketch. pass_combine.c, opt_combine_compact_block. In the
prev==IR_STORE && in==IR_LOAD && same_spill_slot_and_size branch, drop the
same_reg_operand requirement: when the registers differ, rewrite the load
in-place to an IR_COPY rY <- rX instead of keeping it as a memory load. The
existing copy substitution / mir_dce retire the copy and (if rX dead + slot
unread) the store. The store-DSE half is W8, already present.
Stays linear: single forward pass per block with the bounded last-store tracking the compact pass already does; no new analysis, no cross-block reasoning. Correctness boundary: only when no intervening clobber of rX, no aliasing memory write, and no redefinition of the slot between str and ldr — exactly the adjacency the compact pass already guarantees for the same-reg case.
Note vs O1.md: this is the natural extension of the W8 machinery, not a new pass. W8 today finds 0 sites because W2/DCE clear the store-store shape upstream; the store→load-forward-on-mismatch shape is a different and very live pattern that no landed item touches.
L2 — Fuse cmp; cset rD,cc; cbnz/cbz rD → cmp; b.cc (boolean-into-branch) ★ broadest
The gap. For if (relational) goto L, kit materializes the condition into a
GPR (cset rD,cc) and then re-tests it (cbnz/cbz rD), where clang branches
directly off the flags. The original cmp's NZCV already encodes the branch; the
cset is the sole user and dies at the branch.
kit (yyjson _yyjson_arr_iter_init): clang:
cmp w8, #0 cmp w8, #0
cset w12, eq b.eq 0x604 (or cbz w8,0x604)
cbnz w12, 0x604
Where / frequency — pervasive. yyjson 438, lvm 19 (→54 once L1 removes the intervening homing), lapi 27, lparser 5, lz4 10, sqlite (part of the cbnz +2,440 delta). clang emits ~0 of these. This is the most cross-file pattern in the catalog.
Impact. ~2 insns per site where a redundant cmp-against-0 also precedes the
cset (yyjson's "double-cset", see L4), else 1 insn (the cset). yyjson 880 insns
(2.1% of __TEXT); lapi ~27; broad small wins elsewhere.
Host pass + sketch. pass_combine.c (or pass_jump.c, which already owns
IR_CMP_BRANCH/IR_CONDBR terminators). When a block terminator is IR_CONDBR
whose condition register's most-recent same-block def is a single-use IR_CMP,
fuse into IR_CMP_BRANCH carrying the cmp's CmpOp+operands (invert for the
cbz/==0 case) and NOP the IR_CMP. This is the inverse of the fusion
cg_ir_lower.c:941 already performs in the other direction; reuse the CombineCtx
last_def map + the existing hard-live use counts.
Stays linear: same-block last-def lookup + single-use check, both already computed; no SSA.
L3 — Widen aggregate/memset zero-init (one strb per byte → wide stores) ★ cjson/miniz
The gap. aa_set_bytes (the AGG_SET / memset expander, aa64/native.c:2577)
emits exactly one strb per byte in a flat 0..size loop, even for an aligned
whole-struct/array zero. A 64-byte zero is 64 strb; clang does it in 3–4
(movi v0.16b,#0; stp q0,q0,...) or, scalarized, str xzr runs.
kit (cjson _cJSON_PrintPreallocated): clang:
add x8, x29, #16 movi v0.16b, #0
movz x9, 0x0 stp q0, q0, [sp, #32]
strb w9, [x8] stp q0, q0, [sp]
strb w9, [x8, #1] ; 64 bytes in 3 insns
... (64 consecutive strb)
Where / frequency. cjson: 338 strb, a 64-long consecutive run confirmed
(296 of 338 are zero-byte stores from this path; clang has 30 strb total).
Hottest in _cJSON_PrintPreallocated/_PrintBuffered (64 each) and _print_number
(26). miniz: large strb surplus from PNG/struct buffers.
Impact. Largest single win in cjson — ~250–300 insns / ~25% of cjson's 1236-
insn gap. Even a scalar widening (str xzr for 8-aligned runs, str wzr/strh wzr/strb wzr tail) without NEON captures most of it.
Host pass + sketch. aa64/native.c aa_set_bytes: rewrite the per-byte loop
to emit the widest aligned store covering the remaining run — stp q/str q if
NEON is permitted, else str xzr, str wzr, strh wzr, strb wzr tail; or bl _memset above a size threshold. Pure per-call expansion, no analysis. The hardware
zero register is already used for stored zeros (pass_native_emit.c).
Stays linear: it removes emitted instructions from a single expander; no new pass. (rv64/x64 set_bytes should get the same widening for parity, but aa64 is the measured win.)
L4 — Drop the redundant boolean re-normalize (cset; cmp rD,#0; cset) — yyjson
The gap. kit lowers (a==b) to cmp; cset rD,eq (a 0/1 bool), then a
surrounding bool != 0 / if(bool) context lowers to a second cmp rD,#0; cset rD,ne that re-normalizes an already-0/1 value. The outer cmp+cset are a
no-op.
kit (yyjson _unsafe_yyjson_is_raw): clang (at each inline site):
cmp w8, #1 cmp w8, #1
cset w8, eq cset w0, eq
cmp w8, #0 ; redundant
cset w8, ne ; w8 == w8
Where / frequency. yyjson: 573 exact cset; cmp #0; cset triples (0 in
clang); dense in the is_*/get_* predicate family. (lapi/sqlite have the related
single-cset-of-a-bool but the triple is yyjson's signature.)
Impact. ~2 insns each → ~1100 insns / ~2.6% of yyjson __TEXT. Combines with
L2: many of these feed a branch, so the L2 fusion subsumes the outer half.
Host pass + sketch. pass_combine.c same-block forward peephole: when an
IR_CMP's source operand's most-recent same-block def is itself an IR_CMP
(0/1 bool) and the comparison is against imm 0/1 with eq/ne, drop the outer
IR_CMP and rewrite its uses to the inner result (inverting for the #0/ne case).
Uses the existing last_def map + hard-live use counts.
Stays linear: single-block, single-use, checkable from existing state.
L5 — Single-use register copy not coalesced into its consumer
The gap. kit produces a value into one register then movs it into the
register its sole consumer (a sxtw/store/call-arg/return) reads, instead of
producing directly into the final register. The cross-block coalescer W3 cannot
retire these because the destinations are multiply-defined physical pregs (W3's
opt_copy_cleanup only fires when ndef[dst]==1); the per-block mir_combine
substitution doesn't reach all the consumer paths.
kit (lvm): kit (lapi return):
ldrb w13, [x8, #11] and w12, w12, #0x7
mov w12, w13 ; redundant mov w0, w12 ; redundant
sxtw x19, w12 ; sxtw x19,w13 ok ret ; and w0,w8,#0x7 ok
Where / frequency. Reg-to-reg mov wN,wM: lvm 330 vs clang 5; lapi 512 vs 194;
lparser 948 vs 541; miniz 1018 of 3203 are mov-of-prev-insn-dest; yyjson 209 feed a
uxtb + 59 are return copies (mov w0,wN before ret). The single largest mov
contributor across the value-heavy files.
Impact. Several hundred insns across lapi+lparser+miniz+yyjson+lvm (conservative after excluding genuinely class-crossing or still-live moves).
Host pass + sketch. pass_combine.c post-RA peephole using the existing
CombineCtx liveness (ctx_def_changed_since, count_uses_in_live_range): for
mov rD,rS where rD has a single use in its live range before its next redef and
rS is not redefined between the mov and that use, forward rS to the use and drop
the mov. Extend the consumer set beyond what try_substitute/try_sink reach
today: convert/extend, store-value, call-arg, and IR_RET value operands. The
IR_RET→return-reg back-propagation mirrors the existing redundant-second-copy
handling at pass_combine.c:~890.
Stays linear: the CombineCtx machinery already answers both guards; same-block.
L6 — Drop the redundant sxtb/sxtw after a same-width extending load
The gap. Two shapes. (a) yyjson loads a signed byte as ldrsb xD,[addr] (full
64-bit sign-extend) then emits sxtb wE,xD for a W-domain consumer — but xD's low
32 bits are already the sign-extended byte, so the sxtb is a no-op (and clang
just does ldrsb wD). (b) lvm/sqlite sxtw of a value sourced from a zero-extending
ldrb/ldrh: kit loads always zero-extend, so a 0..255 byte widens identically
signed or unsigned → the sxtw is a same-width copy.
kit (yyjson _yyjson_mut_ptr_getx): clang:
ldrsb x12, [x21] ldrsb w9, [x13]
sxtb w8, x12 ; x12<31:0> == byte cmp w9, #34
cmp w8, #47
Where / frequency. yyjson: 112 ldrsb x..; sxtb w.. + 74 ldrb; sxtb. lvm:
the ldrb;mov;sxtw triple subset (~6 tight + more across the body). sqlite: part of
the sxtw +2,582 (the ldrb/ldrh-rooted subset). This is the signed mirror of the
already-landed ZEXT-of-load fold (OPT.md §3 / commit 8826982d).
Impact. yyjson ~186 insns; lvm ~30–80 from the byte-rooted cases; sqlite a slice of the sxtw delta. Medium confidence on the broad count, high on the exact-shape subset.
Host pass + sketch. pass_combine.c combine_exts (already folds ext-of-ext):
a SXTB/SXTH/SXTW whose source's most-recent same-block def is a sign-extending
load of equal-or-smaller width, or a zero-extending narrow load whose value can't
have the sign bit set in the widened position, is redundant → drop it (rewrite uses
to the load dst). Folding ldrb;sxtb→ldrsb needs a load-with-extend MemAccess
rider (noted as structurally needed in prior O0 work); the pure
sxtb-after-ldrsb/sxtw-after-ldrb drop needs no rider and is the easy win.
Stays linear: per-instruction, same-block last-def; no SSA.
L7 — Fold a single-use shift into the consuming ALU op (lsl;add → add ...,lsl #k)
The gap. kit folds shifts into memory operands (try_addr_synth handles ISHL
into an indirect index scale) but not into general ALU consumers. So pointer
scaling whose sum is stored/compared (not a load address) emits lsl xT,xS,#k; add xD,xB,xT where clang uses the one-instruction shifted-register form.
kit (lapi _index2value): clang:
sxtw x12, w1 sxtw x21, w1 ; once
lsl x12, x12, #4 add x8, x8, x21, lsl #4
add x8, x13, x12
Where / frequency. lvm 14 lsl;add (+lsl;sub); miniz 105 lsl-feeds-
add/orr/eor/sub (clang folds 74); lapi ~15–25; sqlite part of the lsl +1,335 delta.
kit emits ~0 shifted-register ALU ops; clang uses them heavily. Strongest in miniz's
LZ hash (eor ...,lsl #5) and byte-pack code.
Impact. miniz ~105 insns; lvm/lapi ~20–30 each; plus a freed scratch that relieves the spill pressure feeding L1.
Host pass + sketch. pass_combine.c recognition + aa64/native.c emission.
Mark a single-use IR_BINOP SHL reg,imm (sh 1..4) feeding an IADD/ISUB/AND/ORR/
EOR and emit the fused shifted-register form. The emitter exists (aa_add_lsl,
native.c:750; the ISA exposes AA64_FMT_LOG_SR/ADDSUB_SR) — it just isn't
reachable for non-memory consumers. Needs a shift rider on the register operand (the
current Operand only carries log2_scale inside the ind memory variant). x64 can
use this only for IADD (LEA scale); rv64 has no shifted-add → skip there.
Stays linear: local single-use peephole, same live-range guards as the existing ISHL-into-EA path. Medium confidence (needs the operand-model shift-rider).
L8 — Fold a sxtw/uxtw index into the load/store addressing mode
The gap. For signed-int array indexing kit emits sxtw to widen the index, a
separate add ...,lsl #scale to form the address, then a plain [reg] access —
three instructions. aa64 has [Xbase, Wm, sxtw #scale] as a single addressing mode
(aa_ldst_regoff, native.c:600); clang uses it everywhere, kit zero times.
kit (miniz): clang:
sxtw x14, w12 ldr w8, [x9, w8, sxtw #2]
add x9, x0, x14, lsl #2 strh w9, [x26, w13, sxtw #1]
ldrh w20, [x9]
Where / frequency. miniz 15 sxtw;add...,lsl feeding a load/store (clang 98
extended-reg memory operands, kit 0). sqlite 546 sxtw immediately feeding a
load/store offset. lapi/lparser index math. Big total across sqlite+miniz.
Impact. sqlite ~546 insns (the standalone sxtw folds into the access); miniz ~100–200 (removes sxtw + the address-forming add). LINEAR slice of the larger sxtw story (the cross-block redundant re-extend is NEEDS-SSA-O2, see §3).
Host pass + sketch. pass_addr_fold.c already builds OPK_INDIRECT with
index + log2_scale but does not absorb a sxtw/uxtw of the index. Add an
extend-into-index rule: fold a single-use sxtw/uxtw producer of an index operand
into the indirect operand, recording the extend kind, and emit aa_ldst_regoff_v
with the extend in aa64/native.c. The scaled-index infra + REGOFF encoding exist.
Stays linear: per-instruction forward fold, existing addr-fold machinery.
L9 — Constant-operand integer divide/multiply not folded
The gap. kit materializes two constants and issues a runtime udiv/sdiv/mul
for fully compile-time-constant expressions (classically ArraySize = sizeof/elem,
and the Lua MAX_SIZET/sizeof(T) >= LIMIT growvector guard). The guard's SIZE_MAX
takes a 4-insn movz/movk chain into a register, then udiv + cmp + b.hi;
clang decides the always-true compare and materializes only the LIMIT immediate.
kit (sqlite _sqlite3_status64): clang:
movz x9, 0x50 cmp w0, #0xa
movz x10, 0x8
udiv x8, x9, x10 ; = 10 at compile time
cmp w20, w8
Where / frequency. sqlite 108 div/mul sites with both operands freshly movz'd.
lparser 22 udiv (12 udiv-by-1, 10 the SIZE_MAX guard); lapi 9 sdiv; tinyexpr 5.
The SIZE_MAX guard is the dominant reason lparser is larger than clang.
Impact. sqlite ~300–400 insns (each site collapses movz+movz+udiv+mov). lparser ~80–90 (the guard is ~7–8 insns → 1 movz at ~10 sites + the udiv-by-1). Each udiv is also the slowest integer op, so a latency win too.
Host pass + sketch. Two pieces. (a) pass_simplify.c: add a same-block forward
LOAD_IMM→known-value tracker (per-block reg→value map, invalidated on redef/block
boundary — no SSA, consecutive within-block) and feed it into operand_const so the
existing BO_UDIV/BO_SDIV folds and a new const-op-const fold fire when the
constant lives in a register rather than an inline OPK_IMM. The resulting LOAD_IMM
then lets simplify_cmp decide the always-true guard. (b) The divide-by-1 →
copy/sub falls out of (a).
Stays linear: a bounded per-block reg map + the existing folds. Strength-reduction
of non-constant divide-by-constant (magic multiply, miniz adler32) is a separate,
also-linear pass_lower.c item but lower priority (~25 sites, medium confidence).
L10 — Frame elision on no-spill leaf functions
The gap. kit emits a full stp x29,x30,[sp,#-16]!; add x29,sp,#0 ... ldp frame
in functions that are leaves (no call), spill nothing, and never reference [x29] —
i.e. need no frame. clang omits it entirely; for a one-op accessor the frame is the
majority of the function.
kit (tinyexpr _add): clang:
stp x29, x30, [sp, #-16]! fadd d0, d0, d1
add x29, sp, #0 ret
fadd d0, d0, d1
ldp x29, x30, [sp], #16
ret
Where / frequency. yyjson 90 frameless-able leaves; lapi ~21; lparser ~10;
tinyexpr 8. The is_*/get_*/cast accessor family. (Caveat: many of these are
exactly the predicates clang inlines away, so the realizable win depends on
whether kit also inlines them — the frame-elision is independently correct for the
leaves that remain.)
Impact. ~3 insns/function (stp+add+ldp). yyjson ~360, lapi ~63, lparser ~30, tinyexpr ~24. Medium confidence on realizable total (the inlining caveat).
Host pass + sketch. aa64/native.c prologue/epilogue (NativeFrame): a
per-function needs_frame flag = false when no IR_CALL, no spill slots, no
escaping IR_ADDR_OF(local), no varargs. All known at frame-finalize time in one
pass; emit the stp/add/ldp only when set. x29 already sits at frame bottom (W1.1), so
omission is straightforward.
Stays linear: single per-function check, no analysis.
3. Needs SSA / the O2 mid-end
Real gaps, but none is a linear no-SSA peephole — each maps to a parked O2 item
(OPT.md §3 schedule, pass_o2.c/pass_ssa.c; O1.md §4 out-of-scope list).
- Register-resident locals across blocks/calls (the over-spilling itself). The largest remaining gap on the big functions: kit's frame traffic is ~6× clang's on sqlite (62,708 vs 10,412), ~4.3× on lz4, dominant in lvm/miniz. clang keeps loop-carried accumulators/pointers in callee-saved regs across the whole live range; kit's no-SSA linear-scan allocator (no splitting, falls to memory residency in big bodies) cannot. L1 (store→load forwarding) is the linear slice that reaches the intra-block subset; the cross-block residency needs the SSA RA with live-range splitting + global copy propagation. → O2 register allocator + GVN.
- csel / csinc / ccmp selection (if-conversion). kit emits 0 csel/csinc/ccmp;
clang uses them heavily (lvm 72+19, miniz 218+34, sqlite 1,264+436, cjson 32, lz4
499). Every
x = cond ? a : b, MZ_MIN/MAX clamp, and double→int saturate becomes a compare+branch diamond with the selected value threaded through the frame (feeding the spill gap). Doing it well needs a select op in the IR + a profitability/legality gate that proves both arms side-effect-free and reasons about the merge of two definitions — the SSA/O2 domain. (A narrow peephole for the exactcmp;b.cc;mov-imm;b;mov-immdiamond could fitpass_jump.cbut is fragile and lower-value than the §2 items — flag, do not prioritize.) → O2 if-conversion. tbz/tbnzfromand #(1<<k); cbz/cbnz. sqlite has 715and wN,#bit; cbz/cbnzthat fold to a singletbz/tbnz; clang uses tbz/tbnz 3,102 times, kit 0. The narrow single-bit-AND-feeding-an-adjacent-branch case is arguably LINEAR (apass_native_emit/pass_jumppeephole dropping theandand emitting tbz/tbnz); it is listed here rather than §2 because the broader csel/ccmp family it sits inside is SSA-shaped, and the narrow slice should be validated before committing. → mostly O2, with a possible lineartbzslice worth a spike.- Cross-block redundant re-extension / re-materialization. Repeated
sxtw rX,wYof an unchanged source across blocks/calls (sqlite ~173 conservatively measured; more in reality). L6/L8 handle the same-block and addressing-mode-foldable cases; the cross-block redundant re-extend needs GVN / cross-block copy propagation. → O2 GVN. - FP constant folded at the wrong (narrower) width. tinyexpr's
NAN = 0.0/0.0/INFINITY = 1.0/0.0fold to a float NaN/Inf, materialize into a GPR, thenfcvt d,sto widen (12 sites, +24 insns). Folding at the result width avoids the widen, but correctness needs care that float→double of the folded constant is the same value (canonical NaN payload), so it is a width-selection fix in the constant-fold path, not a trivial peephole. Marked NLOGN/needs-care. → folder fix (cg_ir_lower.c), schedule with care, low priority.
4. Not a deficiency
Where clang is bigger via inlining/unrolling, or kit is already smaller — so future readers don't chase these.
- clang inlines/unrolls far more at
-O1. lz4 (kit 0.38× clang) and yyjson (0.86×), lapi (0.83×) are smaller in kit because clang inlines aggressively (yyjson kit bl=942 vs clang 294; lz4LZ4_compress_generic/LZ4_count/LZ4_hash5fully inlined into every caller). Every clang-heavy mnemonic on those files (movz/cmp/add/sub/movk/csel/madd/strh/ldrh) is an inline-site materialization/ selection artifact, not a kit gap. Tuning kit's inline-pressure cap (W4, landed) upward would increase total__TEXT, not decrease it — net size impact neutral-to-negative. ret/ function-count deltas are inlining shape. lparser kit 96 funcs / 96 ret vs clang 24 / 20; kit emits exactly one ret per function (optimal per-function, no duplicated epilogues). tinyexprblr16 vs clangbr(tail-call dispatch after inlining). These belong to the parked O2 inliner/tail-call work.- NEON /
.instcolumns. clang's hundreds of.instareumulh/smulhmagic-multiply divides,movivector zeroing, andstp q— vectorization/strength- reduction kit doesn't do. The zero-init half is recoverable as L3 (scalar widening); the magic-multiply half is the optional L9 strength-reduction tail. cbzdeltas favoring clang. cjsoncbzis −107 (kit 208 vs clang 315) — kit is not over-emitting branches here; do not read the raw cbz delta as a kit gap.
Downgraded on spot-check: the lz4/miniz "address bases spilled and reloaded" and "large struct-field address rebuilt per use" items the per-file agents rated high were largely the cross-block residency problem (NEEDS-SSA-O2, §3), not a clean linear peephole — only their strictly-intra-block, same-base-unchanged subset is linear, and that subset is already covered by L1 (store→load forwarding) and the landed W1a (frame-address sub/add-CSE) + W5 (local CSE). They are not given separate §2 entries to avoid double-counting.
5. Suggested next O1 work
In the style of O1.md's worklist — ranked by impact × confidence × breadth, each a
linear no-SSA change with a clear host pass and a test/opt/ structural guard.
- P1 — store→load forwarding on register mismatch (
pass_combine.c,opt_combine_compact_block). Highest single lever (lvm ~1400 insns, plus lz4/ sqlite/miniz intra-block). Drop thesame_reg_operandrequirement in the store;load branch; emit a copy on mismatch, let substitution + W8 + mir_dce clean up. Guard: assert the str;ldr-different-reg pattern is gone on an lvm fixture. - P2 —
cset;cbnz/cbz→b.ccfusion (pass_combine.c/pass_jump.c). Broadest cross-file win (yyjson 438, lapi 27, lvm/lz4/sqlite). FuseIR_CONDBRof a single-useIR_CMPintoIR_CMP_BRANCH; inverse of the existing cg_ir_lower fusion. - P3 — widen
aa_set_byteszero-init (aa64/native.c). ~25% of cjson's gap; helps miniz. Emitstr xzr/stp q0aligned runs (orbl _memset) instead of per-bytestrb. Self-contained expander change. - P4 — drop yyjson double-cset + redundant
sxtb-after-ldrsb(pass_combine.c/combine_exts). ~1100 + ~186 insns on yyjson. Same-block last-def peepholes; the sxtb-drop is the signed mirror of the landed ZEXT-of-load fold. - P5 — constant-operand div/mul fold + same-block
LOAD_IMMconst tracker (pass_simplify.c). Fixes lparser being larger than clang (the SIZE_MAX guard) + sqlite's 108 const-divide sites. A bounded per-block reg→value map feeding the existing BO_UDIV/SDIV folds. - P6 — single-use copy coalescing into convert/store/call-arg/return consumers
(
pass_combine.c) andsxtw-index-into-addressing-mode (pass_addr_fold.c). Themovand standalone-sxtwsurplus across the value-heavy files; both reuse existing CombineCtx/addr-fold machinery.
Definition of done per item: measurable __TEXT/instruction reduction on the
affected corpus files (via scripts/o1_quality.sh), sqlite -O1 compile time
unchanged (linearity held — the §8 synthetic sweep), all correctness gates green
(make test-opt test-toy, ecosystem -O0/-O1 vs clang, smoke-x64/rv64), and a
test/opt/ structural disasm guard added.
Sequencing note: P1 + P3 are the two biggest size levers and touch disjoint files (
pass_combine.cvsaa64/native.c) → parallelizable. P2/P4/P5/P6 sharepass_combine.c/pass_simplify.c→ one owner, land + re-measure one at a time (gate = correctness, not byte-identity). The true ceiling on lvm/sqlite/miniz/lz4 remains the SSA register allocator (§3) — these linear items close the local waste, not the over-spilling.