| 1 | module optimize |
| 2 | |
| 3 | import v3.ssa |
| 4 | |
| 5 | // --- Simplify trivial phi nodes --- |
| 6 | // A phi is trivial if all its non-self operands resolve to a single value, or if |
| 7 | // it has no uses at all. Trivial phis are replaced and marked dead (nop bitcast). |
| 8 | fn simplify_phi_nodes(mut m ssa.Module) bool { |
| 9 | mut any_changed := false |
| 10 | mut changed := true |
| 11 | for changed { |
| 12 | changed = false |
| 13 | for fi in 0 .. m.funcs.len { |
| 14 | func := m.funcs[fi] |
| 15 | for blk_id in func.blocks { |
| 16 | blk := m.blocks[blk_id] |
| 17 | for val_id in blk.instrs { |
| 18 | val := m.values[val_id] |
| 19 | if val.kind != .instruction { |
| 20 | continue |
| 21 | } |
| 22 | instr := m.instrs[val.index] |
| 23 | if instr.op != .phi { |
| 24 | continue |
| 25 | } |
| 26 | |
| 27 | // Dead phi: no uses -> remove. |
| 28 | if val.uses.len == 0 { |
| 29 | for i := 0; i < instr.operands.len; i += 2 { |
| 30 | op_val := instr.operands[i] |
| 31 | if op_val != val_id { |
| 32 | remove_use(mut m, op_val, val_id) |
| 33 | } |
| 34 | } |
| 35 | mut dead_phi := m.instrs[val.index] |
| 36 | dead_phi.op = .bitcast |
| 37 | dead_phi.operands = [] |
| 38 | m.instrs[val.index] = dead_phi |
| 39 | changed = true |
| 40 | any_changed = true |
| 41 | continue |
| 42 | } |
| 43 | |
| 44 | // Trivial phi: all non-self value operands identical. |
| 45 | mut unique_val := -1 |
| 46 | mut is_trivial := true |
| 47 | for i := 0; i < instr.operands.len; i += 2 { |
| 48 | op_val := instr.operands[i] |
| 49 | if op_val == val_id { |
| 50 | continue |
| 51 | } |
| 52 | if unique_val == -1 { |
| 53 | unique_val = op_val |
| 54 | } else if unique_val != op_val { |
| 55 | is_trivial = false |
| 56 | break |
| 57 | } |
| 58 | } |
| 59 | if is_trivial && unique_val != -1 { |
| 60 | m.replace_uses(val_id, unique_val) |
| 61 | mut triv_phi := m.instrs[val.index] |
| 62 | triv_phi.op = .bitcast |
| 63 | triv_phi.operands = [] |
| 64 | m.instrs[val.index] = triv_phi |
| 65 | changed = true |
| 66 | any_changed = true |
| 67 | } |
| 68 | } |
| 69 | } |
| 70 | } |
| 71 | } |
| 72 | return any_changed |
| 73 | } |
| 74 | |
| 75 | // prune_phi_operands drops phi operand pairs whose predecessor block is no longer |
| 76 | // an actual predecessor (e.g. after branch folding / unreachable-block removal), |
| 77 | // keeping phis consistent with the current CFG. A phi reduced to a single distinct |
| 78 | // incoming value is replaced by that value. Requires up-to-date preds + uses. |
| 79 | fn prune_phi_operands(mut m ssa.Module) { |
| 80 | for fi in 0 .. m.funcs.len { |
| 81 | for blk_id in m.funcs[fi].blocks { |
| 82 | if blk_id < 0 || blk_id >= m.blocks.len { |
| 83 | continue |
| 84 | } |
| 85 | preds := m.blocks[blk_id].preds |
| 86 | for val_id in m.blocks[blk_id].instrs { |
| 87 | if val_id <= 0 || val_id >= m.values.len || m.values[val_id].kind != .instruction { |
| 88 | continue |
| 89 | } |
| 90 | idx := m.values[val_id].index |
| 91 | if m.instrs[idx].op != .phi { |
| 92 | continue |
| 93 | } |
| 94 | ops := m.instrs[idx].operands |
| 95 | mut kept := []ssa.ValueID{} |
| 96 | mut distinct := []ssa.ValueID{} |
| 97 | for i := 0; i + 1 < ops.len; i += 2 { |
| 98 | pred := int(ops[i + 1]) |
| 99 | if pred in preds { |
| 100 | kept << ops[i] |
| 101 | kept << ops[i + 1] |
| 102 | if ops[i] !in distinct { |
| 103 | distinct << ops[i] |
| 104 | } |
| 105 | } |
| 106 | } |
| 107 | if kept.len == ops.len && distinct.len > 1 { |
| 108 | continue // unchanged, still a real phi |
| 109 | } |
| 110 | if distinct.len == 1 { |
| 111 | // Trivial phi -> its single incoming value. |
| 112 | m.replace_uses(val_id, distinct[0]) |
| 113 | mut nop := m.instrs[idx] |
| 114 | nop.op = .bitcast |
| 115 | nop.operands = [] |
| 116 | m.instrs[idx] = nop |
| 117 | } else if distinct.len == 0 { |
| 118 | mut nop := m.instrs[idx] |
| 119 | nop.op = .bitcast |
| 120 | nop.operands = [] |
| 121 | m.instrs[idx] = nop |
| 122 | } else { |
| 123 | mut pruned := m.instrs[idx] |
| 124 | pruned.operands = kept |
| 125 | m.instrs[idx] = pruned |
| 126 | } |
| 127 | } |
| 128 | } |
| 129 | } |
| 130 | } |
| 131 | |
| 132 | // --- Critical edge splitting --- |
| 133 | // Inserts an empty block on every edge whose source has multiple successors and |
| 134 | // whose target has multiple predecessors, so phi-elimination copies have a unique |
| 135 | // home. Operands here are raw block ids (v3 convention). |
| 136 | fn split_critical_edges(mut m ssa.Module) { |
| 137 | build_cfg(mut m) |
| 138 | for fi in 0 .. m.funcs.len { |
| 139 | mut edges_to_split := [][]int{} |
| 140 | func := m.funcs[fi] |
| 141 | for blk_id in func.blocks { |
| 142 | if blk_id < 0 || blk_id >= m.blocks.len { |
| 143 | continue |
| 144 | } |
| 145 | if m.blocks[blk_id].succs.len > 1 { |
| 146 | for succ_id in m.blocks[blk_id].succs { |
| 147 | if succ_id < 0 || succ_id >= m.blocks.len { |
| 148 | continue |
| 149 | } |
| 150 | if m.blocks[succ_id].preds.len > 1 { |
| 151 | edges_to_split << [int(blk_id), int(succ_id)] |
| 152 | } |
| 153 | } |
| 154 | } |
| 155 | } |
| 156 | |
| 157 | for edge in edges_to_split { |
| 158 | pred_id := edge[0] |
| 159 | succ_id := edge[1] |
| 160 | if pred_id < 0 || pred_id >= m.blocks.len || succ_id < 0 || succ_id >= m.blocks.len { |
| 161 | continue |
| 162 | } |
| 163 | split_blk := m.add_block(fi, 'split_${pred_id}_${succ_id}') |
| 164 | m.add_instr(.jmp, split_blk, 0, [ssa.ValueID(succ_id)]) |
| 165 | |
| 166 | // Re-point predecessor terminator from succ to the split block. |
| 167 | pred_blk := m.blocks[pred_id] |
| 168 | if pred_blk.instrs.len > 0 { |
| 169 | term_val_id := pred_blk.instrs[pred_blk.instrs.len - 1] |
| 170 | if term_val_id >= 0 && term_val_id < m.values.len { |
| 171 | idx := m.values[term_val_id].index |
| 172 | mut term := m.instrs[idx] |
| 173 | for i in 0 .. term.operands.len { |
| 174 | if int(term.operands[i]) == succ_id { |
| 175 | term.operands[i] = ssa.ValueID(split_blk) |
| 176 | } |
| 177 | } |
| 178 | m.instrs[idx] = term |
| 179 | } |
| 180 | } |
| 181 | |
| 182 | // Re-point phi predecessors in succ from pred to the split block. |
| 183 | for vid in m.blocks[succ_id].instrs { |
| 184 | if vid < 0 || vid >= m.values.len || m.values[vid].kind != .instruction { |
| 185 | continue |
| 186 | } |
| 187 | pidx := m.values[vid].index |
| 188 | if m.instrs[pidx].op != .phi { |
| 189 | continue |
| 190 | } |
| 191 | mut phi := m.instrs[pidx] |
| 192 | for i := 1; i < phi.operands.len; i += 2 { |
| 193 | if int(phi.operands[i]) == pred_id { |
| 194 | phi.operands[i] = ssa.ValueID(split_blk) |
| 195 | } |
| 196 | } |
| 197 | m.instrs[pidx] = phi |
| 198 | } |
| 199 | } |
| 200 | } |
| 201 | build_cfg(mut m) |
| 202 | } |
| 203 | |
| 204 | // eliminate_phi_nodes lowers phis to `assign` copies placed in predecessor blocks, |
| 205 | // sequencing the per-predecessor parallel copies (Briggs) to respect dependencies. |
| 206 | fn eliminate_phi_nodes(mut m ssa.Module) { |
| 207 | split_critical_edges(mut m) |
| 208 | |
| 209 | n_blocks := m.blocks.len |
| 210 | mut pred_copy_dests := [][]int{len: n_blocks} |
| 211 | mut pred_copy_srcs := [][]int{len: n_blocks} |
| 212 | |
| 213 | for fi in 0 .. m.funcs.len { |
| 214 | func := m.funcs[fi] |
| 215 | mut pred_copy_blocks := []int{} |
| 216 | |
| 217 | for blk_id in func.blocks { |
| 218 | if blk_id < 0 || blk_id >= m.blocks.len { |
| 219 | continue |
| 220 | } |
| 221 | for val_id in m.blocks[blk_id].instrs { |
| 222 | if val_id < 0 || val_id >= m.values.len { |
| 223 | continue |
| 224 | } |
| 225 | val := m.values[val_id] |
| 226 | if val.kind != .instruction { |
| 227 | continue |
| 228 | } |
| 229 | instr := m.instrs[val.index] |
| 230 | if instr.op != .phi { |
| 231 | continue |
| 232 | } |
| 233 | for i := 0; i + 1 < instr.operands.len; i += 2 { |
| 234 | val_in := instr.operands[i] |
| 235 | pred_blk_idx := int(instr.operands[i + 1]) |
| 236 | if pred_blk_idx < 0 || pred_blk_idx >= n_blocks { |
| 237 | continue |
| 238 | } |
| 239 | if pred_copy_dests[pred_blk_idx].len == 0 { |
| 240 | pred_copy_blocks << pred_blk_idx |
| 241 | } |
| 242 | array2d_append(mut pred_copy_dests, pred_blk_idx, val_id) |
| 243 | array2d_append(mut pred_copy_srcs, pred_blk_idx, val_in) |
| 244 | } |
| 245 | } |
| 246 | } |
| 247 | |
| 248 | for pred_blk in pred_copy_blocks { |
| 249 | resolve_parallel_copies(mut m, pred_blk, pred_copy_dests[pred_blk], |
| 250 | pred_copy_srcs[pred_blk]) |
| 251 | } |
| 252 | for pred_blk in pred_copy_blocks { |
| 253 | pred_copy_dests[pred_blk] = [] |
| 254 | pred_copy_srcs[pred_blk] = [] |
| 255 | } |
| 256 | |
| 257 | // Remove phi instructions (nop them). |
| 258 | for blk_id in func.blocks { |
| 259 | if blk_id < 0 || blk_id >= m.blocks.len { |
| 260 | continue |
| 261 | } |
| 262 | for val_id in m.blocks[blk_id].instrs { |
| 263 | if val_id < 0 || val_id >= m.values.len { |
| 264 | continue |
| 265 | } |
| 266 | val := m.values[val_id] |
| 267 | if val.kind != .instruction { |
| 268 | continue |
| 269 | } |
| 270 | idx := val.index |
| 271 | if m.instrs[idx].op == .phi { |
| 272 | mut cleanup := m.instrs[idx] |
| 273 | cleanup.op = .bitcast |
| 274 | cleanup.operands = [] |
| 275 | m.instrs[idx] = cleanup |
| 276 | } |
| 277 | } |
| 278 | } |
| 279 | } |
| 280 | } |
| 281 | |
| 282 | // resolve_parallel_copies sequences a set of parallel copies dest[i] = src[i] |
| 283 | // using Briggs' worklist algorithm, breaking cycles with a temp. |
| 284 | fn resolve_parallel_copies(mut m ssa.Module, blk_id int, dests []int, srcs []int) { |
| 285 | if dests.len == 0 { |
| 286 | return |
| 287 | } |
| 288 | mut p_dest := []int{} |
| 289 | mut p_src := []int{} |
| 290 | for ci in 0 .. dests.len { |
| 291 | if dests[ci] != srcs[ci] { |
| 292 | p_dest << dests[ci] |
| 293 | p_src << srcs[ci] |
| 294 | } |
| 295 | } |
| 296 | np := p_dest.len |
| 297 | if np == 0 { |
| 298 | return |
| 299 | } |
| 300 | |
| 301 | mut max_id := m.values.len |
| 302 | for i in 0 .. np { |
| 303 | if p_dest[i] >= max_id { |
| 304 | max_id = p_dest[i] + 1 |
| 305 | } |
| 306 | if p_src[i] >= max_id { |
| 307 | max_id = p_src[i] + 1 |
| 308 | } |
| 309 | } |
| 310 | mut src_ref_count := []int{len: max_id + np + 4} |
| 311 | |
| 312 | for i in 0 .. np { |
| 313 | src_ref_count[p_src[i]]++ |
| 314 | } |
| 315 | |
| 316 | mut alive := []int{len: np} |
| 317 | for i in 0 .. np { |
| 318 | alive[i] = 1 |
| 319 | } |
| 320 | |
| 321 | mut worklist := []int{} |
| 322 | for i in 0 .. np { |
| 323 | if src_ref_count[p_dest[i]] == 0 { |
| 324 | worklist << i |
| 325 | } |
| 326 | } |
| 327 | |
| 328 | mut s_dest := []int{} |
| 329 | mut s_src := []int{} |
| 330 | mut remaining := np |
| 331 | |
| 332 | for remaining > 0 { |
| 333 | if worklist.len > 0 { |
| 334 | idx := worklist.pop() |
| 335 | if idx < 0 || idx >= np || alive[idx] == 0 { |
| 336 | continue |
| 337 | } |
| 338 | alive[idx] = 0 |
| 339 | remaining-- |
| 340 | d := p_dest[idx] |
| 341 | s := p_src[idx] |
| 342 | s_dest << d |
| 343 | s_src << s |
| 344 | if src_ref_count[s] > 0 { |
| 345 | src_ref_count[s]-- |
| 346 | } |
| 347 | if src_ref_count[s] == 0 { |
| 348 | for j in 0 .. np { |
| 349 | if alive[j] == 1 && p_dest[j] == s { |
| 350 | worklist << j |
| 351 | } |
| 352 | } |
| 353 | } |
| 354 | continue |
| 355 | } |
| 356 | |
| 357 | // Cycle: break it by copying one source into a temp. |
| 358 | mut ci := -1 |
| 359 | for k in 0 .. np { |
| 360 | if alive[k] == 1 { |
| 361 | ci = k |
| 362 | break |
| 363 | } |
| 364 | } |
| 365 | if ci < 0 { |
| 366 | break |
| 367 | } |
| 368 | cycle_src := p_src[ci] |
| 369 | mut typ := 0 |
| 370 | if cycle_src >= 0 && cycle_src < m.values.len { |
| 371 | typ = m.values[cycle_src].typ |
| 372 | } |
| 373 | if typ == 0 && p_dest[ci] >= 0 && p_dest[ci] < m.values.len { |
| 374 | typ = m.values[p_dest[ci]].typ |
| 375 | } |
| 376 | temp := insert_temp_in_block(mut m, blk_id, cycle_src, typ) |
| 377 | for temp >= src_ref_count.len { |
| 378 | src_ref_count << 0 |
| 379 | } |
| 380 | src_ref_count[temp] = 0 |
| 381 | for i in 0 .. np { |
| 382 | if alive[i] == 1 && p_src[i] == cycle_src { |
| 383 | p_src[i] = temp |
| 384 | src_ref_count[temp]++ |
| 385 | } |
| 386 | } |
| 387 | src_ref_count[cycle_src] = 0 |
| 388 | for j in 0 .. np { |
| 389 | if alive[j] == 1 && p_dest[j] == cycle_src { |
| 390 | worklist << j |
| 391 | } |
| 392 | } |
| 393 | } |
| 394 | |
| 395 | for si in 0 .. s_dest.len { |
| 396 | insert_copy_in_block(mut m, blk_id, s_dest[si], s_src[si]) |
| 397 | } |
| 398 | } |
| 399 | |
| 400 | // insert_temp_in_block updates insert temp in block state for optimize. |
| 401 | fn insert_temp_in_block(mut m ssa.Module, blk_id int, src int, typ int) int { |
| 402 | m.instrs << ssa.Instruction{ |
| 403 | op: .bitcast |
| 404 | block: blk_id |
| 405 | typ: typ |
| 406 | operands: [ssa.ValueID(src)] |
| 407 | } |
| 408 | temp_id := m.add_value(.instruction, typ, 'phi_tmp_${m.values.len}', m.instrs.len - 1) |
| 409 | insert_before_terminator(mut m, blk_id, temp_id) |
| 410 | return temp_id |
| 411 | } |
| 412 | |
| 413 | // insert_copy_in_block updates insert copy in block state for optimize. |
| 414 | fn insert_copy_in_block(mut m ssa.Module, blk_id int, dest int, src int) { |
| 415 | typ := m.values[dest].typ |
| 416 | m.instrs << ssa.Instruction{ |
| 417 | op: .assign |
| 418 | block: blk_id |
| 419 | typ: typ |
| 420 | operands: [ssa.ValueID(dest), ssa.ValueID(src)] |
| 421 | } |
| 422 | val_id := m.add_value(.instruction, typ, 'copy', m.instrs.len - 1) |
| 423 | insert_before_terminator(mut m, blk_id, val_id) |
| 424 | } |
| 425 | |
| 426 | // insert_before_terminator updates insert before terminator state for optimize. |
| 427 | fn insert_before_terminator(mut m ssa.Module, blk_id int, new_val int) { |
| 428 | mut blk := m.blocks[blk_id] |
| 429 | mut insert_idx := blk.instrs.len |
| 430 | if insert_idx > 0 { |
| 431 | last_val_id := blk.instrs[blk.instrs.len - 1] |
| 432 | last_val := m.values[last_val_id] |
| 433 | last_instr := m.instrs[last_val.index] |
| 434 | if last_instr.op in [.ret, .br, .jmp, .switch_, .unreachable] { |
| 435 | insert_idx = blk.instrs.len - 1 |
| 436 | } |
| 437 | } |
| 438 | mut new_instrs := []ssa.ValueID{cap: blk.instrs.len + 1} |
| 439 | for ii in 0 .. insert_idx { |
| 440 | new_instrs << blk.instrs[ii] |
| 441 | } |
| 442 | new_instrs << ssa.ValueID(new_val) |
| 443 | for ii in insert_idx .. blk.instrs.len { |
| 444 | new_instrs << blk.instrs[ii] |
| 445 | } |
| 446 | blk.instrs = new_instrs |
| 447 | m.blocks[blk_id] = blk |
| 448 | } |
| 449 | |