kit

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

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 }