| 1 | import v3.ssa |
| 2 | import v3.ssa.optimize |
| 3 | |
| 4 | // Build a function body inside a worker module and merge it back, exercising the |
| 5 | // raw-block-id operand remapping (branch/phi block operands must shift by the |
| 6 | // block offset, value operands by the value offset). |
| 7 | fn test_worker_clone_merge_remaps_block_and_value_operands() { |
| 8 | mut m := ssa.Module.new() |
| 9 | i64t := m.type_store.get_int(64) |
| 10 | // A pre-existing (seed) constant and function registered serially. |
| 11 | _ := m.get_or_add_const(i64t, '111') |
| 12 | fidx := m.new_function('compute', i64t) |
| 13 | |
| 14 | // Clone a worker and snapshot the seed sizes. |
| 15 | mut w := m.new_worker_module() |
| 16 | seed_values := m.values.len |
| 17 | seed_instrs := m.instrs.len |
| 18 | seed_blocks := m.blocks.len |
| 19 | seed_types := m.type_store.types.len |
| 20 | seed_funcs := m.funcs.len |
| 21 | |
| 22 | // Build a diamond + merge phi entirely inside the worker. |
| 23 | entry := w.add_block(fidx, 'entry') |
| 24 | then_b := w.add_block(fidx, 'then') |
| 25 | else_b := w.add_block(fidx, 'else') |
| 26 | merge := w.add_block(fidx, 'merge') |
| 27 | |
| 28 | cond := w.get_or_add_const(w.type_store.get_int(1), '1') |
| 29 | w.add_instr(.br, entry, ssa.TypeID(0), [cond, ssa.ValueID(then_b), ssa.ValueID(else_b)]) |
| 30 | |
| 31 | ten := w.get_or_add_const(i64t, '10') |
| 32 | w.add_instr(.jmp, then_b, ssa.TypeID(0), [ssa.ValueID(merge)]) |
| 33 | twenty := w.get_or_add_const(i64t, '20') |
| 34 | w.add_instr(.jmp, else_b, ssa.TypeID(0), [ssa.ValueID(merge)]) |
| 35 | |
| 36 | // phi: [ten, then_b, twenty, else_b] (odd operands are raw block ids) |
| 37 | phi := w.add_instr(.phi, merge, i64t, [ten, ssa.ValueID(then_b), twenty, ssa.ValueID(else_b)]) |
| 38 | w.add_instr(.ret, merge, ssa.TypeID(0), [phi]) |
| 39 | |
| 40 | fd := ssa.FuncSSAData{ |
| 41 | func_idx: fidx |
| 42 | blocks: w.funcs[fidx].blocks |
| 43 | params: w.funcs[fidx].params |
| 44 | } |
| 45 | |
| 46 | m.merge_worker_module(w, [fd], seed_values, seed_instrs, seed_blocks, seed_types, seed_funcs) |
| 47 | |
| 48 | // The function now has its 4 blocks, all parented correctly. |
| 49 | assert m.funcs[fidx].blocks.len == 4 |
| 50 | for blk_id in m.funcs[fidx].blocks { |
| 51 | assert blk_id >= 0 && blk_id < m.blocks.len |
| 52 | assert m.blocks[blk_id].parent == fidx |
| 53 | } |
| 54 | |
| 55 | // The merged branch must target real blocks inside the function (this fails if |
| 56 | // block operands were remapped as values). |
| 57 | entry_id := m.funcs[fidx].blocks[0] |
| 58 | term_id := m.blocks[entry_id].instrs.last() |
| 59 | term := m.instrs[m.values[term_id].index] |
| 60 | assert term.op == .br |
| 61 | for oi in [1, 2] { |
| 62 | tgt := int(term.operands[oi]) |
| 63 | assert tgt >= 0 && tgt < m.blocks.len |
| 64 | assert tgt in m.funcs[fidx].blocks |
| 65 | } |
| 66 | |
| 67 | // The whole thing must verify and optimize cleanly (optimize() panics on a |
| 68 | // broken CFG / dangling phi predecessor, so reaching the asserts proves the |
| 69 | // remapping is consistent). |
| 70 | optimize.optimize_with_options(mut m, optimize.OptimizeOptions{ |
| 71 | mem2reg: false |
| 72 | eliminate_phis: true |
| 73 | }) |
| 74 | errors := optimize.verify(m) |
| 75 | for e in errors { |
| 76 | assert !e.msg.contains('invalid') |
| 77 | assert !e.msg.contains('outside function') |
| 78 | } |
| 79 | } |
| 80 | |