Skip to content

Commit d352678

Browse files
topolarityKristofferC
authored andcommitted
Fix goto insertion when dom-sorting IR in slot2ssa pass (#56189)
Fix-up this pass a bit to correctly handle fall-through terminators that cannot have their BasicBlock extended (e.g. `Expr(:leave, ...)`)
1 parent 362086a commit d352678

File tree

2 files changed

+75
-29
lines changed

2 files changed

+75
-29
lines changed

base/compiler/ssair/slot2ssa.jl

Lines changed: 46 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -339,43 +339,58 @@ RPO traversal and in particular, any use of an SSA value must come after
339339
(by linear order) its definition.
340340
"""
341341
function domsort_ssa!(ir::IRCode, domtree::DomTree)
342-
# First compute the new order of basic blocks
342+
# Mapping from new → old BB index
343+
# An "old" index of 0 means that this was a BB inserted as part of a fixup (see below)
343344
result_order = Int[]
344-
stack = Int[]
345+
346+
# Mapping from old → new BB index
345347
bb_rename = fill(-1, length(ir.cfg.blocks))
346-
node = 1
347-
ncritbreaks = 0
348-
nnewfallthroughs = 0
349-
while node !== -1
350-
push!(result_order, node)
351-
bb_rename[node] = length(result_order)
352-
cs = domtree.nodes[node].children
353-
terminator = ir[SSAValue(last(ir.cfg.blocks[node].stmts))][:stmt]
354-
next_node = node + 1
355-
node = -1
348+
349+
# The number of GotoNodes we need to insert to preserve control-flow after sorting
350+
nfixupstmts = 0
351+
352+
# node queued up for scheduling (-1 === nothing)
353+
node_to_schedule = 1
354+
worklist = Int[]
355+
while node_to_schedule !== -1
356+
# First assign a new BB index to `node_to_schedule`
357+
push!(result_order, node_to_schedule)
358+
bb_rename[node_to_schedule] = length(result_order)
359+
cs = domtree.nodes[node_to_schedule].children
360+
terminator = ir[SSAValue(last(ir.cfg.blocks[node_to_schedule].stmts))][:stmt]
361+
fallthrough = node_to_schedule + 1
362+
node_to_schedule = -1
363+
356364
# Adding the nodes in reverse sorted order attempts to retain
357365
# the original source order of the nodes as much as possible.
358366
# This is not required for correctness, but is easier on the humans
359-
for child in Iterators.Reverse(cs)
360-
if child == next_node
367+
for node in Iterators.Reverse(cs)
368+
if node == fallthrough
361369
# Schedule the fall through node first,
362370
# so we can retain the fall through
363-
node = next_node
371+
node_to_schedule = node
364372
else
365-
push!(stack, child)
373+
push!(worklist, node)
366374
end
367375
end
368-
if node == -1 && !isempty(stack)
369-
node = pop!(stack)
376+
if node_to_schedule == -1 && !isempty(worklist)
377+
node_to_schedule = pop!(worklist)
370378
end
371-
if node != next_node && !isa(terminator, Union{GotoNode, ReturnNode})
379+
# If a fallthrough successor is no longer the fallthrough after sorting, we need to
380+
# add a GotoNode (and either extend or split the basic block as necessary)
381+
if node_to_schedule != fallthrough && !isa(terminator, Union{GotoNode, ReturnNode})
372382
if isa(terminator, GotoIfNot)
373383
# Need to break the critical edge
374-
ncritbreaks += 1
384+
push!(result_order, 0)
385+
elseif isa(terminator, EnterNode) || isexpr(terminator, :leave)
386+
# Cannot extend the BasicBlock with a goto, have to split it
375387
push!(result_order, 0)
376388
else
377-
nnewfallthroughs += 1
389+
# No need for a new block, just extend
390+
@assert !isterminator(terminator)
378391
end
392+
# Reserve space for the fixup goto
393+
nfixupstmts += 1
379394
end
380395
end
381396
new_bbs = Vector{BasicBlock}(undef, length(result_order))
@@ -385,7 +400,7 @@ function domsort_ssa!(ir::IRCode, domtree::DomTree)
385400
nstmts += length(ir.cfg.blocks[i].stmts)
386401
end
387402
end
388-
result = InstructionStream(nstmts + ncritbreaks + nnewfallthroughs)
403+
result = InstructionStream(nstmts + nfixupstmts)
389404
inst_rename = Vector{SSAValue}(undef, length(ir.stmts) + length(ir.new_nodes))
390405
@inbounds for i = 1:length(ir.stmts)
391406
inst_rename[i] = SSAValue(-1)
@@ -394,7 +409,6 @@ function domsort_ssa!(ir::IRCode, domtree::DomTree)
394409
inst_rename[i + length(ir.stmts)] = SSAValue(i + length(result))
395410
end
396411
bb_start_off = 0
397-
crit_edge_breaks_fixup = Tuple{Int, Int}[]
398412
for (new_bb, bb) in pairs(result_order)
399413
if bb == 0
400414
nidx = bb_start_off + 1
@@ -426,20 +440,23 @@ function domsort_ssa!(ir::IRCode, domtree::DomTree)
426440
else
427441
result[inst_range[end]][:stmt] = GotoNode(bb_rename[terminator.label])
428442
end
429-
elseif isa(terminator, GotoIfNot)
430-
# Check if we need to break the critical edge
443+
elseif isa(terminator, GotoIfNot) || isa(terminator, EnterNode) || isexpr(terminator, :leave)
444+
# Check if we need to break the critical edge or split the block
431445
if bb_rename[bb + 1] != new_bb + 1
432446
@assert result_order[new_bb + 1] == 0
433447
# Add an explicit goto node in the next basic block (we accounted for this above)
434448
nidx = inst_range[end] + 1
435449
node = result[nidx]
436450
node[:stmt], node[:type], node[:line] = GotoNode(bb_rename[bb + 1]), Any, NoLineUpdate
437451
end
438-
result[inst_range[end]][:stmt] = GotoIfNot(terminator.cond, bb_rename[terminator.dest])
439-
elseif !isa(terminator, ReturnNode)
440-
if isa(terminator, EnterNode)
452+
if isa(terminator, GotoIfNot)
453+
result[inst_range[end]][:stmt] = GotoIfNot(terminator.cond, bb_rename[terminator.dest])
454+
elseif isa(terminator, EnterNode)
441455
result[inst_range[end]][:stmt] = EnterNode(terminator, terminator.catch_dest == 0 ? 0 : bb_rename[terminator.catch_dest])
456+
else
457+
@assert isexpr(terminator, :leave)
442458
end
459+
elseif !isa(terminator, ReturnNode)
443460
if bb_rename[bb + 1] != new_bb + 1
444461
# Add an explicit goto node
445462
nidx = inst_range[end] + 1
@@ -452,7 +469,7 @@ function domsort_ssa!(ir::IRCode, domtree::DomTree)
452469
local new_preds, new_succs
453470
let bb = bb, bb_rename = bb_rename, result_order = result_order
454471
new_preds = Int[bb for bb in (rename_incoming_edge(i, bb, result_order, bb_rename) for i in ir.cfg.blocks[bb].preds) if bb != -1]
455-
new_succs = Int[ rename_outgoing_edge(i, bb, result_order, bb_rename) for i in ir.cfg.blocks[bb].succs]
472+
new_succs = Int[ rename_outgoing_edge(i, bb, result_order, bb_rename) for i in ir.cfg.blocks[bb].succs]
456473
end
457474
new_bbs[new_bb] = BasicBlock(inst_range, new_preds, new_succs)
458475
end

test/compiler/irpasses.jl

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1967,3 +1967,32 @@ let f = (x)->nothing, mi = Base.method_instance(f, (Base.RefValue{Nothing},)), c
19671967
ir = Core.Compiler.sroa_pass!(ir, inlining)
19681968
Core.Compiler.verify_ir(ir)
19691969
end
1970+
1971+
let code = Any[
1972+
# block 1
1973+
GotoNode(4), # skip
1974+
# block 2
1975+
Expr(:leave, SSAValue(1)), # not domsorted - make sure we move it correctly
1976+
# block 3
1977+
ReturnNode(2),
1978+
# block 4
1979+
EnterNode(7),
1980+
# block 5
1981+
GotoIfNot(Argument(1), 2),
1982+
# block 6
1983+
Expr(:leave, SSAValue(1)),
1984+
# block 7
1985+
ReturnNode(1),
1986+
# block 8
1987+
ReturnNode(nothing),
1988+
]
1989+
ir = make_ircode(code; ssavaluetypes=Any[Any, Any, Union{}, Any, Any, Any, Union{}, Union{}])
1990+
@test length(ir.cfg.blocks) == 8
1991+
Core.Compiler.verify_ir(ir)
1992+
1993+
# The IR should remain valid after domsorting
1994+
# (esp. including the insertion of new BasicBlocks for any fix-ups)
1995+
domtree = Core.Compiler.construct_domtree(ir)
1996+
ir = Core.Compiler.domsort_ssa!(ir, domtree)
1997+
Core.Compiler.verify_ir(ir)
1998+
end

0 commit comments

Comments
 (0)