Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

post-opt: add more test cases for visit_conditional_successors #53642

Merged
merged 1 commit into from
Mar 19, 2024

Conversation

aviatesk
Copy link
Member

@aviatesk aviatesk commented Mar 7, 2024

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> 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)).

@aviatesk aviatesk requested a review from Keno March 7, 2024 17:33
Copy link
Member

@topolarity topolarity left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the augmented domtree still does not have a unique exit (must-throw blocks are not attached to the exit node), I think this is still leaving a bug in place:

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])
           postdomtree = CC.construct_postdomtree(construct_augmented_cfg(ir).blocks)
           [CC.postdominates(postdomtree, bb, 1) for bb in 1:3]
       end
3-element Vector{Bool}:
 1
 1
 0

This seems to be under the impression that BB (2) post-dominates (1), which is clearly false. This is probably computing post-dominance in each exit branch under the assumption that the corresponding exit is ultimately taken (e.g. for the "nothrow" augmented code path, this is now reporting postdominance under a :nothrow assumption)

I think the fix is needed in the DomTree instead, so that the reported post-dominance corresponds to a must-happen-after relation like we'd expect.

test/compiler/ssair.jl Outdated Show resolved Hide resolved
test/compiler/ssair.jl Outdated Show resolved Hide resolved
Base automatically changed from avi/construct_domtree-interfaces to master March 8, 2024 00:42
@aviatesk
Copy link
Member Author

aviatesk commented Mar 8, 2024

This seems to be under the impression that BB (2) post-dominates (1), which is clearly false.

I'm not sure whether to call this a mistake or not. If you don't consider the unreachable node as an exit point, then the only real exit point becomes BB (2), which means you could say BB (2) post-dominates BB (1). The issue seems to be that when you build a post-dominance tree using the original, unaugmented CFG, you end up with results that suggest the opposite:

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])
           postdomtree = CC.construct_postdomtree(ir.cfg.blocks)
           [CC.postdominates(postdomtree, bb, 1) for bb in 1:3]
       end
3-element Vector{Bool}:
 1
 0
 1

So, I'm with you on the need for a fix on the DomTree side of things. We ought to get the right answers even if we don't use CC.construct_augumented_cfg in my opinion.

@aviatesk aviatesk force-pushed the avi/53613-1 branch 2 times, most recently from f1d4535 to c336656 Compare March 8, 2024 01:57
@topolarity
Copy link
Member

topolarity commented Mar 8, 2024

I'm with you there, but do we know enough about the DomTree bug to know that this behaves correctly even though we still don't have a post-dominating exit node (from the POV of the DomTree algorithm)?

@topolarity
Copy link
Member

topolarity commented Mar 8, 2024

Here's an example where this still seems to go wrong:

julia> let code = Any[
               # block 1 (we should visit this block)
               #= %1 =# Expr(:call, :opaque), # maybe-throw
               #= %2 =# GotoIfNot(Argument(2), 6),
               # block 2
               #= %3 =# GotoIfNot(Argument(3), 7),
               # block 3 (we should visit this block)
               #= %4 =# Expr(:call, throw, "potential throw"),
               #= %5 =# ReturnNode(), # unreachable
               # block 4
               #= %6 =# GotoNode(1),
               # block 5
               #= %7 =# ReturnNode(nothing)
           ]
           ir = make_ircode(code; slottypes=Any[Any,Bool,Bool])
           lazy = CC.LazyAugmentedDomtrees(ir)
           visited = BitSet()
           Core.Compiler.visit_conditional_successors(lazy, ir, #=bb=#1) do succ::Int
               push!(visited, succ)
               return false
           end
           visited
       end
BitSet([4])

Whether this returns nothing or either of two different errors can depend on Argument(2), but the only block we explore is GotoNode(1)

@aviatesk
Copy link
Member Author

aviatesk commented Mar 8, 2024

Actually, what's happening here is correct. That's because visit_conditional_successors is designed to check out the "conditional" successors w.r.t. a given bb. If you look at the control graph of the code above, any code execution from BB (1) will reach BB (2), no matter what code path is taken, which means BB (2) isn't a "conditional successor". So, there's no need to visit BB (3), which is a successor of BB (2).
Screenshot 2024-03-08 at 4 34 38 PM

test/compiler/ssair.jl Outdated Show resolved Hide resolved
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)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Keno, do you think it's appropriate to always add a virtual edge, regardless of whether the actual return node is reachable? I believe this adjustment is necessary for visit_conditional_successors but I'm uncertain about its implications for the usage in iterated_dominance_frontier at L853.

@Keno
Copy link
Member

Keno commented Mar 9, 2024

I agree with @topolarity that the postdomination query is wrong in the example. A block "A", postdominates "B" if every path that passes through B must also pass through "A". Our notion of postdominance does ignore throws, but I for those purposes, the unreachable is just treated as a return. In this case, I still think that's the correct query to cut off the walk.

@aviatesk
Copy link
Member Author

So, are we saying that postdominates should automatically make use of the augmented domtree? Also, just to clarify, the behavior of the code shared by Cody above is right. The reason is, visit_conditional_successors(..., bb) isn't meant to visit successors that post-dominate bb. It's actually supposed to visit the conditional ones that don't postdominate it, which means visiting only BB (4) is the expected behavior.

@aviatesk
Copy link
Member Author

@nanosoldier runbenchmarks("inference", vs=":master")

@nanosoldier
Copy link
Collaborator

Your benchmark job has completed - possible performance regressions were detected. A full report can be found here.

aviatesk pushed a commit that referenced this pull request Mar 19, 2024
This is an alternative to #53642

The `dom_edges()` for an exit block in the CFG are empty when computing
the PostDomTree so the loop below this may not actually run. In that
case, the right semidominator is the ancestor from the DFSTree, which is
the "virtual" -1 block.

This resolves half of the issue in
#53613:
```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 Passed
```

This needs some tests (esp. since I don't think we have any DomTree
tests at all right now), but otherwise should be good to go.
This commit was initially crafted to fix an issue with post-opt analysis
that came to light in #53613, planning to solve it by
incorporating an augmented CFG directly into the post-opt analysis. But,
with the issue now sorted out by #53739, this commit
shifted focus to just adding test cases.

=== The original commit message ===

post-opt: use augmented post-domtree for `visit_conditional_successors`

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`, while also enforcing the
augmented control flow graph (`construct_augmented_cfg`) to have a
single exit node really. Since the augmented post domtree is now
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)).
@aviatesk aviatesk changed the title post-opt: use augmented post-domtree for visit_conditional_successors post-opt: add more test cases for visit_conditional_successors Mar 19, 2024
@aviatesk aviatesk merged commit 9df47f2 into master Mar 19, 2024
6 of 8 checks passed
@aviatesk aviatesk deleted the avi/53613-1 branch March 19, 2024 11:10
@aviatesk aviatesk mentioned this pull request Apr 23, 2024
59 tasks
aviatesk pushed a commit that referenced this pull request Apr 23, 2024
This is an alternative to #53642

The `dom_edges()` for an exit block in the CFG are empty when computing
the PostDomTree so the loop below this may not actually run. In that
case, the right semidominator is the ancestor from the DFSTree, which is
the "virtual" -1 block.

This resolves half of the issue in
#53613:
```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 Passed
```

This needs some tests (esp. since I don't think we have any DomTree
tests at all right now), but otherwise should be good to go.
aviatesk added a commit that referenced this pull request Apr 23, 2024
)

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)).
topolarity added a commit that referenced this pull request Oct 30, 2024
This is an alternative to #53642

The `dom_edges()` for an exit block in the CFG are empty when computing
the PostDomTree so the loop below this may not actually run. In that
case, the right semidominator is the ancestor from the DFSTree, which is
the "virtual" -1 block.

This resolves half of the issue in
#53613:
```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 Passed
```

This needs some tests (esp. since I don't think we have any DomTree
tests at all right now), but otherwise should be good to go.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants