kit

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

pass_combine.c (104969B)


      1 #include <kit/cg.h>
      2 #include <stdint.h>
      3 #include <string.h>
      4 
      5 #include "cg/ir_eval.h"
      6 #include "core/arena.h"
      7 #include "opt/opt_internal.h"
      8 
      9 /* O1 combine, MIR-shaped (see mir-gen.c:8808-9146). One forward pass per BB
     10  * maintains last-def / last-mem-def tracking; per-BB fixpoint loop iterates
     11  * until no fold fires. Rewrites in this file:
     12  *
     13  *   1. Substitute producer source into consumer operand
     14  *      (IR_COPY / IR_LOAD_IMM / IR_CONVERT producer; into register slot,
     15  *       indirect base, or indirect index).
     16  *   2. Address-mode synthesis (IR_BINOP IADD/ISHL producer; into OPK_INDIRECT
     17  *       base/index/scale/ofs).
     18  *   3. Sink producer into single-use IR_COPY destination.
     19  *   4. combine_exts: fold ext-of-ext chains with size+signedness rules.
     20  *
     21  * Spill compaction (store-then-reload, etc.) and self-copy removal continue
     22  * to run in opt_combine_compact_block, unchanged. */
     23 
     24 /* ---- shared helpers (operand inspection / counting) ---- */
     25 
     26 static int same_reg_operand(const Operand* a, const Operand* b) {
     27   return a->kind == OPK_REG && b->kind == OPK_REG && a->cls == b->cls &&
     28          a->v.reg == b->v.reg;
     29 }
     30 
     31 static int producer_def_aliases_source(const Operand* def, const Operand* src) {
     32   return def && src && same_reg_operand(def, src);
     33 }
     34 
     35 static int frame_slot_is_spill(Func* f, FrameSlot fs) {
     36   if (fs == FRAME_SLOT_NONE || fs > f->nframe_slots) return 0;
     37   return f->frame_slots[fs - 1u].kind == FS_SPILL;
     38 }
     39 
     40 typedef enum SpillAccessKind {
     41   SPILL_ACCESS_NONE = 0,
     42   SPILL_ACCESS_LOAD,
     43   SPILL_ACCESS_STORE,
     44 } SpillAccessKind;
     45 
     46 typedef struct SpillAccess {
     47   SpillAccessKind kind;
     48   FrameSlot slot;
     49   u32 size;
     50   u16 addr_space;
     51   const Operand* value;
     52 } SpillAccess;
     53 
     54 /* W6 address-offset profitability gate: is `ofs` an offset the target can keep
     55  * inline in a load/store (so folding it into an indirect whose base producer
     56  * survives does not force an emit-time address build)? Conservatively keyed on
     57  * the target's NATIVE_IMM_ADDR_OFFSET legality. With no target oracle, treat
     58  * only a zero offset as safe. */
     59 static int combine_offset_fold_ok(NativeTarget* target, KitCgTypeId type,
     60                                   i64 ofs) {
     61   if (ofs == 0) return 1;
     62   if (!target || !target->imm_legal) return 0;
     63   return target->imm_legal(target, NATIVE_IMM_ADDR_OFFSET, 0, type, ofs);
     64 }
     65 
     66 /* Normalize location-MIR stack copies to the one spill-access vocabulary
     67  * consumed by compaction and stack DSE. Spill traffic exists only after
     68  * allocation and deliberately uses IR_COPY + OPK_STACK: recognizing semantic
     69  * IR_LOAD/IR_STORE + OPK_LOCAL here would collapse a spill value back into an
     70  * addressable source-language local and violate the HIR/MIR boundary. */
     71 static int direct_spill_access(Func* f, const Inst* in, SpillAccess* out) {
     72   SpillAccess access;
     73   const Operand* stack = NULL;
     74   memset(&access, 0, sizeof access);
     75   if (!f || !in || (IROp)in->op != IR_COPY || in->nopnds < 2u ||
     76       !in->opnds)
     77     return 0;
     78 
     79   if (in->opnds[0].kind == OPK_STACK &&
     80       in->opnds[1].kind != OPK_STACK) {
     81     access.kind = SPILL_ACCESS_STORE;
     82     stack = &in->opnds[0];
     83     access.value = &in->opnds[1];
     84   } else if (in->opnds[1].kind == OPK_STACK &&
     85              in->opnds[0].kind != OPK_STACK) {
     86     access.kind = SPILL_ACCESS_LOAD;
     87     stack = &in->opnds[1];
     88     access.value = &in->opnds[0];
     89   } else {
     90     return 0;
     91   }
     92 
     93   if (!frame_slot_is_spill(f, stack->v.frame_slot)) return 0;
     94   {
     95     u64 size = cg_type_size(f->c, stack->type);
     96     if (!size || size > UINT32_MAX) return 0;
     97     access.slot = stack->v.frame_slot;
     98     access.size = (u32)size;
     99   }
    100   access.addr_space = 0u;
    101 
    102   if (!access.size || access.slot == FRAME_SLOT_NONE || !access.value)
    103     return 0;
    104   if (out) *out = access;
    105   return 1;
    106 }
    107 
    108 static int same_spill_shape(const SpillAccess* a, const SpillAccess* b) {
    109   return a && b && a->slot == b->slot && a->size == b->size &&
    110          a->addr_space == b->addr_space;
    111 }
    112 
    113 static int same_phys_reg(const Operand* a, const Operand* b) {
    114   return a && b && a->kind == OPK_REG && b->kind == OPK_REG &&
    115          a->cls == b->cls && a->v.reg == b->v.reg;
    116 }
    117 
    118 static int operand_uses_phys_reg(const Operand* op, const Operand* r) {
    119   if (!op || !r || r->kind != OPK_REG) return 0;
    120   if (op->kind == OPK_REG)
    121     return op->cls == r->cls && op->v.reg == r->v.reg ? 1 : 0;
    122   if (op->kind == OPK_INDIRECT) {
    123     if (r->cls != RC_INT) return 0;
    124     return (op->v.ind.base_kind == OPT_INDIRECT_REG &&
    125             op->v.ind.base == r->v.reg) ||
    126            (op->v.ind.index_kind == OPT_INDIRECT_REG &&
    127             op->v.ind.index != (Reg)REG_NONE &&
    128             op->v.ind.index == r->v.reg);
    129   }
    130   return 0;
    131 }
    132 
    133 typedef struct CombinePhysRegEffect {
    134   u8 uses;
    135   u8 defines;
    136   u8 clobbers;
    137 } CombinePhysRegEffect;
    138 
    139 static CombinePhysRegEffect inst_phys_reg_effect(Func* f, const Inst* in,
    140                                                  const Operand* reg) {
    141   OptRegEffects effects;
    142   CombinePhysRegEffect result = {0};
    143   if (!reg || reg->kind != OPK_REG || reg->cls >= OPT_REG_CLASSES ||
    144       reg->v.reg >= OPT_MAX_HARD_REGS)
    145     return result;
    146   opt_inst_reg_effects(f, in, &effects);
    147   result.uses = effects.use_count[reg->cls][reg->v.reg];
    148   result.defines =
    149       (u8)((effects.defs.cls[reg->cls] & (1u << reg->v.reg)) != 0u);
    150   result.clobbers =
    151       (u8)((effects.clobbers.cls[reg->cls] & (1u << reg->v.reg)) != 0u);
    152   return result;
    153 }
    154 
    155 static int hard_set_has_reg(const OptHardRegSet* set, const Operand* reg) {
    156   if (!set || !reg || reg->kind != OPK_REG ||
    157       reg->cls >= OPT_REG_CLASSES || reg->v.reg >= OPT_MAX_HARD_REGS)
    158     return 0;
    159   return (set->cls[reg->cls] & (1u << reg->v.reg)) != 0;
    160 }
    161 
    162 static int inst_kills_phys_reg(Func* f, const Inst* in, const Operand* reg) {
    163   OptHardRegSet kills;
    164   opt_inst_reg_kills(f, in, &kills);
    165   return hard_set_has_reg(&kills, reg);
    166 }
    167 
    168 /* True if `in` may write to memory. Used to invalidate memory-reading
    169  * producers (IR_LOAD) when the consumer is past an intervening write. */
    170 static int inst_writes_memory(const Inst* in) {
    171   switch ((IROp)in->op) {
    172     case IR_COPY:
    173       return in->nopnds >= 1u && in->opnds &&
    174              in->opnds[0].kind == OPK_STACK;
    175     case IR_STORE:
    176     case IR_AGG_COPY:
    177     case IR_AGG_SET:
    178     case IR_BITFIELD_STORE:
    179     case IR_ATOMIC_STORE:
    180     case IR_ATOMIC_RMW:
    181     case IR_ATOMIC_CAS:
    182     case IR_CALL:
    183     case IR_ASM_BLOCK:
    184     case IR_INTRINSIC:
    185       return 1;
    186     default:
    187       return 0;
    188   }
    189 }
    190 
    191 static int inst_reads_memory(const Inst* in) {
    192   switch ((IROp)in->op) {
    193     case IR_COPY:
    194       return in->nopnds >= 2u && in->opnds &&
    195              in->opnds[1].kind == OPK_STACK;
    196     case IR_LOAD:
    197     case IR_BITFIELD_LOAD:
    198     case IR_ATOMIC_LOAD:
    199       return 1;
    200     default:
    201       return 0;
    202   }
    203 }
    204 
    205 /* ---- substitution-slot whitelist (operand positions that accept a register
    206  * substitute, an immediate substitute, or are valid for ext-of-ext folding)
    207  * ---- */
    208 
    209 typedef enum SubstKind {
    210   SK_REG, /* register-to-register (IR_COPY producer) */
    211   SK_IMM, /* register-to-immediate (IR_LOAD_IMM producer) */
    212   SK_CV,  /* register-to-register through identical convert (IR_CONVERT prod) */
    213 } SubstKind;
    214 
    215 /* Returns 1 if the given operand-index `idx` of `in` is foldable for `kind`.
    216  * SK_REG / SK_CV: register substitution slots. SK_IMM: immediate substitution
    217  * slots. */
    218 static int combine_subst_slot(const Inst* in, u32 idx, SubstKind kind,
    219                               int copy_imm_ok) {
    220   switch ((IROp)in->op) {
    221     case IR_COPY:
    222       /* Normally IR_COPY stays register-to-register so that, after coalescing
    223        * assigns src and dst the same hard reg, it becomes a self-copy combine
    224        * removes. The O1 path never coalesces, so folding the immediate
    225        * (copy_imm_ok) collapses `load_imm rT,k; copy rD,rT` into `copy rD,#k`,
    226        * which the emit path turns into a single `load_imm rD,k`. */
    227       return (kind != SK_IMM || copy_imm_ok) && idx == 1;
    228     case IR_UNOP:
    229       return kind != SK_IMM && idx == 1;
    230     case IR_CONVERT:
    231       return kind != SK_IMM && idx == 1;
    232     case IR_BINOP:
    233     case IR_CMP:
    234       return idx == 1 || idx == 2;
    235     case IR_CMP_BRANCH:
    236       return idx == 0 || idx == 1;
    237     case IR_CONDBR:
    238       return kind != SK_IMM && idx == 0;
    239     case IR_STORE:
    240       /* opnds[0] is the address; register subst into the indirect base/index
    241        * is handled separately. The data slot at idx==1 accepts reg or imm. */
    242       return idx == 1;
    243     case IR_LOAD:
    244     case IR_ATOMIC_LOAD:
    245     case IR_ATOMIC_RMW:
    246     case IR_BITFIELD_LOAD:
    247     case IR_BITFIELD_STORE:
    248     case IR_AGG_COPY:
    249     case IR_AGG_SET:
    250       /* OPK_INDIRECT base/index substitution is handled inside
    251        * subst_consumer_operands; these slots take no direct OPK_REG. */
    252       return 0;
    253     case IR_ALLOCA:
    254       return kind != SK_IMM && idx == 1;
    255     default:
    256       return 0;
    257   }
    258 }
    259 
    260 /* ---- per-BB tracking context ---- */
    261 
    262 /* W1a (O1.md): local frame-address `sub`-CSE. A bounded ring of the most
    263  * recently produced frame-/global-address values in this BB so a back-to-back
    264  * `IR_ADDR_OF rD, <same addr>` recompute (which lowers to a re-`sub xN,x29,#k`
    265  * frame build, or an `adrp;add`/GOT global build) can be rewritten to an
    266  * `IR_COPY` off the still-live earlier producer. Bounded slot count keeps the
    267  * scan O(1) per inst → the pass stays linear. */
    268 enum { COMBINE_ADDR_CSE_SLOTS = 8 };
    269 
    270 typedef struct AddrCseEntry {
    271   i32 inst_idx;      /* producing IR_ADDR_OF index in this BB, -1 if empty */
    272   Operand dst;       /* the produced address register (OPK_REG) */
    273   Operand addr;      /* the address operand (OPK_LOCAL / OPK_GLOBAL) */
    274 } AddrCseEntry;
    275 
    276 /* W5 (O1.md): local (same-block) redundant-load + pure-compute CSE. Two small
    277  * most-recent-wins rings:
    278  *
    279  *  - LOAD ring: the most-recently-loaded `{address, result, MemAccess}` and
    280  *    the register that received the value. A later plain (non-observable,
    281  *    non-bit-field) IR_LOAD of the IDENTICAL semantic shape, with the producer
    282  *    register still live and NO intervening memory write, is rewritten to an
    283  *    IR_COPY off the producer. Aliasing is the whole game: ANY memory write
    284  *    (inst_writes_memory: store/aggregate/atomic/call/asm/intrinsic) forgets
    285  *    the entire load ring in O(1), so a may-aliasing store between the two
    286  *    loads always preserves the reload. Volatile/atomic loads
    287  *    (opt_mem_observable) are never recorded or reused.
    288  *
    289  *  - COMPUTE ring: the most-recent pure IR_BINOP/IR_UNOP `{op, mode, result,
    290  *    operands}` and its destination register. A later identical pure compute
    291  *    whose inputs are unchanged since (and whose producer is still live) is
    292  *    rewritten to an IR_COPY off the producer. Pure computes touch no memory,
    293  *    so only register redefinitions/clobbers invalidate them (tracked via
    294  *    ctx->last_def, which a clobber barrier bumps for every register).
    295  *
    296  * Both rings are bounded → O(1) per inst → the pass stays linear. Each rewrite
    297  * is the broader, map-based same-block form distinct from the adjacent
    298  * store/load pairs opt_combine_compact_block already coalesces. */
    299 enum { COMBINE_LOAD_CSE_SLOTS = 8 };
    300 enum { COMBINE_COMPUTE_CSE_SLOTS = 8 };
    301 
    302 typedef struct LoadCseEntry {
    303   i32 inst_idx;  /* producing IR_LOAD index in this BB, -1 if empty */
    304   Operand dst;   /* the register that received the loaded value (OPK_REG) */
    305   Operand addr;  /* the load's address operand (OPK_INDIRECT/LOCAL/GLOBAL) */
    306   KitCgTypeId result_type;
    307   MemAccess mem;
    308 } LoadCseEntry;
    309 
    310 typedef struct ComputeCseEntry {
    311   i32 inst_idx; /* producing IR_BINOP/IR_UNOP index in this BB, -1 if empty */
    312   Operand dst;  /* the result register (OPK_REG) */
    313   u16 op;       /* IROp (IR_BINOP / IR_UNOP) */
    314   u16 flags;    /* per-instruction semantic mode */
    315   KitCgTypeId result_type;
    316   i64 sub;      /* extra.imm = BinOp/UnOp selector */
    317   Operand a;    /* operand 1 */
    318   Operand b;    /* operand 2 (kind == 0 sentinel for a unary compute) */
    319 } ComputeCseEntry;
    320 
    321 typedef struct CombineCtx {
    322   Func* f;
    323   Block* bl;
    324   NativeTarget* target; /* W6 immediate-legality oracle (may be NULL) */
    325   const OptHardBlockLive* hard_live;
    326   /* Index of the most recent inst in this BB that defined (cls, reg);
    327    * -1 means no definition seen this BB. */
    328   i32 last_def[OPT_REG_CLASSES][OPT_MAX_HARD_REGS];
    329   i32 last_mem_def;
    330   /* W1a addr-of CSE ring (most-recent-wins). */
    331   AddrCseEntry addr_cse[COMBINE_ADDR_CSE_SLOTS];
    332   u32 addr_cse_next;
    333   /* W5 local same-block load + pure-compute CSE rings (most-recent-wins). */
    334   LoadCseEntry load_cse[COMBINE_LOAD_CSE_SLOTS];
    335   u32 load_cse_next;
    336   ComputeCseEntry compute_cse[COMBINE_COMPUTE_CSE_SLOTS];
    337   u32 compute_cse_next;
    338   /* W6 cmp-immediate tracking: the constant currently held by each integer
    339    * hard register, if known. const_valid is a per-RC_INT-reg bit set when a
    340    * reaching IR_LOAD_IMM defined that register and nothing since has redefined
    341    * or clobbered it (incl. call/asm/machine clobbers, tracked precisely via
    342    * opt_inst_reg_effects). Lets a `cmp wN, wM` whose wM holds a small constant
    343    * fold to `cmp wN, #k`, even across a call that preserves wM (a callee-saved
    344    * reg). Integer-only: cmp immediates are integer. */
    345   i64 const_val[OPT_MAX_HARD_REGS];
    346   u32 const_valid;
    347   int block_change_p;
    348 } CombineCtx;
    349 
    350 static void ctx_reset(CombineCtx* ctx) {
    351   memset(ctx->last_def, 0xff, sizeof ctx->last_def); /* all -1 */
    352   ctx->last_mem_def = -1;
    353   for (u32 k = 0; k < COMBINE_ADDR_CSE_SLOTS; ++k)
    354     ctx->addr_cse[k].inst_idx = -1;
    355   ctx->addr_cse_next = 0;
    356   for (u32 k = 0; k < COMBINE_LOAD_CSE_SLOTS; ++k)
    357     ctx->load_cse[k].inst_idx = -1;
    358   ctx->load_cse_next = 0;
    359   for (u32 k = 0; k < COMBINE_COMPUTE_CSE_SLOTS; ++k)
    360     ctx->compute_cse[k].inst_idx = -1;
    361   ctx->compute_cse_next = 0;
    362   ctx->const_valid = 0;
    363   ctx->block_change_p = 0;
    364 }
    365 
    366 static void ctx_record_mask(CombineCtx* ctx, u8 cls, u32 mask, i32 i) {
    367   if (cls >= OPT_REG_CLASSES) return;
    368   while (mask) {
    369     Reg r = (Reg)__builtin_ctz(mask);
    370     ctx->last_def[cls][r] = i;
    371     mask &= mask - 1u;
    372   }
    373 }
    374 
    375 /* Record the canonical explicit definitions and implicit clobbers after one
    376  * instruction. Calls and asm no longer erase every producer: only the ABI or
    377  * target-declared registers are invalidated. The driver shares this instruction
    378  * effect with constant tracking, so the hot forward walk scans operands once. */
    379 static void ctx_record(CombineCtx* ctx, const Inst* in,
    380                        const OptHardRegSet* kills, i32 i) {
    381   if (inst_writes_memory(in)) ctx->last_mem_def = i;
    382   for (u32 cls = 0; cls < OPT_REG_CLASSES; ++cls)
    383     ctx_record_mask(ctx, (u8)cls, kills->cls[cls], i);
    384 }
    385 
    386 /* Lookup the producer of (cls, reg) in this BB, if any. Returns -1 if no
    387  * definer has been seen yet in this BB. */
    388 static i32 ctx_producer_of(const CombineCtx* ctx, u8 cls, Reg reg) {
    389   if (reg >= OPT_MAX_HARD_REGS || cls >= OPT_REG_CLASSES) return -1;
    390   return ctx->last_def[cls][reg];
    391 }
    392 
    393 /* Does (cls, reg) get redefined strictly after `since_idx` (exclusive) in this
    394  * BB, given current ctx state? We use last_def: if last_def[cls][reg] >
    395  * since_idx, then yes. (since_idx is typically the producer's index.) */
    396 static int ctx_def_changed_since(const CombineCtx* ctx, u8 cls, Reg reg,
    397                                  i32 since_idx) {
    398   if (reg >= OPT_MAX_HARD_REGS || cls >= OPT_REG_CLASSES) return 0;
    399   return ctx->last_def[cls][reg] > since_idx;
    400 }
    401 
    402 static i32 ctx_prev_def_before(const CombineCtx* ctx, const Operand* reg,
    403                                i32 before_idx) {
    404   if (!reg || reg->kind != OPK_REG) return -1;
    405   for (i32 j = before_idx - 1; j >= 0; --j) {
    406     const Inst* prev = &ctx->bl->insts[j];
    407     if (inst_kills_phys_reg(ctx->f, prev, reg)) return j;
    408   }
    409   return -1;
    410 }
    411 
    412 static void ctx_restore_removed_def(CombineCtx* ctx, const Operand* reg,
    413                                     i32 removed_idx) {
    414   if (!reg || reg->kind != OPK_REG) return;
    415   if (reg->cls >= OPT_REG_CLASSES || reg->v.reg >= OPT_MAX_HARD_REGS) return;
    416   if (ctx->last_def[reg->cls][reg->v.reg] == removed_idx)
    417     ctx->last_def[reg->cls][reg->v.reg] =
    418         ctx_prev_def_before(ctx, reg, removed_idx);
    419 }
    420 
    421 /* ---- forward-scan use accounting (used for single-use checks) ---- */
    422 
    423 /* Count phys uses of `def` within the live range starting just after
    424  * `prod_idx`. The live range ends at the first inst that redefines `def`'s
    425  * physical register or that is a clobber barrier (CALL/ASM/INTRINSIC); uses
    426  * after that point belong to a different live range of the same physreg and
    427  * are irrelevant to this producer.
    428  *
    429  * `*killed_in_block_out` is set non-zero when the live range terminates
    430  * inside the block (caller may then skip the cross-block live-out check).
    431  *
    432  * Without this live-range scoping, reuse of a scratch physreg later in the
    433  * block (`sxtw x12, ...; mov x13, x12; mov x12, ...`) makes every fold look
    434  * multi-use and combine rejects almost everything. */
    435 static int count_uses_in_live_range(Func* f, const Block* bl, i32 prod_idx,
    436                                     const Operand* def,
    437                                     int* killed_in_block_out) {
    438   int n = 0;
    439   int killed = 0;
    440   for (i32 i = prod_idx + 1; i < (i32)bl->ninsts; ++i) {
    441     const Inst* in = &bl->insts[i];
    442     CombinePhysRegEffect effect = inst_phys_reg_effect(f, in, def);
    443     n += effect.uses;
    444     if (effect.defines || effect.clobbers) {
    445       killed = 1;
    446       break;
    447     }
    448   }
    449   if (killed_in_block_out) *killed_in_block_out = killed;
    450   return n;
    451 }
    452 
    453 static int use_after_clobber_before_redef(Func* f, const Block* bl,
    454                                           i32 prod_idx, const Operand* def) {
    455   int saw_clobber = 0;
    456   for (i32 i = prod_idx + 1; i < (i32)bl->ninsts; ++i) {
    457     const Inst* in = &bl->insts[i];
    458     CombinePhysRegEffect effect = inst_phys_reg_effect(f, in, def);
    459     if (saw_clobber && effect.uses) return 1;
    460     if (effect.defines) return 0;
    461     if (effect.clobbers) saw_clobber = 1;
    462   }
    463   return 0;
    464 }
    465 
    466 /* ---- ConvKind helpers (for combine_exts) ---- */
    467 
    468 static u32 builtin_int_bytes(KitCgTypeId t) {
    469   KitCgBuiltinType b;
    470   if (t == KIT_CG_TYPE_NONE || t > (KitCgTypeId)KIT_CG_BUILTIN_COUNT) return 0;
    471   b = (KitCgBuiltinType)(t - 1u);
    472   switch (b) {
    473     case KIT_CG_BUILTIN_BOOL:
    474     case KIT_CG_BUILTIN_I8:
    475       return 1;
    476     case KIT_CG_BUILTIN_I16:
    477       return 2;
    478     case KIT_CG_BUILTIN_I32:
    479       return 4;
    480     case KIT_CG_BUILTIN_I64:
    481       return 8;
    482     case KIT_CG_BUILTIN_I128:
    483       return 16;
    484     default:
    485       return 0;
    486   }
    487 }
    488 
    489 /* For an IR_CONVERT inst, decode (src_bytes, dst_bytes, sign_p). Returns 0
    490  * if this isn't an integer ext convert (CV_SEXT or CV_ZEXT). */
    491 static int ext_params(const Inst* in, u32* src_bytes_out, u32* dst_bytes_out,
    492                       int* sign_p_out) {
    493   if ((IROp)in->op != IR_CONVERT || in->nopnds < 2) return 0;
    494   ConvKind k = (ConvKind)in->extra.imm;
    495   if (k != CV_SEXT && k != CV_ZEXT) return 0;
    496   u32 sb = builtin_int_bytes(in->opnds[1].type);
    497   u32 db = builtin_int_bytes(in->opnds[0].type);
    498   if (!sb || !db || db < sb) return 0;
    499   *src_bytes_out = sb;
    500   *dst_bytes_out = db;
    501   *sign_p_out = (k == CV_SEXT);
    502   return 1;
    503 }
    504 
    505 /* Width in bytes of a scalar integer or pointer type (1..8), else 0. Mirrors
    506  * pass_simplify's simplify_width: builtin ints decode without the compiler,
    507  * pointers fall back to the type-size query. */
    508 static u32 combine_scalar_width_bytes(Func* f, KitCgTypeId t) {
    509   u32 b = builtin_int_bytes(t);
    510   if (b) return b > 8u ? 0u : b;
    511   if (f->c && kit_cg_type_kind((KitCompiler*)f->c, t) == KIT_CG_TYPE_PTR) {
    512     u64 sz = kit_cg_type_size((KitCompiler*)f->c, t);
    513     if (sz && sz <= 8u) return (u32)sz;
    514   }
    515   return 0;
    516 }
    517 
    518 /* Compute the constant produced by applying an integer/pointer convert `k`
    519  * (src width `sb`, dst width `db`, both BYTES) to immediate `imm`. Returns 0
    520  * for kinds that aren't a bit-preserving integer/pointer move (the float
    521  * conversions reinterpret the value and must not be folded this way). The
    522  * materialized-constant model: a load_imm puts (u64)imm into a register, so a
    523  * widening move/zext keeps the low `sb` bits and a trunc/narrowing keeps the
    524  * low `db` bits.
    525  *
    526  * The arithmetic is the shared cg/ir_eval convert core (in BITS); combine's
    527  * byte widths convert at the boundary. combine's BITCAST is the bit-preserving
    528  * register move (== ZEXT here, no equal-width requirement), so it maps to
    529  * CV_ZEXT rather than the shared eval's stricter equal-width BITCAST. */
    530 static int const_convert_value(ConvKind k, i64 imm, u32 sb, u32 db, i64* out) {
    531   if (!sb || !db) return 0;
    532   if (k == CV_BITCAST) k = CV_ZEXT;
    533   return kit_ir_eval_convert(k, sb * 8u, db * 8u, imm, out);
    534 }
    535 
    536 /* ---- producer-retarget legality (for sink rewrite) ---- */
    537 
    538 /* combine retargets the destination of a commutative producer, so it includes
    539  * the FP commutatives (FADD/FMUL) on top of the shared integer set. */
    540 static int binop_is_commutative(BinOp op) {
    541   return kit_ir_binop_is_commutative_int(op) || op == BO_FADD || op == BO_FMUL;
    542 }
    543 
    544 /* Is `producer` an op whose destination register can be safely retargeted
    545  * without backend consultation? Excludes ops with implicit destination
    546  * constraints, ABI pinning, multi-def outputs, or aux-driven dst layout. */
    547 static int producer_retargetable_op(IROp op) {
    548   switch (op) {
    549     case IR_BINOP:
    550     case IR_UNOP:
    551     case IR_LOAD_IMM:
    552     case IR_LOAD_CONST:
    553     case IR_LOAD:
    554     case IR_CONVERT:
    555     case IR_CMP:
    556     case IR_ADDR_OF:
    557     case IR_LOAD_LABEL_ADDR:
    558       return 1;
    559     default:
    560       return 0;
    561   }
    562 }
    563 
    564 /* Can `producer`'s destination be retargeted to `new_dst` without violating
    565  * IR shape? For binop, may signal that a commutative-swap is needed. */
    566 static int retarget_producer_legal(Inst* producer, const Operand* new_dst,
    567                                    int* swap_binop) {
    568   *swap_binop = 0;
    569   if (!new_dst || new_dst->kind != OPK_REG) return 0;
    570   if (producer->nopnds < 1 || producer->opnds[0].kind != OPK_REG) return 0;
    571   if (producer->opnds[0].cls != new_dst->cls) return 0;
    572   if (producer->opnds[0].type != new_dst->type) return 0;
    573 
    574   switch ((IROp)producer->op) {
    575     case IR_LOAD_IMM:
    576     case IR_LOAD_CONST:
    577     case IR_LOAD_LABEL_ADDR:
    578     case IR_ADDR_OF:
    579       /* Single-defining ops with no register source to alias against. */
    580       return 1;
    581     case IR_UNOP:
    582     case IR_CONVERT:
    583     case IR_LOAD:
    584       return 1;
    585     case IR_CMP:
    586       return 1;
    587     case IR_BINOP: {
    588       if (producer->nopnds < 3) return 0;
    589       int dst_is_lhs = operand_uses_phys_reg(&producer->opnds[1], new_dst);
    590       int dst_is_rhs = operand_uses_phys_reg(&producer->opnds[2], new_dst);
    591       if (!dst_is_lhs && !dst_is_rhs) return 1;
    592       if (dst_is_lhs) return 1;
    593       if (binop_is_commutative((BinOp)producer->extra.imm)) {
    594         *swap_binop = 1;
    595         return 1;
    596       }
    597       return 0;
    598     }
    599     default:
    600       return 0;
    601   }
    602 }
    603 
    604 static int first_return_reg(Func* f, u8 cls, Reg* out) {
    605   if (!f || cls >= OPT_REG_CLASSES) return 0;
    606   u32 mask = f->opt_ret_regs[cls];
    607   for (Reg r = 0; r < 32; ++r) {
    608     if (mask & (1u << r)) {
    609       *out = r;
    610       return 1;
    611     }
    612   }
    613   return 0;
    614 }
    615 
    616 static int reg_is_emit_temp(Func* f, u8 cls, Reg reg) {
    617   if (!f || cls >= OPT_REG_CLASSES || reg >= OPT_MAX_HARD_REGS) return 0;
    618   for (u32 i = 0; i < f->emit_temp_reg_count[cls]; ++i)
    619     if (f->emit_temp_regs[cls][i] == reg) return 1;
    620   return 0;
    621 }
    622 
    623 static int ret_scalar_storage(CGABIValue* v, Operand** out) {
    624   if (!v || v->storage.kind != OPK_REG) return 0;
    625   if (v->nparts > 1) return 0;
    626   *out = &v->storage;
    627   return 1;
    628 }
    629 
    630 /* ---- Rewrite 1: substitute producer source into uses ---- */
    631 
    632 /* Retarget a direct register use through a physical copy.  The register is a
    633  * location; the rest of the operand belongs to the consumer and describes how
    634  * that location is interpreted there.  In particular, same-register-class
    635  * conversions can become IR_COPY, so replacing the whole operand can narrow a
    636  * compare or ABI move.  It would also discard consumer riders such as `shift`.
    637  */
    638 static int retarget_reg_use(Operand* use, const Operand* def,
    639                             const Operand* src) {
    640   if (!use || !def || !src || use->kind != OPK_REG ||
    641       src->kind != OPK_REG || !same_phys_reg(use, def) ||
    642       use->cls != src->cls)
    643     return 0;
    644   use->v.reg = src->v.reg;
    645   return 1;
    646 }
    647 
    648 /* Substitute a register-valued address component without confusing a MIR
    649  * frame-slot id for a hard-register number.  Only physical identity follows a
    650  * copy: the component type belongs to the use and records how those bits are
    651  * interpreted there.  This matters when a same-width conversion became a
    652  * copy (pointer source consumed as an integer ptrdiff index, for example). */
    653 static int set_indirect_reg(Operand* ind, Reg old_reg, const Operand* src) {
    654   int n = 0;
    655   if (!ind || !src || src->kind != OPK_REG) return 0;
    656   if (ind->v.ind.base_kind == OPT_INDIRECT_REG &&
    657       ind->v.ind.base == old_reg) {
    658     ind->v.ind.base = src->v.reg;
    659     ++n;
    660   }
    661   if (ind->v.ind.index_kind == OPT_INDIRECT_REG &&
    662       ind->v.ind.index != (Reg)REG_NONE &&
    663       ind->v.ind.index == old_reg) {
    664     ind->v.ind.index = src->v.reg;
    665     ++n;
    666   }
    667   return n;
    668 }
    669 
    670 /* Substitute `def` -> `src` in a single aux register slot (no slot-index
    671  * whitelist; caller has already restricted to SK_REG and ensured src is a
    672  * register). Returns 1 if rewritten. */
    673 static int subst_one_aux_operand(Operand* op, const Operand* def,
    674                                  const Operand* src) {
    675   if (retarget_reg_use(op, def, src)) return 1;
    676   if (op->kind == OPK_INDIRECT && src->kind == OPK_REG && def->cls == RC_INT &&
    677       src->cls == RC_INT)
    678     return set_indirect_reg(op, def->v.reg, src) != 0;
    679   return 0;
    680 }
    681 
    682 static int subst_abivalue_uses(CGABIValue* v, const Operand* def,
    683                                const Operand* src) {
    684   int n = 0;
    685   n += subst_one_aux_operand(&v->storage, def, src);
    686   for (u32 i = 0; i < v->nparts; ++i)
    687     n += subst_one_aux_operand((Operand*)&v->parts[i].op, def, src);
    688   return n;
    689 }
    690 
    691 /* Walk the operands of consumer `in` and try to substitute uses of `def`
    692  * (producer's destination, OPK_REG) with `src` (producer's source, OPK_REG
    693  * or OPK_IMM). For OPK_INDIRECT operands, substitution into base/index is
    694  * only valid for OPK_REG `src`. Returns the number of operands actually
    695  * rewritten. */
    696 static int subst_consumer_operands(Inst* in, const Operand* def,
    697                                    const Operand* src, SubstKind kind,
    698                                    int copy_imm_ok) {
    699   int n = 0;
    700   for (u32 oi = 0; oi < in->nopnds; ++oi) {
    701     Operand* op = &in->opnds[oi];
    702     /* Direct OPK_REG substitution: requires the slot to be on the whitelist. */
    703     if (op->kind == OPK_REG && same_phys_reg(op, def) &&
    704         combine_subst_slot(in, oi, kind, copy_imm_ok)) {
    705       if (kind == SK_REG) {
    706         if (retarget_reg_use(op, def, src)) {
    707           ++n;
    708           continue;
    709         }
    710       } else {
    711         *op = *src;
    712         ++n;
    713         continue;
    714       }
    715     }
    716     /* Indirect base/index substitution: only OPK_REG src may land here. The
    717      * substitution only changes which register computes the address, not the
    718      * value being stored — safe for IR_STORE / IR_ATOMIC_STORE /
    719      * IR_BITFIELD_STORE / IR_AGG_COPY / IR_AGG_SET as well. */
    720     if (op->kind == OPK_INDIRECT && kind == SK_REG && src->kind == OPK_REG &&
    721         def->kind == OPK_REG && def->cls == RC_INT && src->cls == RC_INT)
    722       n += set_indirect_reg(op, def->v.reg, src);
    723   }
    724 
    725   /* Aux register uses: IR_CALL plan/desc args + callee. SK_REG only —
    726    * substituting an immediate or convert into an ABI-bound use would break
    727    * the call lowering. IR_RET is intentionally excluded: the return register
    728    * is live-out by ABI, so a producer copying into it stays alive even after
    729    * substituting val.storage, and emit_ret would emit a redundant second
    730    * move. */
    731   if (kind == SK_REG) {
    732     if ((IROp)in->op == IR_CALL) {
    733       IRCallAux* aux = (IRCallAux*)in->extra.aux;
    734       if (aux) {
    735         if (aux->use_plan_replay) {
    736           n += subst_one_aux_operand(&aux->plan.callee, def, src);
    737           for (u32 k = 0; k < aux->plan.nargs; ++k)
    738             n += subst_one_aux_operand(&aux->plan.args[k].src, def, src);
    739         } else {
    740           n += subst_one_aux_operand(&aux->desc.callee, def, src);
    741           for (u32 k = 0; k < aux->desc.nargs; ++k)
    742             n += subst_abivalue_uses((CGABIValue*)&aux->desc.args[k], def, src);
    743         }
    744       }
    745     }
    746   }
    747   return n;
    748 }
    749 
    750 /* Try to substitute the producer of (cls, reg) into uses of that register
    751  * in `in`. Returns 1 if at least one operand was rewritten. */
    752 static int try_substitute_for_reg(CombineCtx* ctx, Inst* in, i32 i, u8 cls,
    753                                   Reg reg) {
    754   i32 prod_idx = ctx_producer_of(ctx, cls, reg);
    755   if (prod_idx < 0 || prod_idx >= i) return 0;
    756   Inst* prod = &ctx->bl->insts[prod_idx];
    757   IROp pop = (IROp)prod->op;
    758   if (pop != IR_COPY && pop != IR_LOAD_IMM && pop != IR_CONVERT) return 0;
    759   if (prod->nopnds < 1 || prod->opnds[0].kind != OPK_REG) return 0;
    760   if (prod->opnds[0].cls != cls || prod->opnds[0].v.reg != reg) return 0;
    761 
    762   Operand def = prod->opnds[0];
    763   SubstKind kind;
    764   Operand src_op;
    765   memset(&src_op, 0, sizeof src_op);
    766   if (pop == IR_COPY) {
    767     if (prod->nopnds < 2 || prod->opnds[1].kind != OPK_REG) return 0;
    768     if (ctx_def_changed_since(ctx, prod->opnds[1].cls, prod->opnds[1].v.reg,
    769                               prod_idx))
    770       return 0;
    771     kind = SK_REG;
    772     src_op = prod->opnds[1];
    773   } else if (pop == IR_LOAD_IMM) {
    774     kind = SK_IMM;
    775     src_op.kind = OPK_IMM;
    776     src_op.cls = def.cls;
    777     src_op.type = def.type;
    778     src_op.v.imm = prod->extra.imm;
    779   } else { /* IR_CONVERT */
    780     if (prod->nopnds < 2 || prod->opnds[1].kind != OPK_REG) return 0;
    781     /* Fold only when consumer is also a convert with matching shape (the
    782      * broader ext-of-ext rules run in try_combine_exts). */
    783     if ((IROp)in->op != IR_CONVERT) return 0;
    784     if (in->nopnds < 2 || in->opnds[1].kind != OPK_REG) return 0;
    785     if (in->extra.imm != prod->extra.imm) return 0;
    786     if (in->opnds[1].type != prod->opnds[0].type) return 0;
    787     if (in->opnds[0].type != prod->opnds[0].type) return 0;
    788     if (ctx_def_changed_since(ctx, prod->opnds[1].cls, prod->opnds[1].v.reg,
    789                               prod_idx))
    790       return 0;
    791     kind = SK_CV;
    792     src_op = prod->opnds[1];
    793   }
    794 
    795   /* For a register-source IR_COPY this is local copy propagation: rewriting
    796    * one use of the copy's dest to its source is value-safe whenever the source
    797    * is unchanged between the copy and this consumer — already verified by the
    798    * ctx_def_changed_since check above (which treats CALL/ASM/INTRINSIC as a
    799    * clobber of every register). The copy itself is not removed here; if all its
    800    * uses fold away and it is not live-out, post-combine DCE deletes it. So no
    801    * single-use or cross-block live-out restriction is required, which lets the
    802    * O1 path collapse the multi-use reg->reg copy chains it would otherwise
    803    * leave behind (it never runs the O2 coalescer).
    804    *
    805    * The immediate / convert producers don't propagate a register value, so
    806    * they keep the stricter single-use + live-out gate: substituting them into
    807    * every use would duplicate a (possibly multi-instruction) materialization
    808    * rather than shorten a copy chain. */
    809   if (kind != SK_REG) {
    810     int killed = 0;
    811     int uses_total =
    812         count_uses_in_live_range(ctx->f, ctx->bl, prod_idx, &def, &killed);
    813     if (uses_total != 1) return 0;
    814     if (!killed && opt_block_live_out_has_phys_reg(ctx->f, ctx->hard_live,
    815                                                    ctx->bl->id, &def))
    816       return 0;
    817   }
    818 
    819   /* O1 (no coalescing) folds immediates into IR_COPY; O2 leaves the copy
    820    * register-to-register so coalescing + self-copy removal handles it. */
    821   int copy_imm_ok =
    822       ctx->f && (ctx->f->opt_o1_coalescing || !ctx->f->opt_coalesce_parent);
    823   int n = subst_consumer_operands(in, &def, &src_op, kind, copy_imm_ok);
    824   if (n > 0) {
    825     ctx->block_change_p = 1;
    826     return 1;
    827   }
    828   return 0;
    829 }
    830 
    831 /* Mark (cls, reg) as already-attempted for this consumer so the same producer
    832  * is not looked up twice when a register appears in multiple operand slots
    833  * (e.g. `[r4 + r4*4]`, or `add r1, r4, r4`). */
    834 static int seen_mark(u32 seen[OPT_REG_CLASSES], u8 cls, Reg reg) {
    835   if (cls >= OPT_REG_CLASSES || reg >= 32) return 1;
    836   u32 bit = 1u << reg;
    837   if (seen[cls] & bit) return 1;
    838   seen[cls] |= bit;
    839   return 0;
    840 }
    841 
    842 static void try_substitute_aux_operand(CombineCtx* ctx, Inst* in, i32 i,
    843                                        const Operand* op,
    844                                        u32 seen[OPT_REG_CLASSES], int* any) {
    845   if (op->kind == OPK_REG) {
    846     if (!seen_mark(seen, op->cls, op->v.reg))
    847       *any |= try_substitute_for_reg(ctx, in, i, op->cls, op->v.reg);
    848   } else if (op->kind == OPK_INDIRECT) {
    849     if (op->v.ind.base_kind == OPT_INDIRECT_REG &&
    850         op->v.ind.base != (Reg)REG_NONE &&
    851         !seen_mark(seen, RC_INT, op->v.ind.base))
    852       *any |= try_substitute_for_reg(ctx, in, i, RC_INT, op->v.ind.base);
    853     if (op->v.ind.index_kind == OPT_INDIRECT_REG &&
    854         op->v.ind.index != (Reg)REG_NONE &&
    855         !seen_mark(seen, RC_INT, op->v.ind.index))
    856       *any |= try_substitute_for_reg(ctx, in, i, RC_INT, op->v.ind.index);
    857   }
    858 }
    859 
    860 static void try_substitute_aux_abivalue(CombineCtx* ctx, Inst* in, i32 i,
    861                                         const CGABIValue* v,
    862                                         u32 seen[OPT_REG_CLASSES], int* any) {
    863   try_substitute_aux_operand(ctx, in, i, &v->storage, seen, any);
    864   for (u32 k = 0; k < v->nparts; ++k)
    865     try_substitute_aux_operand(ctx, in, i, &v->parts[k].op, seen, any);
    866 }
    867 
    868 /* Try to substitute producers into operand slots of `in`. Walks the direct
    869  * operands of `in` and looks up only the producers of registers actually
    870  * referenced (typically 2-3 per inst). Also walks IR_CALL aux register uses
    871  * (args + callee) so reg->reg copies feeding call args or the callee get
    872  * propagated. See subst_consumer_operands for why IR_RET is excluded. */
    873 static int try_substitute(CombineCtx* ctx, Inst* in, i32 i) {
    874   int any = 0;
    875   u32 seen[OPT_REG_CLASSES] = {0};
    876   for (u32 oi = 0; oi < in->nopnds; ++oi) {
    877     const Operand* op = &in->opnds[oi];
    878     try_substitute_aux_operand(ctx, in, i, op, seen, &any);
    879   }
    880   if ((IROp)in->op == IR_CALL) {
    881     IRCallAux* aux = (IRCallAux*)in->extra.aux;
    882     if (aux) {
    883       if (aux->use_plan_replay) {
    884         try_substitute_aux_operand(ctx, in, i, &aux->plan.callee, seen, &any);
    885         for (u32 k = 0; k < aux->plan.nargs; ++k)
    886           try_substitute_aux_operand(ctx, in, i, &aux->plan.args[k].src, seen,
    887                                      &any);
    888       } else {
    889         try_substitute_aux_operand(ctx, in, i, &aux->desc.callee, seen, &any);
    890         for (u32 k = 0; k < aux->desc.nargs; ++k)
    891           try_substitute_aux_abivalue(
    892               ctx, in, i, (const CGABIValue*)&aux->desc.args[k], seen, &any);
    893       }
    894     }
    895   }
    896   return any;
    897 }
    898 
    899 /* ---- Rewrite 2: address-mode synthesis ---- */
    900 
    901 /* Try to fold add/shl producers into one OPK_INDIRECT operand of `in`. */
    902 static int try_addr_synth_one_op(CombineCtx* ctx, Inst* in, i32 i,
    903                                  Operand* op) {
    904   (void)in;
    905   if (op->kind != OPK_INDIRECT) return 0;
    906   int any = 0;
    907 
    908   /* (a/c) base producer is IR_BINOP IADD. reg+reg synthesizes a base+index
    909    * pair (requires no existing index, sub-rule a); reg+imm folds the immediate
    910    * into ofs (sub-rule c, ok with or without an existing index). */
    911   if (op->v.ind.base_kind == OPT_INDIRECT_REG &&
    912       op->v.ind.base != (Reg)REG_NONE) {
    913     Reg b = op->v.ind.base;
    914     i32 prod_idx = ctx_producer_of(ctx, RC_INT, b);
    915     if (prod_idx >= 0 && prod_idx < i) {
    916       Inst* prod = &ctx->bl->insts[prod_idx];
    917       if ((IROp)prod->op == IR_BINOP && prod->nopnds == 3 &&
    918           (BinOp)prod->extra.imm == BO_IADD && prod->opnds[0].kind == OPK_REG &&
    919           prod->opnds[0].cls == RC_INT && prod->opnds[0].v.reg == b) {
    920         Operand prod_def = prod->opnds[0];
    921         /* Single-use within producer's live range (the only further use
    922          * before next redef/barrier). */
    923         int killed = 0;
    924         int uses_after = count_uses_in_live_range(ctx->f, ctx->bl, prod_idx,
    925                                                   &prod_def, &killed);
    926         int single_use =
    927             uses_after == 1 &&
    928             (killed || !opt_block_live_out_has_phys_reg(
    929                            ctx->f, ctx->hard_live, ctx->bl->id, &prod_def));
    930         Operand lhs = prod->opnds[1];
    931         Operand rhs = prod->opnds[2];
    932         int has_no_index =
    933             op->v.ind.index_kind == OPT_INDIRECT_REG &&
    934             op->v.ind.index == (Reg)REG_NONE;
    935         /* reg + reg: base = lhs, index = rhs, scale=0. Needs an empty index
    936          * slot — cannot stack two indices. This synthesizes a new index, a
    937          * structural change, so keep the single-use guard: with other live
    938          * uses the add must stay, and we'd add an index without retiring it. */
    939         if (single_use && has_no_index && lhs.kind == OPK_REG &&
    940             rhs.kind == OPK_REG && lhs.cls == RC_INT && rhs.cls == RC_INT &&
    941             opt_indirect_index_type_valid(ctx->f, rhs.type) &&
    942             !producer_def_aliases_source(&prod_def, &lhs) &&
    943             !producer_def_aliases_source(&prod_def, &rhs) &&
    944             !ctx_def_changed_since(ctx, RC_INT, lhs.v.reg, prod_idx) &&
    945             !ctx_def_changed_since(ctx, RC_INT, rhs.v.reg, prod_idx)) {
    946           op->v.ind.base = lhs.v.reg;
    947           op->v.ind.base_type = lhs.type;
    948           op->v.ind.index = rhs.v.reg;
    949           op->v.ind.index_type = rhs.type;
    950           op->v.ind.log2_scale = 0;
    951           any = 1;
    952         }
    953         /* (W6) reg + imm: base = lhs, fold imm k into ofs (`add xN,xM,#k;
    954          * ldr [xN,#j]` -> `ldr [xM,#k+j]`). This rewrites only this indirect's
    955          * base+offset; the producer's def of xN is untouched and stays valid
    956          * for any other uses, so the rewrite is value-correct regardless of use
    957          * count. The aliasing guards (lhs unchanged since the add) still apply
    958          * and are independent of use count.
    959          *
    960          * Use count gates *profitability*, not correctness: when the add is
    961          * single-use it becomes dead (DCE retires it) and folding any in-range
    962          * offset is a clear win. When the add survives (other live uses), we
    963          * only fold an offset the target can keep inline in the access
    964          * (combine_offset_fold_ok) — otherwise the surviving add plus an
    965          * out-of-reach offset would cost an extra emit-time address build. */
    966         else if (lhs.kind == OPK_REG && rhs.kind == OPK_IMM &&
    967                  lhs.cls == RC_INT &&
    968                  !producer_def_aliases_source(&prod_def, &lhs) &&
    969                  !ctx_def_changed_since(ctx, RC_INT, lhs.v.reg, prod_idx)) {
    970           i64 sum = (i64)op->v.ind.ofs + rhs.v.imm;
    971           if (sum >= INT32_MIN && sum <= INT32_MAX &&
    972               (single_use ||
    973                combine_offset_fold_ok(ctx->target, op->type, sum))) {
    974             op->v.ind.base = lhs.v.reg;
    975             op->v.ind.base_type = lhs.type;
    976             op->v.ind.ofs = (i32)sum;
    977             any = 1;
    978           }
    979         }
    980         /* (W6) imm + reg: base = rhs, fold imm into ofs (commutative IADD).
    981          * Same reasoning as the reg+imm case — pure base/offset rewrite. */
    982         else if (lhs.kind == OPK_IMM && rhs.kind == OPK_REG &&
    983                  rhs.cls == RC_INT &&
    984                  !producer_def_aliases_source(&prod_def, &rhs) &&
    985                  !ctx_def_changed_since(ctx, RC_INT, rhs.v.reg, prod_idx)) {
    986           i64 sum = (i64)op->v.ind.ofs + lhs.v.imm;
    987           if (sum >= INT32_MIN && sum <= INT32_MAX &&
    988               (single_use ||
    989                combine_offset_fold_ok(ctx->target, op->type, sum))) {
    990             op->v.ind.base = rhs.v.reg;
    991             op->v.ind.base_type = rhs.type;
    992             op->v.ind.ofs = (i32)sum;
    993             any = 1;
    994           }
    995         }
    996       }
    997     }
    998   }
    999 
   1000   /* (b) index producer is IR_BINOP ISHL reg, imm with scale=0. */
   1001   if (op->v.ind.index_kind == OPT_INDIRECT_REG &&
   1002       op->v.ind.index != (Reg)REG_NONE && op->v.ind.log2_scale == 0) {
   1003     Reg idx = op->v.ind.index;
   1004     i32 prod_idx = ctx_producer_of(ctx, RC_INT, idx);
   1005     if (prod_idx >= 0 && prod_idx < i) {
   1006       Inst* prod = &ctx->bl->insts[prod_idx];
   1007       if ((IROp)prod->op == IR_BINOP && prod->nopnds == 3 &&
   1008           (BinOp)prod->extra.imm == BO_SHL && prod->opnds[0].kind == OPK_REG &&
   1009           prod->opnds[0].cls == RC_INT && prod->opnds[0].v.reg == idx &&
   1010           prod->opnds[1].kind == OPK_REG && prod->opnds[1].cls == RC_INT &&
   1011           prod->opnds[2].kind == OPK_IMM) {
   1012         i64 sh = prod->opnds[2].v.imm;
   1013         if (sh >= 1 && sh <= 3) {
   1014           Operand prod_def = prod->opnds[0];
   1015           int killed = 0;
   1016           int uses_after = count_uses_in_live_range(ctx->f, ctx->bl, prod_idx,
   1017                                                     &prod_def, &killed);
   1018           /* `<= 2` allows the degenerate [base == index] case where the SHL
   1019            * dst appears twice in the same indirect; rewriting only the index
   1020            * still yields equivalent arithmetic when base also held the SHL
   1021            * dst (base unchanged: r*4; index rewritten: r_src*4; r*4 == r*4). */
   1022           if (uses_after >= 1 && uses_after <= 2 &&
   1023               (killed || !opt_block_live_out_has_phys_reg(
   1024                              ctx->f, ctx->hard_live, ctx->bl->id, &prod_def)) &&
   1025               !producer_def_aliases_source(&prod_def, &prod->opnds[1]) &&
   1026               !ctx_def_changed_since(ctx, RC_INT, prod->opnds[1].v.reg,
   1027                                      prod_idx)) {
   1028             op->v.ind.index = prod->opnds[1].v.reg;
   1029             op->v.ind.index_type = prod->opnds[1].type;
   1030             op->v.ind.log2_scale = (u8)sh;
   1031             any = 1;
   1032           }
   1033         }
   1034       }
   1035     }
   1036   }
   1037 
   1038   /* (L8) index producer is a single-use 32->64 widening convert (sxtw/uxtw):
   1039    * fold the extend into the addressing mode (`[Xbase, Wm, sxtw/uxtw #scale]`),
   1040    * one instruction instead of `sxtw xT,wS; add ...,lsl #scale; [reg]`. Runs
   1041    * after the ISHL fold above, so an `sxtw; lsl; [reg]` chain first collapses
   1042    * the shift into log2_scale and then this absorbs the sxtw producing that
   1043    * index. Gated behind the target capability: only a backend that emits the
   1044    * extended-register addressing form is handed an index_ext rider. */
   1045   if (op->v.ind.index_kind == OPT_INDIRECT_REG &&
   1046       op->v.ind.index != (Reg)REG_NONE &&
   1047       op->v.ind.index_ext == OPT_IDX_EXT_NONE && ctx->target &&
   1048       ctx->target->can_fold_extend_into_addr &&
   1049       ctx->target->can_fold_extend_into_addr(ctx->target)) {
   1050     Reg idx = op->v.ind.index;
   1051     i32 prod_idx = ctx_producer_of(ctx, RC_INT, idx);
   1052     if (prod_idx >= 0 && prod_idx < i) {
   1053       Inst* prod = &ctx->bl->insts[prod_idx];
   1054       u32 sb = 0, db = 0;
   1055       int sign_p = 0;
   1056       if (ext_params(prod, &sb, &db, &sign_p) && sb == 4u && db == 8u &&
   1057           prod->opnds[0].kind == OPK_REG && prod->opnds[0].cls == RC_INT &&
   1058           prod->opnds[0].v.reg == idx && prod->opnds[1].kind == OPK_REG &&
   1059           prod->opnds[1].cls == RC_INT) {
   1060         Operand prod_def = prod->opnds[0];
   1061         int killed = 0;
   1062         int uses_after = count_uses_in_live_range(ctx->f, ctx->bl, prod_idx,
   1063                                                   &prod_def, &killed);
   1064         /* `<= 2` mirrors the ISHL fold: tolerate the degenerate
   1065          * [base == index] aliasing where the convert dst appears twice. The
   1066          * convert source must be unchanged since the convert (so the W index
   1067          * names the same 32-bit value), and must not alias the convert dst. */
   1068         if (uses_after >= 1 && uses_after <= 2 &&
   1069             (killed || !opt_block_live_out_has_phys_reg(
   1070                            ctx->f, ctx->hard_live, ctx->bl->id, &prod_def)) &&
   1071             !producer_def_aliases_source(&prod_def, &prod->opnds[1]) &&
   1072             !ctx_def_changed_since(ctx, RC_INT, prod->opnds[1].v.reg,
   1073                                    prod_idx)) {
   1074           op->v.ind.index = prod->opnds[1].v.reg;
   1075           op->v.ind.index_type = prod->opnds[1].type;
   1076           op->v.ind.index_ext =
   1077               sign_p ? (u8)OPT_IDX_EXT_SXTW : (u8)OPT_IDX_EXT_UXTW;
   1078           any = 1;
   1079         }
   1080       }
   1081     }
   1082   }
   1083 
   1084   return any;
   1085 }
   1086 
   1087 static int try_addr_synth(CombineCtx* ctx, Inst* in, i32 i) {
   1088   int any = 0;
   1089   for (u32 oi = 0; oi < in->nopnds; ++oi) {
   1090     if (in->opnds[oi].kind == OPK_INDIRECT) {
   1091       if (try_addr_synth_one_op(ctx, in, i, &in->opnds[oi])) {
   1092         any = 1;
   1093         ctx->block_change_p = 1;
   1094       }
   1095     }
   1096   }
   1097   return any;
   1098 }
   1099 
   1100 /* ---- L7: fold a single-use left shift into the consuming ALU op ---- */
   1101 
   1102 /* True for the integer binops aa64 can emit as a shifted-register form
   1103  * (`<op> rd,rn,rm,lsl #k`). ISUB is included: it is non-commutative but the
   1104  * shifted operand is always the SECOND source (the subtrahend), which is
   1105  * exactly where the rider sits. IMUL/div/etc. have no shifted-register form. */
   1106 static int binop_takes_shifted_rhs(BinOp op) {
   1107   switch (op) {
   1108     case BO_IADD:
   1109     case BO_ISUB:
   1110     case BO_AND:
   1111     case BO_OR:
   1112     case BO_XOR:
   1113       return 1;
   1114     default:
   1115       return 0;
   1116   }
   1117 }
   1118 
   1119 /* L7 recognition. When `in` is one of the shifted-register-capable binops and
   1120  * its second source register is produced by a single-use `IR_BINOP SHL reg,imm`
   1121  * (shift 1..4) at the SAME operand width, fold the shift into the binop by
   1122  * rewriting opnds[2] to the shift's source register and stamping the shift
   1123  * rider. The aa64 backend then emits the one-instruction shifted form; the dead
   1124  * SHL is retired by mir_dce. Gated by the target capability so only a backend
   1125  * that emits the shifted form ever receives a rider. */
   1126 static int try_fold_shift_into_alu(CombineCtx* ctx, Inst* in, i32 i) {
   1127   if ((IROp)in->op != IR_BINOP || in->nopnds != 3) return 0;
   1128   if (!binop_takes_shifted_rhs((BinOp)in->extra.imm)) return 0;
   1129   Operand* rhs = &in->opnds[2];
   1130   if (rhs->kind != OPK_REG || rhs->cls != RC_INT || rhs->shift != 0) return 0;
   1131   if (!ctx->target || !ctx->target->can_fold_shift_into_alu ||
   1132       !ctx->target->can_fold_shift_into_alu(ctx->target))
   1133     return 0;
   1134 
   1135   i32 prod_idx = ctx_producer_of(ctx, RC_INT, rhs->v.reg);
   1136   if (prod_idx < 0 || prod_idx >= i) return 0;
   1137   Inst* prod = &ctx->bl->insts[prod_idx];
   1138   if ((IROp)prod->op != IR_BINOP || prod->nopnds != 3 ||
   1139       (BinOp)prod->extra.imm != BO_SHL || prod->opnds[0].kind != OPK_REG ||
   1140       prod->opnds[0].cls != RC_INT || prod->opnds[0].v.reg != rhs->v.reg ||
   1141       prod->opnds[1].kind != OPK_REG || prod->opnds[1].cls != RC_INT ||
   1142       prod->opnds[2].kind != OPK_IMM)
   1143     return 0;
   1144   i64 sh = prod->opnds[2].v.imm;
   1145   if (sh < 1 || sh > 4) return 0;
   1146 
   1147   /* Width safety: the shifted-register operand shifts the rm register at the
   1148    * CONSUMING binop's width. If the SHL produced a narrower value (e.g. a
   1149    * 32-bit shift) than the binop reads (64-bit), the high bits of the source
   1150    * register are not the shift's, so the inline shift would be wrong. Require
   1151    * the shift's result width to equal the binop's result width. */
   1152   u32 shl_w = combine_scalar_width_bytes(ctx->f, prod->opnds[0].type);
   1153   u32 alu_w = combine_scalar_width_bytes(ctx->f, in->opnds[0].type);
   1154   if (!shl_w || !alu_w || shl_w != alu_w) return 0;
   1155 
   1156   Operand prod_def = prod->opnds[0];
   1157   int killed = 0;
   1158   int uses_after =
   1159       count_uses_in_live_range(ctx->f, ctx->bl, prod_idx, &prod_def, &killed);
   1160   /* The shift def must be single-use (it dies at this binop); folding while
   1161    * other uses survive would leave the SHL live AND add a redundant inline
   1162    * shift. The source must be unchanged since the SHL and must not alias the
   1163    * SHL dst (else rewriting the operand to the source changes its value). */
   1164   if (uses_after != 1) return 0;
   1165   if (!killed && opt_block_live_out_has_phys_reg(ctx->f, ctx->hard_live,
   1166                                                  ctx->bl->id, &prod_def))
   1167     return 0;
   1168   if (producer_def_aliases_source(&prod_def, &prod->opnds[1])) return 0;
   1169   if (ctx_def_changed_since(ctx, RC_INT, prod->opnds[1].v.reg, prod_idx))
   1170     return 0;
   1171 
   1172   rhs->v.reg = prod->opnds[1].v.reg;
   1173   rhs->shift = (u8)sh;
   1174   ctx->block_change_p = 1;
   1175   return 1;
   1176 }
   1177 
   1178 /* ---- Rewrite 3: sink producer into single-use IR_COPY destination ---- */
   1179 
   1180 static int try_sink(CombineCtx* ctx, Inst* in, i32 i) {
   1181   if ((IROp)in->op != IR_COPY || in->nopnds < 2) return 0;
   1182   if (in->opnds[0].kind != OPK_REG || in->opnds[1].kind != OPK_REG) return 0;
   1183   if (same_phys_reg(&in->opnds[0], &in->opnds[1])) return 0;
   1184 
   1185   Operand src = in->opnds[1];
   1186   Operand dst = in->opnds[0];
   1187   i32 prod_idx = ctx_producer_of(ctx, src.cls, src.v.reg);
   1188   if (prod_idx < 0 || prod_idx >= i) return 0;
   1189   Inst* prod = &ctx->bl->insts[prod_idx];
   1190   if (!producer_retargetable_op((IROp)prod->op)) return 0;
   1191   if (prod->nopnds < 1 || prod->opnds[0].kind != OPK_REG) return 0;
   1192   if (!same_phys_reg(&prod->opnds[0], &src)) return 0;
   1193 
   1194   /* Producer's source operands must not have been redefined since. */
   1195   for (u32 oi = 1; oi < prod->nopnds; ++oi) {
   1196     const Operand* p = &prod->opnds[oi];
   1197     if (p->kind == OPK_REG) {
   1198       if (ctx_def_changed_since(ctx, p->cls, p->v.reg, prod_idx)) return 0;
   1199     } else if (p->kind == OPK_INDIRECT) {
   1200       if (p->v.ind.base_kind == OPT_INDIRECT_REG &&
   1201           ctx_def_changed_since(ctx, RC_INT, p->v.ind.base, prod_idx))
   1202         return 0;
   1203       if (p->v.ind.index_kind == OPT_INDIRECT_REG &&
   1204           p->v.ind.index != (Reg)REG_NONE &&
   1205           ctx_def_changed_since(ctx, RC_INT, p->v.ind.index, prod_idx))
   1206         return 0;
   1207     }
   1208   }
   1209   /* If producer reads memory, no intervening memory write. */
   1210   if (inst_reads_memory(prod) && ctx->last_mem_def > prod_idx) return 0;
   1211   /* Retargeting moves the producer's write to `dst` earlier. That is only
   1212    * legal if the hard register is dead throughout the interval before the
   1213    * copy that originally wrote it. */
   1214   for (i32 j = prod_idx + 1; j < i; ++j) {
   1215     Inst* mid = &ctx->bl->insts[j];
   1216     CombinePhysRegEffect effect = inst_phys_reg_effect(ctx->f, mid, &dst);
   1217     if (effect.uses || effect.defines || effect.clobbers)
   1218       return 0;
   1219   }
   1220 
   1221   /* Producer dst must have exactly one use within its live range (the copy
   1222    * at i). If the live range terminates inside the block, the cross-block
   1223    * live-out check is moot. */
   1224   int killed = 0;
   1225   int uses_total =
   1226       count_uses_in_live_range(ctx->f, ctx->bl, prod_idx, &src, &killed);
   1227   if (uses_total != 1) return 0;
   1228   if (killed &&
   1229       use_after_clobber_before_redef(ctx->f, ctx->bl, prod_idx, &src))
   1230     return 0;
   1231   if (!killed && opt_block_live_out_has_phys_reg(ctx->f, ctx->hard_live,
   1232                                                  ctx->bl->id, &src))
   1233     return 0;
   1234 
   1235   int swap_binop = 0;
   1236   if (!retarget_producer_legal(prod, &dst, &swap_binop)) return 0;
   1237 
   1238   if (swap_binop) {
   1239     Operand tmp = prod->opnds[1];
   1240     prod->opnds[1] = prod->opnds[2];
   1241     prod->opnds[2] = tmp;
   1242   }
   1243   prod->opnds[0] = dst;
   1244 
   1245   /* Update last-def: producer no longer defines src, now defines dst. If src
   1246    * had an earlier reaching definition in the block, keep it visible for
   1247    * later source-availability checks. */
   1248   ctx_restore_removed_def(ctx, &src, prod_idx);
   1249   ctx->last_def[dst.cls][dst.v.reg] = prod_idx;
   1250 
   1251   /* Mark the copy NOP'd so compact removes it. */
   1252   in->op = IR_NOP;
   1253   in->def = VAL_NONE;
   1254   in->ndefs = 0;
   1255   in->defs = NULL;
   1256   in->nopnds = 0;
   1257   in->opnds = NULL;
   1258   ctx->block_change_p = 1;
   1259   return 1;
   1260 }
   1261 
   1262 /* ---- Rewrite 4: combine_exts (ext-of-ext chains) ---- */
   1263 
   1264 static int try_combine_exts(CombineCtx* ctx, Inst* in, i32 i) {
   1265   if ((IROp)in->op != IR_CONVERT || in->nopnds < 2) return 0;
   1266   if (in->opnds[1].kind != OPK_REG) return 0;
   1267   u32 sb, db;
   1268   int outer_sign;
   1269   if (!ext_params(in, &sb, &db, &outer_sign)) return 0;
   1270 
   1271   Reg src_reg = in->opnds[1].v.reg;
   1272   u8 src_cls = in->opnds[1].cls;
   1273   i32 prod_idx = ctx_producer_of(ctx, src_cls, src_reg);
   1274   if (prod_idx < 0 || prod_idx >= i) return 0;
   1275   Inst* prod = &ctx->bl->insts[prod_idx];
   1276 
   1277   /* Redundant zero-extension of a zero-extending load. A narrow integer load
   1278    * with no MF_SEXT_LOAD rider zero-extends the whole destination register on
   1279    * every target (sign extension is always either a separate convert or a
   1280    * flagged sign-extending load). So a later ZEXT of that register reproduces
   1281    * bits that are already zero -- as long as the convert keeps at least the
   1282    * loaded bytes (`sb >= load size`), the result equals the source. Rewrite the
   1283    * convert to a copy and let copy-prop + DCE retire it; worst case the copy
   1284    * survives as a same-cost register move (it is never larger than the uxt). */
   1285   if (!outer_sign && src_cls == RC_INT && (IROp)prod->op == IR_LOAD &&
   1286       !(prod->extra.mem.flags & MF_SEXT_LOAD) && prod->extra.mem.size &&
   1287       prod->extra.mem.size <= sb && prod->nopnds >= 1 &&
   1288       same_reg_operand(&prod->opnds[0], &in->opnds[1])) {
   1289     in->op = IR_COPY;
   1290     in->nopnds = 2; /* opnds[0]=dst, opnds[1]=src already in place */
   1291     ctx->block_change_p = 1;
   1292     return 1;
   1293   }
   1294 
   1295   /* L6 (O1-PATTERNS): the SIGNED mirror of the ZEXT-of-load fold above. A
   1296    * narrow integer load fills the ENTIRE destination register. A backend that
   1297    * advertises load_sext honors MF_SEXT_LOAD with a sign-extending load
   1298    * (`ldrsb`/`ldrsh`); every backend's plain load (`ldrb`/`ldrh`) zero-extends.
   1299    * A subsequent SXTB/SXTH/SXTW convert is then a no-op in two shapes — drop it
   1300    * to an IR_COPY (copy-prop + DCE retire it; worst case a same-cost move):
   1301    *
   1302    *  (a) SEXT of a sign-extending load of equal-or-smaller width, on a target
   1303    *      that honors MF_SEXT_LOAD (`mem.size <= sb`): the load already
   1304    *      replicated the loaded sign bit across the whole register, so
   1305    *      re-sign-extending from `sb >= mem.size` bytes is an identity.
   1306    *
   1307    *  (b) SEXT of a ZERO-extending load whose value cannot have the sign bit set
   1308    *      in the widened position (`mem.size < sb`, STRICT): the loaded value
   1309    *      occupies only the low `mem.size` bytes and bit `8*sb-1` is in the
   1310    *      zeroed region, so the sign bit the SEXT replicates is guaranteed 0 ->
   1311    *      the SEXT equals the ZEXT the load already produced -> identity. Strict
   1312    *      `<` is required: at `mem.size == sb` the load's top byte may set the
   1313    *      sign bit, and SEXT would then differ from the zero-extended value.
   1314    *      (`ldrb w; sxtw x` of a 0..255 byte — lvm/sqlite.) */
   1315   if (outer_sign && src_cls == RC_INT && (IROp)prod->op == IR_LOAD &&
   1316       prod->extra.mem.size && prod->nopnds >= 1 &&
   1317       same_reg_operand(&prod->opnds[0], &in->opnds[1])) {
   1318     int sext_load = (prod->extra.mem.flags & MF_SEXT_LOAD) != 0;
   1319     int load_sign_extends =
   1320         sext_load && ctx->target && ctx->target->regs &&
   1321         ctx->target->regs->load_sext;
   1322     if ((load_sign_extends && prod->extra.mem.size <= sb) ||
   1323         (!sext_load && prod->extra.mem.size < sb)) {
   1324       in->op = IR_COPY;
   1325       in->nopnds = 2; /* opnds[0]=dst, opnds[1]=src already in place */
   1326       ctx->block_change_p = 1;
   1327       return 1;
   1328     }
   1329   }
   1330 
   1331   u32 isb, idb;
   1332   int inner_sign;
   1333   if (!ext_params(prod, &isb, &idb, &inner_sign)) return 0;
   1334   if (prod->opnds[1].kind != OPK_REG) return 0;
   1335   if (ctx_def_changed_since(ctx, prod->opnds[1].cls, prod->opnds[1].v.reg,
   1336                             prod_idx))
   1337     return 0;
   1338 
   1339   /* Single-use of inner ext dst within its live range (only this insn). */
   1340   Operand prod_def = prod->opnds[0];
   1341   int killed = 0;
   1342   int uses_total =
   1343       count_uses_in_live_range(ctx->f, ctx->bl, prod_idx, &prod_def, &killed);
   1344   if (uses_total != 1) return 0;
   1345   if (!killed && opt_block_live_out_has_phys_reg(ctx->f, ctx->hard_live,
   1346                                                  ctx->bl->id, &prod_def))
   1347     return 0;
   1348 
   1349   /* Outer width `db`, inner width `idb`. Inner reads `isb` bytes. */
   1350   u32 outer_in = sb;                  /* outer's source width */
   1351   u32 inner_out = idb;                /* inner's output width */
   1352   if (outer_in > inner_out) return 0; /* sanity: inner feeds outer */
   1353 
   1354   /* If inner is an identity-shaped convert that reads back its own def
   1355    * register (e.g. `convert rB, rB`), the rewrite would set in.opnds[1] to
   1356    * the same register it already holds. Reporting a change spins the
   1357    * per-BB fixpoint forever. Skip. */
   1358   if (prod->opnds[1].kind == OPK_REG && in->opnds[1].kind == OPK_REG &&
   1359       prod->opnds[1].cls == in->opnds[1].cls &&
   1360       prod->opnds[1].v.reg == in->opnds[1].v.reg)
   1361     return 0;
   1362 
   1363   /* Pattern A: outer width <= inner-source width.  Outer can reach past the
   1364    * inner ext to the inner source directly, keeping outer's signedness.
   1365    * MIR allows any signedness combination here. */
   1366   if (db <= isb) {
   1367     in->opnds[1] = prod->opnds[1];
   1368     in->opnds[1].type = prod->opnds[1].type;
   1369     ctx->block_change_p = 1;
   1370     return 1;
   1371   }
   1372 
   1373   /* Pattern B: inner-source width < outer width. Allowed iff outer is signed
   1374    * OR inner is unsigned; excludes the unsafe uext(sext) combination. */
   1375   if (isb < db && (outer_sign || !inner_sign)) {
   1376     in->opnds[1] = prod->opnds[1];
   1377     in->opnds[1].type = prod->opnds[1].type;
   1378     /* outer's ConvKind already encodes the outer signedness; keep it. */
   1379     ctx->block_change_p = 1;
   1380     return 1;
   1381   }
   1382 
   1383   return 0;
   1384 }
   1385 
   1386 /* Invert a CmpOp (negate the relation). Returns 0 for an op outside the
   1387  * known total set. Kept byte-for-byte in sync with src/cg/fold.c
   1388  * api_invert_cmp and pass_jump.c invert_cmp — including the FP rule that
   1389  * negation flips ordered<->unordered (so the NaN outcome flips too): the
   1390  * negation of ordered `a<b` is *unordered* `a>=b`. */
   1391 static int combine_invert_cmp(CmpOp op, CmpOp* out) {
   1392   switch (op) {
   1393     case CMP_EQ: *out = CMP_NE; return 1;
   1394     case CMP_NE: *out = CMP_EQ; return 1;
   1395     case CMP_LT_S: *out = CMP_GE_S; return 1;
   1396     case CMP_LE_S: *out = CMP_GT_S; return 1;
   1397     case CMP_GT_S: *out = CMP_LE_S; return 1;
   1398     case CMP_GE_S: *out = CMP_LT_S; return 1;
   1399     case CMP_LT_U: *out = CMP_GE_U; return 1;
   1400     case CMP_LE_U: *out = CMP_GT_U; return 1;
   1401     case CMP_GT_U: *out = CMP_LE_U; return 1;
   1402     case CMP_GE_U: *out = CMP_LT_U; return 1;
   1403     case CMP_OEQ_F: *out = CMP_UNE_F; return 1;
   1404     case CMP_ONE_F: *out = CMP_UEQ_F; return 1;
   1405     case CMP_OLT_F: *out = CMP_UGE_F; return 1;
   1406     case CMP_OLE_F: *out = CMP_UGT_F; return 1;
   1407     case CMP_OGT_F: *out = CMP_ULE_F; return 1;
   1408     case CMP_OGE_F: *out = CMP_ULT_F; return 1;
   1409     case CMP_UEQ_F: *out = CMP_ONE_F; return 1;
   1410     case CMP_UNE_F: *out = CMP_OEQ_F; return 1;
   1411     case CMP_ULT_F: *out = CMP_OGE_F; return 1;
   1412     case CMP_ULE_F: *out = CMP_OGT_F; return 1;
   1413     case CMP_UGT_F: *out = CMP_OLE_F; return 1;
   1414     case CMP_UGE_F: *out = CMP_OLT_F; return 1;
   1415   }
   1416   return 0;
   1417 }
   1418 
   1419 /* ---- L4 (O1-PATTERNS): drop the double-cset bool re-normalize ----
   1420  *
   1421  * kit lowers `(a cc b)` to an IR_CMP producing a 0/1 bool in rInner, then a
   1422  * surrounding `bool != 0` / `if (bool)` context lowers to a SECOND IR_CMP
   1423  * `rOuter = (rInner <eq/ne> 0|1)` that re-normalizes an already-0/1 value
   1424  * (`cmp w8,#1; cset w8,eq; cmp w8,#0; cset w8,ne` — yyjson's 573-triple
   1425  * signature). The outer compare is redundant. When an IR_CMP's source register
   1426  * (opnds[1]) has its most-recent same-block def being another IR_CMP (so a
   1427  * proven 0/1 bool) and the outer compares it against imm 0 or 1 with eq/ne,
   1428  * rewrite the outer in place:
   1429  *
   1430  *   (t != 0) / (t == 1): outer == inner  -> IR_COPY rOuter, rInner
   1431  *   (t == 0) / (t != 1): outer == !inner -> recompute the inner relation
   1432  *                        inverted on the inner's OWN operands:
   1433  *                        IR_CMP rOuter = (inner.a invert(inner.cc) inner.b)
   1434  *
   1435  * The copy form lets copy-prop + DCE retire it; the inverted form drops the
   1436  * dependency on rInner (so the inner cmp dies if single-use) and replaces two
   1437  * cmp+cset pairs with one. Both are same-block forward peepholes off the
   1438  * CombineCtx last-def map. Composes with L2: once the outer is a copy/inverted
   1439  * cmp, a branch consuming rOuter folds through copy-prop / the L2 fusion on a
   1440  * later fixpoint iteration. */
   1441 static int try_drop_double_cmp(CombineCtx* ctx, Inst* in, i32 i) {
   1442   if ((IROp)in->op != IR_CMP || in->nopnds < 3) return 0;
   1443   if (in->opnds[0].kind != OPK_REG || in->opnds[1].kind != OPK_REG) return 0;
   1444   if (in->opnds[2].kind != OPK_IMM) return 0;
   1445   CmpOp outer = (CmpOp)in->extra.imm;
   1446   if (outer != CMP_EQ && outer != CMP_NE) return 0;
   1447   i64 k = in->opnds[2].v.imm;
   1448   if (k != 0 && k != 1) return 0;
   1449 
   1450   Operand srcb = in->opnds[1];
   1451   i32 prod_idx = ctx_producer_of(ctx, srcb.cls, srcb.v.reg);
   1452   if (prod_idx < 0 || prod_idx >= i) return 0;
   1453   Inst* inner = &ctx->bl->insts[prod_idx];
   1454   if ((IROp)inner->op != IR_CMP || inner->nopnds < 3) return 0;
   1455   if (inner->opnds[0].kind != OPK_REG ||
   1456       !same_phys_reg(&inner->opnds[0], &srcb))
   1457     return 0;
   1458   /* The outer dst and the inner bool live in the same register class (both are
   1459    * 0/1 integer bools); a class crossing would need a real move, not a copy. */
   1460   if (in->opnds[0].cls != inner->opnds[0].cls) return 0;
   1461 
   1462   /* Whether the outer result equals the inner bool or its negation:
   1463    *   (t != 0) -> t ;  (t == 1) -> t        (non-inverted)
   1464    *   (t == 0) -> !t;  (t != 1) -> !t        (inverted) */
   1465   int invert = (outer == CMP_EQ && k == 0) || (outer == CMP_NE && k == 1);
   1466 
   1467   if (!invert) {
   1468     /* outer == inner: rewrite to a copy off rInner (copy-prop + DCE retire). */
   1469     in->op = (u16)IR_COPY;
   1470     in->opnds[1] = inner->opnds[0]; /* = srcb, already in place; keep explicit */
   1471     in->nopnds = 2;
   1472     in->extra.imm = 0;
   1473     ctx->block_change_p = 1;
   1474     return 1;
   1475   }
   1476 
   1477   /* Inverted: recompute the inner relation negated on the inner's own operands.
   1478    * That requires the inner's operands to still hold their pre-cmp values: an
   1479    * operand must not have been redefined since the inner cmp AND must not alias
   1480    * the inner cmp's own destination (a self-referencing `cmp w8,#1` writing w8
   1481    * leaves w8 holding the bool, not the compared value — recomputing from it
   1482    * would be wrong). */
   1483   CmpOp inv;
   1484   if (!combine_invert_cmp((CmpOp)inner->extra.imm, &inv)) return 0;
   1485   for (u32 oi = 1; oi < 3; ++oi) {
   1486     const Operand* p = &inner->opnds[oi];
   1487     if (p->kind == OPK_REG) {
   1488       if (same_phys_reg(p, &inner->opnds[0])) return 0;
   1489       if (ctx_def_changed_since(ctx, p->cls, p->v.reg, prod_idx)) return 0;
   1490     }
   1491   }
   1492   /* Keep the outer's dst (opnds[0]); replace its inputs with the inner's and
   1493    * its CmpOp with the inverted inner relation. The dst type/class is the same
   1494    * bool the outer already produced. */
   1495   in->opnds[1] = inner->opnds[1];
   1496   in->opnds[2] = inner->opnds[2];
   1497   in->extra.imm = (i64)inv;
   1498   ctx->block_change_p = 1;
   1499   return 1;
   1500 }
   1501 
   1502 /* ---- L2 (O1-PATTERNS): fuse cmp rD; cmp_branch(NE/EQ, rD, #0) -> cmp_branch
   1503  *
   1504  * A relational whose 0/1 bool was MATERIALIZED into a register (`cmp; cset
   1505  * rD,cc`) and then re-tested by the branch (`cbnz/cbz rD`) lowers, at this
   1506  * MIR level, to `IR_CMP rD = (a cc b)` followed by the terminator
   1507  * `IR_CMP_BRANCH(CMP_NE|CMP_EQ, rD, #0)` (control.c synthesizes cmp_branch of
   1508  * the bool against IMM_ZERO; cg's delayed-compare path already fuses the
   1509  * un-materialized `if (a<b)` form upstream, so only the materialized residual
   1510  * reaches here). The branch's NE/EQ-vs-0 just re-tests a bool the original cmp
   1511  * already computed in flags.
   1512  *
   1513  * When the terminator is `IR_CMP_BRANCH(NE|EQ, rD, #0)` whose rD's most-recent
   1514  * same-block def is a single-use IR_CMP, fuse into the cmp's own relation:
   1515  *   cbnz (CMP_NE vs 0): branch-if-true  -> cmp_branch(cmp.op, a, b)
   1516  *   cbz  (CMP_EQ vs 0): branch-if-false -> cmp_branch(invert(cmp.op), a, b)
   1517  * and NOP the now-dead cmp. This is the inverse of the SSA fusion in
   1518  * pass_o2.c (ssa_combine_fold_cmp_branch), here over the no-SSA CombineCtx
   1519  * (last-def map + hard-live single-use). Inversion uses the full CmpOp table
   1520  * (combine_invert_cmp), which is correct for FP too: the cset collapsed the
   1521  * comparison to a definite 0/1 even for NaN, and the inverse op reproduces the
   1522  * same NaN branch direction (e.g. !(a OEQ b) == a UNE b).
   1523  *
   1524  * Single-block, single forward pass, all guards from existing CombineCtx state
   1525  * -> linear. */
   1526 static int try_fuse_cmp_branch(CombineCtx* ctx, Inst* in, i32 i) {
   1527   if ((IROp)in->op != IR_CMP_BRANCH || in->nopnds < 2) return 0;
   1528   /* Only the block terminator carries the two CFG successors. */
   1529   if (i != (i32)ctx->bl->ninsts - 1 || ctx->bl->nsucc < 2) return 0;
   1530   /* Branch must test a register bool against immediate 0 with EQ/NE. */
   1531   CmpOp brop = (CmpOp)in->extra.imm;
   1532   if (brop != CMP_EQ && brop != CMP_NE) return 0;
   1533   if (in->opnds[0].kind != OPK_REG) return 0;
   1534   if (in->opnds[1].kind != OPK_IMM || in->opnds[1].v.imm != 0) return 0;
   1535 
   1536   Operand cond = in->opnds[0];
   1537   i32 prod_idx = ctx_producer_of(ctx, cond.cls, cond.v.reg);
   1538   if (prod_idx < 0 || prod_idx >= i) return 0;
   1539   Inst* cmp = &ctx->bl->insts[prod_idx];
   1540   if ((IROp)cmp->op != IR_CMP || cmp->nopnds < 3) return 0;
   1541   if (cmp->opnds[0].kind != OPK_REG || !same_phys_reg(&cmp->opnds[0], &cond))
   1542     return 0;
   1543 
   1544   /* Resolve the fused relation (invert for the cbz / EQ-vs-0 case). */
   1545   CmpOp fused = (CmpOp)cmp->extra.imm;
   1546   if (brop == CMP_EQ && !combine_invert_cmp(fused, &fused)) return 0;
   1547 
   1548   /* The cmp's input operands must be unchanged between the cmp and the branch
   1549    * (an intervening inst may have redefined a register the cmp reads). */
   1550   for (u32 oi = 1; oi < 3; ++oi) {
   1551     const Operand* p = &cmp->opnds[oi];
   1552     if (p->kind == OPK_REG &&
   1553         ctx_def_changed_since(ctx, p->cls, p->v.reg, prod_idx))
   1554       return 0;
   1555   }
   1556 
   1557   /* The cmp's result must die at this branch: its sole use, not live-out. If it
   1558    * has other uses (or escapes the block) the cmp must stay and we cannot NOP
   1559    * it. */
   1560   Operand cmp_def = cmp->opnds[0];
   1561   int killed = 0;
   1562   if (count_uses_in_live_range(ctx->f, ctx->bl, prod_idx, &cmp_def, &killed) !=
   1563       1)
   1564     return 0;
   1565   if (!killed && opt_block_live_out_has_phys_reg(ctx->f, ctx->hard_live,
   1566                                                  ctx->bl->id, &cmp_def))
   1567     return 0;
   1568 
   1569   /* Fuse: rewrite the branch to test the cmp's own relation directly; NOP the
   1570    * cmp. Successors are unchanged (succ[0]=taken, succ[1]=fallthrough). */
   1571   Operand* opnds = arena_array(ctx->f->arena, Operand, 2);
   1572   opnds[0] = cmp->opnds[1];
   1573   opnds[1] = cmp->opnds[2];
   1574   in->opnds = opnds;
   1575   in->nopnds = 2;
   1576   in->extra.imm = (i64)fused;
   1577 
   1578   cmp->op = (u16)IR_NOP;
   1579   cmp->def = VAL_NONE;
   1580   cmp->ndefs = 0;
   1581   cmp->defs = NULL;
   1582   cmp->nopnds = 0;
   1583   cmp->opnds = NULL;
   1584   /* The cmp no longer defines cond's register; restore its prior reaching def
   1585    * so later same-block availability checks stay accurate. */
   1586   ctx_restore_removed_def(ctx, &cond, prod_idx);
   1587   ctx->block_change_p = 1;
   1588   return 1;
   1589 }
   1590 
   1591 /* ---- Rewrite 6 (W1a): local frame-address `sub`-CSE ----
   1592  *
   1593  * O1.md W1a. An `IR_ADDR_OF rD, <addr>` materializes a frame-slot or global
   1594  * address into a register; on aa64 that is a `sub xN,x29,#k` (frame) or an
   1595  * `adrp;add`/GOT build (global), on rv64 a `lui;…;add`. A back-to-back
   1596  * `IR_ADDR_OF` of the *same* address operand recomputes the identical value.
   1597  * While an earlier producer register is unclobbered, rewrite the recompute to
   1598  * an `IR_COPY` off it (copy-prop + DCE retire it; worst case it is a same-cost
   1599  * register move). The address sources are immutable — a frame slot's offset and
   1600  * a global sym+addend never change — so no source-availability check beyond the
   1601  * producer register being unclobbered is required.
   1602  *
   1603  * Bounded per-BB ring (COMBINE_ADDR_CSE_SLOTS) → O(1) per inst → linear.
   1604  * Deliberately minimal: W1.1 (positive far-slot layout) subsumes the spill
   1605  * far-slot case; this PR stands up the gate + structural guard and mops up the
   1606  * `&local` / global recompute residual. */
   1607 
   1608 /* Two address operands name the same address iff same kind and same payload. */
   1609 static int same_addr_operand(const Operand* a, const Operand* b) {
   1610   if (a->kind != b->kind) return 0;
   1611   switch (a->kind) {
   1612     case OPK_LOCAL:
   1613       return a->v.frame_slot == b->v.frame_slot;
   1614     case OPK_GLOBAL:
   1615       return a->v.global.sym == b->v.global.sym &&
   1616              a->v.global.addend == b->v.global.addend;
   1617     default:
   1618       return 0;
   1619   }
   1620 }
   1621 
   1622 /* Is this an addr-of whose address is a CSE-able immutable frame/global ref? */
   1623 static int addr_of_is_cseable(const Inst* in) {
   1624   if ((IROp)in->op != IR_ADDR_OF || in->nopnds < 2) return 0;
   1625   if (in->opnds[0].kind != OPK_REG) return 0;
   1626   return in->opnds[1].kind == OPK_LOCAL || in->opnds[1].kind == OPK_GLOBAL;
   1627 }
   1628 
   1629 static void addr_cse_record(CombineCtx* ctx, const Inst* in, i32 i) {
   1630   if (!addr_of_is_cseable(in)) return;
   1631   AddrCseEntry* e = &ctx->addr_cse[ctx->addr_cse_next];
   1632   e->inst_idx = i;
   1633   e->dst = in->opnds[0];
   1634   e->addr = in->opnds[1];
   1635   ctx->addr_cse_next = (ctx->addr_cse_next + 1u) % COMBINE_ADDR_CSE_SLOTS;
   1636 }
   1637 
   1638 static int try_addr_of_cse(CombineCtx* ctx, Inst* in, i32 i) {
   1639   if (!addr_of_is_cseable(in)) return 0;
   1640   const Operand* addr = &in->opnds[1];
   1641   for (u32 k = 0; k < COMBINE_ADDR_CSE_SLOTS; ++k) {
   1642     const AddrCseEntry* e = &ctx->addr_cse[k];
   1643     if (e->inst_idx < 0 || e->inst_idx >= i) continue;
   1644     if (!same_addr_operand(&e->addr, addr)) continue;
   1645     /* The producer register must still hold the address: it is this BB's live
   1646      * producer of (cls,reg) and was not redefined/clobbered since. */
   1647     if (ctx_producer_of(ctx, e->dst.cls, e->dst.v.reg) != e->inst_idx) continue;
   1648     if (ctx_def_changed_since(ctx, e->dst.cls, e->dst.v.reg, e->inst_idx))
   1649       continue;
   1650     /* A copy of the producer reg into self is a no-op the dst already holds. */
   1651     if (same_phys_reg(&in->opnds[0], &e->dst)) continue;
   1652     /* Rewrite `addr_of rD, <addr>` into `copy rD, rP`. */
   1653     in->op = (u16)IR_COPY;
   1654     in->opnds[1] = e->dst;
   1655     in->nopnds = 2;
   1656     ctx->block_change_p = 1;
   1657     return 1;
   1658   }
   1659   return 0;
   1660 }
   1661 
   1662 /* ---- Rewrite 8 (W5): local same-block redundant-load elimination ---- */
   1663 
   1664 /* Two load-address operands name the same typed address iff their kind and
   1665  * payload agree. For an OPK_INDIRECT both the base, index, scale, AND offset
   1666  * must match; the register-unchanged-since check is applied separately by the
   1667  * caller. */
   1668 static int same_load_addr_operand(const Operand* a, const Operand* b) {
   1669   if (a->kind != b->kind || a->cls != b->cls || a->type != b->type)
   1670     return 0;
   1671   switch (a->kind) {
   1672     case OPK_LOCAL:
   1673       return a->v.frame_slot == b->v.frame_slot;
   1674     case OPK_GLOBAL:
   1675       return a->v.global.sym == b->v.global.sym &&
   1676              a->v.global.addend == b->v.global.addend;
   1677     case OPK_INDIRECT:
   1678       return a->v.ind.base == b->v.ind.base &&
   1679              a->v.ind.index == b->v.ind.index &&
   1680              a->v.ind.base_kind == b->v.ind.base_kind &&
   1681              a->v.ind.index_kind == b->v.ind.index_kind &&
   1682              a->v.ind.base_type == b->v.ind.base_type &&
   1683              a->v.ind.index_type == b->v.ind.index_type &&
   1684              a->v.ind.log2_scale == b->v.ind.log2_scale &&
   1685              a->v.ind.index_ext == b->v.ind.index_ext &&
   1686              a->v.ind.ofs == b->v.ind.ofs;
   1687     default:
   1688       return 0;
   1689   }
   1690 }
   1691 
   1692 /* Alias roots are optimizer metadata rather than part of the bytes returned by
   1693  * a load, but keeping them in this deliberately conservative local key avoids
   1694  * letting CSE erase a distinction made by an upstream alias classifier. */
   1695 static int same_load_alias_root(const AliasRoot* a, const AliasRoot* b) {
   1696   if (a->kind != b->kind) return 0;
   1697   switch ((AliasKind)a->kind) {
   1698     case ALIAS_UNKNOWN:
   1699     case ALIAS_HEAP:
   1700       return 1;
   1701     case ALIAS_LOCAL:
   1702       return a->v.local_id == b->v.local_id;
   1703     case ALIAS_GLOBAL:
   1704       return a->v.global == b->v.global;
   1705     case ALIAS_PARAM:
   1706       return a->v.param_idx == b->v.param_idx;
   1707     case ALIAS_STRING:
   1708       return a->v.string_id == b->v.string_id;
   1709     default:
   1710       return 0;
   1711   }
   1712 }
   1713 
   1714 /* Compare explicit fields rather than struct bytes: MemAccess contains padding
   1715  * and a tagged union, so memcmp would make the key depend on inactive storage.
   1716  * Even fields that usually do not alter the returned bits (alignment and alias
   1717  * classification) stay in this bounded key. That keeps reuse conservative and
   1718  * aligned with the richer memory key used by O2 GVN. */
   1719 static int same_load_mem_access(const MemAccess* a, const MemAccess* b) {
   1720   return a->type == b->type && a->size == b->size && a->align == b->align &&
   1721          a->flags == b->flags && a->addr_space == b->addr_space &&
   1722          a->bf_offset == b->bf_offset && a->bf_width == b->bf_width &&
   1723          a->bf_signed == b->bf_signed &&
   1724          same_load_alias_root(&a->alias, &b->alias);
   1725 }
   1726 
   1727 /* Physical register identity is intentionally absent: CSE expects distinct
   1728  * destinations. The value interpretation carried by the instruction and its
   1729  * destination operand must nevertheless agree before a producer can be copied
   1730  * into the later destination. */
   1731 static int same_cse_result_shape(const Inst* in, KitCgTypeId result_type,
   1732                                  const Operand* dst) {
   1733   const Operand* current;
   1734   if (!in || !dst || in->nopnds < 1) return 0;
   1735   current = &in->opnds[0];
   1736   return current->kind == OPK_REG && dst->kind == OPK_REG &&
   1737          in->type == current->type && result_type == dst->type &&
   1738          in->type == result_type && current->cls == dst->cls;
   1739 }
   1740 
   1741 /* A plain, CSE-eligible IR_LOAD: dst is a register, address is a
   1742  * direct/indirect memory operand, and the access is neither observable
   1743  * (volatile/atomic) nor a bit-field rider. */
   1744 static int load_is_cseable(const Inst* in) {
   1745   if ((IROp)in->op != IR_LOAD || in->nopnds < 2) return 0;
   1746   if (in->opnds[0].kind != OPK_REG) return 0;
   1747   const Operand* addr = &in->opnds[1];
   1748   /* W5 owns pointer-deref (OPK_INDIRECT) and global (OPK_GLOBAL) loads — the
   1749    * `*p` / global-variable same-block reload. Semantic OPK_LOCAL traffic stays
   1750    * out of this narrow CSE, while location-MIR spills are represented as
   1751    * IR_COPY/OPK_STACK and owned by opt_combine_compact_block. Reusing either
   1752    * here would duplicate its alias/location authority. */
   1753   if (addr->kind != OPK_INDIRECT && addr->kind != OPK_GLOBAL) return 0;
   1754   if (opt_mem_observable(&in->extra.mem)) return 0;
   1755   if (in->extra.mem.bf_width != 0) return 0; /* bit-field rider */
   1756   /* Self-clobbering load (`ldr x9, [x9, #k]`): the destination overwrites its
   1757    * own address base/index, so after the load the address register no longer
   1758    * holds the loaded-from pointer. The recorded `addr` operand would then match
   1759    * a syntactically-identical later load whose base now denotes a DIFFERENT
   1760    * address — an unsound reuse. Exclude these from both recording and reuse:
   1761    * the same-operand check (load_addr_regs_unchanged, ctx_def_changed_since)
   1762    * uses a strictly-greater index test and cannot see a redefinition that
   1763    * happened AT the recorded load's own index. */
   1764   if (in->opnds[0].cls == RC_INT && addr->kind == OPK_INDIRECT) {
   1765     Reg d = in->opnds[0].v.reg;
   1766     if (addr->v.ind.base_kind == OPT_INDIRECT_REG &&
   1767         addr->v.ind.base == d)
   1768       return 0;
   1769     if (addr->v.ind.index_kind == OPT_INDIRECT_REG &&
   1770         addr->v.ind.index != (Reg)REG_NONE && addr->v.ind.index == d)
   1771       return 0;
   1772   }
   1773   return 1;
   1774 }
   1775 
   1776 /* The address registers (base/index of an OPK_INDIRECT) must not have been
   1777  * redefined since the earlier load produced `dst`. For a direct OPK_LOCAL /
   1778  * OPK_GLOBAL there is no register input — the address is immutable. */
   1779 static int load_addr_regs_unchanged(const CombineCtx* ctx, const Operand* addr,
   1780                                     i32 since_idx) {
   1781   if (addr->kind != OPK_INDIRECT) return 1;
   1782   if (addr->v.ind.base_kind == OPT_INDIRECT_REG &&
   1783       addr->v.ind.base != (Reg)REG_NONE &&
   1784       ctx_def_changed_since(ctx, RC_INT, addr->v.ind.base, since_idx))
   1785     return 0;
   1786   if (addr->v.ind.index_kind == OPT_INDIRECT_REG &&
   1787       addr->v.ind.index != (Reg)REG_NONE &&
   1788       ctx_def_changed_since(ctx, RC_INT, addr->v.ind.index, since_idx))
   1789     return 0;
   1790   return 1;
   1791 }
   1792 
   1793 static void load_cse_record(CombineCtx* ctx, const Inst* in, i32 i) {
   1794   if (!load_is_cseable(in)) return;
   1795   LoadCseEntry* e = &ctx->load_cse[ctx->load_cse_next];
   1796   e->inst_idx = i;
   1797   e->dst = in->opnds[0];
   1798   e->addr = in->opnds[1];
   1799   e->result_type = in->type;
   1800   e->mem = in->extra.mem;
   1801   ctx->load_cse_next = (ctx->load_cse_next + 1u) % COMBINE_LOAD_CSE_SLOTS;
   1802 }
   1803 
   1804 static int try_local_load_cse(CombineCtx* ctx, Inst* in, i32 i) {
   1805   if (!load_is_cseable(in)) return 0;
   1806   const Operand* addr = &in->opnds[1];
   1807   for (u32 k = 0; k < COMBINE_LOAD_CSE_SLOTS; ++k) {
   1808     const LoadCseEntry* e = &ctx->load_cse[k];
   1809     if (e->inst_idx < 0 || e->inst_idx >= i) continue;
   1810     if (!same_cse_result_shape(in, e->result_type, &e->dst)) continue;
   1811     if (!same_load_mem_access(&e->mem, &in->extra.mem)) continue;
   1812     if (!same_load_addr_operand(&e->addr, addr)) continue;
   1813     /* ALIASING: any memory write between the earlier load and now invalidates
   1814      * reuse. ctx->last_mem_def is the index of the most-recent memory writer
   1815      * (set by ctx_record for every inst_writes_memory inst, including the
   1816      * call/asm/intrinsic clobber barrier). If it occurred after the earlier
   1817      * load, the loaded value may have changed -> do not reuse. */
   1818     if (ctx->last_mem_def > e->inst_idx) continue;
   1819     /* The producer register must still hold the loaded value: it is this BB's
   1820      * live producer of (cls,reg) and was not redefined/clobbered since. */
   1821     if (ctx_producer_of(ctx, e->dst.cls, e->dst.v.reg) != e->inst_idx) continue;
   1822     if (ctx_def_changed_since(ctx, e->dst.cls, e->dst.v.reg, e->inst_idx))
   1823       continue;
   1824     /* The address-forming registers must be unchanged since the earlier load,
   1825      * else the two indirects compute different addresses. */
   1826     if (!load_addr_regs_unchanged(ctx, addr, e->inst_idx)) continue;
   1827     /* A copy of the producer reg into self is a no-op the dst already holds. */
   1828     if (same_phys_reg(&in->opnds[0], &e->dst)) continue;
   1829     /* Same-class reuse only (an int load into an FP reg, or vice versa, would
   1830      * need a cross-class move, not a same-class copy). */
   1831     if (in->opnds[0].cls != e->dst.cls) continue;
   1832     /* Rewrite `load rD, [addr]` into `copy rD, rP`. */
   1833     in->op = (u16)IR_COPY;
   1834     in->flags = 0;
   1835     in->opnds[1] = e->dst;
   1836     in->nopnds = 2;
   1837     ctx->block_change_p = 1;
   1838     return 1;
   1839   }
   1840   return 0;
   1841 }
   1842 
   1843 /* ---- Rewrite 9 (W5): local same-block pure-compute CSE ---- */
   1844 
   1845 /* A pure compute eligible for same-block CSE: IR_BINOP or IR_UNOP with a
   1846  * register destination. (IR_CMP is excluded — its result feeds branch/flag
   1847  * lowering and is not a freely-copyable register value here.) */
   1848 static int compute_is_cseable(const Inst* in) {
   1849   IROp op = (IROp)in->op;
   1850   if (op != IR_BINOP && op != IR_UNOP) return 0;
   1851   if (in->nopnds < 1 || in->opnds[0].kind != OPK_REG) return 0;
   1852   if (op == IR_BINOP && in->nopnds < 3) return 0;
   1853   if (op == IR_UNOP && in->nopnds < 2) return 0;
   1854   return 1;
   1855 }
   1856 
   1857 /* Two compute operands match iff identical kind+payload. Only REG and IMM
   1858  * operands appear as binop/unop inputs at this stage; any other kind is treated
   1859  * as non-matching (conservative). */
   1860 static int same_compute_operand(const Operand* a, const Operand* b) {
   1861   if (a->kind != b->kind) return 0;
   1862   switch (a->kind) {
   1863     case OPK_REG:
   1864       /* The L7 shift rider is part of the operand's value: `x2` and `x2,lsl#2`
   1865        * are different inputs, so a ridered and a riderless operand must not be
   1866        * treated as the same compute (else CSE would drop the shift). */
   1867       return a->cls == b->cls && a->v.reg == b->v.reg &&
   1868              a->type == b->type && a->shift == b->shift;
   1869     case OPK_IMM:
   1870       return a->cls == b->cls && a->v.imm == b->v.imm &&
   1871              a->type == b->type;
   1872     default:
   1873       return 0;
   1874   }
   1875 }
   1876 
   1877 /* A compute input register must be unchanged since the earlier compute produced
   1878  * its result, else the recomputation would read different values. */
   1879 static int compute_input_unchanged(const CombineCtx* ctx, const Operand* op,
   1880                                    i32 since_idx) {
   1881   if (op->kind != OPK_REG) return 1; /* immediates never change */
   1882   return !ctx_def_changed_since(ctx, op->cls, op->v.reg, since_idx);
   1883 }
   1884 
   1885 static void compute_cse_record(CombineCtx* ctx, const Inst* in, i32 i) {
   1886   if (!compute_is_cseable(in)) return;
   1887   ComputeCseEntry* e = &ctx->compute_cse[ctx->compute_cse_next];
   1888   e->inst_idx = i;
   1889   e->dst = in->opnds[0];
   1890   e->op = in->op;
   1891   e->flags = in->flags;
   1892   e->result_type = in->type;
   1893   e->sub = in->extra.imm;
   1894   e->a = in->opnds[1];
   1895   if ((IROp)in->op == IR_BINOP)
   1896     e->b = in->opnds[2];
   1897   else
   1898     memset(&e->b, 0, sizeof e->b); /* unary sentinel: kind == 0 */
   1899   ctx->compute_cse_next =
   1900       (ctx->compute_cse_next + 1u) % COMBINE_COMPUTE_CSE_SLOTS;
   1901 }
   1902 
   1903 static int try_local_compute_cse(CombineCtx* ctx, Inst* in, i32 i) {
   1904   if (!compute_is_cseable(in)) return 0;
   1905   IROp op = (IROp)in->op;
   1906   const Operand* a = &in->opnds[1];
   1907   const Operand* b = (op == IR_BINOP) ? &in->opnds[2] : NULL;
   1908   for (u32 k = 0; k < COMBINE_COMPUTE_CSE_SLOTS; ++k) {
   1909     const ComputeCseEntry* e = &ctx->compute_cse[k];
   1910     if (e->inst_idx < 0 || e->inst_idx >= i) continue;
   1911     if (e->op != in->op || e->flags != in->flags ||
   1912         e->sub != in->extra.imm)
   1913       continue;
   1914     if (!same_cse_result_shape(in, e->result_type, &e->dst)) continue;
   1915     if (!same_compute_operand(&e->a, a)) continue;
   1916     if (op == IR_BINOP) {
   1917       if (!same_compute_operand(&e->b, b)) continue;
   1918     } else if (e->b.kind != 0) {
   1919       continue; /* recorded as binary; this is unary -> mismatch */
   1920     }
   1921     /* The producer's result register must still be its live producer and
   1922      * unchanged since (so the copy reads the same value). */
   1923     if (ctx_producer_of(ctx, e->dst.cls, e->dst.v.reg) != e->inst_idx) continue;
   1924     if (ctx_def_changed_since(ctx, e->dst.cls, e->dst.v.reg, e->inst_idx))
   1925       continue;
   1926     /* Inputs must be unchanged since the earlier compute, else its recorded
   1927      * result no longer equals recomputing here. */
   1928     if (!compute_input_unchanged(ctx, a, e->inst_idx)) continue;
   1929     if (op == IR_BINOP && !compute_input_unchanged(ctx, b, e->inst_idx))
   1930       continue;
   1931     /* Self-copy is a no-op; skip. Same class only. */
   1932     if (same_phys_reg(&in->opnds[0], &e->dst)) continue;
   1933     if (in->opnds[0].cls != e->dst.cls) continue;
   1934     /* Rewrite the recompute into `copy rD, rP`. */
   1935     in->op = (u16)IR_COPY;
   1936     in->flags = 0;
   1937     in->opnds[1] = e->dst;
   1938     in->nopnds = 2;
   1939     in->extra.imm = 0;
   1940     ctx->block_change_p = 1;
   1941     return 1;
   1942   }
   1943   return 0;
   1944 }
   1945 
   1946 /* ---- Existing IR_RET retarget (kept; runs in the forward pass) ---- */
   1947 
   1948 static int try_ret_retarget(Func* f, Block* bl, i32 i) {
   1949   if (!f->opt_rewritten || i <= 0) return 0;
   1950   Inst* in = &bl->insts[i];
   1951   if ((IROp)in->op != IR_RET) return 0;
   1952   IRRetAux* aux = (IRRetAux*)in->extra.aux;
   1953   Operand* ret_op = NULL;
   1954   Reg ret_reg = REG_NONE;
   1955   if (!aux || !aux->present || !ret_scalar_storage(&aux->val, &ret_op) ||
   1956       !first_return_reg(f, ret_op->cls, &ret_reg) || ret_reg == (Reg)REG_NONE ||
   1957       reg_is_emit_temp(f, ret_op->cls, ret_reg) ||
   1958       ret_reg == ret_op->v.reg)
   1959     return 0;
   1960   Inst* producer = &bl->insts[i - 1u];
   1961   Operand ret_dst = *ret_op;
   1962   ret_dst.v.reg = ret_reg;
   1963   int swap_binop = 0;
   1964   if (producer->nopnds < 1 || !same_phys_reg(&producer->opnds[0], ret_op) ||
   1965       !retarget_producer_legal(producer, &ret_dst, &swap_binop))
   1966     return 0;
   1967   if (swap_binop) {
   1968     Operand tmp = producer->opnds[1];
   1969     producer->opnds[1] = producer->opnds[2];
   1970     producer->opnds[2] = tmp;
   1971   }
   1972   producer->opnds[0] = ret_dst;
   1973   *ret_op = ret_dst;
   1974   return 1;
   1975 }
   1976 
   1977 /* ---- Rewrite 5: constant-fold a convert of a load_imm into a load_imm ----
   1978  *
   1979  * `load_imm rT,k ; convert rD,rT` where the convert is a bit-preserving
   1980  * integer/pointer move collapses to `load_imm rD,k'` (k' = convert applied to
   1981  * k). The original load_imm, now dead, is removed by post-combine DCE.
   1982  *
   1983  * This is the convert-shaped sibling of the load_imm-into-copy fold (see
   1984  * try_substitute / combine_subst_slot). It matters for pointer-typed constant
   1985  * call args (e.g. NewTreeNode((void*)0, ...)): the arg-register hint reaches
   1986  * the convert's def but not its load_imm source, so without this the source
   1987  * lands in a scratch and the convert emits a `mov rD, scratch`. Folding lets
   1988  * the load_imm inherit the convert's (hinted) dst directly — `movz x0, 0`. */
   1989 static int try_fold_const_convert(CombineCtx* ctx, Inst* in, i32 i) {
   1990   if ((IROp)in->op != IR_CONVERT || in->nopnds < 2) return 0;
   1991   if (in->opnds[0].kind != OPK_REG || in->opnds[1].kind != OPK_REG) return 0;
   1992   /* Integer/pointer domain only: a load_imm produces an int-class value, and
   1993    * the bit-preserving convert kinds we fold (BITCAST/ZEXT/SEXT/TRUNC) keep it
   1994    * int-class. A class change (RC_INT->RC_FP, e.g. an int->float bitcast) is
   1995    * not bit-preserving and must not collapse to a load_imm. */
   1996   if (in->opnds[0].cls != RC_INT || in->opnds[1].cls != RC_INT) return 0;
   1997 
   1998   i32 prod_idx = ctx_producer_of(ctx, in->opnds[1].cls, in->opnds[1].v.reg);
   1999   if (prod_idx < 0 || prod_idx >= i) return 0;
   2000   Inst* prod = &ctx->bl->insts[prod_idx];
   2001   if ((IROp)prod->op != IR_LOAD_IMM || prod->nopnds < 1 ||
   2002       prod->opnds[0].kind != OPK_REG ||
   2003       !same_phys_reg(&prod->opnds[0], &in->opnds[1]))
   2004     return 0;
   2005 
   2006   u32 sb = combine_scalar_width_bytes(ctx->f, in->opnds[1].type);
   2007   u32 db = combine_scalar_width_bytes(ctx->f, in->opnds[0].type);
   2008   i64 value = 0;
   2009   if (!const_convert_value((ConvKind)in->extra.imm, prod->extra.imm, sb, db,
   2010                            &value))
   2011     return 0;
   2012 
   2013   /* Single-use + not-live-out gate on the load_imm dst, mirroring the SK_IMM
   2014    * gate in try_substitute_for_reg: only fold when this convert is the sole
   2015    * use, so we replace rather than duplicate the materialization. */
   2016   Operand src_def = prod->opnds[0];
   2017   int killed = 0;
   2018   if (count_uses_in_live_range(ctx->f, ctx->bl, prod_idx, &src_def, &killed) !=
   2019       1)
   2020     return 0;
   2021   if (!killed && opt_block_live_out_has_phys_reg(ctx->f, ctx->hard_live,
   2022                                                  ctx->bl->id, &src_def))
   2023     return 0;
   2024 
   2025   /* Rewrite the convert in place into a load_imm of the converted constant.
   2026    * The dst operand (opnds[0]) is the hinted arg/return reg and is kept; the
   2027    * source operand is dropped. The dead load_imm at prod_idx is left for DCE.
   2028    */
   2029   in->op = (u16)IR_LOAD_IMM;
   2030   in->type = in->opnds[0].type;
   2031   in->nopnds = 1;
   2032   in->extra.imm = value;
   2033   ctx->block_change_p = 1;
   2034   return 1;
   2035 }
   2036 
   2037 /* ---- Rewrite 7 (W6): cmp-immediate folding ----
   2038  *
   2039  * O1.md W6. A small constant that feeds a compare can be materialized into a
   2040  * register too late for the emitter's inline-immediate path — e.g. when the
   2041  * `load_imm` of the constant is separated from the `cmp` by a call barrier, the
   2042  * constant survives in a callee-saved register and the compare reads it as a
   2043  * register (`movz w20,#7 ; ... ; cmp w19,w20`). The emitter already routes the
   2044  * compare's immediate slot through `operand_imm_or_reg(..., NATIVE_IMM_CMP)`, so
   2045  * presenting the value as an OPK_IMM there collapses it to `cmp w19,#7`.
   2046  *
   2047  * We track, per integer hard register, the constant a reaching IR_LOAD_IMM put
   2048  * there (const_val / const_valid). On a cmp/cmp_branch we replace an immediate-
   2049  * slot register operand holding a target-legal constant with that immediate.
   2050  * Tracking is invalidated precisely (opt_inst_reg_effects gives each inst's
   2051  * def + clobber set), so a constant in a callee-saved register survives calls
   2052  * that preserve it. Linear: O(1) bookkeeping per inst. */
   2053 
   2054 /* Record / invalidate the per-register known-constant state after visiting the
   2055  * inst at index `i`. Must run for EVERY inst (called from the forward driver),
   2056  * so the tracking stays sound across barriers and redefinitions. */
   2057 static void ctx_track_const(CombineCtx* ctx, const Inst* in,
   2058                             const OptHardRegSet* kills) {
   2059   /* Any register this inst defines or clobbers no longer holds its old known
   2060    * constant. (RC_INT only — cmp immediates are integer.) */
   2061   ctx->const_valid &= ~kills->cls[RC_INT];
   2062   /* A load_imm of an integer hard register records the new constant. */
   2063   if ((IROp)in->op == IR_LOAD_IMM && in->nopnds >= 1 &&
   2064       in->opnds[0].kind == OPK_REG && in->opnds[0].cls == RC_INT &&
   2065       in->opnds[0].v.reg < OPT_MAX_HARD_REGS) {
   2066     Reg r = in->opnds[0].v.reg;
   2067     ctx->const_val[r] = in->extra.imm;
   2068     ctx->const_valid |= 1u << r;
   2069   }
   2070 }
   2071 
   2072 static int reg_known_const(const CombineCtx* ctx, const Operand* op, i64* out) {
   2073   if (!op || op->kind != OPK_REG || op->cls != RC_INT) return 0;
   2074   if (op->v.reg >= OPT_MAX_HARD_REGS) return 0;
   2075   if (!(ctx->const_valid & (1u << op->v.reg))) return 0;
   2076   *out = ctx->const_val[op->v.reg];
   2077   return 1;
   2078 }
   2079 
   2080 /* Fold the immediate-slot operand of a cmp/cmp_branch from a register holding a
   2081  * known constant into an inline OPK_IMM, when the target can encode it. The
   2082  * immediate slot is opnds[2] for IR_CMP and opnds[1] for IR_CMP_BRANCH (opnds
   2083  * before it are the materialized LHS). */
   2084 static int try_cmp_imm_fold(CombineCtx* ctx, Inst* in) {
   2085   if (!ctx->target || !ctx->target->imm_legal) return 0;
   2086   u32 slot;
   2087   if ((IROp)in->op == IR_CMP)
   2088     slot = 2u;
   2089   else if ((IROp)in->op == IR_CMP_BRANCH)
   2090     slot = 1u;
   2091   else
   2092     return 0;
   2093   if (slot >= in->nopnds) return 0;
   2094   Operand* op = &in->opnds[slot];
   2095   i64 k;
   2096   if (!reg_known_const(ctx, op, &k)) return 0;
   2097   u32 cmpop = (u32)in->extra.imm;
   2098   if (!ctx->target->imm_legal(ctx->target, NATIVE_IMM_CMP, cmpop, op->type, k))
   2099     return 0;
   2100   KitCgTypeId type = op->type;
   2101   u8 cls = op->cls;
   2102   memset(op, 0, sizeof *op);
   2103   op->kind = OPK_IMM;
   2104   op->cls = cls;
   2105   op->type = type;
   2106   op->v.imm = k;
   2107   ctx->block_change_p = 1;
   2108   return 1;
   2109 }
   2110 
   2111 /* ---- per-BB driver ---- */
   2112 
   2113 static int opt_combine_fold_block(Func* f, Block* bl,
   2114                                   const OptHardBlockLive* hard_live,
   2115                                   NativeTarget* target) {
   2116   enum { enable_o1_combine_rewrites = 1 };
   2117   enum { enable_o1_sink_rewrites = 1 };
   2118   CombineCtx ctx;
   2119   ctx.f = f;
   2120   ctx.bl = bl;
   2121   ctx.target = target;
   2122   ctx.hard_live = hard_live;
   2123   ctx_reset(&ctx);
   2124 
   2125   for (i32 i = 0; i < (i32)bl->ninsts; ++i) {
   2126     Inst* in = &bl->insts[i];
   2127 
   2128     if (enable_o1_combine_rewrites && try_ret_retarget(f, bl, i)) {
   2129       ctx.block_change_p = 1;
   2130       /* The producer's destination changed. Remove every old effect no longer
   2131        * present, then record its canonical post-rewrite effects. */
   2132       Inst* prev = &bl->insts[i - 1];
   2133       OptHardRegSet kills;
   2134       opt_inst_reg_kills(f, prev, &kills);
   2135       for (u8 c = 0; c < OPT_REG_CLASSES; ++c) {
   2136         for (Reg r = 0; r < OPT_MAX_HARD_REGS; ++r) {
   2137           if (ctx.last_def[c][r] == i - 1 &&
   2138               !(kills.cls[c] & (1u << r))) {
   2139             Operand probe;
   2140             memset(&probe, 0, sizeof probe);
   2141             probe.kind = OPK_REG;
   2142             probe.cls = c;
   2143             probe.v.reg = r;
   2144             ctx_restore_removed_def(&ctx, &probe, i - 1);
   2145           }
   2146         }
   2147       }
   2148       ctx_record(&ctx, prev, &kills, i - 1);
   2149     }
   2150 
   2151     /* Skip NOPs left by prior sink rewrites. */
   2152     if ((IROp)in->op == IR_NOP) continue;
   2153 
   2154     if (enable_o1_sink_rewrites && try_sink(&ctx, in, i)) {
   2155       /* sink NOP'd the copy and updated ctx. */
   2156       continue;
   2157     }
   2158 
   2159     if (enable_o1_combine_rewrites) {
   2160       /* W1a addr-of CSE first: if it rewrites the addr_of into a copy, record
   2161        * it as an ordinary reg def (not as an addr-of producer) below. */
   2162       try_addr_of_cse(&ctx, in, i);
   2163       try_fold_const_convert(&ctx, in, i);
   2164       try_combine_exts(&ctx, in, i);
   2165       /* L4: collapse a double-cset bool re-normalize (outer IR_CMP of an inner
   2166        * 0/1 bool against 0/1) before L2/substitution see the outer's consumer. */
   2167       try_drop_double_cmp(&ctx, in, i);
   2168       /* L2: fuse a same-block cmp feeding the IR_CMP_BRANCH terminator into a
   2169        * direct relational branch (run before try_cmp_imm_fold so the cmp's
   2170        * immediate slot still folds on the fused branch). */
   2171       try_fuse_cmp_branch(&ctx, in, i);
   2172       try_substitute(&ctx, in, i);
   2173       try_addr_synth(&ctx, in, i);
   2174       /* L7: fold a single-use left shift into this binop's shifted-register
   2175        * form. Run after substitution (so a copy-propagated rhs is resolved)
   2176        * and after addr-synth (address shifts go into the EA, not the ALU
   2177        * rider), and before the compute-CSE record below so the ridered operand
   2178        * shape is what gets recorded. */
   2179       try_fold_shift_into_alu(&ctx, in, i);
   2180       /* W5: same-block redundant-load + pure-compute reuse. Run after
   2181        * addr-synth so both the recorded entries and this lookup see the
   2182        * canonical (post-fold) address/operand shapes. Each turns the redundant
   2183        * inst into an IR_COPY off the still-live earlier producer; copy-prop +
   2184        * DCE then retire it (worst case: a same-cost register move). */
   2185       try_local_load_cse(&ctx, in, i);
   2186       try_local_compute_cse(&ctx, in, i);
   2187       /* W6: fold a register holding a known constant into a compare's inline
   2188        * immediate slot (reads the constant tracker maintained below). */
   2189       try_cmp_imm_fold(&ctx, in);
   2190     }
   2191 
   2192     /* Track this inst as an addr-of producer for later W1a CSE (only when it is
   2193      * still an addr-of: a prior rewrite may have turned it into a copy). Same
   2194      * for the W5 load / pure-compute rings (recorded only when the inst is
   2195      * still a load / binop / unop after the rewrites above). */
   2196     addr_cse_record(&ctx, in, i);
   2197     load_cse_record(&ctx, in, i);
   2198     compute_cse_record(&ctx, in, i);
   2199     {
   2200       OptHardRegSet kills;
   2201       opt_inst_reg_kills(f, in, &kills);
   2202       ctx_record(&ctx, in, &kills, i);
   2203       /* W6: update the per-register known-constant tracker for this inst
   2204        * (after rewrites, so a load_imm produced/rewritten here is recorded). */
   2205       ctx_track_const(&ctx, in, &kills);
   2206     }
   2207   }
   2208   return ctx.block_change_p;
   2209 }
   2210 
   2211 static int opt_combine_compact_block(Func* f, Block* bl) {
   2212   u32 w = 0;
   2213   int changed = 0;
   2214   for (u32 i = 0; i < bl->ninsts; ++i) {
   2215     Inst* in = &bl->insts[i];
   2216 
   2217     /* Drop NOPs (e.g. copies sunk by try_sink). */
   2218     if ((IROp)in->op == IR_NOP) {
   2219       changed = 1;
   2220       continue;
   2221     }
   2222 
   2223     if ((IROp)in->op == IR_COPY && in->nopnds == 2 &&
   2224         same_reg_operand(&in->opnds[0], &in->opnds[1])) {
   2225       changed = 1;
   2226       continue;
   2227     }
   2228 
   2229     if (w) {
   2230       Inst* prev = &bl->insts[w - 1u];
   2231       SpillAccess prev_access;
   2232       SpillAccess in_access;
   2233       int prev_is_spill = direct_spill_access(f, prev, &prev_access);
   2234       int in_is_spill = direct_spill_access(f, in, &in_access);
   2235       if (prev_is_spill && in_is_spill &&
   2236           prev_access.kind == SPILL_ACCESS_STORE &&
   2237           in_access.kind == SPILL_ACCESS_LOAD &&
   2238           same_spill_shape(&prev_access, &in_access) &&
   2239           same_reg_operand(prev_access.value, in_access.value)) {
   2240         changed = 1;
   2241         continue;
   2242       }
   2243       /* L1 (O1-PATTERNS): store->load forwarding across a register mismatch.
   2244        * `str rX,[slot]; ldr rY,[slot]` (rX != rY, same slot+size) reloads a
   2245        * value the just-stored register rX still holds. The store and load are
   2246        * adjacent in the COMPACTED stream (prev is the immediately-preceding
   2247        * kept inst; only NOPs — which clobber nothing — may have sat between
   2248        * them), so rX is unchanged and the slot is unwritten between the two:
   2249        * the exact no-clobber adjacency the same-reg case above relies on.
   2250        * Rewrite the reload in place to `copy rY <- rX`; the next fold iteration
   2251        * copy-propagates rX into rY's uses (often deleting the copy), and once
   2252        * rX is dead with the slot unread, W8 stack-DSE + mir_dce retire the
   2253        * store. Guard: only when the stored value is a register of the load
   2254        * dst's class (an immediate/odd-class store is left as a real reload). */
   2255       if (prev_is_spill && in_is_spill &&
   2256           prev_access.kind == SPILL_ACCESS_STORE &&
   2257           in_access.kind == SPILL_ACCESS_LOAD &&
   2258           same_spill_shape(&prev_access, &in_access) &&
   2259           prev_access.value->kind == OPK_REG &&
   2260           in_access.value->kind == OPK_REG &&
   2261           prev_access.value->cls == in_access.value->cls) {
   2262         in->op = (u16)IR_COPY;
   2263         in->opnds[1] = *prev_access.value;
   2264         in->nopnds = 2;
   2265         memset(&in->extra, 0, sizeof in->extra);
   2266         changed = 1;
   2267         bl->insts[w++] = *in;
   2268         continue;
   2269       }
   2270       if (prev_is_spill && in_is_spill &&
   2271           prev_access.kind == SPILL_ACCESS_LOAD &&
   2272           in_access.kind == SPILL_ACCESS_STORE &&
   2273           same_spill_shape(&prev_access, &in_access) &&
   2274           same_reg_operand(prev_access.value, in_access.value)) {
   2275         changed = 1;
   2276         continue;
   2277       }
   2278       if (prev_is_spill && in_is_spill &&
   2279           prev_access.kind == SPILL_ACCESS_LOAD &&
   2280           in_access.kind == SPILL_ACCESS_LOAD &&
   2281           same_spill_shape(&prev_access, &in_access) &&
   2282           same_reg_operand(prev_access.value, in_access.value)) {
   2283         changed = 1;
   2284         continue;
   2285       }
   2286       if (prev_is_spill && in_is_spill &&
   2287           prev_access.kind == SPILL_ACCESS_STORE &&
   2288           in_access.kind == SPILL_ACCESS_STORE &&
   2289           same_spill_shape(&prev_access, &in_access)) {
   2290         bl->insts[w - 1u] = *in;
   2291         changed = 1;
   2292         continue;
   2293       }
   2294     }
   2295 
   2296     bl->insts[w++] = *in;
   2297   }
   2298   bl->ninsts = w;
   2299   return changed;
   2300 }
   2301 
   2302 /* ---- W8: same-block stack dead-store elimination ----
   2303  *
   2304  * O1.md W8. A forward MIR scan deletes a spill store when a later store in the
   2305  * same block fully overwrites the same stack slot before any possible read.
   2306  * Deliberately narrower than the parked O2 DSE: V1 handles exact direct frame
   2307  * spill stores only (location-MIR IR_COPY/OPK_STACK, exact same
   2308  * {slot,size,addr_space}).
   2309  *
   2310  * Fast design (no per-block hash table, no O(nslots) clear): dense per-FrameSlot
   2311  * side arrays plus a `gen` counter that is bumped at block start AND on any
   2312  * memory barrier instead of clearing the arrays. A slot's pending store is
   2313  * "live" iff seen_gen[slot] == gen. The correctness boundary is aliasing and
   2314  * partial overlap: any unknown memory op, call, asm, intrinsic, atomic,
   2315  * volatile, aggregate, or non-direct local access bumps gen (forgetting all
   2316  * pending stores in O(1)); a different size/addr-space/bit-field shape on the
   2317  * same slot clears just that slot; a direct read of the slot clears it. A
   2318  * deleted store is left as IR_NOP for the existing compaction + mir_dce. */
   2319 
   2320 typedef struct StackDseState {
   2321   u32* seen_gen;    /* per slot (1-based): the gen at which last_store_idx was set */
   2322   i32* last_idx;    /* per slot: index of the pending (not-yet-killed) store */
   2323   u32* last_size;   /* per slot: byte size of the pending store */
   2324   u16* last_aspace; /* per slot: addr_space of the pending store */
   2325   u32 nslots;       /* f->nframe_slots */
   2326   u32 gen;
   2327 } StackDseState;
   2328 
   2329 /* A direct location-MIR spill store (IR_COPY into OPK_STACK). */
   2330 static int dse_direct_spill_store(Func* f, const Inst* in,
   2331                                   SpillAccess* access) {
   2332   SpillAccess found;
   2333   if (!direct_spill_access(f, in, &found) ||
   2334       found.kind != SPILL_ACCESS_STORE)
   2335     return 0;
   2336   if (access) *access = found;
   2337   return 1;
   2338 }
   2339 
   2340 typedef struct StackDseOperandCtx {
   2341   StackDseState* state;
   2342   int forget_defs;
   2343 } StackDseOperandCtx;
   2344 
   2345 /* Every OPK_STACK use is a real read of its spill home, including locations
   2346  * nested in an indirect address or an aux descriptor. A non-candidate stack
   2347  * definition also ends the simple pending-store chain. Use the canonical
   2348  * operand walker so DSE cannot silently acquire a narrower view of MIR than
   2349  * liveness/emission. For a candidate IR_COPY store, its one destination is the
   2350  * new pending store and is therefore retained; any nested source use is still
   2351  * forgotten before that store is recorded. */
   2352 static void stack_dse_forget_operand(Func* f, Inst* in, Operand* op,
   2353                                      int is_def, void* arg) {
   2354   StackDseOperandCtx* ctx = (StackDseOperandCtx*)arg;
   2355   StackDseState* st = ctx ? ctx->state : NULL;
   2356   FrameSlot slot;
   2357   u32 si;
   2358   (void)f;
   2359   (void)in;
   2360   if (!st || !op || op->kind != OPK_STACK || (is_def && !ctx->forget_defs))
   2361     return;
   2362   slot = op->v.frame_slot;
   2363   if (slot == FRAME_SLOT_NONE || slot > st->nslots) return;
   2364   si = slot - 1u;
   2365   if (st->seen_gen[si] == st->gen) st->seen_gen[si] = st->gen - 1u;
   2366 }
   2367 
   2368 static void stack_dse_block(Func* f, Block* bl, StackDseState* st,
   2369                             int* changed) {
   2370   if (st->nslots == 0) return;
   2371   ++st->gen; /* block start: forget all pending stores */
   2372   for (u32 i = 0; i < bl->ninsts; ++i) {
   2373     Inst* in = &bl->insts[i];
   2374     if ((IROp)in->op == IR_NOP) continue;
   2375 
   2376     SpillAccess access;
   2377     int candidate_store = dse_direct_spill_store(f, in, &access);
   2378     StackDseOperandCtx operand_ctx;
   2379     operand_ctx.state = st;
   2380     operand_ctx.forget_defs = !candidate_store;
   2381     opt_walk_inst_operands(f, in, stack_dse_forget_operand, &operand_ctx);
   2382 
   2383     /* Accepted direct spill store: try to kill a prior pending store of the
   2384      * exact same key, then record this one. */
   2385     if (candidate_store && access.slot >= 1u && access.slot <= st->nslots) {
   2386       u32 sz = access.size;
   2387       u16 as = access.addr_space;
   2388       u32 si = access.slot - 1u;
   2389       if (st->seen_gen[si] == st->gen) {
   2390         if (st->last_size[si] == sz && st->last_aspace[si] == as) {
   2391           /* Same {slot,size,addr_space}: the prior store is fully overwritten
   2392            * before any read -> dead. NOP it; this store becomes the pending
   2393            * one. */
   2394           Inst* dead = &bl->insts[st->last_idx[si]];
   2395           dead->op = (u16)IR_NOP;
   2396           dead->def = VAL_NONE;
   2397           dead->ndefs = 0;
   2398           dead->defs = NULL;
   2399           dead->nopnds = 0;
   2400           dead->opnds = NULL;
   2401           *changed = 1;
   2402         }
   2403         /* else: different size/addr-space shape on this slot -> the entry is
   2404          * about to be replaced by this store's shape anyway; fall through. */
   2405       }
   2406       st->seen_gen[si] = st->gen;
   2407       st->last_idx[si] = (i32)i;
   2408       st->last_size[si] = sz;
   2409       st->last_aspace[si] = as;
   2410       continue;
   2411     }
   2412 
   2413     /* The canonical operand walk above already consumed every explicit spill
   2414      * read, including stack-valued arithmetic, aux operands, and spilled
   2415      * indirect components. Any remaining plain memory read cannot alias an
   2416      * FS_SPILL slot: those homes are allocator-private and never address-taken.
   2417      * A struct-field/global/heap read is therefore transparent to this
   2418      * spill-only DSE. */
   2419     /* Any other memory writer is a full barrier: a non-spill / non-direct store
   2420      * (handled per-slot above only for direct spill stores), an aggregate op, a
   2421      * call/asm/intrinsic, or an atomic/volatile access. These could, in
   2422      * principle, touch state this narrow pass does not model, so forget every
   2423      * pending store in O(1) by bumping gen. (Plain register-only insts —
   2424      * binop/copy/convert/load_imm/etc. — are transparent.) */
   2425     if (inst_writes_memory(in)) {
   2426       ++st->gen;
   2427       continue;
   2428     }
   2429 
   2430     if (inst_reads_memory(in)) continue;
   2431   }
   2432 }
   2433 
   2434 void opt_combine(Func* f, NativeTarget* target) {
   2435   if (!f) return;
   2436   opt_analysis_invalidate(f, OPT_ANALYSIS_DEF_USE);
   2437   OptHardBlockLive* hard_live = opt_maybe_build_hard_live(f);
   2438 
   2439   /* W8 side arrays, allocated once per function (frame slots are dense). */
   2440   StackDseState dse;
   2441   memset(&dse, 0, sizeof dse);
   2442   dse.nslots = f->nframe_slots;
   2443   if (dse.nslots) {
   2444     dse.seen_gen = arena_array(f->arena, u32, dse.nslots);
   2445     dse.last_idx = arena_array(f->arena, i32, dse.nslots);
   2446     dse.last_size = arena_array(f->arena, u32, dse.nslots);
   2447     dse.last_aspace = arena_array(f->arena, u16, dse.nslots);
   2448     memset(dse.seen_gen, 0, sizeof(u32) * dse.nslots);
   2449     dse.gen = 0;
   2450   }
   2451 
   2452   /* Per-BB fixpoint, matching MIR's combine loop (mir-gen.c:9037-9038). */
   2453   /* Per-BB fixpoint with a defensive iteration cap. Each rewrite is supposed
   2454    * to be monotonic (it only fires when it strictly improves the IR), so we
   2455    * shouldn't hit the cap in practice — but a noisy abort beats a hang if a
   2456    * future rewrite accidentally oscillates. */
   2457   enum { MAX_COMBINE_ITERS = 64 };
   2458   for (u32 b = 0; b < f->nblocks; ++b) {
   2459     Block* bl = &f->blocks[b];
   2460     for (int iter = 0; iter < MAX_COMBINE_ITERS; ++iter) {
   2461       int folded = opt_combine_fold_block(f, bl, hard_live, target);
   2462       /* W8 same-block stack DSE right before compaction; NOP'd stores are
   2463        * dropped by opt_combine_compact_block (and their dead source producers
   2464        * by the following mir_dce). */
   2465       int dse_changed = 0;
   2466       stack_dse_block(f, bl, &dse, &dse_changed);
   2467       int compacted = opt_combine_compact_block(f, bl);
   2468       if (!folded && !dse_changed && !compacted) break;
   2469     }
   2470   }
   2471 }