Skip to content

Commit

Permalink
post-opt: use augmented post-domtree for visit_conditional_successors
Browse files Browse the repository at this point in the history
This commit fixes the first problem that was found while digging into
#53613. It turns out that the post-domtree constructed
from regular `IRCode` doesn't work for visiting conditional successors
for post-opt analysis in cases like:
```julia
julia> let code = Any[
               # block 1
               GotoIfNot(Argument(2), 3),
               # block 2
               ReturnNode(Argument(3)),
               # block 3 (we should visit this block)
               Expr(:call, throw, "potential throw"),
               ReturnNode(), # unreachable
           ]
           ir = make_ircode(code; slottypes=Any[Any,Bool,Bool])
           visited = BitSet()
           @test !Core.Compiler.visit_conditional_successors(CC.LazyPostDomtree(ir), ir, #=bb=#1) do succ::Int
               push!(visited, succ)
               return false
           end
           @test 2 ∉ visited
           @test 3 ∈ visited
       end
Test Failed at REPL[14]:16
  Expression: 2 ∉ visited
   Evaluated: 2 ∉ BitSet([2])
```

This might mean that we need to fix on the `postdominates` end, but for
now, this commit tries to get around it by using the augmented post
domtree in `visit_conditional_successors`. Since the augmented post
domtree is enforced to have a single return, we can keep using the
current `postdominates` to fix the issue.

However, this commit isn't enough to fix the NeuralNetworkReachability
segfault as reported in #53613, and we need to tackle the second issue
reported there too (#53613 (comment)).
  • Loading branch information
aviatesk committed Mar 8, 2024
1 parent f6504e4 commit 8c2209f
Show file tree
Hide file tree
Showing 3 changed files with 100 additions and 38 deletions.
81 changes: 45 additions & 36 deletions base/compiler/optimize.jl
Original file line number Diff line number Diff line change
Expand Up @@ -527,15 +527,53 @@ function any_stmt_may_throw(ir::IRCode, bb::Int)
return false
end

function visit_conditional_successors(callback, lazypostdomtree::LazyPostDomtree, ir::IRCode, bb::Int)
mutable struct LazyAugmentedDomtrees
const ir::IRCode
cfg::CFG
domtree::DomTree
postdomtree::PostDomTree
LazyAugmentedDomtrees(ir::IRCode) = new(ir)
end

function get!(lazyagdomtrees::LazyAugmentedDomtrees, sym::Symbol)
isdefined(lazyagdomtrees, sym) && return getfield(lazyagdomtrees, sym)
if sym === :cfg
return lazyagdomtrees.cfg = construct_augmented_cfg(lazyagdomtrees.ir)
elseif sym === :domtree
return lazyagdomtrees.domtree = construct_domtree(get!(lazyagdomtrees, :cfg))
elseif sym === :postdomtree
return lazyagdomtrees.postdomtree = construct_postdomtree(get!(lazyagdomtrees, :cfg))
else
error("invalid field access")
end
end

function construct_augmented_cfg(ir::IRCode)
cfg = copy(ir.cfg)
# Add a virtual basic block to represent the exit
push!(cfg.blocks, BasicBlock(StmtRange(0:-1)))
for bb = 1:(length(cfg.blocks)-1)
terminator = ir[SSAValue(last(cfg.blocks[bb].stmts))][:stmt]
if isa(terminator, ReturnNode) && isdefined(terminator, :val)
cfg_insert_edge!(cfg, bb, length(cfg.blocks))
end
end
return cfg
end

visit_conditional_successors(callback, ir::IRCode, bb::Int) =
visit_conditional_successors(callback, construct_postdomtree(construct_augmented_cfg(ir)), ir, bb)
visit_conditional_successors(callback, lazyagdomtrees::LazyAugmentedDomtrees, ir::IRCode, bb::Int) =
visit_conditional_successors(callback, get!(lazyagdomtrees, :postdomtree), ir, bb)
function visit_conditional_successors(callback, postdomtree::PostDomTree, ir::IRCode, bb::Int)
visited = BitSet((bb,))
worklist = Int[bb]
while !isempty(worklist)
thisbb = popfirst!(worklist)
for succ in ir.cfg.blocks[thisbb].succs
succ in visited && continue
push!(visited, succ)
if postdominates(get!(lazypostdomtree), succ, bb)
if postdominates(postdomtree, succ, bb)
# this successor is not conditional, so no need to visit it further
continue
elseif callback(succ)
Expand All @@ -548,40 +586,12 @@ function visit_conditional_successors(callback, lazypostdomtree::LazyPostDomtree
return false
end

struct AugmentedDomtree
cfg::CFG
domtree::DomTree
end

mutable struct LazyAugmentedDomtree
const ir::IRCode
agdomtree::AugmentedDomtree
LazyAugmentedDomtree(ir::IRCode) = new(ir)
end

function get!(lazyagdomtree::LazyAugmentedDomtree)
isdefined(lazyagdomtree, :agdomtree) && return lazyagdomtree.agdomtree
ir = lazyagdomtree.ir
cfg = copy(ir.cfg)
# Add a virtual basic block to represent the exit
push!(cfg.blocks, BasicBlock(StmtRange(0:-1)))
for bb = 1:(length(cfg.blocks)-1)
terminator = ir[SSAValue(last(cfg.blocks[bb].stmts))][:stmt]
if isa(terminator, ReturnNode) && isdefined(terminator, :val)
cfg_insert_edge!(cfg, bb, length(cfg.blocks))
end
end
domtree = construct_domtree(cfg)
return lazyagdomtree.agdomtree = AugmentedDomtree(cfg, domtree)
end

mutable struct PostOptAnalysisState
const result::InferenceResult
const ir::IRCode
const inconsistent::BitSetBoundedMinPrioritySet
const tpdum::TwoPhaseDefUseMap
const lazypostdomtree::LazyPostDomtree
const lazyagdomtree::LazyAugmentedDomtree
const lazyagdomtrees::LazyAugmentedDomtrees
const ea_analysis_pending::Vector{Int}
all_retpaths_consistent::Bool
all_effect_free::Bool
Expand All @@ -592,9 +602,8 @@ mutable struct PostOptAnalysisState
function PostOptAnalysisState(result::InferenceResult, ir::IRCode)
inconsistent = BitSetBoundedMinPrioritySet(length(ir.stmts))
tpdum = TwoPhaseDefUseMap(length(ir.stmts))
lazypostdomtree = LazyPostDomtree(ir)
lazyagdomtree = LazyAugmentedDomtree(ir)
return new(result, ir, inconsistent, tpdum, lazypostdomtree, lazyagdomtree, Int[],
lazyagdomtrees = LazyAugmentedDomtrees(ir)
return new(result, ir, inconsistent, tpdum, lazyagdomtrees, Int[],
true, true, nothing, true, true, false)
end
end
Expand Down Expand Up @@ -834,13 +843,13 @@ function ((; sv)::ScanStmt)(inst::Instruction, lstmt::Int, bb::Int)
# inconsistent region.
if !sv.result.ipo_effects.terminates
sv.all_retpaths_consistent = false
elseif visit_conditional_successors(sv.lazypostdomtree, sv.ir, bb) do succ::Int
elseif visit_conditional_successors(sv.lazyagdomtrees, sv.ir, bb) do succ::Int
return any_stmt_may_throw(sv.ir, succ)
end
# check if this `GotoIfNot` leads to conditional throws, which taints consistency
sv.all_retpaths_consistent = false
else
(; cfg, domtree) = get!(sv.lazyagdomtree)
cfg, domtree = get!(sv.lazyagdomtrees, :cfg), get!(sv.lazyagdomtrees, :domtree)
for succ in iterated_dominance_frontier(cfg, BlockLiveness(sv.ir.cfg.blocks[bb].succs, nothing), domtree)
if succ == length(cfg.blocks)
# Phi node in the virtual exit -> We have a conditional
Expand Down
10 changes: 10 additions & 0 deletions test/compiler/effects.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1387,3 +1387,13 @@ let; Base.Experimental.@force_compile; func52843(); end
# https://github.com/JuliaLang/julia/issues/53508
@test !Core.Compiler.is_consistent(Base.infer_effects(getindex, (UnitRange{Int},Int)))
@test !Core.Compiler.is_consistent(Base.infer_effects(getindex, (Base.OneTo{Int},Int)))

@noinline f53613() = @assert isdefined(@__MODULE__, :v53613)
g53613() = f53613()
@test !Core.Compiler.is_consistent(Base.infer_effects(f53613))
@test_broken !Core.Compiler.is_consistent(Base.infer_effects(g53613))
@test_throws AssertionError f53613()
@test_throws AssertionError g53613()
global v53613 = nothing
@test f53613() === nothing
@test g53613() === nothing
47 changes: 45 additions & 2 deletions test/compiler/ssair.jl
Original file line number Diff line number Diff line change
Expand Up @@ -252,9 +252,8 @@ let code = Any[
ReturnNode(SSAValue(4))
]
ir = make_ircode(code; slottypes=Any[Any,Bool,Int,Int])
lazypostdomtree = Core.Compiler.LazyPostDomtree(ir)
visited = BitSet()
@test !Core.Compiler.visit_conditional_successors(lazypostdomtree, ir, #=bb=#1) do succ::Int
@test !Core.Compiler.visit_conditional_successors(ir, #=bb=#1) do succ::Int
push!(visited, succ)
return false
end
Expand All @@ -267,6 +266,50 @@ let code = Any[
@test_throws "potential throw" oc(true, 1, 1)
end

let code = Any[
# block 1
GotoIfNot(Argument(2), 4),
# block 2
Expr(:call, throw, "potential throw"),
ReturnNode(), # unreachable
# block 3
ReturnNode(Argument(3)),
]
ir = make_ircode(code; slottypes=Any[Any,Bool,Bool])
visited = BitSet()
@test !Core.Compiler.visit_conditional_successors(ir, #=bb=#1) do succ::Int
push!(visited, succ)
return false
end
@test 2 visited
@test 3 visited
oc = Core.OpaqueClosure(ir)
@test oc(false, 1) == 1
@test_throws "potential throw" oc(true, 1)
end

let code = Any[
# block 1
GotoIfNot(Argument(2), 3),
# block 2
ReturnNode(Argument(3)),
# block 3
Expr(:call, throw, "potential throw"),
ReturnNode(), # unreachable
]
ir = make_ircode(code; slottypes=Any[Any,Bool,Bool])
visited = BitSet()
@test !Core.Compiler.visit_conditional_successors(ir, #=bb=#1) do succ::Int
push!(visited, succ)
return false
end
@test 2 visited
@test 3 visited
oc = Core.OpaqueClosure(ir)
@test oc(true, 1) == 1
@test_throws "potential throw" oc(false, 1)
end

# Test dynamic update of domtree with edge insertions and deletions in the
# following CFG:
#
Expand Down

0 comments on commit 8c2209f

Please sign in to comment.