pass_cfg.c (9542B)
1 /* pass_cfg.c — derive Block.preds and Block.succ/nsucc from each 2 * block's terminator. doc/OPT.md §3 Phase 3. 3 * 4 * Terminator inventory: 5 * IR_BR — 1 succ (succ[0]) 6 * IR_CONDBR — 2 succs ([true, false]) 7 * IR_CMP_BRANCH — 2 succs ([taken, fallthrough]) 8 * IR_RET — 0 succs 9 * IR_INTRINSIC LONGJMP/TRAP/UNREACHABLE — 0 succs 10 * IR_BREAK_TO / IR_CONTINUE_TO — 0 succs (control transferred to 11 * the scope's break/continue label, 12 * which is a successor encoded on 13 * the IRScopeAux; pass populates 14 * succ from there) 15 * 16 * INTRIN_SETJMP falls through, so pass_cfg sees it as a normal inst. 17 * 18 * For scope ops the wrapper's recording assigns succ[] at emit time 19 * (since it owns the vlabel→block_id mapping). pass_cfg trusts that 20 * and only repopulates from the trailing terminator inst when one is 21 * present. */ 22 23 #include <string.h> 24 25 #include "core/arena.h" 26 #include "core/core.h" 27 #include "opt/opt_internal.h" 28 29 static int is_terminator(const Inst* in) { 30 switch ((IROp)in->op) { 31 case IR_BR: 32 case IR_CONDBR: 33 case IR_CMP_BRANCH: 34 case IR_SWITCH: 35 case IR_INDIRECT_BRANCH: 36 case IR_RET: 37 case IR_UNREACHABLE: 38 case IR_BREAK_TO: 39 case IR_CONTINUE_TO: 40 return 1; 41 case IR_INTRINSIC: { 42 IRIntrinAux* aux = (IRIntrinAux*)in->extra.aux; 43 return aux && (aux->kind == INTRIN_LONGJMP || aux->kind == INTRIN_TRAP); 44 } 45 default: 46 return 0; 47 } 48 } 49 50 static int scope_control_succ_count(const Block* bl, const Inst* in, 51 u8* nsucc_out) { 52 switch ((IROp)in->op) { 53 case IR_SCOPE_BEGIN: { 54 *nsucc_out = bl->nsucc; 55 return bl->nsucc != 0; 56 } 57 case IR_SCOPE_END: 58 *nsucc_out = bl->nsucc; 59 return bl->nsucc != 0; 60 default: 61 return 0; 62 } 63 } 64 65 static void push_reachable(Func* f, u8* reachable, u32* stack, u32* sp, 66 u32 block) { 67 if (block < f->nblocks && !reachable[block]) { 68 reachable[block] = 1; 69 stack[(*sp)++] = block; 70 } 71 } 72 73 static void mark_label_addr_targets_reachable(Func* f, const Inst* in, 74 u8* reachable, u32* stack, 75 u32* sp) { 76 switch ((IROp)in->op) { 77 case IR_LOAD_LABEL_ADDR: 78 push_reachable(f, reachable, stack, sp, (u32)in->extra.imm); 79 break; 80 case IR_LOCAL_STATIC_DATA_LABEL_ADDR: { 81 CgIrLocalStaticLabelAux* aux = 82 (CgIrLocalStaticLabelAux*)in->extra.aux; 83 if (aux) push_reachable(f, reachable, stack, sp, (u32)aux->target); 84 break; 85 } 86 default: 87 break; 88 } 89 } 90 91 static u8* mark_reachable(Func* f) { 92 u8* reachable = arena_zarray(f->arena, u8, f->nblocks ? f->nblocks : 1u); 93 if (f->entry >= f->nblocks) return reachable; 94 95 u32* stack = arena_array(f->arena, u32, f->nblocks ? f->nblocks : 1u); 96 u32 sp = 0; 97 push_reachable(f, reachable, stack, &sp, f->entry); 98 while (sp) { 99 u32 b = stack[--sp]; 100 if (b >= f->nblocks) continue; 101 Block* bl = &f->blocks[b]; 102 for (u32 s = 0; s < bl->nsucc; ++s) { 103 u32 t = bl->succ[s]; 104 push_reachable(f, reachable, stack, &sp, t); 105 } 106 for (u32 i = 0; i < bl->ninsts; ++i) 107 mark_label_addr_targets_reachable(f, &bl->insts[i], reachable, stack, 108 &sp); 109 } 110 return reachable; 111 } 112 113 static void prune_unreachable(Func* f, const u8* reachable) { 114 for (u32 b = 0; b < f->nblocks; ++b) { 115 if (reachable[b]) continue; 116 Block* bl = &f->blocks[b]; 117 bl->insts = NULL; 118 bl->ninsts = 0; 119 bl->cap = 0; 120 bl->preds = NULL; 121 bl->npreds = 0; 122 bl->nsucc = 0; 123 } 124 125 u32 w = 0; 126 for (u32 i = 0; i < f->emit_order_n; ++i) { 127 u32 b = f->emit_order[i]; 128 if (b < f->nblocks && reachable[b]) f->emit_order[w++] = b; 129 } 130 f->emit_order_n = w; 131 } 132 133 void opt_replace_succ_ref(Func* f, u32 pred, u32 old_succ, u32 new_succ) { 134 if (!f || pred >= f->nblocks) return; 135 Block* bl = &f->blocks[pred]; 136 for (u32 s = 0; s < bl->nsucc; ++s) { 137 if (bl->succ[s] == old_succ) bl->succ[s] = new_succ; 138 } 139 if (!bl->ninsts) return; 140 Inst* term = &bl->insts[bl->ninsts - 1u]; 141 if ((IROp)term->op == IR_SWITCH) { 142 IRSwitchAux* aux = (IRSwitchAux*)term->extra.aux; 143 if (!aux) return; 144 for (u32 i = 0; i < aux->ncases; ++i) 145 if (aux->cases[i].block == old_succ) aux->cases[i].block = new_succ; 146 if (aux->default_block == old_succ) aux->default_block = new_succ; 147 } else if ((IROp)term->op == IR_INDIRECT_BRANCH) { 148 IRIndirectAux* aux = (IRIndirectAux*)term->extra.aux; 149 if (!aux) return; 150 for (u32 i = 0; i < aux->ntargets; ++i) 151 if (aux->targets[i] == old_succ) aux->targets[i] = new_succ; 152 } 153 } 154 155 void opt_emit_order_insert_after(Func* f, u32 after, u32 block) { 156 if (!f) return; 157 for (u32 i = 0; i < f->emit_order_n; ++i) 158 if (f->emit_order[i] == block) return; 159 if (f->emit_order_n == f->emit_order_cap) { 160 u32 ncap = f->emit_order_cap ? f->emit_order_cap * 2u : 8u; 161 u32* order = arena_array(f->arena, u32, ncap); 162 if (f->emit_order) 163 memcpy(order, f->emit_order, sizeof(order[0]) * f->emit_order_n); 164 f->emit_order = order; 165 f->emit_order_cap = ncap; 166 } 167 u32 pos = f->emit_order_n; 168 for (u32 i = 0; i < f->emit_order_n; ++i) { 169 if (f->emit_order[i] == after) { 170 pos = i + 1u; 171 break; 172 } 173 } 174 for (u32 i = f->emit_order_n; i > pos; --i) 175 f->emit_order[i] = f->emit_order[i - 1u]; 176 f->emit_order[pos] = block; 177 ++f->emit_order_n; 178 } 179 180 int opt_edge_is_fallthrough(Func* f, u32 pred, u32 succ) { 181 if (!f || pred >= f->nblocks) return 0; 182 Block* bl = &f->blocks[pred]; 183 if (!bl->ninsts) return 0; 184 IROp op = (IROp)bl->insts[bl->ninsts - 1u].op; 185 if (op != IR_CMP_BRANCH && op != IR_CONDBR) return 0; 186 return bl->nsucc >= 2 && bl->succ[1] == succ; 187 } 188 189 u32 opt_split_edge(Func* f, u32 pred, u32 succ) { 190 if (!f) return 0; 191 u32 edge = ir_block_new(f); 192 Block* eb = &f->blocks[edge]; 193 Inst* br = ir_emit(f, edge, IR_BR); 194 (void)br; 195 eb->succ[0] = succ; 196 eb->nsucc = 1; 197 int place_after = opt_edge_is_fallthrough(f, pred, succ); 198 opt_replace_succ_ref(f, pred, succ, edge); 199 if (succ < f->nblocks) { 200 Block* sb = &f->blocks[succ]; 201 for (u32 i = 0; i < sb->ninsts; ++i) { 202 Inst* phi = &sb->insts[i]; 203 if ((IROp)phi->op != IR_PHI) break; 204 IRPhiAux* aux = (IRPhiAux*)phi->extra.aux; 205 if (!aux) continue; 206 for (u32 p = 0; p < aux->npreds; ++p) { 207 if (aux->pred_blocks[p] == pred) { 208 aux->pred_blocks[p] = edge; 209 aux->pred_vals[p] = phi->def; 210 } 211 } 212 } 213 } 214 if (place_after) { 215 opt_emit_order_insert_after(f, pred, edge); 216 } else { 217 ir_note_emit(f, edge); 218 } 219 opt_analysis_invalidate(f, OPT_ANALYSIS_CFG | OPT_ANALYSIS_DEF_USE | 220 OPT_ANALYSIS_DOM | OPT_ANALYSIS_LOOP); 221 return edge; 222 } 223 224 void opt_build_cfg(Func* f) { 225 if (!f) return; 226 opt_analysis_invalidate( 227 f, OPT_ANALYSIS_DEF_USE | OPT_ANALYSIS_DOM | OPT_ANALYSIS_LOOP); 228 for (u32 b = 0; b < f->nblocks; ++b) { 229 f->blocks[b].preds = NULL; 230 f->blocks[b].npreds = 0; 231 } 232 233 /* Trust the recorder's succ[] for terminators that don't have a fixed 234 * succ count from the inst alone (IR_BR, IR_CONDBR, IR_CMP_BRANCH, 235 * IR_BREAK_TO, IR_CONTINUE_TO). Only fix nsucc for ops where we can 236 * read it directly from the op tag. */ 237 for (u32 b = 0; b < f->nblocks; ++b) { 238 Block* bl = &f->blocks[b]; 239 if (bl->ninsts == 0) { 240 /* Empty blocks are valid label-only blocks. Their fallthrough successor 241 * is assigned by the lowering/layout pass and must survive CFG rebuilds 242 * so branches to labels placed immediately before another block remain 243 * connected. */ 244 continue; 245 } 246 const Inst* last = &bl->insts[bl->ninsts - 1]; 247 if (!is_terminator(last)) { 248 u8 nsucc = 0; 249 if (scope_control_succ_count(bl, last, &nsucc)) { 250 bl->nsucc = nsucc; 251 continue; 252 } 253 continue; 254 } 255 switch ((IROp)last->op) { 256 case IR_RET: 257 bl->nsucc = 0; 258 break; 259 case IR_UNREACHABLE: 260 bl->nsucc = 0; 261 break; 262 case IR_INTRINSIC: 263 bl->nsucc = 0; 264 break; 265 case IR_BR: 266 case IR_BREAK_TO: 267 case IR_CONTINUE_TO: 268 bl->nsucc = 1; 269 break; 270 case IR_CONDBR: 271 case IR_CMP_BRANCH: 272 bl->nsucc = 2; 273 break; 274 case IR_SWITCH: 275 case IR_INDIRECT_BRANCH: 276 /* nsucc was set by the recorder at emit time; trust it. */ 277 break; 278 default: 279 break; 280 } 281 } 282 283 u8* reachable = mark_reachable(f); 284 prune_unreachable(f, reachable); 285 286 /* Count predecessors. */ 287 u32* counts = arena_zarray(f->arena, u32, f->nblocks); 288 for (u32 b = 0; b < f->nblocks; ++b) { 289 Block* bl = &f->blocks[b]; 290 for (u32 s = 0; s < bl->nsucc; ++s) { 291 u32 t = bl->succ[s]; 292 if (t < f->nblocks) counts[t]++; 293 } 294 } 295 for (u32 b = 0; b < f->nblocks; ++b) { 296 if (counts[b]) { 297 f->blocks[b].preds = arena_array(f->arena, u32, counts[b]); 298 } 299 } 300 for (u32 b = 0; b < f->nblocks; ++b) { 301 Block* bl = &f->blocks[b]; 302 for (u32 s = 0; s < bl->nsucc; ++s) { 303 u32 t = bl->succ[s]; 304 if (t >= f->nblocks) continue; 305 f->blocks[t].preds[f->blocks[t].npreds++] = b; 306 } 307 } 308 opt_analysis_mark_valid(f, OPT_ANALYSIS_CFG); 309 }