mirror of
https://github.com/RPCS3/llvm-mirror.git
synced 2024-11-29 22:30:33 +00:00
[LCG] Construct an actual call graph with call-edge SCCs nested inside
reference-edge SCCs. This essentially builds a more normal call graph as a subgraph of the "reference graph" that was the old model. This allows both to exist and the different use cases to use the aspect which addresses their needs. Specifically, the pass manager and other *ordering* constrained logic can use the reference graph to achieve conservative order of visit, while analyses reasoning about attributes and other properties derived from reachability can reason about the direct call graph. Note that this isn't necessarily complete: it doesn't model edges to declarations or indirect calls. Those can be found by scanning the instructions of the function if desirable, and in fact every user currently does this in order to handle things like calls to instrinsics. If useful, we could consider caching this information in the call graph to save the instruction scans, but currently that doesn't seem to be important. An important realization for why the representation chosen here works is that the call graph is a formal subset of the reference graph and thus both can live within the same data structure. All SCCs of the call graph are necessarily contained within an SCC of the reference graph, etc. The design is to build 'RefSCC's to model SCCs of the reference graph, and then within them more literal SCCs for the call graph. The formation of actual call edge SCCs is not done lazily, unlike reference edge 'RefSCC's. Instead, once a reference SCC is formed, it directly builds the call SCCs within it and stores them in a post-order sequence. This is used to provide a consistent platform for mutation and update of the graph. The post-order also allows for very efficient updates in common cases by bounding the number of nodes (and thus edges) considered. There is considerable common code that I'm still looking for the best way to factor out between the various DFS implementations here. So far, my attempts have made the code harder to read and understand despite reducing the duplication, which seems a poor tradeoff. I've not given up on figuring out the right way to do this, but I wanted to wait until I at least had the system working and tested to continue attempting to factor it differently. This also requires introducing several new algorithms in order to handle all of the incremental update scenarios for the more complex structure involving two edge colorings. I've tried to comment the algorithms sufficiently to make it clear how this is expected to work, but they may still need more extensive documentation. I know that there are some changes which are not strictly necessarily coupled here. The process of developing this started out with a very focused set of changes for the new structure of the graph and algorithms, but subsequent changes to bring the APIs and code into consistent and understandable patterns also ended up touching on other aspects. There was no good way to separate these out without causing *massive* merge conflicts. Ultimately, to a large degree this is a rewrite of most of the core algorithms in the LCG class and so I don't think it really matters much. Many thanks to the careful review by Sanjoy Das! Differential Revision: http://reviews.llvm.org/D16802 llvm-svn: 261040
This commit is contained in:
parent
30231d4f95
commit
4ec346556c
@ -226,23 +226,24 @@ public:
|
||||
LazyCallGraph &CG = AM->getResult<LazyCallGraphAnalysis>(M);
|
||||
|
||||
PreservedAnalyses PA = PreservedAnalyses::all();
|
||||
for (LazyCallGraph::SCC &C : CG.postorder_sccs()) {
|
||||
PreservedAnalyses PassPA = Pass.run(C, &CGAM);
|
||||
for (LazyCallGraph::RefSCC &OuterC : CG.postorder_ref_sccs())
|
||||
for (LazyCallGraph::SCC &C : OuterC) {
|
||||
PreservedAnalyses PassPA = Pass.run(C, &CGAM);
|
||||
|
||||
// We know that the CGSCC pass couldn't have invalidated any other
|
||||
// SCC's analyses (that's the contract of a CGSCC pass), so
|
||||
// directly handle the CGSCC analysis manager's invalidation here. We
|
||||
// also update the preserved set of analyses to reflect that invalidated
|
||||
// analyses are now safe to preserve.
|
||||
// FIXME: This isn't quite correct. We need to handle the case where the
|
||||
// pass updated the CG, particularly some child of the current SCC, and
|
||||
// invalidate its analyses.
|
||||
PassPA = CGAM.invalidate(C, std::move(PassPA));
|
||||
// We know that the CGSCC pass couldn't have invalidated any other
|
||||
// SCC's analyses (that's the contract of a CGSCC pass), so
|
||||
// directly handle the CGSCC analysis manager's invalidation here. We
|
||||
// also update the preserved set of analyses to reflect that invalidated
|
||||
// analyses are now safe to preserve.
|
||||
// FIXME: This isn't quite correct. We need to handle the case where the
|
||||
// pass updated the CG, particularly some child of the current SCC, and
|
||||
// invalidate its analyses.
|
||||
PassPA = CGAM.invalidate(C, std::move(PassPA));
|
||||
|
||||
// Then intersect the preserved set so that invalidation of module
|
||||
// analyses will eventually occur when the module pass completes.
|
||||
PA.intersect(std::move(PassPA));
|
||||
}
|
||||
// Then intersect the preserved set so that invalidation of module
|
||||
// analyses will eventually occur when the module pass completes.
|
||||
PA.intersect(std::move(PassPA));
|
||||
}
|
||||
|
||||
// By definition we preserve the proxy. This precludes *any* invalidation
|
||||
// of CGSCC analyses by the proxy, but that's OK because we've taken
|
||||
@ -446,8 +447,8 @@ public:
|
||||
FAM = &AM->getResult<FunctionAnalysisManagerCGSCCProxy>(C).getManager();
|
||||
|
||||
PreservedAnalyses PA = PreservedAnalyses::all();
|
||||
for (LazyCallGraph::Node *N : C) {
|
||||
PreservedAnalyses PassPA = Pass.run(N->getFunction(), FAM);
|
||||
for (LazyCallGraph::Node &N : C) {
|
||||
PreservedAnalyses PassPA = Pass.run(N.getFunction(), FAM);
|
||||
|
||||
// We know that the function pass couldn't have invalidated any other
|
||||
// function's analyses (that's the contract of a function pass), so
|
||||
@ -455,7 +456,7 @@ public:
|
||||
// Also, update the preserved analyses to reflect that once invalidated
|
||||
// these can again be preserved.
|
||||
if (FAM)
|
||||
PassPA = FAM->invalidate(N->getFunction(), std::move(PassPA));
|
||||
PassPA = FAM->invalidate(N.getFunction(), std::move(PassPA));
|
||||
|
||||
// Then intersect the preserved set so that invalidation of module
|
||||
// analyses will eventually occur when the module pass completes.
|
||||
|
@ -49,6 +49,7 @@
|
||||
#include "llvm/IR/PassManager.h"
|
||||
#include "llvm/Support/Allocator.h"
|
||||
#include <iterator>
|
||||
#include <utility>
|
||||
|
||||
namespace llvm {
|
||||
class PreservedAnalyses;
|
||||
@ -104,7 +105,9 @@ class LazyCallGraph {
|
||||
public:
|
||||
class Node;
|
||||
class SCC;
|
||||
class RefSCC;
|
||||
class edge_iterator;
|
||||
class call_edge_iterator;
|
||||
|
||||
/// A class used to represent edges in the call graph.
|
||||
///
|
||||
@ -172,7 +175,11 @@ public:
|
||||
Node &getNode(LazyCallGraph &G);
|
||||
|
||||
private:
|
||||
friend class LazyCallGraph::Node;
|
||||
|
||||
PointerIntPair<PointerUnion<Function *, Node *>, 1, Kind> Value;
|
||||
|
||||
void setKind(Kind K) { Value.setInt(K); }
|
||||
};
|
||||
|
||||
typedef SmallVector<Edge, 4> EdgeVectorT;
|
||||
@ -191,12 +198,13 @@ public:
|
||||
Function &F;
|
||||
|
||||
// We provide for the DFS numbering and Tarjan walk lowlink numbers to be
|
||||
// stored directly within the node.
|
||||
// stored directly within the node. These are both '-1' when nodes are part
|
||||
// of an SCC (or RefSCC), or '0' when not yet reached in a DFS walk.
|
||||
int DFSNumber;
|
||||
int LowLink;
|
||||
|
||||
mutable EdgeVectorT Edges;
|
||||
DenseMap<Function *, size_t> EdgeIndexMap;
|
||||
DenseMap<Function *, int> EdgeIndexMap;
|
||||
|
||||
/// Basic constructor implements the scanning of F into Edges and
|
||||
/// EdgeIndexMap.
|
||||
@ -208,12 +216,13 @@ public:
|
||||
/// Internal helper to insert an edge to a node.
|
||||
void insertEdgeInternal(Node &ChildN, Edge::Kind EK);
|
||||
|
||||
/// Internal helper to change an edge kind.
|
||||
void setEdgeKind(Function &ChildF, Edge::Kind EK);
|
||||
|
||||
/// Internal helper to remove the edge to the given function.
|
||||
void removeEdgeInternal(Function &ChildF);
|
||||
|
||||
public:
|
||||
typedef LazyCallGraph::edge_iterator edge_iterator;
|
||||
|
||||
LazyCallGraph &getGraph() const { return *G; }
|
||||
|
||||
Function &getFunction() const { return F; }
|
||||
@ -223,6 +232,24 @@ public:
|
||||
}
|
||||
edge_iterator end() const { return edge_iterator(Edges.end(), Edges.end()); }
|
||||
|
||||
const Edge &operator[](int i) const { return Edges[i]; }
|
||||
const Edge &operator[](Function &F) const {
|
||||
assert(EdgeIndexMap.find(&F) != EdgeIndexMap.end() && "No such edge!");
|
||||
return Edges[EdgeIndexMap.find(&F)->second];
|
||||
}
|
||||
const Edge &operator[](Node &N) const { return (*this)[N.getFunction()]; }
|
||||
|
||||
call_edge_iterator call_begin() const {
|
||||
return call_edge_iterator(Edges.begin(), Edges.end());
|
||||
}
|
||||
call_edge_iterator call_end() const {
|
||||
return call_edge_iterator(Edges.end(), Edges.end());
|
||||
}
|
||||
|
||||
iterator_range<call_edge_iterator> calls() const {
|
||||
return make_range(call_begin(), call_end());
|
||||
}
|
||||
|
||||
/// Equality is defined as address equality.
|
||||
bool operator==(const Node &N) const { return this == &N; }
|
||||
bool operator!=(const Node &N) const { return !operator==(N); }
|
||||
@ -301,60 +328,146 @@ public:
|
||||
|
||||
/// An SCC of the call graph.
|
||||
///
|
||||
/// This represents a Strongly Connected Component of the call graph as
|
||||
/// This represents a Strongly Connected Component of the direct call graph
|
||||
/// -- ignoring indirect calls and function references. It stores this as
|
||||
/// a collection of call graph nodes. While the order of nodes in the SCC is
|
||||
/// stable, it is not any particular order.
|
||||
///
|
||||
/// The SCCs are nested within a \c RefSCC, see below for details about that
|
||||
/// outer structure. SCCs do not support mutation of the call graph, that
|
||||
/// must be done through the containing \c RefSCC in order to fully reason
|
||||
/// about the ordering and connections of the graph.
|
||||
class SCC {
|
||||
friend class LazyCallGraph;
|
||||
friend class LazyCallGraph::Node;
|
||||
|
||||
LazyCallGraph *G;
|
||||
SmallPtrSet<SCC *, 1> ParentSCCs;
|
||||
RefSCC *OuterRefSCC;
|
||||
SmallVector<Node *, 1> Nodes;
|
||||
|
||||
SCC(LazyCallGraph &G) : G(&G) {}
|
||||
template <typename NodeRangeT>
|
||||
SCC(RefSCC &OuterRefSCC, NodeRangeT &&Nodes)
|
||||
: OuterRefSCC(&OuterRefSCC), Nodes(std::forward<NodeRangeT>(Nodes)) {}
|
||||
|
||||
void insert(Node &N);
|
||||
void clear() {
|
||||
OuterRefSCC = nullptr;
|
||||
Nodes.clear();
|
||||
}
|
||||
|
||||
void
|
||||
internalDFS(SmallVectorImpl<std::pair<Node *, Node::edge_iterator>> &DFSStack,
|
||||
SmallVectorImpl<Node *> &PendingSCCStack, Node *N,
|
||||
SmallVectorImpl<SCC *> &ResultSCCs);
|
||||
#ifndef NDEBUG
|
||||
/// Verify invariants about the SCC.
|
||||
///
|
||||
/// This will attempt to validate all of the basic invariants within an
|
||||
/// SCC, but not that it is a strongly connected componet per-se. Primarily
|
||||
/// useful while building and updating the graph to check that basic
|
||||
/// properties are in place rather than having inexplicable crashes later.
|
||||
void verify();
|
||||
#endif
|
||||
|
||||
public:
|
||||
typedef SmallVectorImpl<Node *>::const_iterator iterator;
|
||||
typedef pointee_iterator<SmallPtrSet<SCC *, 1>::const_iterator>
|
||||
parent_iterator;
|
||||
typedef pointee_iterator<SmallVectorImpl<Node *>::const_iterator> iterator;
|
||||
|
||||
iterator begin() const { return Nodes.begin(); }
|
||||
iterator end() const { return Nodes.end(); }
|
||||
|
||||
parent_iterator parent_begin() const { return ParentSCCs.begin(); }
|
||||
parent_iterator parent_end() const { return ParentSCCs.end(); }
|
||||
int size() const { return Nodes.size(); }
|
||||
|
||||
RefSCC &getOuterRefSCC() const { return *OuterRefSCC; }
|
||||
|
||||
/// Short name useful for debugging or logging.
|
||||
///
|
||||
/// We use the name of the first function in the SCC to name the SCC for
|
||||
/// the purposes of debugging and logging.
|
||||
StringRef getName() const { return begin()->getFunction().getName(); }
|
||||
};
|
||||
|
||||
/// A RefSCC of the call graph.
|
||||
///
|
||||
/// This models a Strongly Connected Component of function reference edges in
|
||||
/// the call graph. As opposed to actual SCCs, these can be used to scope
|
||||
/// subgraphs of the module which are independent from other subgraphs of the
|
||||
/// module because they do not reference it in any way. This is also the unit
|
||||
/// where we do mutation of the graph in order to restrict mutations to those
|
||||
/// which don't violate this independence.
|
||||
///
|
||||
/// A RefSCC contains a DAG of actual SCCs. All the nodes within the RefSCC
|
||||
/// are necessarily within some actual SCC that nests within it. Since
|
||||
/// a direct call *is* a reference, there will always be at least one RefSCC
|
||||
/// around any SCC.
|
||||
class RefSCC {
|
||||
friend class LazyCallGraph;
|
||||
friend class LazyCallGraph::Node;
|
||||
|
||||
LazyCallGraph *G;
|
||||
SmallPtrSet<RefSCC *, 1> Parents;
|
||||
|
||||
/// A postorder list of the inner SCCs.
|
||||
SmallVector<SCC *, 4> SCCs;
|
||||
|
||||
/// A map from SCC to index in the postorder list.
|
||||
SmallDenseMap<SCC *, int, 4> SCCIndices;
|
||||
|
||||
/// Fast-path constructor. RefSCCs should instead be constructed by calling
|
||||
/// formRefSCCFast on the graph itself.
|
||||
RefSCC(LazyCallGraph &G);
|
||||
|
||||
#ifndef NDEBUG
|
||||
/// Verify invariants about the RefSCC and all its SCCs.
|
||||
///
|
||||
/// This will attempt to validate all of the invariants *within* the
|
||||
/// RefSCC, but not that it is a strongly connected component of the larger
|
||||
/// graph. This makes it useful even when partially through an update.
|
||||
///
|
||||
/// Invariants checked:
|
||||
/// - SCCs and their indices match.
|
||||
/// - The SCCs list is in fact in post-order.
|
||||
void verify();
|
||||
#endif
|
||||
|
||||
public:
|
||||
typedef pointee_iterator<SmallVectorImpl<SCC *>::const_iterator> iterator;
|
||||
typedef iterator_range<iterator> range;
|
||||
typedef pointee_iterator<SmallPtrSetImpl<RefSCC *>::const_iterator>
|
||||
parent_iterator;
|
||||
|
||||
iterator begin() const { return SCCs.begin(); }
|
||||
iterator end() const { return SCCs.end(); }
|
||||
|
||||
ssize_t size() const { return SCCs.size(); }
|
||||
|
||||
SCC &operator[](int Idx) { return *SCCs[Idx]; }
|
||||
|
||||
iterator find(SCC &C) const {
|
||||
return SCCs.begin() + SCCIndices.find(&C)->second;
|
||||
}
|
||||
|
||||
parent_iterator parent_begin() const { return Parents.begin(); }
|
||||
parent_iterator parent_end() const { return Parents.end(); }
|
||||
|
||||
iterator_range<parent_iterator> parents() const {
|
||||
return make_range(parent_begin(), parent_end());
|
||||
}
|
||||
|
||||
/// Test if this SCC is a parent of \a C.
|
||||
bool isParentOf(const SCC &C) const { return C.isChildOf(*this); }
|
||||
bool isParentOf(const RefSCC &C) const { return C.isChildOf(*this); }
|
||||
|
||||
/// Test if this SCC is an ancestor of \a C.
|
||||
bool isAncestorOf(const SCC &C) const { return C.isDescendantOf(*this); }
|
||||
/// Test if this RefSCC is an ancestor of \a C.
|
||||
bool isAncestorOf(const RefSCC &C) const { return C.isDescendantOf(*this); }
|
||||
|
||||
/// Test if this SCC is a child of \a C.
|
||||
bool isChildOf(const SCC &C) const {
|
||||
return ParentSCCs.count(const_cast<SCC *>(&C));
|
||||
/// Test if this RefSCC is a child of \a C.
|
||||
bool isChildOf(const RefSCC &C) const {
|
||||
return Parents.count(const_cast<RefSCC *>(&C));
|
||||
}
|
||||
|
||||
/// Test if this SCC is a descendant of \a C.
|
||||
bool isDescendantOf(const SCC &C) const;
|
||||
/// Test if this RefSCC is a descendant of \a C.
|
||||
bool isDescendantOf(const RefSCC &C) const;
|
||||
|
||||
/// Short name useful for debugging or logging.
|
||||
///
|
||||
/// We use the name of the first function in the SCC to name the SCC for
|
||||
/// the purposes of debugging and logging.
|
||||
StringRef getName() const { return (*begin())->getFunction().getName(); }
|
||||
StringRef getName() const {
|
||||
return begin()->begin()->getFunction().getName();
|
||||
}
|
||||
|
||||
///@{
|
||||
/// \name Mutation API
|
||||
@ -365,81 +478,151 @@ public:
|
||||
/// Note that these methods sometimes have complex runtimes, so be careful
|
||||
/// how you call them.
|
||||
|
||||
/// Insert an edge from one node in this SCC to another in this SCC.
|
||||
/// Make an existing internal ref edge into a call edge.
|
||||
///
|
||||
/// By the definition of an SCC, this does not change the nature or make-up
|
||||
/// of any SCCs.
|
||||
void insertIntraSCCEdge(Node &ParentN, Node &ChildN, Edge::Kind EK);
|
||||
/// This may form a larger cycle and thus collapse SCCs into TargetN's SCC.
|
||||
/// If that happens, the deleted SCC pointers are returned. These SCCs are
|
||||
/// not in a valid state any longer but the pointers will remain valid
|
||||
/// until destruction of the parent graph instance for the purpose of
|
||||
/// clearing cached information.
|
||||
///
|
||||
/// After this operation, both SourceN's SCC and TargetN's SCC may move
|
||||
/// position within this RefSCC's postorder list. Any SCCs merged are
|
||||
/// merged into the TargetN's SCC in order to preserve reachability analyses
|
||||
/// which took place on that SCC.
|
||||
SmallVector<SCC *, 1> switchInternalEdgeToCall(Node &SourceN,
|
||||
Node &TargetN);
|
||||
|
||||
/// Insert an edge whose tail is in this SCC and head is in some child SCC.
|
||||
/// Make an existing internal call edge into a ref edge.
|
||||
///
|
||||
/// There must be an existing path from the caller to the callee. This
|
||||
/// operation is inexpensive and does not change the set of SCCs in the
|
||||
/// graph.
|
||||
void insertOutgoingEdge(Node &ParentN, Node &ChildN, Edge::Kind EK);
|
||||
/// If SourceN and TargetN are part of a single SCC, it may be split up due
|
||||
/// to breaking a cycle in the call edges that formed it. If that happens,
|
||||
/// then this routine will insert new SCCs into the postorder list *before*
|
||||
/// the SCC of TargetN (previously the SCC of both). This preserves
|
||||
/// postorder as the TargetN can reach all of the other nodes by definition
|
||||
/// of previously being in a single SCC formed by the cycle from SourceN to
|
||||
/// TargetN. The newly added nodes are added *immediately* and contiguously
|
||||
/// prior to the TargetN SCC and so they may be iterated starting from
|
||||
/// there.
|
||||
void switchInternalEdgeToRef(Node &SourceN, Node &TargetN);
|
||||
|
||||
/// Insert an edge whose tail is in a descendant SCC and head is in this
|
||||
/// SCC.
|
||||
/// Make an existing outgoing ref edge into a call edge.
|
||||
///
|
||||
/// There must be an existing path from the callee to the caller in this
|
||||
/// case. NB! This is has the potential to be a very expensive function. It
|
||||
/// inherently forms a cycle in the prior SCC DAG and we have to merge SCCs
|
||||
/// to resolve that cycle. But finding all of the SCCs which participate in
|
||||
/// the cycle can in the worst case require traversing every SCC in the
|
||||
/// graph. Every attempt is made to avoid that, but passes must still
|
||||
/// exercise caution calling this routine repeatedly.
|
||||
/// Note that this is trivial as there are no cyclic impacts and there
|
||||
/// remains a reference edge.
|
||||
void switchOutgoingEdgeToCall(Node &SourceN, Node &TargetN);
|
||||
|
||||
/// Make an existing outgoing call edge into a ref edge.
|
||||
///
|
||||
/// This is trivial as there are no cyclic impacts and there remains
|
||||
/// a reference edge.
|
||||
void switchOutgoingEdgeToRef(Node &SourceN, Node &TargetN);
|
||||
|
||||
/// Insert a ref edge from one node in this RefSCC to another in this
|
||||
/// RefSCC.
|
||||
///
|
||||
/// This is always a trivial operation as it doesn't change any part of the
|
||||
/// graph structure besides connecting the two nodes.
|
||||
///
|
||||
/// Note that we don't support directly inserting internal *call* edges
|
||||
/// because that could change the graph structure and requires returning
|
||||
/// information about what became invalid. As a consequence, the pattern
|
||||
/// should be to first insert the necessary ref edge, and then to switch it
|
||||
/// to a call edge if needed and handle any invalidation that results. See
|
||||
/// the \c switchInternalEdgeToCall routine for details.
|
||||
void insertInternalRefEdge(Node &SourceN, Node &TargetN);
|
||||
|
||||
/// Insert an edge whose parent is in this RefSCC and child is in some
|
||||
/// child RefSCC.
|
||||
///
|
||||
/// There must be an existing path from the \p SourceN to the \p TargetN.
|
||||
/// This operation is inexpensive and does not change the set of SCCs and
|
||||
/// RefSCCs in the graph.
|
||||
void insertOutgoingEdge(Node &SourceN, Node &TargetN, Edge::Kind EK);
|
||||
|
||||
/// Insert an edge whose source is in a descendant RefSCC and target is in
|
||||
/// this RefSCC.
|
||||
///
|
||||
/// There must be an existing path from the target to the source in this
|
||||
/// case.
|
||||
///
|
||||
/// NB! This is has the potential to be a very expensive function. It
|
||||
/// inherently forms a cycle in the prior RefSCC DAG and we have to merge
|
||||
/// RefSCCs to resolve that cycle. But finding all of the RefSCCs which
|
||||
/// participate in the cycle can in the worst case require traversing every
|
||||
/// RefSCC in the graph. Every attempt is made to avoid that, but passes
|
||||
/// must still exercise caution calling this routine repeatedly.
|
||||
///
|
||||
/// Also note that this can only insert ref edges. In order to insert
|
||||
/// a call edge, first insert a ref edge and then switch it to a call edge.
|
||||
/// These are intentionally kept as separate interfaces because each step
|
||||
/// of the operation invalidates a different set of data structures.
|
||||
///
|
||||
/// This returns all the RefSCCs which were merged into the this RefSCC
|
||||
/// (the target's). This allows callers to invalidate any cached
|
||||
/// information.
|
||||
///
|
||||
/// FIXME: We could possibly optimize this quite a bit for cases where the
|
||||
/// caller and callee are very nearby in the graph. See comments in the
|
||||
/// implementation for details, but that use case might impact users.
|
||||
SmallVector<SCC *, 1> insertIncomingEdge(Node &ParentN, Node &ChildN,
|
||||
Edge::Kind EK);
|
||||
SmallVector<RefSCC *, 1> insertIncomingRefEdge(Node &SourceN,
|
||||
Node &TargetN);
|
||||
|
||||
/// Remove an edge whose source is in this SCC and target is *not*.
|
||||
/// Remove an edge whose source is in this RefSCC and target is *not*.
|
||||
///
|
||||
/// This removes an inter-SCC edge. All inter-SCC edges originating from
|
||||
/// this SCC have been fully explored by any in-flight DFS SCC formation,
|
||||
/// so this is always safe to call once you have the source SCC.
|
||||
/// This removes an inter-RefSCC edge. All inter-RefSCC edges originating
|
||||
/// from this SCC have been fully explored by any in-flight DFS graph
|
||||
/// formation, so this is always safe to call once you have the source
|
||||
/// RefSCC.
|
||||
///
|
||||
/// This operation does not change the set of SCCs or the members of the
|
||||
/// SCCs and so is very inexpensive. It may change the connectivity graph
|
||||
/// of the SCCs though, so be careful calling this while iterating over
|
||||
/// them.
|
||||
void removeInterSCCEdge(Node &ParentN, Node &ChildN);
|
||||
/// This operation does not change the cyclic structure of the graph and so
|
||||
/// is very inexpensive. It may change the connectivity graph of the SCCs
|
||||
/// though, so be careful calling this while iterating over them.
|
||||
void removeOutgoingEdge(Node &SourceN, Node &TargetN);
|
||||
|
||||
/// Remove an edge which is entirely within this SCC.
|
||||
/// Remove a ref edge which is entirely within this RefSCC.
|
||||
///
|
||||
/// Both the \a ParentN and the \a ChildN must be within this SCC. Removing
|
||||
/// such an edge make break cycles that form this SCC and thus this
|
||||
/// operation may change the SCC graph significantly. In particular, this
|
||||
/// operation will re-form new SCCs based on the remaining connectivity of
|
||||
/// the graph. The following invariants are guaranteed to hold after
|
||||
/// calling this method:
|
||||
/// 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
|
||||
/// 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 SCC is still an SCC in the graph.
|
||||
/// 2) This SCC will be the parent of any new SCCs. Thus, this SCC is
|
||||
/// preserved as the root of any new SCC directed graph formed.
|
||||
/// 3) No SCC other than this SCC has its member set changed (this is
|
||||
/// 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
|
||||
/// inherent in the definition of removing such an edge).
|
||||
/// 4) All of the parent links of the SCC graph will be updated to reflect
|
||||
/// the new SCC structure.
|
||||
/// 5) All SCCs formed out of this SCC, excluding this SCC, will be
|
||||
/// returned in a vector.
|
||||
/// 6) The order of the SCCs in the vector will be a valid postorder
|
||||
/// traversal of the new SCCs.
|
||||
/// 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 pipeliens on top of the CGSCC pass manager which
|
||||
/// intelligently update the SCC graph without invalidating other parts of
|
||||
/// the SCC graph.
|
||||
/// optimization pipelines on top of the CGSCC pass manager which
|
||||
/// intelligently update the RefSCC graph without invalidating other parts
|
||||
/// of the RefSCC graph.
|
||||
///
|
||||
/// Note that we provide no routine to remove a *call* edge. Instead, you
|
||||
/// must first switch it to a ref edge using \c switchInternalEdgeToRef.
|
||||
/// This split API is intentional as each of these two steps can invalidate
|
||||
/// a different aspect of the graph structure and needs to have the
|
||||
/// invalidation handled independently.
|
||||
///
|
||||
/// The runtime complexity of this method is, in the worst case, O(V+E)
|
||||
/// where V is the number of nodes in this SCC and E is the number of edges
|
||||
/// leaving the nodes in this SCC. Note that E includes both edges within
|
||||
/// this SCC and edges from this SCC to child SCCs. 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.
|
||||
SmallVector<SCC *, 1> removeIntraSCCEdge(Node &ParentN, Node &ChildN);
|
||||
/// where V is the number of nodes in this RefSCC and E is the number of
|
||||
/// edges leaving the nodes in this RefSCC. Note that E includes both edges
|
||||
/// 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.
|
||||
SmallVector<RefSCC *, 1> removeInternalRefEdge(Node &SourceN,
|
||||
Node &TargetN);
|
||||
|
||||
///@}
|
||||
};
|
||||
@ -450,9 +633,9 @@ public:
|
||||
/// 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 iterator_facade_base<postorder_scc_iterator,
|
||||
std::forward_iterator_tag, SCC> {
|
||||
class postorder_ref_scc_iterator
|
||||
: public iterator_facade_base<postorder_ref_scc_iterator,
|
||||
std::forward_iterator_tag, RefSCC> {
|
||||
friend class LazyCallGraph;
|
||||
friend class LazyCallGraph::Node;
|
||||
|
||||
@ -460,27 +643,27 @@ public:
|
||||
struct IsAtEndT {};
|
||||
|
||||
LazyCallGraph *G;
|
||||
SCC *C;
|
||||
RefSCC *C;
|
||||
|
||||
// Build the begin iterator for a node.
|
||||
postorder_scc_iterator(LazyCallGraph &G) : G(&G) {
|
||||
C = G.getNextSCCInPostOrder();
|
||||
postorder_ref_scc_iterator(LazyCallGraph &G) : G(&G) {
|
||||
C = G.getNextRefSCCInPostOrder();
|
||||
}
|
||||
|
||||
// Build the end iterator for a node. This is selected purely by overload.
|
||||
postorder_scc_iterator(LazyCallGraph &G, IsAtEndT /*Nonce*/)
|
||||
postorder_ref_scc_iterator(LazyCallGraph &G, IsAtEndT /*Nonce*/)
|
||||
: G(&G), C(nullptr) {}
|
||||
|
||||
public:
|
||||
bool operator==(const postorder_scc_iterator &Arg) const {
|
||||
bool operator==(const postorder_ref_scc_iterator &Arg) const {
|
||||
return G == Arg.G && C == Arg.C;
|
||||
}
|
||||
|
||||
reference operator*() const { return *C; }
|
||||
|
||||
using iterator_facade_base::operator++;
|
||||
postorder_scc_iterator &operator++() {
|
||||
C = G->getNextSCCInPostOrder();
|
||||
postorder_ref_scc_iterator &operator++() {
|
||||
C = G->getNextRefSCCInPostOrder();
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
@ -502,15 +685,16 @@ public:
|
||||
return edge_iterator(EntryEdges.end(), EntryEdges.end());
|
||||
}
|
||||
|
||||
postorder_scc_iterator postorder_scc_begin() {
|
||||
return postorder_scc_iterator(*this);
|
||||
postorder_ref_scc_iterator postorder_ref_scc_begin() {
|
||||
return postorder_ref_scc_iterator(*this);
|
||||
}
|
||||
postorder_scc_iterator postorder_scc_end() {
|
||||
return postorder_scc_iterator(*this, postorder_scc_iterator::IsAtEndT());
|
||||
postorder_ref_scc_iterator postorder_ref_scc_end() {
|
||||
return postorder_ref_scc_iterator(*this,
|
||||
postorder_ref_scc_iterator::IsAtEndT());
|
||||
}
|
||||
|
||||
iterator_range<postorder_scc_iterator> postorder_sccs() {
|
||||
return make_range(postorder_scc_begin(), postorder_scc_end());
|
||||
iterator_range<postorder_ref_scc_iterator> postorder_ref_sccs() {
|
||||
return make_range(postorder_ref_scc_begin(), postorder_ref_scc_end());
|
||||
}
|
||||
|
||||
/// Lookup a function in the graph which has already been scanned and added.
|
||||
@ -522,6 +706,17 @@ public:
|
||||
/// iterator walk.
|
||||
SCC *lookupSCC(Node &N) const { return SCCMap.lookup(&N); }
|
||||
|
||||
/// Lookup a function's RefSCC in the graph.
|
||||
///
|
||||
/// \returns null if the function hasn't been assigned a RefSCC via the
|
||||
/// RefSCC iterator walk.
|
||||
RefSCC *lookupRefSCC(Node &N) const {
|
||||
if (SCC *C = lookupSCC(N))
|
||||
return &C->getOuterRefSCC();
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/// Get a graph node for a given function, scanning it to populate the graph
|
||||
/// data as necessary.
|
||||
Node &get(Function &F) {
|
||||
@ -561,6 +756,9 @@ public:
|
||||
///@}
|
||||
|
||||
private:
|
||||
typedef SmallVectorImpl<Node *>::reverse_iterator node_stack_iterator;
|
||||
typedef iterator_range<node_stack_iterator> node_stack_range;
|
||||
|
||||
/// Allocator that holds all the call graph nodes.
|
||||
SpecificBumpPtrAllocator<Node> BPA;
|
||||
|
||||
@ -574,7 +772,7 @@ private:
|
||||
EdgeVectorT EntryEdges;
|
||||
|
||||
/// Map of the entry nodes in the graph to their indices in \c EntryEdges.
|
||||
DenseMap<Function *, size_t> EntryIndexMap;
|
||||
DenseMap<Function *, int> EntryIndexMap;
|
||||
|
||||
/// Allocator that holds all the call graph SCCs.
|
||||
SpecificBumpPtrAllocator<SCC> SCCBPA;
|
||||
@ -582,19 +780,22 @@ private:
|
||||
/// Maps Function -> SCC for fast lookup.
|
||||
DenseMap<Node *, SCC *> SCCMap;
|
||||
|
||||
/// The leaf SCCs of the graph.
|
||||
/// Allocator that holds all the call graph RefSCCs.
|
||||
SpecificBumpPtrAllocator<RefSCC> RefSCCBPA;
|
||||
|
||||
/// The leaf RefSCCs of the graph.
|
||||
///
|
||||
/// These are all of the SCCs which have no children.
|
||||
SmallVector<SCC *, 4> LeafSCCs;
|
||||
/// These are all of the RefSCCs which have no children.
|
||||
SmallVector<RefSCC *, 4> LeafRefSCCs;
|
||||
|
||||
/// Stack of nodes in the DFS walk.
|
||||
SmallVector<std::pair<Node *, edge_iterator>, 4> DFSStack;
|
||||
|
||||
/// Set of entry nodes not-yet-processed into SCCs.
|
||||
SmallVector<Function *, 4> SCCEntryNodes;
|
||||
/// Set of entry nodes not-yet-processed into RefSCCs.
|
||||
SmallVector<Function *, 4> RefSCCEntryNodes;
|
||||
|
||||
/// Stack of nodes the DFS has walked but not yet put into a SCC.
|
||||
SmallVector<Node *, 4> PendingSCCStack;
|
||||
SmallVector<Node *, 4> PendingRefSCCStack;
|
||||
|
||||
/// Counter for the next DFS number to assign.
|
||||
int NextDFSNumber;
|
||||
@ -606,12 +807,31 @@ private:
|
||||
/// Helper to update pointers back to the graph object during moves.
|
||||
void updateGraphPtrs();
|
||||
|
||||
/// Helper to form a new SCC out of the top of a DFSStack-like
|
||||
/// structure.
|
||||
SCC *formSCC(Node *RootN, SmallVectorImpl<Node *> &NodeStack);
|
||||
/// Allocates an SCC and constructs it using the graph allocator.
|
||||
///
|
||||
/// The arguments are forwarded to the constructor.
|
||||
template <typename... Ts> SCC *createSCC(Ts &&... Args) {
|
||||
return new (SCCBPA.Allocate()) SCC(std::forward<Ts>(Args)...);
|
||||
}
|
||||
|
||||
/// Retrieve the next node in the post-order SCC walk of the call graph.
|
||||
SCC *getNextSCCInPostOrder();
|
||||
/// Allocates a RefSCC and constructs it using the graph allocator.
|
||||
///
|
||||
/// The arguments are forwarded to the constructor.
|
||||
template <typename... Ts> RefSCC *createRefSCC(Ts &&... Args) {
|
||||
return new (RefSCCBPA.Allocate()) RefSCC(std::forward<Ts>(Args)...);
|
||||
}
|
||||
|
||||
/// Build the SCCs for a RefSCC out of a list of nodes.
|
||||
void buildSCCs(RefSCC &RC, node_stack_range Nodes);
|
||||
|
||||
/// Connect a RefSCC into the larger graph.
|
||||
///
|
||||
/// This walks the edges to connect the RefSCC to its children's parent set,
|
||||
/// and updates the root leaf list.
|
||||
void connectRefSCC(RefSCC &RC);
|
||||
|
||||
/// Retrieve the next node in the post-order RefSCC walk of the call graph.
|
||||
RefSCC *getNextRefSCCInPostOrder();
|
||||
};
|
||||
|
||||
inline LazyCallGraph::Edge::Edge() : Value() {}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -125,52 +125,219 @@ define void @test2() {
|
||||
ret void
|
||||
}
|
||||
|
||||
@test3_ptr = external global void ()*
|
||||
|
||||
define void @test3_aa1() {
|
||||
; CHECK-LABEL: Edges in function: test3_aa1
|
||||
; CHECK-NEXT: call -> test3_aa2
|
||||
; CHECK-NEXT: ref -> test3_ab1
|
||||
; CHECK-NOT: ->
|
||||
|
||||
entry:
|
||||
call void @test3_aa2()
|
||||
store void ()* @test3_ab1, void ()** @test3_ptr
|
||||
ret void
|
||||
}
|
||||
|
||||
define void @test3_aa2() {
|
||||
; CHECK-LABEL: Edges in function: test3_aa2
|
||||
; CHECK-NEXT: call -> test3_aa1
|
||||
; CHECK-NEXT: call -> test3_ab2
|
||||
; CHECK-NOT: ->
|
||||
|
||||
entry:
|
||||
call void @test3_aa1()
|
||||
call void @test3_ab2()
|
||||
ret void
|
||||
}
|
||||
|
||||
define void @test3_ab1() {
|
||||
; CHECK-LABEL: Edges in function: test3_ab1
|
||||
; CHECK-NEXT: call -> test3_ab2
|
||||
; CHECK-NEXT: call -> test3_ac1
|
||||
; CHECK-NOT: ->
|
||||
|
||||
entry:
|
||||
call void @test3_ab2()
|
||||
call void @test3_ac1()
|
||||
ret void
|
||||
}
|
||||
|
||||
define void @test3_ab2() {
|
||||
; CHECK-LABEL: Edges in function: test3_ab2
|
||||
; CHECK-NEXT: call -> test3_ab1
|
||||
; CHECK-NEXT: call -> test3_ba1
|
||||
; CHECK-NOT: ->
|
||||
|
||||
entry:
|
||||
call void @test3_ab1()
|
||||
call void @test3_ba1()
|
||||
ret void
|
||||
}
|
||||
|
||||
define void @test3_ac1() {
|
||||
; CHECK-LABEL: Edges in function: test3_ac1
|
||||
; CHECK-NEXT: call -> test3_ac2
|
||||
; CHECK-NEXT: ref -> test3_aa2
|
||||
; CHECK-NOT: ->
|
||||
|
||||
entry:
|
||||
call void @test3_ac2()
|
||||
store void ()* @test3_aa2, void ()** @test3_ptr
|
||||
ret void
|
||||
}
|
||||
|
||||
define void @test3_ac2() {
|
||||
; CHECK-LABEL: Edges in function: test3_ac2
|
||||
; CHECK-NEXT: call -> test3_ac1
|
||||
; CHECK-NEXT: ref -> test3_ba1
|
||||
; CHECK-NOT: ->
|
||||
|
||||
entry:
|
||||
call void @test3_ac1()
|
||||
store void ()* @test3_ba1, void ()** @test3_ptr
|
||||
ret void
|
||||
}
|
||||
|
||||
define void @test3_ba1() {
|
||||
; CHECK-LABEL: Edges in function: test3_ba1
|
||||
; CHECK-NEXT: call -> test3_bb1
|
||||
; CHECK-NEXT: ref -> test3_ca1
|
||||
; CHECK-NOT: ->
|
||||
|
||||
entry:
|
||||
call void @test3_bb1()
|
||||
store void ()* @test3_ca1, void ()** @test3_ptr
|
||||
ret void
|
||||
}
|
||||
|
||||
define void @test3_bb1() {
|
||||
; CHECK-LABEL: Edges in function: test3_bb1
|
||||
; CHECK-NEXT: call -> test3_ca2
|
||||
; CHECK-NEXT: ref -> test3_ba1
|
||||
; CHECK-NOT: ->
|
||||
|
||||
entry:
|
||||
call void @test3_ca2()
|
||||
store void ()* @test3_ba1, void ()** @test3_ptr
|
||||
ret void
|
||||
}
|
||||
|
||||
define void @test3_ca1() {
|
||||
; CHECK-LABEL: Edges in function: test3_ca1
|
||||
; CHECK-NEXT: call -> test3_ca2
|
||||
; CHECK-NOT: ->
|
||||
|
||||
entry:
|
||||
call void @test3_ca2()
|
||||
ret void
|
||||
}
|
||||
|
||||
define void @test3_ca2() {
|
||||
; CHECK-LABEL: Edges in function: test3_ca2
|
||||
; CHECK-NEXT: call -> test3_ca3
|
||||
; CHECK-NOT: ->
|
||||
|
||||
entry:
|
||||
call void @test3_ca3()
|
||||
ret void
|
||||
}
|
||||
|
||||
define void @test3_ca3() {
|
||||
; CHECK-LABEL: Edges in function: test3_ca3
|
||||
; CHECK-NEXT: call -> test3_ca1
|
||||
; CHECK-NOT: ->
|
||||
|
||||
entry:
|
||||
call void @test3_ca1()
|
||||
ret void
|
||||
}
|
||||
|
||||
; Verify the SCCs formed.
|
||||
;
|
||||
; CHECK-LABEL: SCC with 1 functions:
|
||||
; CHECK-NEXT: f7
|
||||
; CHECK-LABEL: RefSCC with 1 call SCCs:
|
||||
; CHECK-NEXT: SCC with 3 functions:
|
||||
; CHECK-NEXT: test3_ca3
|
||||
; CHECK-NEXT: test3_ca1
|
||||
; CHECK-NEXT: test3_ca2
|
||||
;
|
||||
; CHECK-LABEL: SCC with 1 functions:
|
||||
; CHECK-NEXT: f6
|
||||
; CHECK-LABEL: RefSCC with 2 call SCCs:
|
||||
; CHECK-NEXT: SCC with 1 functions:
|
||||
; CHECK-NEXT: test3_bb1
|
||||
; CHECK-NEXT: SCC with 1 functions:
|
||||
; CHECK-NEXT: test3_ba1
|
||||
;
|
||||
; CHECK-LABEL: SCC with 1 functions:
|
||||
; CHECK-NEXT: f5
|
||||
; CHECK-LABEL: RefSCC with 3 call SCCs:
|
||||
; CHECK-NEXT: SCC with 2 functions:
|
||||
; CHECK-NEXT: test3_ac2
|
||||
; CHECK-NEXT: test3_ac1
|
||||
; CHECK-NEXT: SCC with 2 functions:
|
||||
; CHECK-NEXT: test3_ab2
|
||||
; CHECK-NEXT: test3_ab1
|
||||
; CHECK-NEXT: SCC with 2 functions:
|
||||
; CHECK-NEXT: test3_aa2
|
||||
; CHECK-NEXT: test3_aa1
|
||||
;
|
||||
; CHECK-LABEL: SCC with 1 functions:
|
||||
; CHECK-NEXT: f4
|
||||
; CHECK-LABEL: RefSCC with 1 call SCCs:
|
||||
; CHECK-NEXT: SCC with 1 functions:
|
||||
; CHECK-NEXT: f7
|
||||
;
|
||||
; CHECK-LABEL: SCC with 1 functions:
|
||||
; CHECK-NEXT: f3
|
||||
; CHECK-LABEL: RefSCC with 1 call SCCs:
|
||||
; CHECK-NEXT: SCC with 1 functions:
|
||||
; CHECK-NEXT: f6
|
||||
;
|
||||
; CHECK-LABEL: SCC with 1 functions:
|
||||
; CHECK-NEXT: f2
|
||||
; CHECK-LABEL: RefSCC with 1 call SCCs:
|
||||
; CHECK-NEXT: SCC with 1 functions:
|
||||
; CHECK-NEXT: f5
|
||||
;
|
||||
; CHECK-LABEL: SCC with 1 functions:
|
||||
; CHECK-NEXT: f1
|
||||
; CHECK-LABEL: RefSCC with 1 call SCCs:
|
||||
; CHECK-NEXT: SCC with 1 functions:
|
||||
; CHECK-NEXT: f4
|
||||
;
|
||||
; CHECK-LABEL: SCC with 1 functions:
|
||||
; CHECK-NEXT: test2
|
||||
; CHECK-LABEL: RefSCC with 1 call SCCs:
|
||||
; CHECK-NEXT: SCC with 1 functions:
|
||||
; CHECK-NEXT: f3
|
||||
;
|
||||
; CHECK-LABEL: SCC with 1 functions:
|
||||
; CHECK-NEXT: f10
|
||||
; CHECK-LABEL: RefSCC with 1 call SCCs:
|
||||
; CHECK-NEXT: SCC with 1 functions:
|
||||
; CHECK-NEXT: f2
|
||||
;
|
||||
; CHECK-LABEL: SCC with 1 functions:
|
||||
; CHECK-NEXT: f12
|
||||
; CHECK-LABEL: RefSCC with 1 call SCCs:
|
||||
; CHECK-NEXT: SCC with 1 functions:
|
||||
; CHECK-NEXT: f1
|
||||
;
|
||||
; CHECK-LABEL: SCC with 1 functions:
|
||||
; CHECK-NEXT: f11
|
||||
; CHECK-LABEL: RefSCC with 1 call SCCs:
|
||||
; CHECK-NEXT: SCC with 1 functions:
|
||||
; CHECK-NEXT: test2
|
||||
;
|
||||
; CHECK-LABEL: SCC with 1 functions:
|
||||
; CHECK-NEXT: f9
|
||||
; CHECK-LABEL: RefSCC with 1 call SCCs:
|
||||
; CHECK-NEXT: SCC with 1 functions:
|
||||
; CHECK-NEXT: f10
|
||||
;
|
||||
; CHECK-LABEL: SCC with 1 functions:
|
||||
; CHECK-NEXT: f8
|
||||
; CHECK-LABEL: RefSCC with 1 call SCCs:
|
||||
; CHECK-NEXT: SCC with 1 functions:
|
||||
; CHECK-NEXT: f12
|
||||
;
|
||||
; CHECK-LABEL: SCC with 1 functions:
|
||||
; CHECK-NEXT: test1
|
||||
; CHECK-LABEL: RefSCC with 1 call SCCs:
|
||||
; CHECK-NEXT: SCC with 1 functions:
|
||||
; CHECK-NEXT: f11
|
||||
;
|
||||
; CHECK-LABEL: SCC with 1 functions:
|
||||
; CHECK-NEXT: f
|
||||
; CHECK-LABEL: RefSCC with 1 call SCCs:
|
||||
; CHECK-NEXT: SCC with 1 functions:
|
||||
; CHECK-NEXT: f9
|
||||
;
|
||||
; CHECK-LABEL: SCC with 1 functions:
|
||||
; CHECK-NEXT: test0
|
||||
; CHECK-LABEL: RefSCC with 1 call SCCs:
|
||||
; CHECK-NEXT: SCC with 1 functions:
|
||||
; CHECK-NEXT: f8
|
||||
;
|
||||
; CHECK-LABEL: RefSCC with 1 call SCCs:
|
||||
; CHECK-NEXT: SCC with 1 functions:
|
||||
; CHECK-NEXT: test1
|
||||
;
|
||||
; CHECK-LABEL: RefSCC with 1 call SCCs:
|
||||
; CHECK-NEXT: SCC with 1 functions:
|
||||
; CHECK-NEXT: f
|
||||
;
|
||||
; CHECK-LABEL: RefSCC with 1 call SCCs:
|
||||
; CHECK-NEXT: SCC with 1 functions:
|
||||
; CHECK-NEXT: test0
|
||||
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user