mirror of
https://github.com/apple/swift.git
synced 2025-12-25 12:15:36 +01:00
We don't want passes directly querying the CallGraph because managing invalidation will be so difficult. This is still a workaround for the proper fix to AST/SIL type substitution: <rdar://problem/21025088> SIL should properly represent binding dynamic self to generic parameters.
1176 lines
37 KiB
C++
1176 lines
37 KiB
C++
//===------ CallGraph.cpp - The Call Graph Data Structure ----*- C++ -*----===//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2014 - 2015 Apple Inc. and the Swift project authors
|
|
// Licensed under Apache License v2.0 with Runtime Library Exception
|
|
//
|
|
// See http://swift.org/LICENSE.txt for license information
|
|
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "swift/SILAnalysis/CallGraph.h"
|
|
#include "swift/SILPasses/Utils/Local.h"
|
|
#include "swift/Basic/Fallthrough.h"
|
|
#include "swift/Basic/DemangleWrappers.h"
|
|
#include "swift/SIL/SILFunction.h"
|
|
#include "swift/SIL/SILInstruction.h"
|
|
#include "swift/SIL/SILModule.h"
|
|
#include "swift/SIL/SILWitnessTable.h"
|
|
#include "llvm/ADT/DenseMap.h"
|
|
#include "llvm/ADT/SetVector.h"
|
|
#include "llvm/ADT/SmallVector.h"
|
|
#include "llvm/ADT/Statistic.h"
|
|
#include "llvm/Support/CommandLine.h"
|
|
#include "llvm/Support/GraphWriter.h"
|
|
#include "llvm/Support/raw_ostream.h"
|
|
#include <algorithm>
|
|
#include <utility>
|
|
|
|
using namespace swift;
|
|
|
|
#define DEBUG_TYPE "call-graph"
|
|
|
|
STATISTIC(NumCallGraphNodes, "# of call graph nodes created");
|
|
STATISTIC(NumAppliesWithEdges, "# of call sites with edges");
|
|
STATISTIC(NumCallGraphsBuilt, "# of times the call graph is built");
|
|
|
|
llvm::cl::opt<bool> DumpCallGraph("sil-dump-call-graph",
|
|
llvm::cl::init(false), llvm::cl::Hidden);
|
|
|
|
llvm::cl::opt<bool> DumpCallGraphStats("sil-dump-call-graph-stats",
|
|
llvm::cl::init(false), llvm::cl::Hidden);
|
|
|
|
CallGraph::CallGraph(SILModule *Mod, bool completeModule)
|
|
: M(*Mod), NodeOrdinal(0), EdgeOrdinal(0) {
|
|
++NumCallGraphsBuilt;
|
|
|
|
// Create a call graph node for each function in the module and add
|
|
// each to a worklist of functions to process.
|
|
std::vector<SILFunction *> Workitems;
|
|
for (auto &F : M) {
|
|
addCallGraphNode(&F);
|
|
|
|
if (F.isDefinition())
|
|
Workitems.push_back(&F);
|
|
}
|
|
|
|
// Now compute the sets of call graph nodes any given class method
|
|
// decl could target.
|
|
computeMethodCallees();
|
|
|
|
// Add edges for each function in the worklist. We capture the
|
|
// initial functions in the module up-front into this worklist
|
|
// because the process of adding edges can deseralize new functions,
|
|
// at which point we process those functions (adding edges), and it
|
|
// would be an error to process those functions again when we come
|
|
// across them in the module.
|
|
for (auto I = Workitems.begin(), E = Workitems.end(); I != E; ++I)
|
|
addEdges(*I);
|
|
|
|
if (DumpCallGraph)
|
|
dump();
|
|
|
|
if (DumpCallGraphStats)
|
|
dumpStats();
|
|
}
|
|
|
|
CallGraph::~CallGraph() {
|
|
// Clean up all call graph nodes.
|
|
for (auto &P : FunctionToNodeMap) {
|
|
P.second->~CallGraphNode();
|
|
}
|
|
|
|
// Clean up all call graph edges.
|
|
for (auto &P : InstToEdgeMap) {
|
|
P.second->~CallGraphEdge();
|
|
}
|
|
|
|
// Clean up all SCCs.
|
|
for (CallGraphSCC *SCC : BottomUpSCCOrder) {
|
|
SCC->~CallGraphSCC();
|
|
}
|
|
}
|
|
|
|
/// Update the callee set for each method of a given class, along with
|
|
/// all the overridden methods from superclasses.
|
|
void CallGraph::computeClassMethodCalleesForClass(ClassDecl *CD) {
|
|
for (auto *Member : CD->getMembers()) {
|
|
auto *AFD = dyn_cast<AbstractFunctionDecl>(Member);
|
|
if (!AFD)
|
|
continue;
|
|
|
|
auto Method = SILDeclRef(AFD);
|
|
auto *CalledFn = M.lookUpFunctionInVTable(CD, Method);
|
|
if (!CalledFn)
|
|
continue;
|
|
|
|
// FIXME: Link in external declarations, at least for complete call sets.
|
|
|
|
auto *Node = getOrAddCallGraphNode(CalledFn);
|
|
|
|
bool canCallUnknown = !calleesAreStaticallyKnowable(M, Method);
|
|
|
|
// Update the callee sets for this method and all the methods it
|
|
// overrides by inserting the call graph node for the function
|
|
// that this method invokes.
|
|
do {
|
|
auto &TheCalleeSet = getOrCreateCalleeSetForMethod(Method);
|
|
assert(TheCalleeSet.getPointer() && "Unexpected null callee set!");
|
|
|
|
TheCalleeSet.getPointer()->insert(Node);
|
|
if (canCallUnknown)
|
|
TheCalleeSet.setInt(true);
|
|
|
|
Method = Method.getOverriddenVTableEntry();
|
|
} while (Method);
|
|
}
|
|
}
|
|
|
|
void
|
|
CallGraph::computeWitnessMethodCalleesForWitnessTable(SILWitnessTable &WTable) {
|
|
for (const SILWitnessTable::Entry &Entry : WTable.getEntries()) {
|
|
if (Entry.getKind() != SILWitnessTable::Method)
|
|
continue;
|
|
|
|
auto &WitnessEntry = Entry.getMethodWitness();
|
|
auto Requirement = WitnessEntry.Requirement;
|
|
auto *WitnessFn = WitnessEntry.Witness;
|
|
// Dead function elimination nulls out entries for functions it removes.
|
|
if (!WitnessFn)
|
|
continue;
|
|
|
|
auto *Node = getOrAddCallGraphNode(WitnessFn);
|
|
|
|
auto &TheCalleeSet = getOrCreateCalleeSetForMethod(Requirement);
|
|
assert(TheCalleeSet.getPointer() && "Unexpected null callee set!");
|
|
|
|
TheCalleeSet.getPointer()->insert(Node);
|
|
|
|
// FIXME: For now, conservatively assume that unknown functions
|
|
// can be called from any witness_method call site.
|
|
TheCalleeSet.setInt(true);
|
|
}
|
|
}
|
|
|
|
/// Remove a function from all the callee sets. This should be called
|
|
/// on any function that we'll eventually remove.
|
|
// FIXME: Consider adding a reverse mapping from call graph node to
|
|
// the sets it appears in.
|
|
void CallGraph::removeFunctionFromCalleeSets(SILFunction *F) {
|
|
auto *Node = getCallGraphNode(F);
|
|
|
|
for (auto I = CalleeSetCache.begin(), E = CalleeSetCache.end();
|
|
I != E; ++I) {
|
|
auto *Callees = I->second.getPointer();
|
|
|
|
Callees->remove(Node);
|
|
}
|
|
}
|
|
|
|
/// Compute the callees for each method that appears in a VTable or
|
|
/// Witness Table.
|
|
void CallGraph::computeMethodCallees() {
|
|
// Remove contents of old callee sets - in case we are updating the sets.
|
|
for (auto Iter : CalleeSetCache) {
|
|
auto *TheCalleeSet = Iter.second.getPointer();
|
|
Iter.second.setInt(false);
|
|
TheCalleeSet->clear();
|
|
}
|
|
|
|
for (auto &VTable : M.getVTableList())
|
|
computeClassMethodCalleesForClass(VTable.getClass());
|
|
|
|
for (auto &WTable : M.getWitnessTableList())
|
|
computeWitnessMethodCalleesForWitnessTable(WTable);
|
|
}
|
|
|
|
CallGraphNode *CallGraph::addCallGraphNode(SILFunction *F) {
|
|
// TODO: Compute this from the call graph itself after stripping
|
|
// unreachable nodes from graph.
|
|
++NumCallGraphNodes;
|
|
|
|
auto *Node = new (Allocator) CallGraphNode(F, ++NodeOrdinal);
|
|
|
|
assert(!FunctionToNodeMap.count(F) &&
|
|
"Added function already has a call graph node!");
|
|
|
|
FunctionToNodeMap[F] = Node;
|
|
|
|
return Node;
|
|
}
|
|
|
|
CallGraphEdge::CalleeSet &
|
|
CallGraph::getOrCreateCalleeSetForMethod(SILDeclRef Decl) {
|
|
auto *AFD = cast<AbstractFunctionDecl>(Decl.getDecl());
|
|
auto Found = CalleeSetCache.find(AFD);
|
|
if (Found != CalleeSetCache.end())
|
|
return Found->second;
|
|
|
|
auto *NodeSet = new (Allocator) CallGraphEdge::CallGraphNodeSet;
|
|
|
|
bool canCallUnknown = !calleesAreStaticallyKnowable(M, Decl);
|
|
|
|
// Allocate a new callee set, and for now just assume it can call
|
|
// unknown functions.
|
|
CallGraphEdge::CalleeSet TheCalleeSet(NodeSet, canCallUnknown);
|
|
|
|
bool Inserted;
|
|
CalleeSetMap::iterator It;
|
|
std::tie(It, Inserted) =
|
|
CalleeSetCache.insert(std::make_pair(AFD, TheCalleeSet));
|
|
assert(Inserted && "Expected new entry to be inserted!");
|
|
|
|
return It->second;
|
|
}
|
|
|
|
SILFunction *
|
|
CallGraphEdge::Callees::getFunctionFromNode(CallGraphNode *const &Node) {
|
|
return Node->getFunction();
|
|
}
|
|
|
|
CallGraphEdge *CallGraph::makeCallGraphEdgeForCallee(FullApplySite Apply,
|
|
SILValue Callee) {
|
|
switch (Callee->getKind()) {
|
|
case ValueKind::ThinToThickFunctionInst:
|
|
Callee = cast<ThinToThickFunctionInst>(Callee)->getOperand();
|
|
SWIFT_FALLTHROUGH;
|
|
case ValueKind::FunctionRefInst: {
|
|
auto *CalleeFn = cast<FunctionRefInst>(Callee)->getReferencedFunction();
|
|
if (CalleeFn->isExternalDeclaration())
|
|
M.linkFunction(CalleeFn, SILModule::LinkingMode::LinkAll,
|
|
CallGraphLinkerEditor(this).getCallback());
|
|
|
|
auto *CalleeNode = getOrAddCallGraphNode(CalleeFn);
|
|
return new (Allocator) CallGraphEdge(Apply.getInstruction(), CalleeNode,
|
|
EdgeOrdinal++);
|
|
}
|
|
|
|
case ValueKind::PartialApplyInst: {
|
|
Callee = cast<PartialApplyInst>(Callee)->getCallee();
|
|
return makeCallGraphEdgeForCallee(Apply, Callee);
|
|
}
|
|
|
|
case ValueKind::DynamicMethodInst:
|
|
// TODO: Decide how to handle these in graph construction and
|
|
// analysis passes. We might just leave them out of the
|
|
// graph.
|
|
return new (Allocator) CallGraphEdge(Apply.getInstruction(), EdgeOrdinal++);
|
|
|
|
case ValueKind::SILArgument:
|
|
// First-pass call-graph construction will not do anything with
|
|
// these, but a second pass can potentially statically determine
|
|
// the called function in some cases.
|
|
return new (Allocator) CallGraphEdge(Apply.getInstruction(), EdgeOrdinal++);
|
|
|
|
case ValueKind::ApplyInst:
|
|
case ValueKind::TryApplyInst:
|
|
// TODO: Probably not worth iterating invocation- then
|
|
// reverse-invocation order to catch this.
|
|
return new (Allocator) CallGraphEdge(Apply.getInstruction(), EdgeOrdinal++);
|
|
|
|
case ValueKind::TupleExtractInst:
|
|
// TODO: It would be good to tunnel through extracts so that we
|
|
// can build a more accurate call graph prior to any
|
|
// optimizations.
|
|
return new (Allocator) CallGraphEdge(Apply.getInstruction(), EdgeOrdinal++);
|
|
|
|
case ValueKind::StructExtractInst:
|
|
// TODO: It would be good to tunnel through extracts so that we
|
|
// can build a more accurate call graph prior to any
|
|
// optimizations.
|
|
return new (Allocator) CallGraphEdge(Apply.getInstruction(), EdgeOrdinal++);
|
|
|
|
case ValueKind::WitnessMethodInst: {
|
|
auto *WMI = cast<WitnessMethodInst>(Callee);
|
|
SILFunction *CalleeFn;
|
|
ArrayRef<Substitution> Subs;
|
|
SILWitnessTable *WT;
|
|
|
|
// Attempt to find a specific callee for the given conformance and member.
|
|
std::tie(CalleeFn, WT, Subs) =
|
|
WMI->getModule().lookUpFunctionInWitnessTable(WMI->getConformance(),
|
|
WMI->getMember());
|
|
|
|
if (CalleeFn) {
|
|
if (CalleeFn->isExternalDeclaration())
|
|
M.linkFunction(CalleeFn, SILModule::LinkingMode::LinkAll,
|
|
CallGraphLinkerEditor(this).getCallback());
|
|
|
|
auto *CalleeNode = getOrAddCallGraphNode(CalleeFn);
|
|
return new (Allocator) CallGraphEdge(Apply.getInstruction(), CalleeNode,
|
|
EdgeOrdinal++);
|
|
}
|
|
|
|
// Lookup the previously computed callee set if we didn't find a
|
|
// specific callee.
|
|
auto *AFD = cast<AbstractFunctionDecl>(WMI->getMember().getDecl());
|
|
|
|
auto Found = CalleeSetCache.find(AFD);
|
|
if (Found != CalleeSetCache.end())
|
|
return new (Allocator) CallGraphEdge(Apply.getInstruction(),
|
|
Found->second, EdgeOrdinal++);
|
|
|
|
// Otherwise default to an edge with unknown callees.
|
|
return new (Allocator) CallGraphEdge(Apply.getInstruction(), EdgeOrdinal++);
|
|
}
|
|
|
|
case ValueKind::ClassMethodInst: {
|
|
auto *CMI = cast<ClassMethodInst>(Callee);
|
|
auto *AFD = cast<AbstractFunctionDecl>(CMI->getMember().getDecl());
|
|
|
|
auto Found = CalleeSetCache.find(AFD);
|
|
if (Found != CalleeSetCache.end())
|
|
return new (Allocator) CallGraphEdge(Apply.getInstruction(),
|
|
Found->second, EdgeOrdinal++);
|
|
|
|
// We don't know the callees in cases where the method isn't
|
|
// present in a vtable, so create a call graph edge with no known
|
|
// callees, assumed to be able to call unknown functions.
|
|
return new (Allocator) CallGraphEdge(Apply.getInstruction(), EdgeOrdinal++);
|
|
}
|
|
|
|
case ValueKind::SuperMethodInst:
|
|
return new (Allocator) CallGraphEdge(Apply.getInstruction(), EdgeOrdinal++);
|
|
|
|
default:
|
|
assert(!isa<MethodInst>(Callee) &&
|
|
"Unhandled method instruction in call graph construction!");
|
|
return new (Allocator) CallGraphEdge(Apply.getInstruction(), EdgeOrdinal++);
|
|
}
|
|
}
|
|
|
|
static void orderEdges(const llvm::SmallPtrSetImpl<CallGraphEdge *> &Edges,
|
|
llvm::SmallVectorImpl<CallGraphEdge *> &OrderedEdges) {
|
|
for (auto *Edge : Edges)
|
|
OrderedEdges.push_back(Edge);
|
|
|
|
std::sort(OrderedEdges.begin(), OrderedEdges.end(),
|
|
[](CallGraphEdge *left, CallGraphEdge *right) {
|
|
return left->getOrdinal() < right->getOrdinal();
|
|
});
|
|
}
|
|
|
|
void CallGraph::addEdgesForInstruction(SILInstruction *I,
|
|
CallGraphNode *CallerNode) {
|
|
auto Apply = FullApplySite::isa(I);
|
|
|
|
// TODO: Support non-apply instructions.
|
|
if (!Apply)
|
|
return;
|
|
|
|
auto *Edge = makeCallGraphEdgeForCallee(Apply, Apply.getCallee());
|
|
assert(Edge && "Expected to be able to make call graph edge for callee!");
|
|
assert(!InstToEdgeMap.count(Apply.getInstruction()) &&
|
|
"Added apply that already has an edge node!\n");
|
|
InstToEdgeMap[Apply.getInstruction()] = Edge;
|
|
CallerNode->addCalleeEdge(Edge);
|
|
|
|
for (auto *CalleeNode : Edge->getCalleeSet())
|
|
CalleeNode->addCallerEdge(Edge);
|
|
|
|
// TODO: Compute this from the call graph itself after stripping
|
|
// unreachable nodes from graph.
|
|
++NumAppliesWithEdges;
|
|
}
|
|
|
|
void CallGraph::removeEdgeFromFunction(CallGraphEdge *Edge, SILFunction *F) {
|
|
// Remove the edge from all the potential callee call graph nodes.
|
|
auto CalleeSet = Edge->getCalleeSet();
|
|
for (auto *CalleeNode : CalleeSet)
|
|
CalleeNode->removeCallerEdge(Edge);
|
|
|
|
// Remove the edge from the caller's call graph node.
|
|
auto *CallerNode = getCallGraphNode(F);
|
|
CallerNode->removeCalleeEdge(Edge);
|
|
|
|
// Remove the mapping from the apply to this edge.
|
|
auto Apply = Edge->getInstruction();
|
|
InstToEdgeMap.erase(Apply);
|
|
|
|
// Call the destructor for the edge. The memory will be reclaimed
|
|
// when the call graph is deleted by virtue of the bump pointer
|
|
// allocator.
|
|
Edge->~CallGraphEdge();
|
|
}
|
|
|
|
void CallGraph::removeNode(CallGraphNode *Node) {
|
|
assert(Node->getCallerEdges().size() == 0 &&
|
|
"Node to delete must not have any caller edges");
|
|
assert(Node->getCalleeEdges().size() == 0 &&
|
|
"Node to delete must not have any callee edges");
|
|
|
|
// Avoid keeping a dangling node pointers in the vectors.
|
|
// Next time they will be computed from scratch.
|
|
BottomUpFunctionOrder.clear();
|
|
clearBottomUpSCCOrder();
|
|
|
|
FunctionToNodeMap.erase(Node->getFunction());
|
|
|
|
// Call the destructor for the node. The memory will be reclaimed
|
|
// when the call graph is deleted by virtue of the bump pointer
|
|
// allocator.
|
|
Node->~CallGraphNode();
|
|
}
|
|
|
|
// Remove the call graph edges associated with an apply, where the
|
|
// apply is known to the call graph.
|
|
void CallGraph::removeEdgesForInstruction(SILInstruction *I) {
|
|
assert(InstToEdgeMap.count(I) && "Expected apply to be in edge map!");
|
|
removeEdgeFromFunction(InstToEdgeMap[I], I->getFunction());
|
|
}
|
|
|
|
void CallGraph::addEdges(SILFunction *F) {
|
|
auto *CallerNode = getOrAddCallGraphNode(F);
|
|
|
|
for (auto &BB : *F) {
|
|
for (auto &I : BB) {
|
|
if (FullApplySite::isa(&I))
|
|
addEdgesForInstruction(&I, CallerNode);
|
|
|
|
auto *FRI = dyn_cast<FunctionRefInst>(&I);
|
|
if (!FRI)
|
|
continue;
|
|
|
|
auto *CalleeFn = FRI->getReferencedFunction();
|
|
|
|
if (CalleeFn->isExternalDeclaration())
|
|
M.linkFunction(CalleeFn, SILModule::LinkingMode::LinkAll,
|
|
CallGraphLinkerEditor(this).getCallback());
|
|
|
|
if (CalleeFn->isPossiblyUsedExternally()) {
|
|
auto *CalleeNode = tryGetCallGraphNode(CalleeFn);
|
|
assert((!CalleeNode || !CalleeNode->isCallerEdgesComplete()) &&
|
|
"Expected function to have incomplete set of caller edges!");
|
|
(void) CalleeNode;
|
|
continue;
|
|
}
|
|
|
|
bool hasAllApplyUsers =
|
|
std::none_of(FRI->use_begin(), FRI->use_end(),
|
|
[](Operand *Op) {
|
|
return !FullApplySite::isa(Op->getUser());
|
|
});
|
|
|
|
// If we have a non-apply user of this function, mark its caller set
|
|
// as being incomplete.
|
|
if (!hasAllApplyUsers) {
|
|
auto *CalleeNode = getOrAddCallGraphNode(CalleeFn);
|
|
CalleeNode->markCallerEdgesIncomplete();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Print check lines prior to call graph output, and also print the
|
|
// function bodies. This can be used to minimize the effort in
|
|
// creating new call graph test cases.
|
|
static llvm::cl::opt<std::string>
|
|
CallGraphFileCheckPrefix("call-graph-file-check-prefix", llvm::cl::init(""),
|
|
llvm::cl::desc("Print a FileCheck prefix before each line"));
|
|
|
|
static void indent(llvm::raw_ostream &OS, int Indent) {
|
|
if (!CallGraphFileCheckPrefix.empty()) return;
|
|
|
|
std::string Blanks(Indent, ' ');
|
|
OS << Blanks;
|
|
}
|
|
|
|
static void printFlag(llvm::raw_ostream &OS,
|
|
const char *Description, bool Value, int Indent =0) {
|
|
indent(OS, Indent);
|
|
OS << CallGraphFileCheckPrefix << Description << ": " <<
|
|
(Value ? "yes\n" : "no\n");
|
|
}
|
|
|
|
void CallGraphEdge::print(llvm::raw_ostream &OS, int Indent) const {
|
|
indent(OS, Indent);
|
|
OS << CallGraphFileCheckPrefix << "Call site #" << Ordinal << ": ";
|
|
OS << *getInstruction();
|
|
|
|
printFlag(OS, "Unknown callees", canCallUnknownFunction(), Indent);
|
|
|
|
if (getCalleeSet().empty())
|
|
return;
|
|
|
|
bool First = true;
|
|
indent(OS, Indent);
|
|
OS << CallGraphFileCheckPrefix << "Known callees:\n";
|
|
for (auto *Callee : getCalleeSet()) {
|
|
if (!First)
|
|
OS << "\n";
|
|
First = false;
|
|
auto Name = Callee->getFunction()->getName();
|
|
indent(OS, Indent + 2);
|
|
OS << CallGraphFileCheckPrefix << "Name: " << Name << "\n";
|
|
indent(OS, Indent + 2);
|
|
OS << CallGraphFileCheckPrefix << "Demangled: " <<
|
|
demangle_wrappers::demangleSymbolAsString(Name) << "\n";
|
|
}
|
|
}
|
|
|
|
void CallGraphEdge::dump(int Indent) const {
|
|
#ifndef NDEBUG
|
|
print(llvm::errs(), Indent);
|
|
#endif
|
|
}
|
|
|
|
void CallGraphEdge::dump() const {
|
|
#ifndef NDEBUG
|
|
dump(0);
|
|
#endif
|
|
}
|
|
|
|
|
|
void CallGraphNode::print(llvm::raw_ostream &OS) const {
|
|
OS << CallGraphFileCheckPrefix << "Function #" << Ordinal << ": " <<
|
|
getFunction()->getName() << "\n";
|
|
OS << CallGraphFileCheckPrefix << "Demangled: " <<
|
|
demangle_wrappers::demangleSymbolAsString(getFunction()->getName()) << "\n";
|
|
printFlag(OS, "Trivially dead", isTriviallyDead());
|
|
printFlag(OS, "All callers known", isCallerEdgesComplete());
|
|
|
|
auto &CalleeEdges = getCalleeEdges();
|
|
if (!CalleeEdges.empty()) {
|
|
OS << CallGraphFileCheckPrefix << "Call sites:\n";
|
|
|
|
llvm::SmallVector<CallGraphEdge *, 8> OrderedCalleeEdges;
|
|
orderEdges(CalleeEdges, OrderedCalleeEdges);
|
|
|
|
for (auto *Edge : OrderedCalleeEdges) {
|
|
OS << "\n";
|
|
Edge->print(OS, /* Indent= */ 2);
|
|
}
|
|
OS << "\n";
|
|
}
|
|
|
|
auto &CallerEdges = getCallerEdges();
|
|
if (!CallerEdges.empty()) {
|
|
OS << CallGraphFileCheckPrefix <<
|
|
(!isCallerEdgesComplete() ? "Known " : "");
|
|
OS << "Callers:\n";
|
|
|
|
llvm::SmallVector<CallGraphEdge *, 8> OrderedCallerEdges;
|
|
orderEdges(CallerEdges, OrderedCallerEdges);
|
|
|
|
llvm::SetVector<SILFunction *> Callers;
|
|
|
|
for (auto *Edge : OrderedCallerEdges)
|
|
Callers.insert(Edge->getInstruction()->getFunction());
|
|
|
|
for (auto *Caller : Callers) {
|
|
OS << "\n";
|
|
indent(OS, 2);
|
|
OS << CallGraphFileCheckPrefix << "Name: " << Caller->getName() << "\n";
|
|
indent(OS, 2);
|
|
OS << CallGraphFileCheckPrefix << "Demangled: " <<
|
|
demangle_wrappers::demangleSymbolAsString(Caller->getName()) << "\n";
|
|
}
|
|
OS << "\n";
|
|
}
|
|
|
|
if (!CallGraphFileCheckPrefix.empty())
|
|
getFunction()->print(OS);
|
|
}
|
|
|
|
void CallGraphNode::dump() const {
|
|
#ifndef NDEBUG
|
|
print(llvm::errs());
|
|
#endif
|
|
}
|
|
|
|
void CallGraph::print(llvm::raw_ostream &OS) {
|
|
OS << CallGraphFileCheckPrefix << "*** Call Graph ***\n";
|
|
|
|
auto const &Funcs = getBottomUpFunctionOrder();
|
|
for (auto *F : Funcs) {
|
|
auto *Node = getCallGraphNode(F);
|
|
if (Node)
|
|
Node->print(OS);
|
|
else
|
|
OS << "!!! Missing node for " << F->getName() << "!!!";
|
|
OS << "\n";
|
|
}
|
|
}
|
|
|
|
void CallGraph::dump() {
|
|
#ifndef NDEBUG
|
|
print(llvm::errs());
|
|
#endif
|
|
}
|
|
|
|
namespace {
|
|
|
|
template<int NumBuckets>
|
|
struct Histogram {
|
|
unsigned Data[NumBuckets];
|
|
|
|
Histogram() {
|
|
for (auto i = 0; i < NumBuckets; ++i) {
|
|
Data[i] = 0;
|
|
}
|
|
}
|
|
|
|
void increment(unsigned Bucket) {
|
|
Bucket = Bucket < NumBuckets ? Bucket : NumBuckets - 1;
|
|
Data[Bucket]++;
|
|
}
|
|
|
|
void print(llvm::raw_ostream &OS) {
|
|
auto Last = NumBuckets - 1;
|
|
for (auto i = 0; i < NumBuckets; ++i) {
|
|
auto *Separator = Last == i ? "+: " : ": ";
|
|
if (Data[i])
|
|
OS << CallGraphFileCheckPrefix << i << Separator << Data[i] << "\n";
|
|
}
|
|
OS << "\n";
|
|
}
|
|
};
|
|
|
|
} // end anonymous namespace
|
|
|
|
void CallGraph::printStats(llvm::raw_ostream &OS) {
|
|
Histogram<256> CallSitesPerFunction;
|
|
Histogram<256> CallersPerFunction;
|
|
Histogram<256> CalleesPerCallSite;
|
|
unsigned CountNodes = 0;
|
|
unsigned CountCallSites = 0;
|
|
|
|
auto const &Funcs = getBottomUpFunctionOrder();
|
|
for (auto *F : Funcs) {
|
|
++CountNodes;
|
|
auto *Node = getCallGraphNode(F);
|
|
if (Node) {
|
|
CallSitesPerFunction.increment(Node->getCalleeEdges().size());
|
|
CallersPerFunction.increment(Node->getCallerEdges().size());
|
|
|
|
CountCallSites += Node->getCalleeEdges().size();
|
|
|
|
for (auto *Edge : Node->getCalleeEdges())
|
|
CalleesPerCallSite.increment(Edge->getCalleeSet().size());
|
|
} else {
|
|
OS << "!!! Missing node for " << F->getName() << "!!!";
|
|
}
|
|
}
|
|
|
|
OS << CallGraphFileCheckPrefix << "*** Call Graph Statistics ***\n";
|
|
|
|
OS << CallGraphFileCheckPrefix << "Number of call graph nodes: " <<
|
|
CountNodes << "\n";
|
|
OS << CallGraphFileCheckPrefix << "Number of call graph edges: " <<
|
|
CountCallSites << "\n";
|
|
|
|
OS << CallGraphFileCheckPrefix <<
|
|
"Histogram of number of call sites per function:\n";
|
|
CallSitesPerFunction.print(OS);
|
|
|
|
OS << CallGraphFileCheckPrefix <<
|
|
"Histogram of number of callees per call site:\n";
|
|
CalleesPerCallSite.print(OS);
|
|
|
|
OS << CallGraphFileCheckPrefix <<
|
|
"Histogram of number of callers per function:\n";
|
|
CallersPerFunction.print(OS);
|
|
|
|
OS << CallGraphFileCheckPrefix << "Bump pointer allocated memory (bytes): " <<
|
|
Allocator.getTotalMemory() << "\n";
|
|
|
|
OS << CallGraphFileCheckPrefix << "Number of callee sets allocated: " <<
|
|
CalleeSetCache.size() << "\n";
|
|
}
|
|
|
|
void CallGraph::dumpStats() {
|
|
#ifndef NDEBUG
|
|
printStats(llvm::errs());
|
|
#endif
|
|
}
|
|
|
|
namespace {
|
|
|
|
/// Finds SCCs in the call graph. Our call graph has an unconventional
|
|
/// form where each edge of the graph is really a multi-edge that can
|
|
/// point to multiple call graph nodes in the case where we can call
|
|
/// one of several different functions.
|
|
class CallGraphSCCFinder {
|
|
unsigned NextDFSNum;
|
|
llvm::SmallVectorImpl<CallGraphSCC *> &TheSCCs;
|
|
|
|
llvm::DenseMap<CallGraphNode *, unsigned> DFSNum;
|
|
llvm::DenseMap<CallGraphNode *, unsigned> MinDFSNum;
|
|
llvm::SetVector<CallGraphNode *> DFSStack;
|
|
|
|
/// The CallGraphSCCFinder does not own this bump ptr allocator, so does not
|
|
/// call the destructor of objects allocated from it.
|
|
llvm::BumpPtrAllocator &BPA;
|
|
|
|
public:
|
|
CallGraphSCCFinder(llvm::SmallVectorImpl<CallGraphSCC *> &TheSCCs,
|
|
llvm::BumpPtrAllocator &BPA)
|
|
: NextDFSNum(0), TheSCCs(TheSCCs), BPA(BPA) {}
|
|
|
|
void DFS(CallGraphNode *Node) {
|
|
// Set the DFSNum for this node if we haven't already, and if we
|
|
// have, which indicates it's already been visited, return.
|
|
if (!DFSNum.insert(std::make_pair(Node, NextDFSNum)).second)
|
|
return;
|
|
|
|
assert(MinDFSNum.find(Node) == MinDFSNum.end() &&
|
|
"Node should not already have a minimum DFS number!");
|
|
|
|
MinDFSNum[Node] = NextDFSNum;
|
|
++NextDFSNum;
|
|
|
|
DFSStack.insert(Node);
|
|
|
|
llvm::SmallVector<CallGraphEdge *, 4> OrderedEdges;
|
|
orderEdges(Node->getCalleeEdges(), OrderedEdges);
|
|
|
|
for (auto *ApplyEdge : OrderedEdges) {
|
|
for (auto *CalleeNode : ApplyEdge->getCalleeSet()) {
|
|
if (DFSNum.find(CalleeNode) == DFSNum.end()) {
|
|
DFS(CalleeNode);
|
|
MinDFSNum[Node] = std::min(MinDFSNum[Node], MinDFSNum[CalleeNode]);
|
|
} else if (DFSStack.count(CalleeNode)) {
|
|
MinDFSNum[Node] = std::min(MinDFSNum[Node], DFSNum[CalleeNode]);
|
|
}
|
|
}
|
|
}
|
|
|
|
// If this node is the root of an SCC (including SCCs with a
|
|
// single node), pop the SCC and push it on our SCC stack.
|
|
if (DFSNum[Node] == MinDFSNum[Node]) {
|
|
auto *SCC = new (BPA) CallGraphSCC();
|
|
|
|
CallGraphNode *Popped;
|
|
do {
|
|
Popped = DFSStack.pop_back_val();
|
|
SCC->SCCNodes.push_back(Popped->getFunction());
|
|
} while (Popped != Node);
|
|
|
|
TheSCCs.push_back(SCC);
|
|
}
|
|
}
|
|
};
|
|
|
|
} // end anonymous namespace
|
|
|
|
void CallGraph::clearBottomUpSCCOrder() {
|
|
for (auto *SCC : BottomUpSCCOrder)
|
|
SCC->~CallGraphSCC();
|
|
BottomUpSCCOrder.clear();
|
|
}
|
|
|
|
void CallGraph::computeBottomUpSCCOrder() {
|
|
if (!BottomUpSCCOrder.empty()) {
|
|
clearBottomUpSCCOrder();
|
|
}
|
|
|
|
CallGraphSCCFinder SCCFinder(BottomUpSCCOrder, Allocator);
|
|
for (auto &F : M) {
|
|
if (F.isDefinition()) {
|
|
auto *Node = getCallGraphNode(&F);
|
|
SCCFinder.DFS(Node);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CallGraph::computeBottomUpFunctionOrder() {
|
|
// We do not need to call any destructors here.
|
|
BottomUpFunctionOrder.clear();
|
|
|
|
computeBottomUpSCCOrder();
|
|
|
|
for (auto *SCC : BottomUpSCCOrder)
|
|
for (auto *Fn : SCC->SCCNodes)
|
|
BottomUpFunctionOrder.push_back(Fn);
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// CallGraphEditor
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
void CallGraphEditor::replaceApplyWithNew(FullApplySite Old,
|
|
FullApplySite New) {
|
|
if (!CG)
|
|
return;
|
|
|
|
if (auto *Edge = CG->tryGetCallGraphEdge(Old.getInstruction()))
|
|
CG->removeEdgeFromFunction(Edge, Old.getInstruction()->getFunction());
|
|
|
|
CG->addEdgesForInstruction(New.getInstruction());
|
|
}
|
|
|
|
void CallGraphEditor::replaceApplyWithCallSites(FullApplySite Old,
|
|
llvm::SmallVectorImpl<SILInstruction *> &NewCallSites) {
|
|
if (!CG)
|
|
return;
|
|
|
|
if (auto *Edge = CG->tryGetCallGraphEdge(Old.getInstruction()))
|
|
CG->removeEdgeFromFunction(Edge, Old.getInstruction()->getFunction());
|
|
|
|
for (auto NewApply : NewCallSites)
|
|
CG->addEdgesForInstruction(NewApply);
|
|
}
|
|
|
|
void CallGraphEditor::moveNodeToNewFunction(SILFunction *Old,
|
|
SILFunction *New) {
|
|
if (!CG)
|
|
return;
|
|
|
|
auto Iter = CG->FunctionToNodeMap.find(Old);
|
|
assert(Iter != CG->FunctionToNodeMap.end());
|
|
auto *Node = Iter->second;
|
|
CG->FunctionToNodeMap.erase(Iter);
|
|
CG->FunctionToNodeMap[New] = Node;
|
|
}
|
|
|
|
void CallGraphEditor::removeAllCalleeEdgesFrom(SILFunction *F) {
|
|
if (!CG)
|
|
return;
|
|
|
|
auto &CalleeEdges = CG->getCallGraphNode(F)->getCalleeEdges();
|
|
while (!CalleeEdges.empty()) {
|
|
auto *Edge = *CalleeEdges.begin();
|
|
CG->removeEdgeFromFunction(Edge, F);
|
|
}
|
|
}
|
|
|
|
void CallGraphEditor::removeAllCallerEdgesFrom(SILFunction *F) {
|
|
if (!CG)
|
|
return;
|
|
|
|
auto &CallerEdges = CG->getCallGraphNode(F)->getCallerEdges();
|
|
while (!CallerEdges.empty()) {
|
|
auto *Edge = *CallerEdges.begin();
|
|
auto Apply = Edge->getInstruction();
|
|
CG->removeEdgeFromFunction(Edge, Apply->getFunction());
|
|
}
|
|
}
|
|
|
|
void CallGraphEditor::updatePartialApplyUses(swift::ApplySite AI) {
|
|
if (!CG)
|
|
return;
|
|
|
|
for (auto *Use : AI.getInstruction()->getUses()) {
|
|
if (auto FAS = FullApplySite::isa(Use->getUser()))
|
|
replaceApplyWithNew(FAS, FAS);
|
|
}
|
|
}
|
|
|
|
void CallGraphEditor::eraseFunction(SILFunction *F) {
|
|
auto &M = F->getModule();
|
|
M.eraseFunction(F);
|
|
|
|
if (CG)
|
|
removeCallGraphNode(F);
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// CallGraph Verification
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
void CallGraph::verify() const {
|
|
#ifndef NDEBUG
|
|
// For every function in the module, add it to our SILFunction set.
|
|
llvm::DenseSet<SILFunction *> Functions;
|
|
for (auto &F : M)
|
|
Functions.insert(&F);
|
|
|
|
// For every pair (SILFunction, CallGraphNode) in the
|
|
// function-to-node map, verify:
|
|
//
|
|
// a. The function is in the current module.
|
|
// b. The call graph node is for that same function.
|
|
//
|
|
// In addition, call the verify method for the function.
|
|
unsigned numEdges = 0;
|
|
for (auto &P : FunctionToNodeMap) {
|
|
SILFunction *F = P.first;
|
|
CallGraphNode *Node = P.second;
|
|
assert(Functions.count(F) &&
|
|
"Function in call graph but not in module!?");
|
|
assert(Node->getFunction() == F &&
|
|
"Func mapped to node, but node has different Function inside?!");
|
|
verify(F);
|
|
numEdges += Node->getCalleeEdges().size();
|
|
}
|
|
|
|
assert(InstToEdgeMap.size() == numEdges &&
|
|
"Some edges in InstToEdgeMap are not contained in any node");
|
|
|
|
// Verify the callee sets.
|
|
for (auto Iter : CalleeSetCache) {
|
|
auto *CalleeSet = Iter.second.getPointer();
|
|
for (CallGraphNode *Node : *CalleeSet) {
|
|
SILFunction *F = Node->getFunction();
|
|
assert(tryGetCallGraphNode(F) &&
|
|
"Callee set contains dangling node poiners");
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void CallGraph::verify(SILFunction *F) const {
|
|
#ifndef NDEBUG
|
|
// Collect all full apply sites of the function.
|
|
|
|
auto *Node = getCallGraphNode(F);
|
|
unsigned numEdges = 0;
|
|
|
|
for (auto &BB : *F) {
|
|
for (auto &I : BB) {
|
|
auto FAS = FullApplySite::isa(&I);
|
|
if (!FAS)
|
|
continue;
|
|
|
|
auto *Edge = getCallGraphEdge(FAS.getInstruction());
|
|
|
|
numEdges++;
|
|
|
|
assert(Edge->getInstruction() == &I &&
|
|
"Edge is not linked to the correct apply site");
|
|
|
|
assert(InstToEdgeMap.lookup(FAS.getInstruction()) == Edge &&
|
|
"Edge is not in InstToEdgeMap");
|
|
|
|
if (!Edge->canCallUnknownFunction()) {
|
|
// In the trivial case that we call a known function, check if we have
|
|
// exactly one callee in the edge.
|
|
SILValue Callee = FAS.getCallee();
|
|
if (auto *PAI = dyn_cast<PartialApplyInst>(Callee))
|
|
Callee = PAI->getCallee();
|
|
|
|
if (auto *FRI = dyn_cast<FunctionRefInst>(Callee)) {
|
|
auto *CalleeNode = Edge->getSingleCalleeOrNull();
|
|
assert(CalleeNode &&
|
|
CalleeNode->getFunction() == FRI->getReferencedFunction() &&
|
|
"Direct apply is not represented by a single-callee edge");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check if we have an exact 1-to-1 mapping from full apply sites to edges.
|
|
auto &CalleeEdges = Node->getCalleeEdges();
|
|
assert(numEdges == CalleeEdges.size() && "More edges than full apply sites");
|
|
|
|
// Make structural graph checks:
|
|
// 1.) Check that the callee edges are part of the callee node's caller set.
|
|
for (auto *Edge : CalleeEdges) {
|
|
assert(Edge->getInstruction()->getFunction() == F &&
|
|
"Apply in callee set that is not in the callee function?!");
|
|
|
|
for (auto *CalleeNode : Edge->getCalleeSet()) {
|
|
auto &CallerEdges = CalleeNode->getCallerEdges();
|
|
assert(std::find(CallerEdges.begin(), CallerEdges.end(), Edge) !=
|
|
CallerEdges.end() &&
|
|
"Edge not in caller set of callee");
|
|
}
|
|
}
|
|
// 2.) Check that the caller edges have this node in their callee sets.
|
|
for (auto *Edge : Node->getCallerEdges()) {
|
|
auto CalleeSet = Edge->getCalleeSet();
|
|
assert(std::find(CalleeSet.begin(), CalleeSet.end(), Node) !=
|
|
CalleeSet.end() &&
|
|
"Node not in callee set of caller edge");
|
|
}
|
|
#endif
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// View CG Implementation
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#ifndef NDEBUG
|
|
|
|
namespace swift {
|
|
|
|
/// Another representation of the call graph using sorted vectors instead of
|
|
/// sets. Used for viewing the callgraph as dot file with llvm::ViewGraph.
|
|
struct OrderedCallGraph {
|
|
|
|
struct Node;
|
|
|
|
struct Edge {
|
|
Edge(CallGraphEdge *CGEdge, Node *Child) : CGEdge(CGEdge), Child(Child) { }
|
|
CallGraphEdge *CGEdge;
|
|
Node *Child;
|
|
};
|
|
|
|
struct Node {
|
|
CallGraphNode *CGNode;
|
|
OrderedCallGraph *OCG;
|
|
int NumCallSites = 0;
|
|
SmallVector<Edge, 8> Children;
|
|
};
|
|
|
|
struct child_iterator : public std::iterator<std::random_access_iterator_tag,
|
|
Node *, ptrdiff_t> {
|
|
SmallVectorImpl<Edge>::iterator baseIter;
|
|
|
|
child_iterator(SmallVectorImpl<Edge>::iterator baseIter) :
|
|
baseIter(baseIter)
|
|
{ }
|
|
|
|
child_iterator &operator++() { baseIter++; return *this; }
|
|
child_iterator operator++(int) { auto tmp = *this; baseIter++; return tmp; }
|
|
Node *operator*() const { return baseIter->Child; }
|
|
bool operator==(const child_iterator &RHS) const {
|
|
return baseIter == RHS.baseIter;
|
|
}
|
|
bool operator!=(const child_iterator &RHS) const {
|
|
return baseIter != RHS.baseIter;
|
|
}
|
|
difference_type operator-(const child_iterator &RHS) const {
|
|
return baseIter - RHS.baseIter;
|
|
}
|
|
};
|
|
|
|
OrderedCallGraph(CallGraph *CG);
|
|
|
|
CallGraph *CG;
|
|
std::vector<Node> Nodes;
|
|
|
|
/// The SILValue IDs which are printed as edge source labels.
|
|
llvm::DenseMap<const ValueBase *, unsigned> InstToIDMap;
|
|
|
|
typedef std::vector<Node>::iterator iterator;
|
|
};
|
|
|
|
OrderedCallGraph::OrderedCallGraph(CallGraph *CG) {
|
|
auto const &Funcs = CG->getBottomUpFunctionOrder();
|
|
Nodes.resize(Funcs.size());
|
|
llvm::DenseMap<CallGraphNode *, Node *> NodeMap;
|
|
int idx = 0;
|
|
for (auto *F : Funcs) {
|
|
auto *CGNode = CG->getCallGraphNode(F);
|
|
Node &ONode = Nodes[idx++];
|
|
ONode.CGNode = CGNode;
|
|
ONode.OCG = this;
|
|
NodeMap[CGNode] = &ONode;
|
|
|
|
F->numberValues(InstToIDMap);
|
|
}
|
|
|
|
for (Node &ONode : Nodes) {
|
|
llvm::SmallVector<CallGraphEdge *, 8> OrderedEdges;
|
|
orderEdges(ONode.CGNode->getCalleeEdges(), OrderedEdges);
|
|
|
|
ONode.NumCallSites = OrderedEdges.size();
|
|
for (auto *CGEdge : OrderedEdges) {
|
|
for (auto *CalleeNode : CGEdge->getCalleeSet()) {
|
|
auto *OrderedChild = NodeMap[CalleeNode];
|
|
assert(OrderedChild);
|
|
ONode.Children.push_back(Edge(CGEdge, OrderedChild));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
} // end swift namespace
|
|
|
|
namespace llvm {
|
|
|
|
/// Wraps a dot node label string to multiple lines. The \p NumEdgeLabels
|
|
/// gives an estimate on the minimum width of the node shape.
|
|
static void wrap(std::string &Str, int NumEdgeLabels) {
|
|
unsigned ColNum = 0;
|
|
unsigned LastSpace = 0;
|
|
unsigned MaxColumns = std::max(60, NumEdgeLabels * 8);
|
|
for (unsigned i = 0; i != Str.length(); ++i) {
|
|
if (ColNum == MaxColumns) {
|
|
if (!LastSpace)
|
|
LastSpace = i;
|
|
Str.insert(LastSpace + 1, "\\l");
|
|
ColNum = i - LastSpace - 1;
|
|
LastSpace = 0;
|
|
} else
|
|
++ColNum;
|
|
if (Str[i] == ' ' || Str[i] == '.')
|
|
LastSpace = i;
|
|
}
|
|
}
|
|
|
|
/// CallGraph GraphTraits specialization so the CallGraph can be
|
|
/// iterable by generic graph iterators.
|
|
template <> struct GraphTraits<OrderedCallGraph::Node *> {
|
|
typedef OrderedCallGraph::Node NodeType;
|
|
typedef OrderedCallGraph::child_iterator ChildIteratorType;
|
|
|
|
static NodeType *getEntryNode(NodeType *N) { return N; }
|
|
static inline ChildIteratorType child_begin(NodeType *N) {
|
|
return N->Children.begin();
|
|
}
|
|
static inline ChildIteratorType child_end(NodeType *N) {
|
|
return N->Children.end();
|
|
}
|
|
};
|
|
|
|
template <> struct GraphTraits<OrderedCallGraph *>
|
|
: public GraphTraits<OrderedCallGraph::Node *> {
|
|
typedef OrderedCallGraph *GraphType;
|
|
|
|
static NodeType *getEntryNode(GraphType F) { return nullptr; }
|
|
|
|
typedef OrderedCallGraph::iterator nodes_iterator;
|
|
static nodes_iterator nodes_begin(GraphType OCG) {
|
|
return OCG->Nodes.begin();
|
|
}
|
|
static nodes_iterator nodes_end(GraphType OCG) { return OCG->Nodes.end(); }
|
|
static unsigned size(GraphType CG) { return CG->Nodes.size(); }
|
|
};
|
|
|
|
/// This is everything the llvm::GraphWriter needs to write the call graph in
|
|
/// a dot file.
|
|
template <>
|
|
struct DOTGraphTraits<OrderedCallGraph *> : public DefaultDOTGraphTraits {
|
|
|
|
DOTGraphTraits(bool isSimple = false) : DefaultDOTGraphTraits(isSimple) {}
|
|
|
|
std::string getNodeLabel(const OrderedCallGraph::Node *Node,
|
|
const OrderedCallGraph *Graph) {
|
|
SILFunction *F = Node->CGNode->getFunction();
|
|
std::string Label = F->getName();
|
|
wrap(Label, Node->NumCallSites);
|
|
return Label;
|
|
}
|
|
|
|
std::string getNodeDescription(const OrderedCallGraph::Node *Node,
|
|
const OrderedCallGraph *Graph) {
|
|
SILFunction *F = Node->CGNode->getFunction();
|
|
std::string Label = demangle_wrappers::
|
|
demangleSymbolAsString(F->getName());
|
|
wrap(Label, Node->NumCallSites);
|
|
return Label;
|
|
}
|
|
|
|
static std::string getEdgeSourceLabel(const OrderedCallGraph::Node *Node,
|
|
OrderedCallGraph::child_iterator I) {
|
|
std::string Label;
|
|
raw_string_ostream O(Label);
|
|
SILInstruction *Inst = I.baseIter->CGEdge->getInstruction();
|
|
O << '%' << Node->OCG->InstToIDMap[Inst];
|
|
return Label;
|
|
}
|
|
|
|
static std::string getEdgeAttributes(const OrderedCallGraph::Node *Node,
|
|
OrderedCallGraph::child_iterator I,
|
|
const OrderedCallGraph *Graph) {
|
|
CallGraphEdge *Edge = I.baseIter->CGEdge;
|
|
if (Edge->canCallUnknownFunction())
|
|
return "color=\"red\"";
|
|
return "";
|
|
}
|
|
};
|
|
} // end llvm namespace
|
|
#endif
|
|
|
|
void CallGraph::viewCG() {
|
|
/// When asserts are disabled, this should be a NoOp.
|
|
#ifndef NDEBUG
|
|
OrderedCallGraph OCG(this);
|
|
llvm::ViewGraph(&OCG, "callgraph");
|
|
#endif
|
|
}
|