Skip to content

Commit 729a8c9

Browse files
authored
cfg_simplify: Correctly handle must-throw blocks (#54216)
We have a special exception in our IR that allows `::Union{}` marked instructions to act as an `unreachable` terminator. Without this fix, we were advancing into the subsequent block despite it not being a successor.
1 parent 96866cb commit 729a8c9

File tree

2 files changed

+44
-2
lines changed

2 files changed

+44
-2
lines changed

base/compiler/ssair/passes.jl

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2274,6 +2274,15 @@ function cfg_simplify!(ir::IRCode)
22742274
elseif merge_into[idx] == 0 && is_bb_empty(ir, bb) && is_legal_bb_drop(ir, idx, bb)
22752275
# If this BB is empty, we can still merge it as long as none of our successor's phi nodes
22762276
# reference our predecessors.
2277+
#
2278+
# This is for situations like:
2279+
# #1 - ...
2280+
# goto #3 if not ...
2281+
# #2 - (empty)
2282+
# #3 - ϕ(#2 => true, #1 => false)
2283+
#
2284+
# where we rely on the empty basic block to disambiguate the ϕ-node's value
2285+
22772286
found_interference = false
22782287
preds = Int[ascend_eliminated_preds(bbs, pred) for pred in bb.preds]
22792288
for idx in bbs[succ].stmts
@@ -2331,8 +2340,11 @@ function cfg_simplify!(ir::IRCode)
23312340
end
23322341
curr = merged_succ[curr]
23332342
end
2334-
terminator = ir[SSAValue(ir.cfg.blocks[curr].stmts[end])][:stmt]
2335-
if isa(terminator, GotoNode) || isa(terminator, ReturnNode)
2343+
terminator = ir[SSAValue(bbs[curr].stmts[end])][:stmt]
2344+
is_throw = ir[SSAValue(bbs[curr].stmts[end])][:type] === Union{} && !isa(terminator, PhiNode)
2345+
if isa(terminator, GotoNode) || isa(terminator, ReturnNode) || is_throw
2346+
# Only advance to next block if it's a successor
2347+
# (avoid GotoNode, ReturnNode, throw()/Union{})
23362348
break
23372349
elseif isa(terminator, GotoIfNot)
23382350
if bb_rename_succ[terminator.dest] == 0

test/compiler/irpasses.jl

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1881,3 +1881,33 @@ end
18811881
@test !fully_eliminated() do
18821882
all(iszero, Iterators.repeated(0))
18831883
end
1884+
1885+
## Test that cfg_simplify respects implicit `unreachable` terminators
1886+
let code = Any[
1887+
# block 1
1888+
GotoIfNot(Core.Argument(2), 4),
1889+
# block 2
1890+
Expr(:call, Base.throw, "error"), # an implicit `unreachable` terminator
1891+
# block 3
1892+
Expr(:call, :opaque),
1893+
# block 4
1894+
ReturnNode(nothing),
1895+
]
1896+
ir = make_ircode(code; ssavaluetypes=Any[Union{}, Union{}, Any, Union{}])
1897+
1898+
# Unfortunately `compute_basic_blocks` does not notice the `throw()` so it gives us
1899+
# a slightly imprecise CFG. Instead manually construct the CFG we need for this test:
1900+
empty!(ir.cfg.blocks)
1901+
push!(ir.cfg.blocks, BasicBlock(StmtRange(1,1), [], [2,4]))
1902+
push!(ir.cfg.blocks, BasicBlock(StmtRange(2,2), [1], []))
1903+
push!(ir.cfg.blocks, BasicBlock(StmtRange(3,3), [], []))
1904+
push!(ir.cfg.blocks, BasicBlock(StmtRange(4,4), [1], []))
1905+
empty!(ir.cfg.index)
1906+
append!(ir.cfg.index, Int[2,3,4])
1907+
ir.stmts.stmt[1] = GotoIfNot(Core.Argument(2), 4)
1908+
1909+
Core.Compiler.verify_ir(ir)
1910+
ir = Core.Compiler.cfg_simplify!(ir)
1911+
Core.Compiler.verify_ir(ir)
1912+
@test length(ir.cfg.blocks) == 3 # should have removed block 3
1913+
end

0 commit comments

Comments
 (0)