#include "bolt/Core/BinaryFunctionCallGraph.h"
#include "bolt/Core/BinaryContext.h"
#include "bolt/Core/BinaryFunction.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/Timer.h"
#include <stack>
#define DEBUG_TYPE "callgraph"
using namespace llvm;
namespace opts {
extern cl::opt<bool> TimeOpts;
extern cl::opt<unsigned> Verbosity;
extern cl::OptionCategory BoltCategory;
static cl::opt<std::string>
DumpCGDot("dump-cg", cl::desc("dump callgraph to the given file"),
cl::cat(BoltCategory));
}
namespace llvm {
namespace bolt {
CallGraph::NodeId BinaryFunctionCallGraph::addNode(BinaryFunction *BF,
uint32_t Size,
uint64_t Samples) {
NodeId Id = CallGraph::addNode(Size, Samples);
assert(size_t(Id) == Funcs.size());
Funcs.push_back(BF);
FuncToNodeId[BF] = Id;
assert(Funcs[Id] == BF);
return Id;
}
std::deque<BinaryFunction *> BinaryFunctionCallGraph::buildTraversalOrder() {
NamedRegionTimer T1("buildcgorder", "Build cg traversal order",
"CG breakdown", "CG breakdown", opts::TimeOpts);
std::deque<BinaryFunction *> TopologicalOrder;
enum NodeStatus { NEW, VISITING, VISITED };
std::vector<NodeStatus> NodeStatus(Funcs.size());
std::stack<NodeId> Worklist;
for (BinaryFunction *Func : Funcs) {
auto It = FuncToNodeId.find(Func);
assert(It != FuncToNodeId.end());
const NodeId Id = It->second;
Worklist.push(Id);
NodeStatus[Id] = NEW;
}
while (!Worklist.empty()) {
const NodeId FuncId = Worklist.top();
Worklist.pop();
if (NodeStatus[FuncId] == VISITED)
continue;
if (NodeStatus[FuncId] == VISITING) {
TopologicalOrder.push_back(Funcs[FuncId]);
NodeStatus[FuncId] = VISITED;
continue;
}
assert(NodeStatus[FuncId] == NEW);
NodeStatus[FuncId] = VISITING;
Worklist.push(FuncId);
for (const NodeId Callee : successors(FuncId)) {
if (NodeStatus[Callee] == VISITING || NodeStatus[Callee] == VISITED)
continue;
Worklist.push(Callee);
}
}
return TopologicalOrder;
}
BinaryFunctionCallGraph
buildCallGraph(BinaryContext &BC, CgFilterFunction Filter, bool CgFromPerfData,
bool IncludeSplitCalls, bool UseFunctionHotSize,
bool UseSplitHotSize, bool UseEdgeCounts,
bool IgnoreRecursiveCalls) {
NamedRegionTimer T1("buildcg", "Callgraph construction", "CG breakdown",
"CG breakdown", opts::TimeOpts);
BinaryFunctionCallGraph Cg;
static constexpr uint64_t COUNT_NO_PROFILE =
BinaryBasicBlock::COUNT_NO_PROFILE;
auto functionSize = [&](const BinaryFunction *Function) {
return UseFunctionHotSize && Function->isSplit()
? Function->estimateHotSize(UseSplitHotSize)
: Function->estimateSize();
};
auto lookupNode = [&](BinaryFunction *Function) {
const CallGraph::NodeId Id = Cg.maybeGetNodeId(Function);
if (Id == CallGraph::InvalidId) {
const size_t Size = functionSize(Function);
return Cg.addNode(Function, Size, Function->getKnownExecutionCount());
}
return Id;
};
uint64_t NotProcessed = 0;
uint64_t TotalCallsites = 0;
uint64_t NoProfileCallsites = 0;
uint64_t NumFallbacks = 0;
uint64_t RecursiveCallsites = 0;
for (auto &It : BC.getBinaryFunctions()) {
BinaryFunction *Function = &It.second;
if (Filter(*Function))
continue;
const CallGraph::NodeId SrcId = lookupNode(Function);
uint64_t Offset = 0;
auto recordCall = [&](const MCSymbol *DestSymbol, const uint64_t Count) {
BinaryFunction *DstFunc =
DestSymbol ? BC.getFunctionForSymbol(DestSymbol) : nullptr;
if (!DstFunc) {
LLVM_DEBUG(if (opts::Verbosity > 1) dbgs()
<< "BOLT-DEBUG: buildCallGraph: no function for symbol\n");
return false;
}
if (DstFunc == Function) {
LLVM_DEBUG(dbgs() << "BOLT-INFO: recursive call detected in "
<< *DstFunc << "\n");
++RecursiveCallsites;
if (IgnoreRecursiveCalls)
return false;
}
if (Filter(*DstFunc)) {
LLVM_DEBUG(if (opts::Verbosity > 1) dbgs()
<< "BOLT-DEBUG: buildCallGraph: filtered " << *DstFunc
<< '\n');
return false;
}
const CallGraph::NodeId DstId = lookupNode(DstFunc);
const bool IsValidCount = Count != COUNT_NO_PROFILE;
const uint64_t AdjCount = UseEdgeCounts && IsValidCount ? Count : 1;
if (!IsValidCount)
++NoProfileCallsites;
Cg.incArcWeight(SrcId, DstId, AdjCount, Offset);
LLVM_DEBUG(if (opts::Verbosity > 1) {
dbgs() << "BOLT-DEBUG: buildCallGraph: call " << *Function << " -> "
<< *DstFunc << " @ " << Offset << "\n";
});
return true;
};
using TargetDesc = std::pair<const MCSymbol *, uint64_t>;
using CallInfoTy = std::vector<TargetDesc>;
auto getCallInfo = [&](const BinaryBasicBlock *BB, const MCInst &Inst) {
CallInfoTy Counts;
const MCSymbol *DstSym = BC.MIB->getTargetSymbol(Inst);
if (!DstSym && BC.MIB->hasAnnotation(Inst, "CallProfile")) {
const auto &ICSP = BC.MIB->getAnnotationAs<IndirectCallSiteProfile>(
Inst, "CallProfile");
for (const IndirectCallProfile &CSI : ICSP)
if (CSI.Symbol)
Counts.emplace_back(CSI.Symbol, CSI.Count);
} else {
const uint64_t Count = BB->getExecutionCount();
Counts.emplace_back(DstSym, Count);
}
return Counts;
};
if (!Function->hasValidProfile() && CgFromPerfData &&
!Function->getAllCallSites().empty()) {
LLVM_DEBUG(
dbgs() << "BOLT-DEBUG: buildCallGraph: Falling back to perf data"
<< " for " << *Function << "\n");
++NumFallbacks;
const size_t Size = functionSize(Function);
for (const IndirectCallProfile &CSI : Function->getAllCallSites()) {
++TotalCallsites;
if (!CSI.Symbol)
continue;
Offset = CSI.Offset;
if (Offset > Size)
Offset = Size;
if (!recordCall(CSI.Symbol, CSI.Count))
++NotProcessed;
}
} else {
for (BinaryBasicBlock *BB : Function->getLayout().blocks()) {
if (BB->isSplit() && !IncludeSplitCalls)
continue;
bool BBIncludedInFunctionSize = false;
if (UseFunctionHotSize && Function->isSplit()) {
if (UseSplitHotSize)
BBIncludedInFunctionSize = !BB->isSplit();
else
BBIncludedInFunctionSize = BB->getKnownExecutionCount() != 0;
} else {
BBIncludedInFunctionSize = true;
}
for (MCInst &Inst : *BB) {
if (BC.MIB->isCall(Inst)) {
const CallInfoTy CallInfo = getCallInfo(BB, Inst);
if (!CallInfo.empty()) {
for (const TargetDesc &CI : CallInfo) {
++TotalCallsites;
if (!recordCall(CI.first, CI.second))
++NotProcessed;
}
} else {
++TotalCallsites;
++NotProcessed;
}
}
if (BBIncludedInFunctionSize)
Offset += BC.computeCodeSize(&Inst, &Inst + 1);
}
}
}
}
#ifndef NDEBUG
bool PrintInfo = DebugFlag && isCurrentDebugType("callgraph");
#else
bool PrintInfo = false;
#endif
if (PrintInfo || opts::Verbosity > 0)
BC.outs() << format("BOLT-INFO: buildCallGraph: %u nodes, %u callsites "
"(%u recursive), density = %.6lf, %u callsites not "
"processed, %u callsites with invalid profile, "
"used perf data for %u stale functions.\n",
Cg.numNodes(), TotalCallsites, RecursiveCallsites,
Cg.density(), NotProcessed, NoProfileCallsites,
NumFallbacks);
if (opts::DumpCGDot.getNumOccurrences()) {
Cg.printDot(opts::DumpCGDot, [&](CallGraph::NodeId Id) {
return Cg.nodeIdToFunc(Id)->getPrintName();
});
}
return Cg;
}
}
}