Skip to content

Commit 396abe4

Browse files
Kenoaviatesk
andauthored
ir: Set refined when narrowing PhiNode in IncrementalCompact (#54061)
This is a bit of a tricky one. What happens here is that IncrementalCompact finds that one branch of an if/else is dead, so one of the incoming values of the PhiNode goes away. This may have the effect of narrowing the type of the PhiNode (if the branches have different types). In the latest iteration of our compiler, such situations need to be annotated with IR_FLAG_REFINED or `Refined()`, otherwise irinterp will skip them on revisit. In theory, the fix is quite simple: Check whether the type is being refined and if so, set the flag. However, this code sits inside IncrementalCompcat, which currently neither performs lattice operations nor sets any Refined flags by itself. The three possible options here are: 1. Thread lattice through IncrementalCompact. 2. Use an egal check inside IncrementalCompact rather than the proper lattice query. This is legal, but may overapproximate the need for `Refined`, thus causing unnecessary work later. 3. Move the phi node narrowing outside of IncrementalCompact. This PR takes a hybrid approach of 2 and 3. Approach 1 is undesirable, because we do not want to recompile all of IncrementalCompact for every lattice, just for this one query. I almost took approach 2, but it is unsatisfying, because of the imprecision, which could cuase a lot of extra work in pathological cases. The primary downside of approach 3 is that now every pass that performs IncrementalCompact with CFG manipulation enabled will need to call the `reprocess_phi_node!` helper. We have two of these in base (adce_pass! and `compact!(..., true)` although only the former as access to a lattice. The compromise I came up with here is to do the egality check, except leaving the PhiNode in place when it fails rather than setting `Refined` internally. I think this is ok. There's a bit of an issue with duplicate work, because the first thing that the lattice check will do is check for equality, but that only happens in the case where a refinement actually is required, which is both rarer and profitable. Finally, I'm pretty sure I've found this issue before, but I cannot find any writeup on it, so if somebody remembers me writing it up somewhere, please let me know so I can add appropriate cross references. --------- Co-authored-by: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com>
1 parent d47cbf6 commit 396abe4

File tree

3 files changed

+63
-12
lines changed

3 files changed

+63
-12
lines changed

base/compiler/ssair/ir.jl

Lines changed: 41 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1601,17 +1601,15 @@ function process_node!(compact::IncrementalCompact, result_idx::Int, inst::Instr
16011601
end
16021602

16031603
values = process_phinode_values(values, late_fixup, already_inserted_phi_arg, result_idx, ssa_rename, used_ssas, new_new_used_ssas, do_rename_ssa, mark_refined!)
1604-
# Don't remove the phi node if it is before the definition of its value
1605-
# because doing so can create forward references. This should only
1606-
# happen with dead loops, but can cause problems when optimization
1607-
# passes look at all code, dead or not. This check should be
1608-
# unnecessary when DCE can remove those dead loops entirely, so this is
1609-
# just to be safe.
1610-
before_def = isassigned(values, 1) && (v = values[1]; isa(v, OldSSAValue)) && idx < v.id
1611-
if length(edges) == 1 && isassigned(values, 1) && !before_def &&
1612-
length(cfg_transforms_enabled ?
1613-
result_bbs[bb_rename_succ[active_bb]].preds :
1614-
compact.ir.cfg.blocks[active_bb].preds) == 1
1604+
1605+
# Quick egality check for PhiNode that may be replaced with its incoming
1606+
# value without needing to set the `Refined` flag. We can't do the actual
1607+
# refinement check, because we do not have access to the lattice here.
1608+
# Users may call `reprocess_phi_node!` inside the compaction loop to
1609+
# revisit PhiNodes with the proper lattice refinement check.
1610+
if may_replace_phi(values, cfg_transforms_enabled ?
1611+
result_bbs[bb_rename_succ[active_bb]] :
1612+
compact.ir.cfg.blocks[active_bb], idx) && argextype(values[1], compact) === inst[:type]
16151613
# There's only one predecessor left - just replace it
16161614
v = values[1]
16171615
@assert !isa(v, NewSSAValue)
@@ -1662,6 +1660,38 @@ function process_node!(compact::IncrementalCompact, result_idx::Int, inst::Instr
16621660
return result_idx
16631661
end
16641662

1663+
function may_replace_phi(values::Vector{Any}, phi_bb::BasicBlock, idx::Int)
1664+
length(values) == 1 || return false
1665+
isassigned(values, 1) || return false
1666+
length(phi_bb.preds) == 1 || return false
1667+
1668+
# Don't remove the phi node if it is before the definition of its value
1669+
# because doing so can create forward references. This should only
1670+
# happen with dead loops, but can cause problems when optimization
1671+
# passes look at all code, dead or not. This check should be
1672+
# unnecessary when DCE can remove those dead loops entirely, so this is
1673+
# just to be safe.
1674+
v = values[1]
1675+
before_def = isa(v, OldSSAValue) && idx < v.id
1676+
return !before_def
1677+
end
1678+
1679+
function reprocess_phi_node!(𝕃ₒ::AbstractLattice, compact::IncrementalCompact, phi::PhiNode, old_idx::Int)
1680+
phi_bb = compact.active_result_bb
1681+
did_just_finish_bb(compact) && (phi_bb -= 1)
1682+
may_replace_phi(phi.values, compact.cfg_transform.result_bbs[phi_bb], compact.idx) || return false
1683+
1684+
# There's only one predecessor left - just replace it
1685+
v = phi.values[1]
1686+
if !(𝕃ₒ, compact[compact.ssa_rename[old_idx]][:type], argextype(v, compact))
1687+
v = Refined(v)
1688+
end
1689+
compact.ssa_rename[old_idx] = v
1690+
1691+
delete_inst_here!(compact)
1692+
return true
1693+
end
1694+
16651695
function resize!(compact::IncrementalCompact, nnewnodes::Int)
16661696
old_length = length(compact.result)
16671697
resize!(compact.result, nnewnodes)

base/compiler/ssair/passes.jl

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2009,8 +2009,13 @@ function adce_pass!(ir::IRCode, inlining::Union{Nothing,InliningState}=nothing)
20092009
unionphis = Pair{Int,Any}[] # sorted
20102010
compact = IncrementalCompact(ir, true)
20112011
made_changes = false
2012-
for ((_, idx), stmt) in compact
2012+
for ((old_idx, idx), stmt) in compact
20132013
if isa(stmt, PhiNode)
2014+
if reprocess_phi_node!(𝕃ₒ, compact, stmt, old_idx)
2015+
# Phi node has a single predecessor and was deleted
2016+
made_changes = true
2017+
continue
2018+
end
20142019
push!(all_phis, idx)
20152020
if is_some_union(compact.result[idx][:type])
20162021
push!(unionphis, Pair{Int,Any}(idx, Union{}))

test/compiler/irpasses.jl

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1820,3 +1820,19 @@ function f53521()
18201820
end
18211821
end
18221822
@test code_typed(f53521)[1][2] === Nothing
1823+
1824+
# Test that adce_pass! sets Refined on PhiNode values
1825+
let code = Any[
1826+
# Basic Block 1
1827+
GotoIfNot(false, 3)
1828+
# Basic Block 2
1829+
nothing
1830+
# Basic Block 3
1831+
PhiNode(Int32[1, 2], Any[1.0, 1])
1832+
ReturnNode(Core.SSAValue(3))
1833+
]
1834+
ir = make_ircode(code; ssavaluetypes=Any[Any, Nothing, Union{Int64, Float64}, Any])
1835+
(ir, made_changes) = Core.Compiler.adce_pass!(ir)
1836+
@test made_changes
1837+
@test (ir[Core.SSAValue(length(ir.stmts))][:flag] & Core.Compiler.IR_FLAG_REFINED) != 0
1838+
end

0 commit comments

Comments
 (0)