pass_jump.c (24895B)
1 #include <kit/cg.h> 2 #include <string.h> 3 4 #include "cg/ir_eval.h" 5 #include "opt/opt_internal.h" 6 7 #define BLOCK_NONE ((u32)~0u) 8 9 typedef struct JumpCleanupCtx { 10 Func* f; 11 u32* emit_index_by_block; 12 u32* forwarded_target_by_block; 13 u32* forward_path; 14 u8* has_label_addr_ref; 15 } JumpCleanupCtx; 16 17 static u32 emit_order_index(const JumpCleanupCtx* c, u32 block) { 18 if (block >= c->f->nblocks) return BLOCK_NONE; 19 return c->emit_index_by_block[block]; 20 } 21 22 static u32 next_emit_block(const JumpCleanupCtx* c, u32 block) { 23 u32 idx = emit_order_index(c, block); 24 if (idx == BLOCK_NONE || idx + 1u >= c->f->emit_order_n) return BLOCK_NONE; 25 return c->f->emit_order[idx + 1u]; 26 } 27 28 static void refresh_emit_index_range(JumpCleanupCtx* c, u32 first, u32 last) { 29 Func* f = c->f; 30 if (!f || first >= f->emit_order_n) return; 31 if (last >= f->emit_order_n) last = f->emit_order_n - 1u; 32 for (u32 i = first; i <= last; ++i) { 33 u32 b = f->emit_order[i]; 34 if (b < f->nblocks) c->emit_index_by_block[b] = i; 35 } 36 } 37 38 static void mark_label_addr_refs(Func* f, u8* refs) { 39 if (!f || !refs) return; 40 for (u32 b = 0; b < f->nblocks; ++b) { 41 Block* bl = &f->blocks[b]; 42 for (u32 i = 0; i < bl->ninsts; ++i) { 43 Inst* in = &bl->insts[i]; 44 switch ((IROp)in->op) { 45 case IR_LOAD_LABEL_ADDR: 46 if ((u32)in->extra.imm < f->nblocks) refs[(u32)in->extra.imm] = 1; 47 break; 48 case IR_LOCAL_STATIC_DATA_LABEL_ADDR: { 49 CgIrLocalStaticLabelAux* aux = 50 (CgIrLocalStaticLabelAux*)in->extra.aux; 51 if (aux && (u32)aux->target < f->nblocks) refs[(u32)aux->target] = 1; 52 break; 53 } 54 default: 55 break; 56 } 57 } 58 } 59 } 60 61 static int block_has_label_addr_ref(const JumpCleanupCtx* c, u32 block) { 62 if (!c || block >= c->f->nblocks) return 0; 63 return c->has_label_addr_ref && c->has_label_addr_ref[block]; 64 } 65 66 static JumpCleanupCtx jump_cleanup_ctx(Func* f) { 67 JumpCleanupCtx c; 68 c.f = f; 69 c.emit_index_by_block = 70 arena_array(f->arena, u32, f->nblocks ? f->nblocks : 1u); 71 c.forwarded_target_by_block = 72 arena_array(f->arena, u32, f->nblocks ? f->nblocks : 1u); 73 c.forward_path = arena_array(f->arena, u32, f->nblocks ? f->nblocks : 1u); 74 c.has_label_addr_ref = 75 arena_zarray(f->arena, u8, f->nblocks ? f->nblocks : 1u); 76 for (u32 b = 0; b < f->nblocks; ++b) c.emit_index_by_block[b] = BLOCK_NONE; 77 for (u32 b = 0; b < f->nblocks; ++b) 78 c.forwarded_target_by_block[b] = BLOCK_NONE; 79 for (u32 i = 0; i < f->emit_order_n; ++i) { 80 u32 b = f->emit_order[i]; 81 if (b < f->nblocks) c.emit_index_by_block[b] = i; 82 } 83 mark_label_addr_refs(f, c.has_label_addr_ref); 84 return c; 85 } 86 87 static int single_jump_block(const JumpCleanupCtx* c, u32 block, 88 u32* target_out) { 89 Func* f = c->f; 90 if (block >= f->nblocks) return 0; 91 Block* bl = &f->blocks[block]; 92 if (block_has_label_addr_ref(c, block)) return 0; 93 if (bl->ninsts != 1 || bl->nsucc != 1) return 0; 94 if ((IROp)bl->insts[0].op != IR_BR) return 0; 95 if (target_out) *target_out = bl->succ[0]; 96 return 1; 97 } 98 99 static int empty_fallthrough_block(const JumpCleanupCtx* c, u32 block, 100 u32* target_out) { 101 Func* f = c->f; 102 if (block >= f->nblocks || block == f->entry) return 0; 103 Block* bl = &f->blocks[block]; 104 if (block_has_label_addr_ref(c, block)) return 0; 105 if (bl->ninsts != 0 || bl->nsucc != 0) return 0; 106 u32 idx = emit_order_index(c, block); 107 if (idx == BLOCK_NONE || idx + 1u >= f->emit_order_n) return 0; 108 u32 next = f->emit_order[idx + 1u]; 109 if (next >= f->nblocks || next == block) return 0; 110 if (target_out) *target_out = next; 111 return 1; 112 } 113 114 static int passthrough_succ_block(const JumpCleanupCtx* c, u32 block, 115 u32* target_out) { 116 Func* f = c->f; 117 if (block >= f->nblocks || block == f->entry) return 0; 118 Block* bl = &f->blocks[block]; 119 if (block_has_label_addr_ref(c, block)) return 0; 120 if (bl->nsucc != 1) return 0; 121 if (bl->succ[0] == block) return 0; 122 if (bl->ninsts != 0) { 123 if (bl->ninsts != 1) return 0; 124 switch ((IROp)bl->insts[0].op) { 125 case IR_NOP: 126 case IR_SCOPE_BEGIN: 127 case IR_SCOPE_END: 128 break; 129 default: 130 return 0; 131 } 132 } 133 if (target_out) *target_out = bl->succ[0]; 134 return 1; 135 } 136 137 static u32 forward_jump_target_ex(JumpCleanupCtx* c, u32 target, 138 int allow_empty_fallthrough) { 139 u32 cur = target; 140 Func* f = c->f; 141 u32 npath = 0; 142 u32 result = target; 143 for (u32 step = 0; step < f->nblocks; ++step) { 144 u32 next = BLOCK_NONE; 145 if (cur >= f->nblocks) { 146 result = cur; 147 break; 148 } 149 if (c->forwarded_target_by_block[cur] != BLOCK_NONE) { 150 result = c->forwarded_target_by_block[cur]; 151 break; 152 } 153 if (!single_jump_block(c, cur, &next) && 154 (!allow_empty_fallthrough || 155 (!passthrough_succ_block(c, cur, &next) && 156 !empty_fallthrough_block(c, cur, &next)))) { 157 result = cur; 158 c->forwarded_target_by_block[cur] = result; 159 break; 160 } 161 c->forward_path[npath++] = cur; 162 if (next == cur) { 163 result = target; 164 break; 165 } 166 cur = next; 167 if (step + 1u == f->nblocks) result = target; 168 } 169 for (u32 i = 0; i < npath; ++i) 170 c->forwarded_target_by_block[c->forward_path[i]] = result; 171 return result; 172 } 173 174 static u32 forward_jump_target(JumpCleanupCtx* c, u32 target) { 175 return forward_jump_target_ex(c, target, 0); 176 } 177 178 static int invert_cmp(CmpOp op, CmpOp* out) { 179 switch (op) { 180 case CMP_EQ: 181 *out = CMP_NE; 182 return 1; 183 case CMP_NE: 184 *out = CMP_EQ; 185 return 1; 186 case CMP_LT_S: 187 *out = CMP_GE_S; 188 return 1; 189 case CMP_LE_S: 190 *out = CMP_GT_S; 191 return 1; 192 case CMP_GT_S: 193 *out = CMP_LE_S; 194 return 1; 195 case CMP_GE_S: 196 *out = CMP_LT_S; 197 return 1; 198 case CMP_LT_U: 199 *out = CMP_GE_U; 200 return 1; 201 case CMP_LE_U: 202 *out = CMP_GT_U; 203 return 1; 204 case CMP_GT_U: 205 *out = CMP_LE_U; 206 return 1; 207 case CMP_GE_U: 208 *out = CMP_LT_U; 209 return 1; 210 /* FP: negation flips ordered<->unordered (the NaN outcome flips too) as 211 * well as negating the relation. Kept byte-for-byte in sync with 212 * api_invert_cmp (src/cg/fold.c); see that function for the rationale. */ 213 case CMP_OEQ_F: 214 *out = CMP_UNE_F; 215 return 1; 216 case CMP_ONE_F: 217 *out = CMP_UEQ_F; 218 return 1; 219 case CMP_OLT_F: 220 *out = CMP_UGE_F; 221 return 1; 222 case CMP_OLE_F: 223 *out = CMP_UGT_F; 224 return 1; 225 case CMP_OGT_F: 226 *out = CMP_ULE_F; 227 return 1; 228 case CMP_OGE_F: 229 *out = CMP_ULT_F; 230 return 1; 231 case CMP_UEQ_F: 232 *out = CMP_ONE_F; 233 return 1; 234 case CMP_UNE_F: 235 *out = CMP_OEQ_F; 236 return 1; 237 case CMP_ULT_F: 238 *out = CMP_OGE_F; 239 return 1; 240 case CMP_ULE_F: 241 *out = CMP_OGT_F; 242 return 1; 243 case CMP_UGT_F: 244 *out = CMP_OLE_F; 245 return 1; 246 case CMP_UGE_F: 247 *out = CMP_OLT_F; 248 return 1; 249 default: 250 return 0; 251 } 252 } 253 254 static int block_has_only_pred(const Func* f, u32 block, u32 pred) { 255 if (block >= f->nblocks) return 0; 256 const Block* bl = &f->blocks[block]; 257 return bl->npreds == 1 && bl->preds && bl->preds[0] == pred; 258 } 259 260 static void cleanup_branch_targets(JumpCleanupCtx* c) { 261 Func* f = c->f; 262 for (u32 b = 0; b < f->nblocks; ++b) { 263 Block* bl = &f->blocks[b]; 264 u32 nsucc = 0; 265 if (!bl->ninsts) continue; 266 IROp op = (IROp)bl->insts[bl->ninsts - 1u].op; 267 switch (op) { 268 case IR_BR: 269 nsucc = bl->nsucc; 270 break; 271 case IR_CMP_BRANCH: 272 case IR_CONDBR: 273 nsucc = bl->nsucc ? 1u : 0u; 274 break; 275 default: 276 continue; 277 } 278 for (u32 s = 0; s < nsucc; ++s) { 279 u32 target = bl->succ[s]; 280 u32 forwarded = forward_jump_target_ex(c, target, 1); 281 if (forwarded < f->nblocks) bl->succ[s] = forwarded; 282 } 283 } 284 } 285 286 static void cleanup_invert_jump_fallthrough(JumpCleanupCtx* c) { 287 Func* f = c->f; 288 for (u32 b = 0; b < f->nblocks; ++b) { 289 Block* bl = &f->blocks[b]; 290 if (!bl->ninsts || bl->nsucc != 2) continue; 291 Inst* last = &bl->insts[bl->ninsts - 1u]; 292 if ((IROp)last->op != IR_CMP_BRANCH) continue; 293 294 u32 taken = bl->succ[0]; 295 u32 fallthrough = bl->succ[1]; 296 u32 next = next_emit_block(c, b); 297 u32 fallthrough_idx = emit_order_index(c, fallthrough); 298 u32 jump_target = BLOCK_NONE; 299 CmpOp inverted; 300 if (next != fallthrough) continue; 301 if (fallthrough_idx == BLOCK_NONE || 302 fallthrough_idx + 1u >= f->emit_order_n) 303 continue; 304 if (f->emit_order[fallthrough_idx + 1u] != taken) continue; 305 if (!block_has_only_pred(f, fallthrough, b)) continue; 306 if (!single_jump_block(c, fallthrough, &jump_target)) continue; 307 jump_target = forward_jump_target(c, jump_target); 308 if (jump_target >= f->nblocks) continue; 309 if (!invert_cmp((CmpOp)last->extra.imm, &inverted)) continue; 310 311 last->extra.imm = inverted; 312 bl->succ[0] = jump_target; 313 bl->succ[1] = taken; 314 } 315 } 316 317 static void cleanup_invert_taken_fallthrough(JumpCleanupCtx* c) { 318 Func* f = c->f; 319 for (u32 b = 0; b < f->nblocks; ++b) { 320 Block* bl = &f->blocks[b]; 321 if (!bl->ninsts || bl->nsucc != 2) continue; 322 Inst* last = &bl->insts[bl->ninsts - 1u]; 323 if ((IROp)last->op != IR_CMP_BRANCH) continue; 324 325 u32 taken = bl->succ[0]; 326 u32 fallthrough = bl->succ[1]; 327 if (next_emit_block(c, b) != taken) continue; 328 CmpOp inverted; 329 if (!invert_cmp((CmpOp)last->extra.imm, &inverted)) continue; 330 331 last->extra.imm = inverted; 332 bl->succ[0] = fallthrough; 333 bl->succ[1] = taken; 334 } 335 } 336 337 /* Greedy chain-extension reorder: pick the entry block, then repeatedly 338 * extend the current chain to its preferred unvisited successor. For 339 * conditional branches that's `succ[1]` (fallthrough); for IR_BR or any 340 * 1-succ block it's `succ[0]`. When the chain stalls, start a new one from 341 * the lowest unvisited block id. After this pass, every chained pair `(p, 342 * n)` is adjacent in emit_order, so the existing branch-invert and 343 * trailing-branch-strip cleanups can collapse the jump. 344 * 345 * kit's frontend loop lowering puts `inc` between `head` and `body` 346 * (because `continue` jumps to `inc`), forcing `b body` and `b inc` per 347 * iteration. Without reordering the trailing-branch strip can't fire — 348 * `body`'s next block in original order is `exit`, not `inc`. After 349 * reordering, body→inc is adjacent and the back-edge collapses. */ 350 static u32 chain_preferred_succ(const Block* bl) { 351 if (!bl->ninsts) { 352 return bl->nsucc ? bl->succ[0] : BLOCK_NONE; 353 } 354 IROp op = (IROp)bl->insts[bl->ninsts - 1u].op; 355 switch (op) { 356 case IR_CMP_BRANCH: 357 case IR_CONDBR: 358 return bl->nsucc >= 2u ? bl->succ[1] : BLOCK_NONE; 359 case IR_BR: 360 return bl->nsucc >= 1u ? bl->succ[0] : BLOCK_NONE; 361 default: 362 /* RET/SWITCH/INDIRECT_BRANCH: no implicit fallthrough preference. */ 363 return BLOCK_NONE; 364 } 365 } 366 367 static void cleanup_reorder_for_fallthrough(JumpCleanupCtx* c) { 368 Func* f = c->f; 369 if (f->nblocks < 2u || f->emit_order_n < 2u) return; 370 u8* visited = arena_zarray(f->arena, u8, f->nblocks); 371 u32* new_order = arena_array(f->arena, u32, f->emit_order_n); 372 u32 w = 0; 373 374 /* Entry must come first regardless. */ 375 u32 entry = f->entry; 376 if (entry >= f->nblocks) return; 377 new_order[w++] = entry; 378 visited[entry] = 1; 379 u32 cur = entry; 380 381 /* Extend by preferred successor. When the chain stalls, restart from the 382 * next unvisited block in original emit_order to preserve some locality. */ 383 u32 scan_cursor = 0; 384 for (;;) { 385 u32 next = chain_preferred_succ(&f->blocks[cur]); 386 if (next >= f->nblocks || visited[next]) { 387 /* Fall back: any unvisited successor of cur — extends the chain even 388 * if it's the "taken" arm of a conditional. The subsequent 389 * cleanup_invert_taken_fallthrough will invert the branch. */ 390 const Block* cb = &f->blocks[cur]; 391 next = BLOCK_NONE; 392 for (u32 s = 0; s < cb->nsucc; ++s) { 393 u32 cand = cb->succ[s]; 394 if (cand < f->nblocks && !visited[cand]) { 395 next = cand; 396 break; 397 } 398 } 399 } 400 if (next >= f->nblocks || visited[next]) { 401 /* Start a new chain from the lowest unvisited block in original order. */ 402 next = BLOCK_NONE; 403 while (scan_cursor < f->emit_order_n) { 404 u32 cand = f->emit_order[scan_cursor++]; 405 if (cand < f->nblocks && !visited[cand]) { 406 next = cand; 407 break; 408 } 409 } 410 if (next >= f->nblocks) break; 411 } 412 new_order[w++] = next; 413 visited[next] = 1; 414 cur = next; 415 } 416 417 if (w != f->emit_order_n) return; /* Conservative: bail if we missed any. */ 418 memcpy(f->emit_order, new_order, sizeof(u32) * w); 419 /* Refresh the cached emit-index map so subsequent cleanups see the new 420 * order. */ 421 for (u32 b = 0; b < f->nblocks; ++b) c->emit_index_by_block[b] = BLOCK_NONE; 422 for (u32 i = 0; i < f->emit_order_n; ++i) { 423 u32 b = f->emit_order[i]; 424 if (b < f->nblocks) c->emit_index_by_block[b] = i; 425 } 426 } 427 428 /* Rotate simple counted loops so the test sits at the bottom: the back-edge 429 * becomes the per-iteration conditional branch and the body falls through to 430 * the test, eliminating the unconditional back-jump the head-test shape emits 431 * every iteration. 432 * 433 * Pattern (after the greedy reorder): 434 * 435 * H (emit i): IR_CMP_BRANCH, succ[0]=E (exit, taken), succ[1]=B (body entry, 436 * the fallthrough, placed right after H by the greedy reorder) 437 * ... body chain ... 438 * L (emit k): the latch — a predecessor of H at a later emit position, i.e. 439 * the source of the loop's back-edge to H 440 * 441 * The rewrite moves H from position i to just after L (rotating the emit-order 442 * run [i..k] left by one) and inverts H's test in place: H's taken edge becomes 443 * the back-edge to the body entry (the per-iteration conditional) and its 444 * fallthrough becomes the exit E. No CFG edges move — reordering emit_order is 445 * always valid because pass_native_emit emits an explicit jump for any 446 * successor that isn't the textual next block. After the move L falls through 447 * to H (its back-edge `b H` is stripped), and the preheader keeps its now- 448 * forward `b H` (executed once, serving as the zero-trip guard); the 449 * unconditional back-jump every iteration is gone. 450 * 451 * Conservative guards: the body entry must be H's fallthrough and immediately 452 * follow H (the greedy chain), and H must have exactly one back-edge 453 * predecessor after it in emit order (reducible, single-latch loop). */ 454 static void cleanup_rotate_loops(JumpCleanupCtx* c) { 455 Func* f = c->f; 456 if (f->emit_order_n < 2u) return; 457 for (u32 i = 0; i + 1u < f->emit_order_n; ++i) { 458 u32 h = f->emit_order[i]; 459 if (h >= f->nblocks) continue; 460 Block* H = &f->blocks[h]; 461 if (!H->ninsts || H->nsucc != 2) continue; 462 Inst* hterm = &H->insts[H->ninsts - 1u]; 463 if ((IROp)hterm->op != IR_CMP_BRANCH) continue; 464 u32 body = H->succ[1]; /* loop body entry (fallthrough) */ 465 if (f->emit_order[i + 1u] != body) continue; 466 /* Latch: the single predecessor of H later in emit order (back-edge 467 * source). Zero or multiple => not a simple single-latch loop; skip. */ 468 u32 latch_pos = BLOCK_NONE; 469 int ok = 1; 470 for (u32 p = 0; p < H->npreds; ++p) { 471 u32 pos = emit_order_index(c, H->preds[p]); 472 if (pos == BLOCK_NONE || pos <= i) continue; 473 if (latch_pos != BLOCK_NONE) { 474 ok = 0; 475 break; 476 } 477 latch_pos = pos; 478 } 479 if (!ok || latch_pos == BLOCK_NONE) continue; 480 CmpOp inverted; 481 if (!invert_cmp((CmpOp)hterm->extra.imm, &inverted)) continue; 482 u32 exit = H->succ[0]; 483 for (u32 k = i; k < latch_pos; ++k) 484 f->emit_order[k] = f->emit_order[k + 1u]; 485 f->emit_order[latch_pos] = h; 486 hterm->extra.imm = inverted; 487 H->succ[0] = body; /* taken: back-edge to the loop body */ 488 H->succ[1] = exit; /* fallthrough: loop exit */ 489 refresh_emit_index_range(c, i, latch_pos); 490 } 491 } 492 493 static void cleanup_layout_fallthrough_branches(const JumpCleanupCtx* c) { 494 Func* f = c->f; 495 for (u32 b = 0; b < f->nblocks; ++b) { 496 Block* bl = &f->blocks[b]; 497 if (!bl->ninsts || bl->nsucc != 1) continue; 498 Inst* last = &bl->insts[bl->ninsts - 1u]; 499 if ((IROp)last->op != IR_BR) continue; 500 if (next_emit_block(c, b) != bl->succ[0]) continue; 501 memset(last, 0, sizeof *last); 502 --bl->ninsts; 503 } 504 } 505 506 static int forward_empty_fallthrough_chain(JumpCleanupCtx* c, u32 target, 507 u32* target_out) { 508 u32 cur = target; 509 Func* f = c->f; 510 for (u32 step = 0; step < f->nblocks; ++step) { 511 u32 next_block = BLOCK_NONE; 512 if (!empty_fallthrough_block(c, cur, &next_block)) return 0; 513 if (next_block >= f->nblocks) return 0; 514 if (!empty_fallthrough_block(c, next_block, NULL)) { 515 if (target_out) *target_out = next_block; 516 return 1; 517 } 518 cur = next_block; 519 } 520 return 0; 521 } 522 523 static int full_cond_fallthrough_forwardable(JumpCleanupCtx* c, u32 pred, 524 u32 target, u32* target_out) { 525 u32 next = next_emit_block(c, pred); 526 if (next != target) return 0; 527 return forward_empty_fallthrough_chain(c, target, target_out); 528 } 529 530 static u32 full_forwardable_successors(const Block* bl, const Inst* term) { 531 switch ((IROp)term->op) { 532 case IR_BR: 533 case IR_SWITCH: 534 return bl->nsucc; 535 case IR_CONDBR: 536 case IR_CMP_BRANCH: 537 return bl->nsucc < 2u ? bl->nsucc : 2u; 538 default: 539 return 0; 540 } 541 } 542 543 static void full_rewrite_successor(Func* f, u32 b, u32 old_succ, u32 new_succ) { 544 opt_replace_succ_ref(f, b, old_succ, new_succ); 545 } 546 547 static int full_forward_branch_targets(JumpCleanupCtx* c) { 548 Func* f = c->f; 549 int changed = 0; 550 for (u32 b = 0; b < f->nblocks; ++b) { 551 Block* bl = &f->blocks[b]; 552 if (!bl->ninsts) continue; 553 Inst* term = &bl->insts[bl->ninsts - 1u]; 554 u32 nsucc = full_forwardable_successors(bl, term); 555 for (u32 s = 0; s < nsucc; ++s) { 556 u32 target = bl->succ[s]; 557 u32 forwarded = forward_jump_target_ex(c, target, 1); 558 if (((IROp)term->op == IR_CONDBR || (IROp)term->op == IR_CMP_BRANCH) && 559 s == 1u) { 560 if (!full_cond_fallthrough_forwardable(c, b, target, &forwarded)) 561 continue; 562 } 563 if (forwarded >= f->nblocks || forwarded == target) continue; 564 full_rewrite_successor(f, b, target, forwarded); 565 changed = 1; 566 } 567 } 568 return changed; 569 } 570 571 static int full_collapse_same_target_branches(Func* f) { 572 int changed = 0; 573 for (u32 b = 0; b < f->nblocks; ++b) { 574 Block* bl = &f->blocks[b]; 575 if (!bl->ninsts || bl->nsucc != 2) continue; 576 Inst* term = &bl->insts[bl->ninsts - 1u]; 577 IROp op = (IROp)term->op; 578 if (op != IR_CONDBR && op != IR_CMP_BRANCH) continue; 579 if (bl->succ[0] != bl->succ[1]) continue; 580 InstId id = term->id; 581 SrcLoc loc = term->loc; 582 memset(term, 0, sizeof *term); 583 term->op = IR_BR; 584 term->id = id; 585 term->loc = loc; 586 bl->nsucc = 1; 587 changed = 1; 588 } 589 return changed; 590 } 591 592 /* O1.md W10 — constant `cmp_branch` folding (no SSA). 593 * 594 * Fold an `IR_CMP_BRANCH` whose outcome is statically known by *local* facts 595 * only, then rewrite the terminator to a plain `IR_BR` at the selected 596 * successor (dropping the now-dead CFG edge). Two cases are decidable here: 597 * 598 * 1. both operands are immediates (`OPK_IMM`), or 599 * 2. an integer same-register identity (`x <cmp> x`). 600 * 601 * Both are evaluated through the shared `kit_ir_eval_cmp` integer evaluator, 602 * which masks/sign-extends to the operand type width and returns 0 for any FP 603 * predicate (`op >= CMP_OEQ_F`) — so floating compares (where NaN makes even 604 * `x == x` non-trivial) are never folded. The operand type width also gates the 605 * fold: if the width is unknown we skip. We deliberately do *not* chase 606 * `IR_LOAD_IMM` definitions: at O1 PRegs are mutable, so only direct operands 607 * and the same-reg identity are sound without SSA. */ 608 static u32 cmp_branch_operand_width(Func* f, const Operand* op) { 609 if (!op) return 0; 610 u32 w = kit_cg_type_int_width((KitCompiler*)f->c, op->type); 611 if (w && w <= 64u) return w; 612 if (kit_cg_type_kind((KitCompiler*)f->c, op->type) == KIT_CG_TYPE_PTR) { 613 u64 size = kit_cg_type_size((KitCompiler*)f->c, op->type); 614 if (size && size <= 8u) return (u32)(size * 8u); 615 } 616 return 0; 617 } 618 619 /* Returns 1 and writes *taken_out (the i1 compare result) when the branch 620 * condition is locally decidable; 0 to leave the branch alone. */ 621 static int cmp_branch_const_outcome(Func* f, const Inst* term, int* taken_out) { 622 if ((IROp)term->op != IR_CMP_BRANCH || term->nopnds < 2) return 0; 623 CmpOp op = (CmpOp)term->extra.imm; 624 if (op >= CMP_OEQ_F) return 0; /* never fold FP (NaN). */ 625 const Operand* a = &term->opnds[0]; 626 const Operand* b = &term->opnds[1]; 627 628 /* Integer same-register identity: x <cmp> x. The width only needs to be 629 * known to be a real integer/pointer scalar; the value cancels out. */ 630 if (a->kind == OPK_REG && b->kind == OPK_REG && a->v.reg == b->v.reg) { 631 u32 w = cmp_branch_operand_width(f, a); 632 if (!w) return 0; 633 i64 r; 634 if (!kit_ir_eval_cmp(op, w, 0, 0, &r)) return 0; 635 *taken_out = r != 0; 636 return 1; 637 } 638 639 /* Two immediates. */ 640 if (a->kind == OPK_IMM && b->kind == OPK_IMM) { 641 u32 wa = cmp_branch_operand_width(f, a); 642 u32 wb = cmp_branch_operand_width(f, b); 643 u32 w = wa ? wa : wb; 644 if (!w) return 0; 645 i64 r; 646 if (!kit_ir_eval_cmp(op, w, a->v.imm, b->v.imm, &r)) return 0; 647 *taken_out = r != 0; 648 return 1; 649 } 650 return 0; 651 } 652 653 static int one_pass_fold_const_cmp_branch(Func* f) { 654 int changed = 0; 655 for (u32 b = 0; b < f->nblocks; ++b) { 656 Block* bl = &f->blocks[b]; 657 if (!bl->ninsts || bl->nsucc != 2) continue; 658 Inst* term = &bl->insts[bl->ninsts - 1u]; 659 int taken; 660 if (!cmp_branch_const_outcome(f, term, &taken)) continue; 661 /* succ[0] = taken target, succ[1] = fallthrough/false target. */ 662 u32 keep = taken ? bl->succ[0] : bl->succ[1]; 663 InstId id = term->id; 664 SrcLoc loc = term->loc; 665 memset(term, 0, sizeof *term); 666 term->op = IR_BR; 667 term->id = id; 668 term->loc = loc; 669 bl->succ[0] = keep; 670 bl->nsucc = 1; 671 changed = 1; 672 } 673 return changed; 674 } 675 676 /* O1.md W9 — one-pass branch cleanup subset (linear). 677 * 678 * The single-pass, obviously-linear core of `opt_jump_opt` with its 679 * fixed-point loop removed (the loop is intentionally not an O1 fit). Runs the 680 * W10 constant cmp_branch fold first (it can create same-target / pass-through 681 * blocks the W9 passes then clean up), then forwards branch targets through 682 * trivial pass-through blocks once and collapses same-target conditional 683 * branches once. The forwarding walk uses the memoized 684 * `forward_jump_target_ex`, so repeated target queries are amortized linear for 685 * the function; the label-address guard (`has_label_addr_ref`) keeps 686 * computed-goto-visible blocks from being bypassed. At most one CFG rebuild 687 * after the rewrites. */ 688 void opt_jump_cleanup_o1(Func* f) { 689 if (!f) return; 690 opt_analysis_invalidate( 691 f, OPT_ANALYSIS_DEF_USE | OPT_ANALYSIS_DOM | OPT_ANALYSIS_LOOP); 692 693 int changed = one_pass_fold_const_cmp_branch(f); 694 695 JumpCleanupCtx c = jump_cleanup_ctx(f); 696 changed |= full_forward_branch_targets(&c); 697 changed |= full_collapse_same_target_branches(f); 698 699 if (changed) { 700 opt_analysis_invalidate( 701 f, OPT_ANALYSIS_DEF_USE | OPT_ANALYSIS_DOM | OPT_ANALYSIS_LOOP); 702 opt_build_cfg(f); 703 } 704 } 705 706 void opt_jump_cleanup(Func* f, OptJumpCleanupStage stage) { 707 if (!f) return; 708 opt_analysis_invalidate( 709 f, OPT_ANALYSIS_DEF_USE | OPT_ANALYSIS_DOM | OPT_ANALYSIS_LOOP); 710 JumpCleanupCtx c = jump_cleanup_ctx(f); 711 if (stage == OPT_JUMP_CLEANUP_CFG) { 712 cleanup_invert_jump_fallthrough(&c); 713 cleanup_branch_targets(&c); 714 } else if (stage == OPT_JUMP_CLEANUP_LAYOUT) { 715 cleanup_reorder_for_fallthrough(&c); 716 cleanup_rotate_loops(&c); 717 cleanup_invert_taken_fallthrough(&c); 718 cleanup_layout_fallthrough_branches(&c); 719 } 720 } 721 722 void opt_jump_opt(Func* f) { 723 if (!f) return; 724 int changed = 0; 725 726 opt_analysis_invalidate( 727 f, OPT_ANALYSIS_DEF_USE | OPT_ANALYSIS_DOM | OPT_ANALYSIS_LOOP); 728 729 for (u32 iter = 0; iter < f->nblocks; ++iter) { 730 JumpCleanupCtx c = jump_cleanup_ctx(f); 731 int iter_changed = 0; 732 iter_changed |= full_forward_branch_targets(&c); 733 opt_build_cfg(f); 734 c = jump_cleanup_ctx(f); 735 cleanup_invert_jump_fallthrough(&c); 736 cleanup_branch_targets(&c); 737 iter_changed |= full_collapse_same_target_branches(f); 738 if (!iter_changed) break; 739 changed = 1; 740 opt_build_cfg(f); 741 } 742 743 if (changed) { 744 opt_analysis_invalidate( 745 f, OPT_ANALYSIS_DEF_USE | OPT_ANALYSIS_DOM | OPT_ANALYSIS_LOOP); 746 } 747 opt_build_cfg(f); 748 }