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); }