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 }