This is an archive of the discontinued LLVM Phabricator instance.

[GVNHoist] Fix: PR32821, add check for anticipability in case of infinite loops
AbandonedPublic

Authored by hiraditya on Apr 27 2017, 12:25 PM.

Details

Summary

This fixes the case when gvn-hoist would hoist instructions when there is an infinite loop in the path from HoistBB to the end of the function. https://bugs.llvm.org/show_bug.cgi?id=32821

Diff Detail

Event Timeline

hiraditya created this revision.Apr 27 2017, 12:25 PM
dberlin edited edge metadata.Apr 27 2017, 12:36 PM

I'm at a loss to understand why you believe you need to compute joint post-dominance (which is what this is) to compute ANTIC.

I'm at a loss to understand why you believe you need to compute joint post-dominance (which is what this is) to compute ANTIC.

If the set of BasicBlocks (WL) do not joint-post-dominate the hoisting point (HoistBB), then the expression to be hoisted (from WL) cannot be ANTIC in HoistBB.
It is a necessary condition what I'm checking here. The other check is the availability of operands in each basic block which is being checked elsewhere. Maybe the function name is not appropriate.
Please suggest your views.

Thanks,

I'm at a loss to understand why you believe you need to compute joint post-dominance (which is what this is) to compute ANTIC.

If the set of BasicBlocks (WL) do not joint-post-dominate the hoisting point (HoistBB), then the expression to be hoisted (from WL) cannot be ANTIC in HoistBB.

Sure.
That is definitely a way of computing it.
It is a generally slow and unused way of computing it, because there are only certain points in the SSA graph where ANTIC can actually change.

It is a necessary condition what I'm checking here.

It is not necessary to perform graph reachability to do this.

https://pdfs.semanticscholar.org/6d0f/07ff330402b46e83d46142e202069d9aeceb.pdf

Stare at the down-safety step.
With a few bits, it is only actually necessary to compute and check antic at the possible phis you would insert to do your hoisting.

llvm/lib/Transforms/Scalar/GVNHoist.cpp
334

You can just use the return value of insert.

chandlerc edited edge metadata.Apr 30 2017, 6:36 PM

Regardless of what you do with the code (I'm not an expert on this pass or the stuff Danny is already discussing there), please simplify this test case to a more readable and clear test case for the fundamental issue you're hitting. Having tons of Clang-specific bits in here really opacifies what scenario you're trying to test.

Relatedly, there are many different ways to arrive at an infinite loop, I would really hope to see a reasonable *collection* of test cases that thoroughly exercise the kinds of CFGs that can interefere with reachability analyses due to infinite loops. For example, irreducible control flows that infinitely cycle seem usefully different from regular infinite loops.

There is also the problem brought up in http://lists.llvm.org/pipermail/llvm-dev/2015-July/088095.html now almost two years ago which we still haven't made sense of yet. We really should be able to hoist across an infinite loop without side-effects but not across one with side-effects in C++-modeling LLVM IR, and we shouldn't be able to do so in Java-modeling LLVM IR. =/ You should at least leave test cases that exercise both paths (whichever behavior you choose) and clear comments in the test cases describing how they should be updated and expanded when we have a real answer here.

chandlerc requested changes to this revision.Apr 30 2017, 8:46 PM

(just marking this as needing changes so it isn't on my phab dashboard)

This revision now requires changes to proceed.Apr 30 2017, 8:46 PM

I'm at a loss to understand why you believe you need to compute joint post-dominance (which is what this is) to compute ANTIC.

If the set of BasicBlocks (WL) do not joint-post-dominate the hoisting point (HoistBB), then the expression to be hoisted (from WL) cannot be ANTIC in HoistBB.

Sure.
That is definitely a way of computing it.
It is a generally slow and unused way of computing it, because there are only certain points in the SSA graph where ANTIC can actually change.

It is a necessary condition what I'm checking here.

It is not necessary to perform graph reachability to do this.

https://pdfs.semanticscholar.org/6d0f/07ff330402b46e83d46142e202069d9aeceb.pdf

Stare at the down-safety step.
With a few bits, it is only actually necessary to compute and check antic at the possible phis you would insert to do your hoisting.

Thanks for the reference, I'll read the paper and try to implement if that appears more efficient. Currently, I'm in the middle of preparing for a conference so I'll get back to this in a few days.

llvm/lib/Transforms/Scalar/GVNHoist.cpp
334

I'll do that. Thanks.

hiraditya added a comment.EditedMay 8 2017, 2:59 PM

I'm at a loss to understand why you believe you need to compute joint post-dominance (which is what this is) to compute ANTIC.

If the set of BasicBlocks (WL) do not joint-post-dominate the hoisting point (HoistBB), then the expression to be hoisted (from WL) cannot be ANTIC in HoistBB.

Sure.
That is definitely a way of computing it.
It is a generally slow and unused way of computing it, because there are only certain points in the SSA graph where ANTIC can actually change.

It is a necessary condition what I'm checking here.

It is not necessary to perform graph reachability to do this.

https://pdfs.semanticscholar.org/6d0f/07ff330402b46e83d46142e202069d9aeceb.pdf

Stare at the down-safety step.
With a few bits, it is only actually necessary to compute and check antic at the possible phis you would insert to do your hoisting.

From the paper and the book (http://ssabook.gforge.inria.fr/latest/book.pdf page: 151), it seems DownSafety algorithm does exactly what I'm doing in the function (anticReachable), IIUC.

DownSafety:
...
10: for each f ∈ {Φ’s in the program} do
11: if ∃ path P to program exit or alteration of expression along which f is not used
12: downsafe (f ) ← false
...

It checks for each path from point P to the program exit. They have simplified the problem by assuming that end of the function is reachable from every node which is not the
case with LLVM representation of CFG (and that caused the bug).
If I implement the ANTIC the way they have done, it would require iterating through all dominance frontiers to figure out PHI insertion points.
Also, just ensuring downSafety at the PHI is not sufficient to guarantee downsafety at HoistPt because there might be safety issues between a definition of instruction and its dominance frontier.

e.g.,

B -> D -> E
|               |
C             |
|               |
v             v
F <-------/

F will have the PHI for instructions in C and D, but E might throw etc.
Please correct me if I'm wrong because all this is based on my limited understanding of paper and the book chapter.

Thanks,

hiraditya updated this revision to Diff 98220.May 8 2017, 3:05 PM
hiraditya edited edge metadata.

Addressed comments from @chandlerc and @dberlin

chandlerc requested changes to this revision.May 8 2017, 3:56 PM

I don't see what would have addressed my concerns.

There still is only a single test case for an infinite loop. There are *several different* CFGs that fail to terminate. You should be testing them.

Examples off the top of my head:

  • A non-loop cycle in the CFG. Note that you need to set up the entire hoisting to be *inside* of the outer loop and then make it invalid due to an infinite non-loop cycle that exists within the loop.
  • A *potential* cycle due to indirect-br
  • An exceptional cycle due to cyclic exception edges from invokes
  • Either indirect-br or exceptions combined with a non-loop cycle.

I understand that you may naturally have an algorithm that handles all of these without special casing. That is good! But you should *test* them to ensure that when your code sees unexpected or rarely formed control flows it continues to behave well.

Put differently, it would be good to add fairly extensive testing that actively tries to construct corner cases to break the safety checks of this pass as it has had a long history of subtle and difficult to find correctness bugs.

Have you or anyone that cares about GVN hoist considered building a tool to synthetically generate random CFGs with some properties to see how the pass behaves? That might help more pro-actively find issues.

This revision now requires changes to proceed.May 8 2017, 3:56 PM

I don't see what would have addressed my concerns.

There still is only a single test case for an infinite loop. There are *several different* CFGs that fail to terminate. You should be testing them.

Examples off the top of my head:

  • A non-loop cycle in the CFG.

The code checks for cycle, not loops so it can detect all kinds of explicit cycles as different from other three you mentioned.

Note that you need to set up the entire hoisting to be *inside* of the outer loop and then make it invalid due to an infinite non-loop cycle that exists within the loop.

Dominance relation takes care of that (see partitionCandidates)

  • A *potential* cycle due to indirect-br
  • An exceptional cycle due to cyclic exception edges from invokes
  • Either indirect-br or exceptions combined with a non-loop cycle.

All the indirect branch targets in the path are excluded in the safety checks (see safeToHoistLDSt and safeToHoistScalar)
So all kinds of cycles are handled.

Put differently, it would be good to add fairly extensive testing that actively tries to construct corner cases to break the safety checks of this pass as it has had a long history of subtle and difficult to find correctness bugs.

I have bootstrapped clang, ran SPEC2006. The pass was disabled on multiple occasions, I agree, but on several of these occasions the pass was disabled because it exposed bugs in other places, for example:
https://bugs.llvm.org//show_bug.cgi?id=30806
https://bugs.llvm.org//show_bug.cgi?id=32811
https://bugs.llvm.org/show_bug.cgi?id=32153

From what I understand from the cases you pointed out, this was the only unhandled case.
Thanks for the review.

I understand that you may naturally have an algorithm that handles all of these without special casing. That is good! But you should *test* them to ensure that when your code sees unexpected or rarely formed control flows it continues to behave well.

Put differently, it would be good to add fairly extensive testing that actively tries to construct corner cases to break the safety checks of this pass as it has had a long history of subtle and difficult to find correctness bugs.

Have you or anyone that cares about GVN hoist considered building a tool to synthetically generate random CFGs with some properties to see how the pass behaves? That might help more pro-actively find issues.

You are continuing to argue that you do not need to add tests because they will pass. That seems to be missing the point. Tests are what verify and validate these kinds of arguments and assumptions, both now and in the future.

I already said in my response that I acknowledge that these tests will likely pass. It still seems extremely important to have specific, targeted test coverage of the different kinds of control flows. I will stop repeating myself after this message.

You are continuing to argue that you do not need to add tests because they will pass. That seems to be missing the point. Tests are what verify and validate these kinds of arguments and assumptions, both now and in the future.

I'll add more tests which will reflect different aspects of cyclic control flow. I was trying to explain what program points handle specific cases pointed by you. It will be good if you can suggest any resources which can help create a variety of CFGs.

I already said in my response that I acknowledge that these tests will likely pass. It still seems extremely important to have specific, targeted test coverage of the different kinds of control flows. I will stop repeating myself after this message.

hiraditya updated this revision to Diff 98515.May 10 2017, 1:44 PM
hiraditya edited edge metadata.

Addressed @chandlerc 's comments: Essentially, I've added test cases for cycles due to direct branches, indirect branches and invoke instructions.

Test cases show:

  • hoisting does not happen when anticipability cannot be guaranteed because of infinite loops in one of the paths
  • when it is okay to hoist out of indirect branch targets and irreducible control flow because anticipability is satisfied
  • that cycle due to indirect branches prevents hoisting
  • that no hoisting happens when there is a cycle due to invoke
  • instruction can be hoisted out of catch blocks when legality is satisfied

Please let me know if I may have missed any test case.

hiraditya updated this revision to Diff 98638.May 11 2017, 9:01 AM

Adding ':' for basic block labels in the testcase

sanjoy added a subscriber: sanjoy.May 14 2017, 1:58 PM
sanjoy added inline comments.May 14 2017, 2:07 PM
llvm/lib/Transforms/Scalar/GVNHoist.cpp
325

This is a shallow drive by comment, but do you have to worry about function calls that throw / infloop here?

hiraditya marked an inline comment as done.May 18 2017, 2:37 PM
hiraditya added inline comments.
llvm/lib/Transforms/Scalar/GVNHoist.cpp
325

That is handled by the function hasEHOnPath called later.

@dberlin
When SSAPRE inserts PHI (expression PHIs), it does so at places where (potentially) multiple expressions may merge. If a PHI has a bottom (⊥) entry, that means the expression is partially available.
The concept may work for GVN-Hoist to help factor out anticipability by working on inverted graph. If we insert an outgoing PHI (if I may), to a basic block with multiple successors, as instructions are hoisted upwards to a nearest common dominator.
And we start walking the CFG to figure out if any outgoing PHI has a ⊥, that means the expression is not anticipable in the basic block having that outgoing PHI.
To minimize the number of outgoing PHIs, we will only insert them for expressions with multiple occurrences.
This will help remove the need to check for reachability.

Please let me know if this would be a way to factor out anticipability of expressions.

Thanks,
-Aditya

davide edited edge metadata.Jun 18 2017, 9:29 PM

Drive-by comment.

llvm/test/Transforms/GVNHoist/infinite-loop-indirect.ll
287–290

These tests can be reduced quite a bit, IMHO (for example you don't need the attributes).

dberlin resigned from this revision.Jun 19 2017, 12:01 AM
hiraditya abandoned this revision.Aug 14 2018, 9:41 AM

Merged with GVNHoist long time back.