[llvm] r206581 - [LCG] Add support for building persistent and connected SCCs to the
Benjamin Kramer
benny.kra at gmail.com
Fri Apr 18 07:20:55 PDT 2014
On 18.04.2014, at 12:50, Chandler Carruth <chandlerc at gmail.com> wrote:
> Author: chandlerc
> Date: Fri Apr 18 05:50:32 2014
> New Revision: 206581
>
> URL: http://llvm.org/viewvc/llvm-project?rev=206581&view=rev
> Log:
> [LCG] Add support for building persistent and connected SCCs to the
> LazyCallGraph. This is the start of the whole point of this different
> abstraction, but it is just the initial bits. Here is a run-down of
> what's going on here. I'm planning to incorporate some (or all) of this
> into comments going forward, hopefully with better editing and wording.
> =]
>
> The crux of the problem with the traditional way of building SCCs is
> that they are ephemeral. The new pass manager however really needs the
> ability to associate analysis passes and results of analysis passes with
> SCCs in order to expose these analysis passes to the SCC passes. Making
> this work is kind-of the whole point of the new pass manager. =]
>
> So, when we're building SCCs for the call graph, we actually want to
> build persistent nodes that stick around and can be reasoned about
> later. We'd also like the ability to walk the SCC graph in more complex
> ways than just the traditional postorder traversal of the current CGSCC
> walk. That means that in addition to being persistent, the SCCs need to
> be connected into a useful graph structure.
>
> However, we still want the SCCs to be formed lazily where possible.
>
> These constraints are quite hard to satisfy with the SCC iterator. Also,
> using that would bypass our ability to actually add data to the nodes of
> the call graph to facilite implementing the Tarjan walk. So I've
> re-implemented things in a more direct and embedded way. This
> immediately makes it easy to get the persistence and connectivity
> correct, and it also allows leveraging the existing nodes to simplify
> the algorithm. I've worked somewhat to make this implementation more
> closely follow the traditional paper's nomenclature and strategy,
> although it is still a bit obtuse because it isn't recursive, using
> an explicit stack and a tail call instead, and it is interruptable,
> resuming each time we need another SCC.
>
> The other tricky bit here, and what actually took almost all the time
> and trials and errors I spent building this, is exactly *what* graph
> structure to build for the SCCs. The naive thing to build is the call
> graph in its newly acyclic form. I wrote about 4 versions of this which
> did precisely this. Inevitably, when I experimented with them across
> various use cases, they became incredibly awkward. It was all
> implementable, but it felt like a complete wrong fit. Square peg, round
> hole. There were two overriding aspects that pushed me in a different
> direction:
>
> 1) We want to discover the SCC graph in a postorder fashion. That means
> the root node will be the *last* node we find. Using the call-SCC DAG
> as the graph structure of the SCCs results in an orphaned graph until
> we discover a root.
>
> 2) We will eventually want to walk the SCC graph in parallel, exploring
> distinct sub-graphs independently, and synchronizing at merge points.
> This again is not helped by the call-SCC DAG structure.
>
> The structure which, quite surprisingly, ended up being completely
> natural to use is the *inverse* of the call-SCC DAG. We add the leaf
> SCCs to the graph as "roots", and have edges to the caller SCCs. Once
> I switched to building this structure, everything just fell into place
> elegantly.
>
> Aside from general cleanups (there are FIXMEs and too few comments
> overall) that are still needed, the other missing piece of this is
> support for iterating across levels of the SCC graph. These will become
> useful for implementing #2, but they aren't an immediate priority.
>
> Once SCCs are in good shape, I'll be working on adding mutation support
> for incremental updates and adding the pass manager that this analysis
> enables.
>
> Modified:
> llvm/trunk/include/llvm/Analysis/LazyCallGraph.h
> llvm/trunk/lib/Analysis/LazyCallGraph.cpp
> llvm/trunk/test/Analysis/LazyCallGraph/basic.ll
>
> Modified: llvm/trunk/include/llvm/Analysis/LazyCallGraph.h
> URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/include/llvm/Analysis/LazyCallGraph.h?rev=206581&r1=206580&r2=206581&view=diff
> ==============================================================================
> --- llvm/trunk/include/llvm/Analysis/LazyCallGraph.h (original)
> +++ llvm/trunk/include/llvm/Analysis/LazyCallGraph.h Fri Apr 18 05:50:32 2014
> @@ -38,8 +38,10 @@
> #include "llvm/ADT/DenseMap.h"
> #include "llvm/ADT/PointerUnion.h"
> #include "llvm/ADT/STLExtras.h"
> +#include "llvm/ADT/SetVector.h"
> #include "llvm/ADT/SmallPtrSet.h"
> #include "llvm/ADT/SmallVector.h"
> +#include "llvm/ADT/iterator_range.h"
> #include "llvm/IR/BasicBlock.h"
> #include "llvm/IR/Function.h"
> #include "llvm/IR/Module.h"
> @@ -100,6 +102,7 @@ class raw_ostream;
> class LazyCallGraph {
> public:
> class Node;
> + class SCC;
> typedef SmallVector<PointerUnion<Function *, Node *>, 4> NodeVectorT;
> typedef SmallVectorImpl<PointerUnion<Function *, Node *>> NodeVectorImplT;
>
> @@ -173,9 +176,16 @@ public:
> /// a callee, and facilitate iteration of child nodes in the graph.
> class Node {
> friend class LazyCallGraph;
> + friend class LazyCallGraph::SCC;
>
> LazyCallGraph *G;
> Function &F;
> +
> + // We provide for the DFS numbering and Tarjan walk lowlink numbers to be
> + // stored directly within the node.
> + int DFSNumber;
> + int LowLink;
> +
> mutable NodeVectorT Callees;
> SmallPtrSet<Function *, 4> CalleeSet;
>
> @@ -201,6 +211,79 @@ public:
> bool operator!=(const Node &N) const { return !operator==(N); }
> };
>
> + /// \brief An SCC of the call graph.
> + ///
> + /// This represents a Strongly Connected Component of the call graph as
> + /// a collection of call graph nodes. While the order of nodes in the SCC is
> + /// stable, it is not any particular order.
> + class SCC {
> + friend class LazyCallGraph;
> + friend class LazyCallGraph::Node;
> +
> + SmallSetVector<SCC *, 1> ParentSCCs;
> + SmallVector<Node *, 1> Nodes;
> + SmallPtrSet<Function *, 1> NodeSet;
> +
> + SCC() {}
> +
> + public:
> + typedef SmallVectorImpl<Node *>::const_iterator iterator;
> +
> + iterator begin() const { return Nodes.begin(); }
> + iterator end() const { return Nodes.end(); }
> + };
> +
> + /// \brief A post-order depth-first SCC iterator over the call graph.
> + ///
> + /// This iterator triggers the Tarjan DFS-based formation of the SCC DAG for
> + /// the call graph, walking it lazily in depth-first post-order. That is, it
> + /// always visits SCCs for a callee prior to visiting the SCC for a caller
> + /// (when they are in different SCCs).
> + class postorder_scc_iterator
> + : public std::iterator<std::forward_iterator_tag, SCC *, ptrdiff_t, SCC *,
> + SCC *> {
> + friend class LazyCallGraph;
> + friend class LazyCallGraph::Node;
> + typedef std::iterator<std::forward_iterator_tag, SCC *, ptrdiff_t,
> + SCC *, SCC *> BaseT;
> +
> + /// \brief Nonce type to select the constructor for the end iterator.
> + struct IsAtEndT {};
> +
> + LazyCallGraph *G;
> + SCC *C;
> +
> + // Build the begin iterator for a node.
> + postorder_scc_iterator(LazyCallGraph &G) : G(&G) {
> + C = G.getNextSCCInPostOrder();
> + }
> +
> + // Build the end iterator for a node. This is selected purely by overload.
> + postorder_scc_iterator(LazyCallGraph &G, IsAtEndT /*Nonce*/)
> + : G(&G), C(nullptr) {}
> +
> + public:
> + bool operator==(const postorder_scc_iterator &Arg) {
> + return G == Arg.G && C == Arg.C;
> + }
> + bool operator!=(const postorder_scc_iterator &Arg) {
> + return !operator==(Arg);
> + }
> +
> + reference operator*() const { return C; }
> + pointer operator->() const { return operator*(); }
> +
> + postorder_scc_iterator &operator++() {
> + C = G->getNextSCCInPostOrder();
> + return *this;
> + }
> + postorder_scc_iterator operator++(int) {
> + postorder_scc_iterator prev = *this;
> + ++*this;
> + return prev;
> + }
> + };
> +
> /// \brief Construct a graph for the given module.
> ///
> /// This sets up the graph and computes all of the entry points of the graph.
> @@ -229,6 +312,18 @@ public:
> iterator begin() { return iterator(*this, EntryNodes); }
> iterator end() { return iterator(*this, EntryNodes, iterator::IsAtEndT()); }
>
> + postorder_scc_iterator postorder_scc_begin() {
> + return postorder_scc_iterator(*this);
> + }
> + postorder_scc_iterator postorder_scc_end() {
> + return postorder_scc_iterator(*this, postorder_scc_iterator::IsAtEndT());
> + }
> +
> + iterator_range<postorder_scc_iterator> postorder_sccs() {
> + return iterator_range<postorder_scc_iterator>(postorder_scc_begin(),
> + postorder_scc_end());
> + }
> +
> /// \brief Lookup a function in the graph which has already been scanned and
> /// added.
> Node *lookup(const Function &F) const { return NodeMap.lookup(&F); }
> @@ -259,12 +354,35 @@ private:
> /// \brief Set of the entry nodes to the graph.
> SmallPtrSet<Function *, 4> EntryNodeSet;
>
> + /// \brief Allocator that holds all the call graph SCCs.
> + SpecificBumpPtrAllocator<SCC> SCCBPA;
> +
> + /// \brief Maps Function -> SCC for fast lookup.
> + DenseMap<const Function *, SCC *> SCCMap;
> +
> + /// \brief The leaf SCCs of the graph.
> + ///
> + /// These are all of the SCCs which have no children.
> + SmallVector<SCC *, 4> LeafSCCs;
> +
> + /// \brief Stack of nodes not-yet-processed into SCCs.
> + SmallVector<std::pair<Node *, iterator>, 4> DFSStack;
> +
> + /// \brief Set of entry nodes not-yet-processed into SCCs.
> + SmallSetVector<Function *, 4> SCCEntryNodes;
> +
> + /// \brief Counter for the next DFS number to assign.
> + int NextDFSNumber;
This member var isn't initialized anywhere. MSan and valgrind are upset.
> +
> /// \brief Helper to insert a new function, with an already looked-up entry in
> /// the NodeMap.
> Node *insertInto(Function &F, Node *&MappedN);
>
> /// \brief Helper to copy a node from another graph into this one.
> Node *copyInto(const Node &OtherN);
> +
> + /// \brief Retrieve the next node in the post-order SCC walk of the call graph.
> + SCC *getNextSCCInPostOrder();
> };
>
> // Provide GraphTraits specializations for call graphs.
>
> Modified: llvm/trunk/lib/Analysis/LazyCallGraph.cpp
> URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/lib/Analysis/LazyCallGraph.cpp?rev=206581&r1=206580&r2=206581&view=diff
> ==============================================================================
> --- llvm/trunk/lib/Analysis/LazyCallGraph.cpp (original)
> +++ llvm/trunk/lib/Analysis/LazyCallGraph.cpp Fri Apr 18 05:50:32 2014
> @@ -8,7 +8,7 @@
> //===----------------------------------------------------------------------===//
>
> #include "llvm/Analysis/LazyCallGraph.h"
> -#include "llvm/ADT/SCCIterator.h"
> +#include "llvm/ADT/STLExtras.h"
> #include "llvm/IR/CallSite.h"
> #include "llvm/IR/InstVisitor.h"
> #include "llvm/IR/Instructions.h"
> @@ -46,7 +46,8 @@ static void findCallees(
> }
> }
>
> -LazyCallGraph::Node::Node(LazyCallGraph &G, Function &F) : G(&G), F(F) {
> +LazyCallGraph::Node::Node(LazyCallGraph &G, Function &F)
> + : G(&G), F(F), DFSNumber(0), LowLink(0) {
> SmallVector<Constant *, 16> Worklist;
> SmallPtrSet<Constant *, 16> Visited;
> // Find all the potential callees in this function. First walk the
> @@ -65,7 +66,7 @@ LazyCallGraph::Node::Node(LazyCallGraph
> }
>
> LazyCallGraph::Node::Node(LazyCallGraph &G, const Node &OtherN)
> - : G(&G), F(OtherN.F), CalleeSet(OtherN.CalleeSet) {
> + : G(&G), F(OtherN.F), DFSNumber(0), LowLink(0), CalleeSet(OtherN.CalleeSet) {
> // Loop over the other node's callees, adding the Function*s to our list
> // directly, and recursing to add the Node*s.
> Callees.reserve(OtherN.Callees.size());
> @@ -91,6 +92,12 @@ LazyCallGraph::LazyCallGraph(Module &M)
> Worklist.push_back(GV.getInitializer());
>
> findCallees(Worklist, Visited, EntryNodes, EntryNodeSet);
> +
> + for (auto &Entry : EntryNodes)
> + if (Function *F = Entry.dyn_cast<Function *>())
> + SCCEntryNodes.insert(F);
> + else
> + SCCEntryNodes.insert(&Entry.get<Node *>()->getFunction());
> }
>
> LazyCallGraph::LazyCallGraph(const LazyCallGraph &G)
> @@ -101,11 +108,22 @@ LazyCallGraph::LazyCallGraph(const LazyC
> EntryNodes.push_back(Callee);
> else
> EntryNodes.push_back(copyInto(*EntryNode.get<Node *>()));
> +
> + // Just re-populate the SCCEntryNodes structure so we recompute the SCCs if
> + // needed.
> + for (auto &Entry : EntryNodes)
> + if (Function *F = Entry.dyn_cast<Function *>())
> + SCCEntryNodes.insert(F);
> + else
> + SCCEntryNodes.insert(&Entry.get<Node *>()->getFunction());
> }
>
> LazyCallGraph::LazyCallGraph(LazyCallGraph &&G)
> : BPA(std::move(G.BPA)), EntryNodes(std::move(G.EntryNodes)),
> - EntryNodeSet(std::move(G.EntryNodeSet)) {
> + EntryNodeSet(std::move(G.EntryNodeSet)), SCCBPA(std::move(G.SCCBPA)),
> + SCCMap(std::move(G.SCCMap)), LeafSCCs(std::move(G.LeafSCCs)),
> + DFSStack(std::move(G.DFSStack)),
> + SCCEntryNodes(std::move(G.SCCEntryNodes)) {
The ctor misses two members, NodeMap and NextDFSNumber. Intentional?
Same for the move assignment operator.
- Ben
> // Process all nodes updating the graph pointers.
> SmallVector<Node *, 16> Worklist;
> for (auto &Entry : EntryNodes)
> @@ -133,6 +151,88 @@ LazyCallGraph::Node *LazyCallGraph::copy
> return new (N = BPA.Allocate()) Node(*this, OtherN);
> }
>
> +LazyCallGraph::SCC *LazyCallGraph::getNextSCCInPostOrder() {
> + // When the stack is empty, there are no more SCCs to walk in this graph.
> + if (DFSStack.empty()) {
> + // If we've handled all candidate entry nodes to the SCC forest, we're done.
> + if (SCCEntryNodes.empty())
> + return nullptr;
> +
> + Node *N = get(*SCCEntryNodes.pop_back_val());
> + DFSStack.push_back(std::make_pair(N, N->begin()));
> + }
> +
> + Node *N = DFSStack.back().first;
> + if (N->DFSNumber == 0) {
> + // This node hasn't been visited before, assign it a DFS number and remove
> + // it from the entry set.
> + N->LowLink = N->DFSNumber = NextDFSNumber++;
> + SCCEntryNodes.remove(&N->getFunction());
> + }
> +
> + for (auto I = DFSStack.back().second, E = N->end(); I != E; ++I) {
> + Node *ChildN = *I;
> + if (ChildN->DFSNumber == 0) {
> + // Mark that we should start at this child when next this node is the
> + // top of the stack. We don't start at the next child to ensure this
> + // child's lowlink is reflected.
> + // FIXME: I don't actually think this is required, and we could start
> + // at the next child.
> + DFSStack.back().second = I;
> +
> + // Recurse onto this node via a tail call.
> + DFSStack.push_back(std::make_pair(ChildN, ChildN->begin()));
> + return LazyCallGraph::getNextSCCInPostOrder();
> + }
> +
> + // Track the lowest link of the childen, if any are still in the stack.
> + if (ChildN->LowLink < N->LowLink && !SCCMap.count(&ChildN->getFunction()))
> + N->LowLink = ChildN->LowLink;
> + }
> +
> + // The tail of the stack is the new SCC. Allocate the SCC and pop the stack
> + // into it.
> + SCC *NewSCC = new (SCCBPA.Allocate()) SCC();
> +
> + // Because we don't follow the strict Tarjan recursive formulation, walk
> + // from the top of the stack down, propagating the lowest link and stopping
> + // when the DFS number is the lowest link.
> + int LowestLink = N->LowLink;
> + do {
> + Node *SCCN = DFSStack.pop_back_val().first;
> + SCCMap.insert(std::make_pair(&SCCN->getFunction(), NewSCC));
> + NewSCC->Nodes.push_back(SCCN);
> + LowestLink = std::min(LowestLink, SCCN->LowLink);
> + bool Inserted =
> + NewSCC->NodeSet.insert(&SCCN->getFunction());
> + (void)Inserted;
> + assert(Inserted && "Cannot have duplicates in the DFSStack!");
> + } while (!DFSStack.empty() && LowestLink <= DFSStack.back().first->DFSNumber);
> + assert(LowestLink == NewSCC->Nodes.back()->DFSNumber &&
> + "Cannot stop with a DFS number greater than the lowest link!");
> +
> + // A final pass over all edges in the SCC (this remains linear as we only
> + // do this once when we build the SCC) to connect it to the parent sets of
> + // its children.
> + bool IsLeafSCC = true;
> + for (Node *SCCN : NewSCC->Nodes)
> + for (Node *SCCChildN : *SCCN) {
> + if (NewSCC->NodeSet.count(&SCCChildN->getFunction()))
> + continue;
> + SCC *ChildSCC = SCCMap.lookup(&SCCChildN->getFunction());
> + assert(ChildSCC &&
> + "Must have all child SCCs processed when building a new SCC!");
> + ChildSCC->ParentSCCs.insert(NewSCC);
> + IsLeafSCC = false;
> + }
> +
> + // For the SCCs where we fine no child SCCs, add them to the leaf list.
> + if (IsLeafSCC)
> + LeafSCCs.push_back(NewSCC);
> +
> + return NewSCC;
> +}
> +
> char LazyCallGraphAnalysis::PassID;
>
> LazyCallGraphPrinterPass::LazyCallGraphPrinterPass(raw_ostream &OS) : OS(OS) {}
> @@ -151,6 +251,16 @@ static void printNodes(raw_ostream &OS,
> OS << "\n";
> }
>
> +static void printSCC(raw_ostream &OS, LazyCallGraph::SCC &SCC) {
> + ptrdiff_t SCCSize = std::distance(SCC.begin(), SCC.end());
> + OS << " SCC with " << SCCSize << " functions:\n";
> +
> + for (LazyCallGraph::Node *N : SCC)
> + OS << " " << N->getFunction().getName() << "\n";
> +
> + OS << "\n";
> +}
> +
> PreservedAnalyses LazyCallGraphPrinterPass::run(Module *M,
> ModuleAnalysisManager *AM) {
> LazyCallGraph &G = AM->getResult<LazyCallGraphAnalysis>(M);
> @@ -163,5 +273,9 @@ PreservedAnalyses LazyCallGraphPrinterPa
> if (Printed.insert(N))
> printNodes(OS, *N, Printed);
>
> + for (LazyCallGraph::SCC *SCC : G.postorder_sccs())
> + printSCC(OS, *SCC);
> +
> return PreservedAnalyses::all();
> +
> }
>
> Modified: llvm/trunk/test/Analysis/LazyCallGraph/basic.ll
> URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/test/Analysis/LazyCallGraph/basic.ll?rev=206581&r1=206580&r2=206581&view=diff
> ==============================================================================
> --- llvm/trunk/test/Analysis/LazyCallGraph/basic.ll (original)
> +++ llvm/trunk/test/Analysis/LazyCallGraph/basic.ll Fri Apr 18 05:50:32 2014
> @@ -124,3 +124,53 @@ define void @test2() {
> load i8** bitcast (void ()** @h to i8**)
> ret void
> }
> +
> +; Verify the SCCs formed.
> +;
> +; CHECK-LABEL: SCC with 1 functions:
> +; CHECK-NEXT: f7
> +;
> +; CHECK-LABEL: SCC with 1 functions:
> +; CHECK-NEXT: f6
> +;
> +; CHECK-LABEL: SCC with 1 functions:
> +; CHECK-NEXT: f5
> +;
> +; CHECK-LABEL: SCC with 1 functions:
> +; CHECK-NEXT: f4
> +;
> +; CHECK-LABEL: SCC with 1 functions:
> +; CHECK-NEXT: f3
> +;
> +; CHECK-LABEL: SCC with 1 functions:
> +; CHECK-NEXT: f2
> +;
> +; CHECK-LABEL: SCC with 1 functions:
> +; CHECK-NEXT: f1
> +;
> +; CHECK-LABEL: SCC with 1 functions:
> +; CHECK-NEXT: test2
> +;
> +; CHECK-LABEL: SCC with 1 functions:
> +; CHECK-NEXT: f12
> +;
> +; CHECK-LABEL: SCC with 1 functions:
> +; CHECK-NEXT: f11
> +;
> +; CHECK-LABEL: SCC with 1 functions:
> +; CHECK-NEXT: f10
> +;
> +; CHECK-LABEL: SCC with 1 functions:
> +; CHECK-NEXT: f9
> +;
> +; CHECK-LABEL: SCC with 1 functions:
> +; CHECK-NEXT: f8
> +;
> +; CHECK-LABEL: SCC with 1 functions:
> +; CHECK-NEXT: test1
> +;
> +; CHECK-LABEL: SCC with 1 functions:
> +; CHECK-NEXT: f
> +;
> +; CHECK-LABEL: SCC with 1 functions:
> +; CHECK-NEXT: test0
>
>
> _______________________________________________
> llvm-commits mailing list
> llvm-commits at cs.uiuc.edu
> http://lists.cs.uiuc.edu/mailman/listinfo/llvm-commits
More information about the llvm-commits
mailing list