Skip to content

Commit

Permalink
[LCG] Switch one of the update methods for the LazyCallGraph to support
Browse files Browse the repository at this point in the history
limited batch updates.

Specifically, allow removing multiple reference edges starting from
a common source node. There are a few constraints that play into
supporting this form of batching:

1) The way updates occur during the CGSCC walk, about the most we can
   functionally batch together are those with a common source node. This
   also makes the batching simpler to implement, so it seems
   a worthwhile restriction.
2) The far and away hottest function for large C++ files I measured
   (generated code for protocol buffers) showed a huge amount of time
   was spent removing ref edges specifically, so it seems worth focusing
   there.
3) The algorithm for removing ref edges is very amenable to this
   restricted batching. There are just both API and implementation
   special casing for the non-batch case that gets in the way. Once
   removed, supporting batches is nearly trivial.

This does modify the API in an interesting way -- now, we only preserve
the target RefSCC when the RefSCC structure is unchanged. In the face of
any splits, we create brand new RefSCC objects. However, all of the
users were OK with it that I could find. Only the unittest needed
interesting updates here.

How much does batching these updates help? I instrumented the compiler
when run over a very large generated source file for a protocol buffer
and found that the majority of updates are intrinsically updating one
function at a time. However, nearly 40% of the total ref edges removed
are removed as part of a batch of removals greater than one, so these
are the cases batching can help with.

When compiling the IR for this file with 'opt' and 'O3', this patch
reduces the total time by 8-9%.

Differential Revision: https://reviews.llvm.org/D36352

llvm-svn: 310450
  • Loading branch information
chandlerc committed Aug 9, 2017
1 parent 64c3241 commit 23c2f44
Show file tree
Hide file tree
Showing 4 changed files with 228 additions and 196 deletions.
33 changes: 15 additions & 18 deletions llvm/include/llvm/Analysis/LazyCallGraph.h
Expand Up @@ -795,26 +795,25 @@ class LazyCallGraph {
/// though, so be careful calling this while iterating over them.
void removeOutgoingEdge(Node &SourceN, Node &TargetN);

/// Remove a ref edge which is entirely within this RefSCC.
/// Remove a list of ref edges which are entirely within this RefSCC.
///
/// Both the \a SourceN and the \a TargetN must be within this RefSCC.
/// Removing such an edge may break cycles that form this RefSCC and thus
/// this operation may change the RefSCC graph significantly. In
/// Both the \a SourceN and all of the \a TargetNs must be within this
/// RefSCC. Removing these edges may break cycles that form this RefSCC and
/// thus this operation may change the RefSCC graph significantly. In
/// particular, this operation will re-form new RefSCCs based on the
/// remaining connectivity of the graph. The following invariants are
/// guaranteed to hold after calling this method:
///
/// 1) This RefSCC is still a RefSCC in the graph.
/// 2) This RefSCC will be the parent of any new RefSCCs. Thus, this RefSCC
/// is preserved as the root of any new RefSCC DAG formed.
/// 3) No RefSCC other than this RefSCC has its member set changed (this is
/// 1) If a ref-cycle remains after removal, it leaves this RefSCC intact
/// and in the graph. No new RefSCCs are built.
/// 2) Otherwise, this RefSCC will be dead after this call and no longer in
/// the graph or the postorder traversal of the call graph. Any iterator
/// pointing at this RefSCC will become invalid.
/// 3) All newly formed RefSCCs will be returned and the order of the
/// RefSCCs returned will be a valid postorder traversal of the new
/// RefSCCs.
/// 4) No RefSCC other than this RefSCC has its member set changed (this is
/// inherent in the definition of removing such an edge).
/// 4) All of the parent links of the RefSCC graph will be updated to
/// reflect the new RefSCC structure.
/// 5) All RefSCCs formed out of this RefSCC, excluding this RefSCC, will
/// be returned in post-order.
/// 6) The order of the RefSCCs in the vector will be a valid postorder
/// traversal of the new RefSCCs.
///
/// These invariants are very important to ensure that we can build
/// optimization pipelines on top of the CGSCC pass manager which
Expand All @@ -833,11 +832,9 @@ class LazyCallGraph {
/// within this RefSCC and edges from this RefSCC to child RefSCCs. Some
/// effort has been made to minimize the overhead of common cases such as
/// self-edges and edge removals which result in a spanning tree with no
/// more cycles. There are also detailed comments within the implementation
/// on techniques which could substantially improve this routine's
/// efficiency.
/// more cycles.
SmallVector<RefSCC *, 1> removeInternalRefEdge(Node &SourceN,
Node &TargetN);
ArrayRef<Node *> TargetNs);

/// A convenience wrapper around the above to handle trivial cases of
/// inserting a new call edge.
Expand Down
117 changes: 62 additions & 55 deletions llvm/lib/Analysis/CGSCCPassManager.cpp
Expand Up @@ -459,71 +459,78 @@ LazyCallGraph::SCC &llvm::updateCGAndAnalysisManagerForFunctionPass(
VisitRef(*F);

// First remove all of the edges that are no longer present in this function.
// We have to build a list of dead targets first and then remove them as the
// data structures will all be invalidated by removing them.
SmallVector<PointerIntPair<Node *, 1, Edge::Kind>, 4> DeadTargets;
for (Edge &E : *N)
if (!RetainedEdges.count(&E.getNode()))
DeadTargets.push_back({&E.getNode(), E.getKind()});
for (auto DeadTarget : DeadTargets) {
Node &TargetN = *DeadTarget.getPointer();
bool IsCall = DeadTarget.getInt() == Edge::Call;
SCC &TargetC = *G.lookupSCC(TargetN);
RefSCC &TargetRC = TargetC.getOuterRefSCC();

if (&TargetRC != RC) {
RC->removeOutgoingEdge(N, TargetN);
if (DebugLogging)
dbgs() << "Deleting outgoing edge from '" << N << "' to '" << TargetN
<< "'\n";
// The first step makes these edges uniformly ref edges and accumulates them
// into a separate data structure so removal doesn't invalidate anything.
SmallVector<Node *, 4> DeadTargets;
for (Edge &E : *N) {
if (RetainedEdges.count(&E.getNode()))
continue;
}
if (DebugLogging)
dbgs() << "Deleting internal " << (IsCall ? "call" : "ref")
<< " edge from '" << N << "' to '" << TargetN << "'\n";

if (IsCall) {
SCC &TargetC = *G.lookupSCC(E.getNode());
RefSCC &TargetRC = TargetC.getOuterRefSCC();
if (&TargetRC == RC && E.isCall()) {
if (C != &TargetC) {
// For separate SCCs this is trivial.
RC->switchTrivialInternalEdgeToRef(N, TargetN);
RC->switchTrivialInternalEdgeToRef(N, E.getNode());
} else {
// Now update the call graph.
C = incorporateNewSCCRange(RC->switchInternalEdgeToRef(N, TargetN), G,
N, C, AM, UR, DebugLogging);
C = incorporateNewSCCRange(RC->switchInternalEdgeToRef(N, E.getNode()),
G, N, C, AM, UR, DebugLogging);
}
}

auto NewRefSCCs = RC->removeInternalRefEdge(N, TargetN);
if (!NewRefSCCs.empty()) {
// Note that we don't bother to invalidate analyses as ref-edge
// connectivity is not really observable in any way and is intended
// exclusively to be used for ordering of transforms rather than for
// analysis conclusions.

// The RC worklist is in reverse postorder, so we first enqueue the
// current RefSCC as it will remain the parent of all split RefSCCs, then
// we enqueue the new ones in RPO except for the one which contains the
// source node as that is the "bottom" we will continue processing in the
// bottom-up walk.
UR.RCWorklist.insert(RC);
// Now that this is ready for actual removal, put it into our list.
DeadTargets.push_back(&E.getNode());
}
// Remove the easy cases quickly and actually pull them out of our list.
DeadTargets.erase(
llvm::remove_if(DeadTargets,
[&](Node *TargetN) {
SCC &TargetC = *G.lookupSCC(*TargetN);
RefSCC &TargetRC = TargetC.getOuterRefSCC();

// We can't trivially remove internal targets, so skip
// those.
if (&TargetRC == RC)
return false;

RC->removeOutgoingEdge(N, *TargetN);
if (DebugLogging)
dbgs() << "Deleting outgoing edge from '" << N
<< "' to '" << TargetN << "'\n";
return true;
}),
DeadTargets.end());

// Now do a batch removal of the internal ref edges left.
auto NewRefSCCs = RC->removeInternalRefEdge(N, DeadTargets);
if (!NewRefSCCs.empty()) {
// The old RefSCC is dead, mark it as such.
UR.InvalidatedRefSCCs.insert(RC);

// Note that we don't bother to invalidate analyses as ref-edge
// connectivity is not really observable in any way and is intended
// exclusively to be used for ordering of transforms rather than for
// analysis conclusions.

// Update RC to the "bottom".
assert(G.lookupSCC(N) == C && "Changed the SCC when splitting RefSCCs!");
RC = &C->getOuterRefSCC();
assert(G.lookupRefSCC(N) == RC && "Failed to update current RefSCC!");

// The RC worklist is in reverse postorder, so we enqueue the new ones in
// RPO except for the one which contains the source node as that is the
// "bottom" we will continue processing in the bottom-up walk.
assert(NewRefSCCs.front() == RC &&
"New current RefSCC not first in the returned list!");
for (RefSCC *NewRC :
reverse(make_range(std::next(NewRefSCCs.begin()), NewRefSCCs.end()))) {
assert(NewRC != RC && "Should not encounter the current RefSCC further "
"in the postorder list of new RefSCCs.");
UR.RCWorklist.insert(NewRC);
if (DebugLogging)
dbgs() << "Enqueuing the existing RefSCC in the update worklist: "
<< *RC << "\n";
// Update the RC to the "bottom".
assert(G.lookupSCC(N) == C && "Changed the SCC when splitting RefSCCs!");
RC = &C->getOuterRefSCC();
assert(G.lookupRefSCC(N) == RC && "Failed to update current RefSCC!");
assert(NewRefSCCs.front() == RC &&
"New current RefSCC not first in the returned list!");
for (RefSCC *NewRC : reverse(
make_range(std::next(NewRefSCCs.begin()), NewRefSCCs.end()))) {
assert(NewRC != RC && "Should not encounter the current RefSCC further "
"in the postorder list of new RefSCCs.");
UR.RCWorklist.insert(NewRC);
if (DebugLogging)
dbgs() << "Enqueuing a new RefSCC in the update worklist: " << *NewRC
<< "\n";
}
dbgs() << "Enqueuing a new RefSCC in the update worklist: " << *NewRC
<< "\n";
}
}

Expand Down

0 comments on commit 23c2f44

Please sign in to comment.