Skip to content

Commit ea47c5f

Browse files
authored
IRStructurizer: handle merge phis for if-then regions (#53)
1 parent 8c8cadc commit ea47c5f

3 files changed

Lines changed: 145 additions & 5 deletions

File tree

IRStructurizer/src/control_tree.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ Acyclic regions:
1717
- `REGION_IF_THEN_ELSE`: Conditional with two symmetric branches `u` ─→ `v` and `u` ─→ `w` and a single merge block reachable by `v` ─→ x or `w` ─→ x
1818
- `REGION_SWITCH`: Conditional with any number of branches [`u` ─→ `vᵢ`, `u` ─→ `vᵢ₊₁`, ...] and a single merge block reachable by [`vᵢ` ─→ `w`, `vᵢ₊₁` ─→ `w`, ...]
1919
- `REGION_TERMINATION`: Acyclic region which contains a block `v` with multiple branches, including one or multiple branches to blocks `wᵢ` which end with a function termination instruction. The region is composed of `v` and all the `wᵢ`.
20+
- `REGION_PROPER`: Generic proper region that doesn't match simpler patterns. Single-entry acyclic subgraph where the entry dominates all nodes.
2021
2122
Cyclic regions:
2223
- `REGION_WHILE_LOOP`: Simple cyclic region made of a condition block `u`, a loop body block `v` and a merge block `w` such that `v` ⇆ `u` ─→ `w`
@@ -32,6 +33,7 @@ Cyclic regions:
3233
REGION_FOR_LOOP
3334
REGION_WHILE_LOOP
3435
REGION_NATURAL_LOOP
36+
REGION_PROPER
3537
end
3638

3739
@active block_region(args) begin

IRStructurizer/src/structure.jl

Lines changed: 87 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,48 @@ function get_loop_blocks(tree::ControlTree, ir::IRCode)
2323
return loop_blocks
2424
end
2525

26+
"""
27+
get_region_blocks(tree::ControlTree, ir::IRCode) -> Set{Int}
28+
29+
Get all block indices contained in a control tree region.
30+
"""
31+
function get_region_blocks(tree::ControlTree, ir::IRCode)
32+
blocks = Set{Int}()
33+
nblocks = length(ir.cfg.blocks)
34+
for subtree in PreOrderDFS(tree)
35+
idx = node_index(subtree)
36+
if 1 <= idx <= nblocks
37+
push!(blocks, idx)
38+
end
39+
end
40+
return blocks
41+
end
42+
43+
"""
44+
get_exit_block(tree::ControlTree, ir::IRCode) -> Int
45+
46+
Get the exit block index of a control tree region.
47+
For single-block regions, this is the block itself.
48+
For multi-block regions, this is the block that has successors outside the region.
49+
"""
50+
function get_exit_block(tree::ControlTree, ir::IRCode)
51+
blocks = get_region_blocks(tree, ir)
52+
nblocks = length(ir.cfg.blocks)
53+
54+
# Find block(s) with successors outside the region
55+
for block_idx in blocks
56+
1 <= block_idx <= nblocks || continue
57+
for succ in ir.cfg.blocks[block_idx].succs
58+
if !(succ in blocks)
59+
return block_idx
60+
end
61+
end
62+
end
63+
64+
# Fallback to entry block
65+
return node_index(tree)
66+
end
67+
2668
"""
2769
convert_phi_value(val) -> IRValue
2870
@@ -89,6 +131,9 @@ function tree_to_block(tree::ControlTree, ir::IRCode, ctx::StructurizationContex
89131
handle_loop!(block, tree, ir, ctx)
90132
elseif rtype == REGION_SWITCH
91133
handle_switch!(block, tree, ir, ctx)
134+
elseif rtype == REGION_PROPER
135+
# Generic proper region - treat like REGION_BLOCK
136+
handle_block_region!(block, tree, ir, ctx)
92137
else
93138
error("Unknown region type: $rtype")
94139
end
@@ -150,6 +195,9 @@ function handle_nested_region!(block::Block, tree::ControlTree, ir::IRCode,
150195
handle_loop!(block, tree, ir, ctx)
151196
elseif rtype == REGION_SWITCH
152197
handle_switch!(block, tree, ir, ctx)
198+
elseif rtype == REGION_PROPER
199+
# Generic proper region - treat like REGION_BLOCK
200+
handle_block_region!(block, tree, ir, ctx)
153201
else
154202
error("Unknown region type in nested region: $rtype")
155203
end
@@ -196,8 +244,9 @@ function handle_if_then_else!(block::Block, tree::ControlTree, ir::IRCode,
196244
else_blk = tree_to_block(else_tree, ir, ctx)
197245

198246
# Find merge block and detect merge phis
199-
then_block_idx = node_index(then_tree)
200-
else_block_idx = node_index(else_tree)
247+
# Use exit blocks (not entry blocks) for multi-block regions
248+
then_block_idx = get_exit_block(then_tree, ir)
249+
else_block_idx = get_exit_block(else_tree, ir)
201250
merge_phis = find_merge_phis(ir, then_block_idx, else_block_idx)
202251

203252
# Add YieldOp terminators with phi values
@@ -321,10 +370,43 @@ function handle_if_then!(block::Block, tree::ControlTree, ir::IRCode,
321370
# Empty else block
322371
else_blk = Block()
323372

373+
# Find merge block and detect merge phis
374+
# For if-then, the merge block is the common successor of cond_idx and then_block_idx.
375+
# cond_idx acts as the "else" path since it has a direct edge to merge when condition is false.
376+
# Use exit block (not entry block) for multi-block then regions
377+
then_block_idx = get_exit_block(then_tree, ir)
378+
merge_phis = find_merge_phis(ir, then_block_idx, cond_idx)
379+
380+
# Add YieldOp terminators with phi values
381+
if !isempty(merge_phis)
382+
then_values = [phi.then_val for phi in merge_phis]
383+
else_values = [phi.else_val for phi in merge_phis]
384+
then_blk.terminator = YieldOp(then_values)
385+
else_blk.terminator = YieldOp(else_values)
386+
end
387+
324388
if_op = IfOp(cond_value, then_blk, else_blk)
325-
bb = ir.cfg.blocks[cond_idx]
326-
result_idx = gotoifnot_idx !== nothing ? gotoifnot_idx : last(bb.stmts)
327-
push!(block, result_idx, if_op, Nothing)
389+
390+
# Allocate synthesized SSA index for IfOp
391+
if_result_idx = ctx.next_ssa_idx
392+
ctx.next_ssa_idx += 1
393+
394+
# Use Tuple type for IfOp results (uniform handling in codegen)
395+
if !isempty(merge_phis)
396+
phi_types = [ctx.ssavaluetypes[phi.ssa_idx] for phi in merge_phis]
397+
result_type = Tuple{phi_types...}
398+
push!(block, if_result_idx, if_op, result_type)
399+
400+
# Generate getfield statements at original phi indices
401+
# This preserves SSA reference semantics: `return %7` still works because getfield is at %7
402+
for (i, phi) in enumerate(merge_phis)
403+
getfield_expr = Expr(:call, Core.getfield, SSAValue(if_result_idx), i)
404+
push!(block, phi.ssa_idx, getfield_expr, phi_types[i])
405+
end
406+
else
407+
# No merge phis - use Tuple{} for uniformity
408+
push!(block, if_result_idx, if_op, Tuple{})
409+
end
328410
end
329411

330412
"""

IRStructurizer/test/runtests.jl

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -644,6 +644,62 @@ end
644644
validate_scf(sci)
645645
end
646646

647+
# If-then (no else) must yield phi values, not return Nothing
648+
@testset "if-then yields phi values" begin
649+
@test @filecheck begin
650+
code_structured(Tuple{Bool}) do flag::Bool
651+
x = 0
652+
@check "if"
653+
if flag
654+
x = 1
655+
end
656+
@check "yield"
657+
@check "else"
658+
@check "yield"
659+
@check "getfield"
660+
return x
661+
end
662+
end
663+
end
664+
665+
@testset "if-then phi inside loop" begin
666+
@test @filecheck begin
667+
code_structured(Tuple{Int, Bool}) do n::Int, flag::Bool
668+
acc = 0
669+
j = 1
670+
@check "for"
671+
while j <= n
672+
x = 0
673+
@check "if"
674+
if flag && j >= 2
675+
x = 1
676+
end
677+
@check "yield"
678+
@check "getfield"
679+
acc += x
680+
j += 1
681+
end
682+
return acc
683+
end
684+
end
685+
end
686+
687+
@testset "if-then with multiple phis" begin
688+
@test @filecheck begin
689+
code_structured(Tuple{Bool}) do flag::Bool
690+
x, y = 0, 0
691+
@check "if"
692+
if flag
693+
x, y = 1, 2
694+
end
695+
@check "yield"
696+
@check "getfield"
697+
@check "getfield"
698+
return x + y
699+
end
700+
end
701+
end
702+
647703
end # regression
648704

649705
#=============================================================================

0 commit comments

Comments
 (0)