This is an archive of the discontinued LLVM Phabricator instance.

[LCG] Redesign the lazy post-order iteration mechanism for the LazyCallGraph to support repeated, stable iterations, even in the face of graph updates.
ClosedPublic

Authored by chandlerc on Sep 4 2016, 2:13 AM.

Details

Summary

This is particularly important to allow the CGSCC pass manager to walk
the RefSCCs (and thus everything else) in a module more than once. Lots
of unittests and other tests were hard or impossible to write because
repeated CGSCC pass managers which didn't invalidate the LazyCallGraph
would conclude the module was empty after the first one. =[ Really,
really bad.

The interesting thing is that in many ways this simplifies the code. We
can now re-use the same code for handling reference edge insertion
updates of the RefSCC graph as we use for handling call edge insertion
updates of the SCC graph. Outside of adapting to the shared logic for
this (which isn't trivial, but is *much* simpler than the DFS it
replaces!), the new code involves putting newly created RefSCCs when
deleting a reference edge into the cached list in the correct way, and
to re-formulate the iterator to be stable and effective even in the face
of these kinds of updates.

I've updated the unittests for the LazyCallGraph to re-iterate the
postorder sequence and verify that this all works. We even check for
using alternating iterators to trigger the lazy formation of RefSCCs
after mutation has occured.

It's worth noting that there are a reasonable number of likely simplifications
we can make past this. It isn't clear that we need to keep the "LeafRefSCCs"
around any more. But I've not removed that mostly because I want this to be
a more isolated change.

Diff Detail

Repository
rL LLVM

Event Timeline

chandlerc updated this revision to Diff 70285.Sep 4 2016, 2:13 AM
chandlerc retitled this revision from to [LCG] Redesign the lazy post-order iteration mechanism for the LazyCallGraph to support repeated, stable iterations, even in the face of graph updates..
chandlerc updated this object.
chandlerc added a reviewer: sanjoy.
chandlerc added a subscriber: llvm-commits.
eastig added a subscriber: eastig.Sep 5 2016, 7:32 AM
eraman added a subscriber: eraman.Sep 7 2016, 5:56 PM
eraman added inline comments.
lib/Analysis/LazyCallGraph.cpp
804 ↗(On Diff #70285)

Nit: RefSCC in place of SCC in assert messages here and below.

830 ↗(On Diff #70285)

Why are ref edges excluded here?

906 ↗(On Diff #70285)

The node to SCC mapping doesn't change right?

chandlerc marked 2 inline comments as done.Sep 8 2016, 2:23 AM

Thanks so much for the review! See responses below.

lib/Analysis/LazyCallGraph.cpp
804 ↗(On Diff #70285)

Great catch! Thanks, and fixed. I think I have several more of these that I'm gonna do a pass over this entire code to clean up afterward...

830 ↗(On Diff #70285)

Yikes!

Because this code is based originally on the code for call edges, and I didn't update this.

I've updated the tests to actually check that this works in graphs composed of reference cycles rather than call cycles.

906 ↗(On Diff #70285)

Correct, this should be a no-op. But I'd prefer to remove it in a separate commit as this was already here and I'm not changing the fact that it isn't needed. =] Good catch though.

Sadly, I can't really test for this as it is in fact a no-op. Even an assert doesn't really seem valuable here.

chandlerc updated this revision to Diff 70666.Sep 8 2016, 2:24 AM
chandlerc marked 2 inline comments as done.

Updated with better assert strings, a big "DOH!" bug fix, and an important
added test case to cover that bug fix. Many thanks to Easwaran for the review
that found these issues.

eraman added inline comments.Sep 8 2016, 10:33 AM
lib/Analysis/LazyCallGraph.cpp
883 ↗(On Diff #70666)

The comment is stale now since there is no DAG walk above anymore.

888 ↗(On Diff #70666)

I don't think you should be reversing the MergeRange here. Consider this example where you have three RefSCCs R0, R1 and R2 with the numbers being index to the PostOrderRefSCCs. Assume R2 is this RefSCC and the edge being added is from R0 -> R2. Assume each Ri has one Si (call SCC). MergeRange is [R0, R1]. At the end of this loop, MergedSCCs will have [S1, S0] and outside the loop S2 gets added to MergedSCCs resulting in [S1, S0, S2]. I believe you want it to be [S0, S1, S2] to keep it in post order.

The unit test below doesn't test that call SCC's are in post-order. Ensuring verify on the merged RefSCC doesn't assert helps in general (but still won't catch this)

chandlerc marked an inline comment as done.Sep 11 2016, 8:24 PM
chandlerc added inline comments.
lib/Analysis/LazyCallGraph.cpp
888 ↗(On Diff #70666)

Arrg, still more things that need a more complex test case to trigger.

I've added a couple more tests that exercise this and some other aspects of the code. They cause the verify below to catch this bug, as well as independently verifying the postorder result.

Thanks again!

chandlerc updated this revision to Diff 70970.Sep 11 2016, 8:26 PM

Update with more tests and a fix nicely spotted by Easwaran during review.

sanjoy edited edge metadata.Sep 11 2016, 10:01 PM

Some comments inline.

include/llvm/Analysis/LazyCallGraph.h
920 ↗(On Diff #70970)

Not sure what you mean by "Only RefSCCs which have been created while walking the graph" -- what else is there?

lib/Analysis/LazyCallGraph.cpp
892 ↗(On Diff #70970)

copy_if ?

937 ↗(On Diff #70970)

I know this isn't new, but does it make sense to erase the erased RefSCCs from RefSCCIndices too?

1221 ↗(On Diff #70970)

How about adding an accessor to LCG like getIndexForRefSCC that verifies that the index into the postorder is correct?

1225 ↗(On Diff #70970)

Use llvm::seq ?

1588 ↗(On Diff #70970)

*sequence

827 ↗(On Diff #70666)

Why can't you do a DFS on the parent edges starting from SourceC, and bound it by the range in the postorder list?

870 ↗(On Diff #70666)

s/SCCs/RefSCCs/ right?

Also, I know this is "controversial", but I think using an explicit type for MergeRange here is actually going to be more readable.

874 ↗(On Diff #70666)

Nice!

1215 ↗(On Diff #70666)

s/sequnece/sequence/

Working others, but a quick comment below:

lib/Analysis/LazyCallGraph.cpp
827 ↗(On Diff #70970)

I mean, I could. Do you think that would be simpler? It seems like it would be strictly more complicated than this, and still O(edges), so it didn't seem like it was worth pursuing. But I'm happy to try it out if you think it'd be an improvement.

eraman added inline comments.Sep 12 2016, 3:19 PM
lib/Analysis/LazyCallGraph.cpp
827 ↗(On Diff #70970)

Isn't what Sanjoy proposing O(|edges in RefSCC DAG|) which is a tighter bound than O(|edges in the callgraph|) of the current implementation?

unittests/Analysis/LazyCallGraphTest.cpp
1082 ↗(On Diff #70970)

Incorrect comment. No calls in this RefSCC.

sanjoy added inline comments.Sep 12 2016, 10:55 PM
lib/Analysis/LazyCallGraph.cpp
827 ↗(On Diff #70970)

Do you think that would be simpler?

Not in a one-to-one comparison, but perhaps you could write it in a way that can be shared with ComputeTargetConnectedSet ?

+ What @eraman said.

chandlerc marked 18 inline comments as done.Sep 13 2016, 4:49 AM

Thanks for all the comments Sanjoy and Easwaran!

include/llvm/Analysis/LazyCallGraph.h
920 ↗(On Diff #70970)

No idea. Removed.

lib/Analysis/LazyCallGraph.cpp
827 ↗(On Diff #70970)

Sanjoy and I chatted about this on IRC and now I understand *much* better what your idea is here Sanjoy.

The idea is just to walk the parent edges to build connected sets. It's not the full complexity (in code) of Tarjan's, its just walking like this but with a nicer complexity bound.

However, thinking about this I think I've made this entire problem much more complex than is necessary. But the simplification I have in mind will impact both this code and the SCC caller, so I'd rather do that in a follow-up if its OK.

870–876 ↗(On Diff #70970)

Done on the first count.

The type seems really long, opaque, and to add little value to me? It's: iterator_range<SmallVectorImpl<RefSCC *>::iterator>. That said, if it makes it easier to read, no problem. I've put it in here, and I can update the other caller.

To help me understand this better, what about seeing this type helps you read the code?

892 ↗(On Diff #70970)

It's quite awkward to use. std::inserter doesn't help because the insert routine doesn't accept an iterator position (and even if it did, there is no meaningful iterator to provide).

We could use the range insert routine and a filtered iterator, but that seems also overkill.

1225 ↗(On Diff #70970)

Hah, good idea!

chandlerc updated this revision to Diff 71144.Sep 13 2016, 4:52 AM
chandlerc marked 4 inline comments as done.
chandlerc edited edge metadata.

Updates from codereview (and rebased).

sanjoy accepted this revision.Sep 13 2016, 10:39 PM
sanjoy edited edge metadata.

lgtm modulo minor things here and there

include/llvm/Analysis/LazyCallGraph.h
975 ↗(On Diff #71144)

Can we add an assert(PostOrderRefSCCs[IndexIt->second] == &RC) here?

lib/Analysis/LazyCallGraph.cpp
827 ↗(On Diff #71144)

But the simplification I have in mind will impact both this code and the SCC caller, so I'd rather do that in a follow-up if its OK.

SGTM.

852–876 ↗(On Diff #71144)

Okay, I agree that iterator_range<SmallVectorImpl<RefSCC *>::iterator> isn't great. :)

However, I had made my suggestion under the assumption that updatePostorderSequenceForEdgeInsertion returns an ArrayRef<RefSCC *>. Does changing updatePostorderSequenceForEdgeInsertion to return ArrayRef<RefSCC *> sound generally cleaner?

In any case, there's no reason to hold checking this on this specific point, it's very minor.

895 ↗(On Diff #71144)

Not clear what "This RefSCC should terminate the DFS without being reached." means now.

900 ↗(On Diff #71144)

Ok.

1222 ↗(On Diff #71144)

There's one more ("sequnece").

unittests/Analysis/LazyCallGraphTest.cpp
142 ↗(On Diff #71144)

Are the trailing \n 's needed? Seems like we should be able to replace them with spaces (which may look less distracting).

581 ↗(On Diff #71144)

Writing to dbgs() seems fairly uncommon in unittests/ (I only see one other instance). I
can't see any obvious reason why this will be a problem, but perhaps there is a reason why it is usually avoided?

895 ↗(On Diff #71144)

Can this sequence be a C macro or a C++ class with A1, A2 etc. as fields (in the latter case all of the lookup logic could be in a constructor or some helper function)? Right now this is repeated a few times.

1018 ↗(On Diff #71144)

llvm::find (here and below)?

1082 ↗(On Diff #71144)

@eraman 's comment has not been addressed.

This revision is now accepted and ready to land.Sep 13 2016, 10:39 PM
This revision was automatically updated to reflect the committed changes.
chandlerc marked 9 inline comments as done.

Thanks all! Adjustments made, landed.

Easwaran, if you have more comments, don't hesitate to post them and I'll keep track here. I've still got a few things to clean up post-submit anyways.

lib/Analysis/LazyCallGraph.cpp
852–876 ↗(On Diff #71144)

Doesn't really work ... we hand the range to erase, etc. We could hack it with addresses into the ArrayRef, but that seems pretty gross. Happy to keep tweaking the API though...

unittests/Analysis/LazyCallGraphTest.cpp
142 ↗(On Diff #71144)

I think so. But can try some stuff with this whole file in a follow-up commit.

581 ↗(On Diff #71144)

No idea, it made debugging this stuff easier and it seems harmless...

895 ↗(On Diff #71144)

Yea, I'd like to refactor a bunch of this, probably in a follow-up. The repeated pattern got a lot more frequent somewhat organically.

1082 ↗(On Diff #71144)

Doh, sorry I missed this. Thanks!