kit

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

pass_simplify.c (20427B)


      1 #include <kit/cg.h>
      2 #include <string.h>
      3 
      4 #include "cg/ir_eval.h"
      5 #include "opt/opt_internal.h"
      6 
      7 /* O1.md / O1-PATTERNS.md L9: same-block forward LOAD_IMM -> known-value tracker
      8  * for the local (pre-SSA) simplify pass. A bounded per-PReg `reg -> constant`
      9  * map, populated when an instruction is an IR_LOAD_IMM / IR_CONST_I and
     10  * invalidated whenever any later instruction in the block (re)defines that PReg.
     11  * It is rebuilt from empty at every block boundary -- a single forward scan, no
     12  * SSA, no cross-block reasoning, so -O1 stays linear. Feeding it into
     13  * operand_const lets the existing BO_UDIV/BO_SDIV/BO_IMUL identity folds and
     14  * simplify_cmp fire (and a const-op-const fold below) when the operand is a
     15  * constant materialized into a register rather than an inline OPK_IMM -- the
     16  * classic `sizeof(X)/sizeof(Y)` and the SIZE_MAX/sizeof(T) >= N growvector
     17  * guard, which kit otherwise leaves as a runtime udiv + cmp.
     18  *
     19  * NULL in the SSA pass (opt_simplify), which uses the def-use index instead. */
     20 typedef struct LocalConst {
     21   i64* val;  /* val[r] is the constant in PReg r when known[r] */
     22   u8* known; /* per-PReg validity bit */
     23   u32 n;     /* == f->npregs; PRegs >= n are never tracked */
     24 } LocalConst;
     25 
     26 /* simplify's PTR-aware width derivation is its own type policy; the masking and
     27  * commutativity below are the shared cg/ir_eval core (src/cg/ir_eval.h). */
     28 static u32 simplify_width(Func* f, KitCgTypeId ty) {
     29   u32 width = kit_cg_type_int_width((KitCompiler*)f->c, ty);
     30   if (width && width <= 64u) return width;
     31   if (kit_cg_type_kind((KitCompiler*)f->c, ty) == KIT_CG_TYPE_PTR) {
     32     u64 size = kit_cg_type_size((KitCompiler*)f->c, ty);
     33     if (size && size <= 8u) return (u32)(size * 8u);
     34   }
     35   return 0;
     36 }
     37 
     38 static int imm_value(Func* f, const Operand* op, u32 width, i64* out) {
     39   (void)f;
     40   if (!op || op->kind != OPK_IMM) return 0;
     41   *out = (i64)kit_ir_mask_width((u64)op->v.imm, width);
     42   return 1;
     43 }
     44 
     45 static Inst* def_inst(Func* f, Val v) {
     46   if (!f || v == VAL_NONE || v >= f->nvals) return NULL;
     47   u32 b = f->val_def_block[v];
     48   u32 i = f->val_def_inst[v];
     49   if (b >= f->nblocks || i >= f->blocks[b].ninsts) return NULL;
     50   Inst* in = &f->blocks[b].insts[i];
     51   return in->def == v ? in : NULL;
     52 }
     53 
     54 static int val_load_imm(Func* f, Val v, i64* out) {
     55   Inst* in = def_inst(f, v);
     56   if (!in || ((IROp)in->op != IR_LOAD_IMM && (IROp)in->op != IR_CONST_I))
     57     return 0;
     58   *out = in->extra.imm;
     59   return 1;
     60 }
     61 
     62 /* Same-block tracker lookup: is PReg `r` a currently-known constant? */
     63 static int local_const_lookup(const LocalConst* lc, Reg r, i64* out) {
     64   if (!lc || r == (Reg)REG_NONE || (u32)r >= lc->n || !lc->known[r]) return 0;
     65   *out = lc->val[r];
     66   return 1;
     67 }
     68 
     69 static int operand_const(Func* f, const LocalConst* lc, const Operand* op,
     70                          u32 width, i64* out) {
     71   if (imm_value(f, op, width, out)) return 1;
     72   if (!op || op->kind != OPK_REG) return 0;
     73   /* SSA pass: chase the unique def via the def-use index. Local pass: consult
     74    * the bounded same-block forward tracker. Exactly one of the two is active. */
     75   if (f->opt_reg_ssa) {
     76     if (!val_load_imm(f, (Val)op->v.reg, out)) return 0;
     77   } else {
     78     if (!local_const_lookup(lc, op->v.reg, out)) return 0;
     79   }
     80   *out = (i64)kit_ir_mask_width((u64)*out, width);
     81   return 1;
     82 }
     83 
     84 static int same_reg(const Operand* a, const Operand* b) {
     85   return a && b && a->kind == OPK_REG && b->kind == OPK_REG &&
     86          a->v.reg == b->v.reg;
     87 }
     88 
     89 static int same_shape(Func* f, Val dst, Val src) {
     90   if (dst == src) return 1;
     91   if (dst == VAL_NONE || src == VAL_NONE || dst >= f->nvals || src >= f->nvals)
     92     return 0;
     93   return f->val_type[dst] == f->val_type[src] &&
     94          f->val_cls[dst] == f->val_cls[src];
     95 }
     96 
     97 static void make_load_imm(Func* f, Inst* in, i64 value) {
     98   Operand* opnds = in->opnds;
     99   Val dst = in->def;
    100   KitCgTypeId ty = in->type;
    101   u8 cls = RC_INT;
    102   if (dst != VAL_NONE && dst < f->nvals) {
    103     ty = f->val_type[dst] ? f->val_type[dst] : ty;
    104     cls = f->val_cls[dst];
    105   } else if (in->nopnds >= 1) {
    106     ty = in->opnds[0].type;
    107     cls = in->opnds[0].cls;
    108   }
    109   if (!opnds) opnds = arena_array(f->arena, Operand, 1);
    110   memset(&opnds[0], 0, sizeof opnds[0]);
    111   opnds[0].kind = OPK_REG;
    112   opnds[0].type = ty;
    113   opnds[0].cls = cls;
    114   opnds[0].v.reg = (Reg)(dst != VAL_NONE ? dst : in->opnds[0].v.reg);
    115   in->op = IR_LOAD_IMM;
    116   in->type = ty;
    117   in->opnds = opnds;
    118   in->nopnds = 1;
    119   in->extra.imm = value;
    120 }
    121 
    122 static void make_copy(Func* f, Inst* in, const Operand* src) {
    123   Operand* opnds = in->opnds;
    124   Val dst = in->def;
    125   KitCgTypeId dst_ty = in->type;
    126   u8 dst_cls = RC_INT;
    127   if (dst != VAL_NONE && dst < f->nvals) {
    128     dst_ty = f->val_type[dst] ? f->val_type[dst] : dst_ty;
    129     dst_cls = f->val_cls[dst];
    130   } else if (in->nopnds >= 1) {
    131     dst_ty = in->opnds[0].type;
    132     dst_cls = in->opnds[0].cls;
    133   }
    134   if (!opnds) opnds = arena_array(f->arena, Operand, 2);
    135   memset(&opnds[0], 0, sizeof opnds[0]);
    136   opnds[0].kind = OPK_REG;
    137   opnds[0].type = dst_ty;
    138   opnds[0].cls = dst_cls;
    139   opnds[0].v.reg = (Reg)(dst != VAL_NONE ? dst : in->opnds[0].v.reg);
    140   opnds[1] = *src;
    141   in->op = IR_COPY;
    142   in->type = dst_ty;
    143   in->opnds = opnds;
    144   in->nopnds = 2;
    145 }
    146 
    147 static int convert_noop(Func* f, const Inst* in) {
    148   if (!in || (IROp)in->op != IR_CONVERT || in->nopnds < 2) return 0;
    149   KitCgTypeId dst_ty = in->opnds[0].type;
    150   KitCgTypeId src_ty = in->opnds[1].type;
    151   u32 w = simplify_width(f, dst_ty);
    152   if (!w || w != simplify_width(f, src_ty)) return 0;
    153   /* A same-width int/zext/trunc/bitcast keeps every bit, so it is a pure copy
    154    * even when the named types differ -- the common case being a pointer bitcast
    155    * (`int* -> char*`) the frontend records around `&*p`-style casts, which would
    156    * otherwise lower to a register move that copy-prop cannot then chase (the MIR
    157    * combine only forwards a convert into another convert). The one same-width
    158    * convert that is NOT a copy is a cross-class bitcast (int <-> float), which
    159    * really moves between register files; reject it by requiring the operands to
    160    * agree on float-ness. */
    161   int dst_flt = kit_cg_type_kind((KitCompiler*)f->c, dst_ty) == KIT_CG_TYPE_FLOAT;
    162   int src_flt = kit_cg_type_kind((KitCompiler*)f->c, src_ty) == KIT_CG_TYPE_FLOAT;
    163   if (dst_flt != src_flt) return 0;
    164   switch ((ConvKind)in->extra.imm) {
    165     case CV_ZEXT:
    166     case CV_SEXT:
    167     case CV_TRUNC:
    168     case CV_BITCAST:
    169       return 1;
    170     default:
    171       return 0;
    172   }
    173 }
    174 
    175 /* Constant integer divide/remainder at `width` bits. Mirrors the authoritative
    176  * constant-expression semantics in src/cg/const.c (const_divrem64) exactly:
    177  *
    178  *   - DIVISION BY ZERO IS NEVER FOLDED (returns 0): leaving the runtime
    179  *     udiv/sdiv in place preserves the program's fault behavior, matching what
    180  *     the existing inline-immediate path does (kit_ir_eval_binop also declines
    181  *     div/rem, and the identity folds only fire for a non-zero divisor).
    182  *   - Unsigned ops mask to `width` and divide/modulo the masked values.
    183  *   - Signed ops compute on the magnitudes and reapply the sign, so INT_MIN/-1
    184  *     wraps to INT_MIN (an == bn, no negate) -- the defined two's-complement
    185  *     result the hardware sdiv also produces, so the fold is value-preserving.
    186  *
    187  * Returns 1 and stores the masked result, or 0 (b==0, or not a div/rem). */
    188 static int simplify_divrem(BinOp op, u32 width, i64 a64, i64 b64, i64* out) {
    189   u64 mask = kit_ir_width_mask(width);
    190   u64 a = (u64)a64 & mask;
    191   u64 b = (u64)b64 & mask;
    192   if (b == 0) return 0; /* preserve the runtime trap -- do NOT fold /0 */
    193   switch (op) {
    194     case BO_UDIV:
    195       *out = (i64)((a / b) & mask);
    196       return 1;
    197     case BO_UREM:
    198       *out = (i64)((a % b) & mask);
    199       return 1;
    200     case BO_SDIV:
    201     case BO_SREM: {
    202       int an = width && ((a >> (width - 1u)) & 1u) != 0;
    203       int bn = width && ((b >> (width - 1u)) & 1u) != 0;
    204       u64 aa = an ? (((~a) + 1u) & mask) : a;
    205       u64 bb = bn ? (((~b) + 1u) & mask) : b;
    206       u64 r;
    207       if (bb == 0) return 0;
    208       if (op == BO_SDIV) {
    209         r = aa / bb;
    210         if (an != bn) r = ((~r) + 1u) & mask;
    211       } else {
    212         r = aa % bb;
    213         if (an) r = ((~r) + 1u) & mask;
    214       }
    215       *out = (i64)(r & mask);
    216       return 1;
    217     }
    218     default:
    219       return 0;
    220   }
    221 }
    222 
    223 /* Fold a fully-constant integer binop to its value. Delegates the wrapping
    224  * arithmetic/bitwise/shift ops to the shared cg/ir_eval core and the div/rem
    225  * ops (which that core deliberately declines) to simplify_divrem above, so the
    226  * /0 guard and signed/width semantics match the constant-expression evaluator
    227  * exactly. Returns 1 (and stores the masked result) only when the op is
    228  * genuinely constant-foldable; div-by-zero returns 0. */
    229 static int simplify_fold_const(BinOp op, u32 width, i64 a, i64 b, i64* out) {
    230   if (kit_ir_eval_binop(op, width, a, b, out)) return 1;
    231   return simplify_divrem(op, width, a, b, out);
    232 }
    233 
    234 /* Commutative integer binops the canonicalizer below may swap operands of: the
    235  * shared integer predicate (kit_ir_binop_is_commutative_int). Floating-point
    236  * commutative ops are intentionally excluded here -- the shared predicate
    237  * excludes them too: the backend routes FP through the fp branch (which doesn't
    238  * consult imm_legal) so canonicalization gains nothing, and IEEE-754 may
    239  * distinguish operand order for NaN payloads. Non-commutative ops (sub, shifts,
    240  * div/rem) are skipped. */
    241 
    242 static int simplify_binop(Func* f, const LocalConst* lc, Inst* in) {
    243   if (!in || (IROp)in->op != IR_BINOP || in->flags || in->nopnds < 3) return 0;
    244   if (in->opnds[0].kind != OPK_REG) return 0;
    245   u32 width = simplify_width(f, in->type ? in->type : in->opnds[0].type);
    246   if (!width) return 0;
    247 
    248   /* Canonicalize commutative binops to put any inline OPK_IMM on the rhs.
    249    * The native emitter and the loop-imm-hoist pass both inspect only opnds[2]
    250    * for imm-legality; putting the constant there lets aa64 strength-reduce
    251    * `mul x, 5` to a shifted-add and lets the hoist pass lift loop-invariant
    252    * non-foldable imms. Value-preserving by commutativity. */
    253   if (kit_ir_binop_is_commutative_int((BinOp)in->extra.imm) &&
    254       in->opnds[1].kind == OPK_IMM && in->opnds[2].kind != OPK_IMM) {
    255     Operand tmp = in->opnds[1];
    256     in->opnds[1] = in->opnds[2];
    257     in->opnds[2] = tmp;
    258   }
    259 
    260   Operand* a = &in->opnds[1];
    261   Operand* b = &in->opnds[2];
    262   i64 av = 0;
    263   i64 bv = 0;
    264   /* A constant operand is either an inline OPK_IMM or a register whose value is
    265    * known -- via the def-use index (SSA pass) or the same-block forward tracker
    266    * (local pass). operand_const handles both, masking to the op width. */
    267   int ac = operand_const(f, lc, a, width, &av);
    268   int bc = operand_const(f, lc, b, width, &bv);
    269   u64 all = kit_ir_width_mask(width);
    270 
    271   /* const op const -> the value. Folds the classic sizeof/sizeof and the
    272    * SIZE_MAX/sizeof guard's udiv into a single LOAD_IMM; the resulting constant
    273    * then lets simplify_cmp decide the always-true compare. simplify_fold_const
    274    * declines div-by-zero (and the non-arithmetic ops), so the runtime trap and
    275    * any non-foldable op fall through to the identity folds below unchanged. */
    276   if (ac && bc) {
    277     i64 r;
    278     if (simplify_fold_const((BinOp)in->extra.imm, width, av, bv, &r)) {
    279       make_load_imm(f, in, r);
    280       return 1;
    281     }
    282   }
    283 
    284   switch ((BinOp)in->extra.imm) {
    285     case BO_IADD:
    286       if (bc && bv == 0 && a->kind == OPK_REG) {
    287         make_copy(f, in, a);
    288         return 1;
    289       }
    290       if (ac && av == 0 && b->kind == OPK_REG) {
    291         make_copy(f, in, b);
    292         return 1;
    293       }
    294       break;
    295     case BO_ISUB:
    296       if (bc && bv == 0 && a->kind == OPK_REG) {
    297         make_copy(f, in, a);
    298         return 1;
    299       }
    300       if (same_reg(a, b)) {
    301         make_load_imm(f, in, 0);
    302         return 1;
    303       }
    304       break;
    305     case BO_IMUL:
    306       if (bc && bv == 1 && a->kind == OPK_REG) {
    307         make_copy(f, in, a);
    308         return 1;
    309       }
    310       if (ac && av == 1 && b->kind == OPK_REG) {
    311         make_copy(f, in, b);
    312         return 1;
    313       }
    314       if ((bc && bv == 0) || (ac && av == 0)) {
    315         make_load_imm(f, in, 0);
    316         return 1;
    317       }
    318       break;
    319     case BO_SDIV:
    320     case BO_UDIV:
    321       if (bc && bv == 1 && a->kind == OPK_REG) {
    322         make_copy(f, in, a);
    323         return 1;
    324       }
    325       break;
    326     case BO_SREM:
    327     case BO_UREM:
    328       if (bc && bv == 1) {
    329         make_load_imm(f, in, 0);
    330         return 1;
    331       }
    332       break;
    333     case BO_AND:
    334       if (bc && (u64)bv == all && a->kind == OPK_REG) {
    335         make_copy(f, in, a);
    336         return 1;
    337       }
    338       if (ac && av == (i64)all && b->kind == OPK_REG) {
    339         make_copy(f, in, b);
    340         return 1;
    341       }
    342       if ((bc && bv == 0) || (ac && av == 0)) {
    343         make_load_imm(f, in, 0);
    344         return 1;
    345       }
    346       if (same_reg(a, b)) {
    347         make_copy(f, in, a);
    348         return 1;
    349       }
    350       break;
    351     case BO_OR:
    352       if (bc && bv == 0 && a->kind == OPK_REG) {
    353         make_copy(f, in, a);
    354         return 1;
    355       }
    356       if (ac && av == 0 && b->kind == OPK_REG) {
    357         make_copy(f, in, b);
    358         return 1;
    359       }
    360       if ((bc && (u64)bv == all) || (ac && (u64)av == all)) {
    361         make_load_imm(f, in, (i64)all);
    362         return 1;
    363       }
    364       if (same_reg(a, b)) {
    365         make_copy(f, in, a);
    366         return 1;
    367       }
    368       break;
    369     case BO_XOR:
    370       if (bc && bv == 0 && a->kind == OPK_REG) {
    371         make_copy(f, in, a);
    372         return 1;
    373       }
    374       if (ac && av == 0 && b->kind == OPK_REG) {
    375         make_copy(f, in, b);
    376         return 1;
    377       }
    378       if (same_reg(a, b)) {
    379         make_load_imm(f, in, 0);
    380         return 1;
    381       }
    382       break;
    383     case BO_SHL:
    384     case BO_SHR_S:
    385     case BO_SHR_U:
    386       if (bc && bv == 0 && a->kind == OPK_REG) {
    387         make_copy(f, in, a);
    388         return 1;
    389       }
    390       break;
    391     default:
    392       break;
    393   }
    394   return 0;
    395 }
    396 
    397 /* addr_of [base + 0] with no index == base. The frontend leaves the `&*p` /
    398  * `p + 0` idiom as an explicit IR_ADDR_OF of a zero-offset indirect; without
    399  * this fold it lowers to `add base, #0` (a register move that copy-prop must
    400  * then chase). Folding it to a copy here collapses the address computation at
    401  * the source, on every consumer of the pipeline (O1 native, O2, interp tap).
    402  * Address-of-local (OPK_LOCAL) and global (OPK_GLOBAL) sources are untouched —
    403  * only a zero-offset register indirect is a pure copy of its base. */
    404 static int simplify_addr_of(Func* f, Inst* in) {
    405   (void)f;
    406   if (!in || (IROp)in->op != IR_ADDR_OF || in->flags || in->nopnds < 2) return 0;
    407   if (in->opnds[0].kind != OPK_REG) return 0;
    408   const Operand* src = &in->opnds[1];
    409   if (src->kind != OPK_INDIRECT) return 0;
    410   if (src->v.ind.ofs != 0 || src->v.ind.index != (Reg)REG_NONE) return 0;
    411   if (src->v.ind.base == (Reg)REG_NONE) return 0;
    412   Operand base;
    413   memset(&base, 0, sizeof base);
    414   base.kind = OPK_REG;
    415   base.cls = RC_INT;
    416   base.type = in->opnds[0].type;
    417   base.v.reg = src->v.ind.base;
    418   make_copy(f, in, &base);
    419   return 1;
    420 }
    421 
    422 static int simplify_cmp(Func* f, const LocalConst* lc, Inst* in) {
    423   if (!in || (IROp)in->op != IR_CMP || in->nopnds < 3) return 0;
    424   u32 width = simplify_width(f, in->opnds[1].type);
    425   if (!width) return 0;
    426 
    427   /* const cmp const -> its boolean. This is what decides the always-true
    428    * SIZE_MAX/sizeof(T) >= N growvector guard once the udiv above has folded its
    429    * dividend/divisor into a known constant; the dead arm then drops via the
    430    * existing branch/CFG cleanup. Mask/sign-extend handling lives in the shared
    431    * kit_ir_eval_cmp (signed predicates compare the sign-extended values). */
    432   {
    433     i64 av;
    434     i64 bv;
    435     i64 r;
    436     if (operand_const(f, lc, &in->opnds[1], width, &av) &&
    437         operand_const(f, lc, &in->opnds[2], width, &bv) &&
    438         kit_ir_eval_cmp((CmpOp)in->extra.imm, width, av, bv, &r)) {
    439       make_load_imm(f, in, r);
    440       return 1;
    441     }
    442   }
    443 
    444   if (!same_reg(&in->opnds[1], &in->opnds[2])) return 0;
    445   switch ((CmpOp)in->extra.imm) {
    446     case CMP_EQ:
    447     case CMP_LE_S:
    448     case CMP_LE_U:
    449     case CMP_GE_S:
    450     case CMP_GE_U:
    451       make_load_imm(f, in, 1);
    452       return 1;
    453     case CMP_NE:
    454     case CMP_LT_S:
    455     case CMP_LT_U:
    456     case CMP_GT_S:
    457     case CMP_GT_U:
    458       make_load_imm(f, in, 0);
    459       return 1;
    460     default:
    461       return 0;
    462   }
    463 }
    464 
    465 static int simplify_unop_ssa(Func* f, Inst* in) {
    466   if (!f->opt_reg_ssa || !in || (IROp)in->op != IR_UNOP || in->nopnds < 2 ||
    467       (UnOp)in->extra.imm != UO_BNOT || in->opnds[1].kind != OPK_REG)
    468     return 0;
    469   Inst* inner = def_inst(f, (Val)in->opnds[1].v.reg);
    470   if (!inner || (IROp)inner->op != IR_UNOP || inner->nopnds < 2 ||
    471       (UnOp)inner->extra.imm != UO_BNOT || inner->opnds[1].kind != OPK_REG)
    472     return 0;
    473   if (!same_shape(f, in->def, (Val)inner->opnds[1].v.reg)) return 0;
    474   make_copy(f, in, &inner->opnds[1]);
    475   return 1;
    476 }
    477 
    478 static int simplify_convert_chain_ssa(Func* f, Inst* in) {
    479   if (!f->opt_reg_ssa || !in || (IROp)in->op != IR_CONVERT || in->nopnds < 2 ||
    480       in->opnds[1].kind != OPK_REG)
    481     return 0;
    482   Inst* inner = def_inst(f, (Val)in->opnds[1].v.reg);
    483   if (!inner || (IROp)inner->op != IR_CONVERT || inner->nopnds < 2 ||
    484       inner->opnds[1].kind != OPK_REG)
    485     return 0;
    486   if ((ConvKind)in->extra.imm != CV_BITCAST ||
    487       (ConvKind)inner->extra.imm != CV_BITCAST)
    488     return 0;
    489   if (in->opnds[0].type != inner->opnds[1].type) return 0;
    490   if (!same_shape(f, in->def, (Val)inner->opnds[1].v.reg)) return 0;
    491   u32 dw = simplify_width(f, in->opnds[0].type);
    492   u32 sw = simplify_width(f, inner->opnds[1].type);
    493   if (!dw || dw != sw) return 0;
    494   make_copy(f, in, &inner->opnds[1]);
    495   return 1;
    496 }
    497 
    498 static int simplify_one(Func* f, const LocalConst* lc, Inst* in, int ssa) {
    499   switch ((IROp)in->op) {
    500     case IR_BINOP:
    501       return simplify_binop(f, lc, in);
    502     case IR_CMP:
    503       return simplify_cmp(f, lc, in);
    504     case IR_CONVERT:
    505       if (convert_noop(f, in)) {
    506         make_copy(f, in, &in->opnds[1]);
    507         return 1;
    508       }
    509       return ssa ? simplify_convert_chain_ssa(f, in) : 0;
    510     case IR_ADDR_OF:
    511       return simplify_addr_of(f, in);
    512     case IR_UNOP:
    513       return ssa ? simplify_unop_ssa(f, in) : 0;
    514     default:
    515       return 0;
    516   }
    517 }
    518 
    519 /* Record the constant a (possibly just-rewritten) LOAD_IMM / CONST_I puts in its
    520  * destination PReg, and invalidate the tracker entry for every other PReg the
    521  * instruction defines. Both kinds carry the immediate in extra.imm and the dst
    522  * PReg in opnds[0] (LOAD_IMM) -- the same shape make_load_imm produces, so a
    523  * binop/cmp this pass just folded to a constant becomes visible to the next
    524  * instruction in the same block (e.g. the SIZE_MAX/sizeof udiv feeding a cmp). */
    525 static void local_const_update(LocalConst* lc, Inst* in) {
    526   Reg made = (Reg)REG_NONE;
    527   if (((IROp)in->op == IR_LOAD_IMM || (IROp)in->op == IR_CONST_I) &&
    528       in->nopnds >= 1 && in->opnds[0].kind == OPK_REG &&
    529       in->opnds[0].cls == RC_INT) {
    530     made = in->opnds[0].v.reg;
    531     if (made != (Reg)REG_NONE && (u32)made < lc->n) {
    532       lc->val[made] = in->extra.imm;
    533       lc->known[made] = 1;
    534     }
    535   }
    536   /* Any def that is not the freshly-recorded constant invalidates its slot. */
    537   if (in->def != VAL_NONE && in->def != made && (u32)in->def < lc->n)
    538     lc->known[in->def] = 0;
    539   for (u32 i = 0; i < in->ndefs; ++i) {
    540     Reg d = (Reg)in->defs[i];
    541     if (d != made && d != (Reg)REG_NONE && (u32)d < lc->n) lc->known[d] = 0;
    542   }
    543 }
    544 
    545 static void simplify_run(Func* f, int ssa) {
    546   if (!f || f->opt_rewritten) return;
    547   if (ssa) opt_rebuild_def_use(f);
    548   int changed = 0;
    549   LocalConst lc;
    550   LocalConst* lcp = NULL;
    551   if (!ssa && f->npregs) {
    552     lc.n = f->npregs;
    553     lc.val = arena_array(f->arena, i64, lc.n);
    554     lc.known = arena_array(f->arena, u8, lc.n);
    555     lcp = &lc;
    556   }
    557   for (u32 b = 0; b < f->nblocks; ++b) {
    558     Block* bl = &f->blocks[b];
    559     /* The tracker is rebuilt from empty at each block boundary -- no cross-block
    560      * reasoning, so the pass stays a single linear forward scan per block. */
    561     if (lcp) memset(lcp->known, 0, sizeof(u8) * lcp->n);
    562     for (u32 i = 0; i < bl->ninsts; ++i) {
    563       Inst* in = &bl->insts[i];
    564       if (simplify_one(f, lcp, in, ssa)) changed = 1;
    565       if (lcp) local_const_update(lcp, in);
    566     }
    567   }
    568   if (changed) opt_analysis_invalidate(f, OPT_ANALYSIS_DEF_USE);
    569   if (ssa || changed) opt_rebuild_def_use(f);
    570 }
    571 
    572 void opt_simplify_local(Func* f) { simplify_run(f, 0); }
    573 
    574 void opt_simplify(Func* f) { simplify_run(f, 1); }