Skip to content

Commit

Permalink
[BOLT] More CG refactoring
Browse files Browse the repository at this point in the history
Summary:
Do some additional refactoring of the CallGraph class.  Add a BinaryFunctionCallGraph class that has the BOLT specific bits.  This is in preparation to moving the generic CallGraph class into a library that both BOLT and HHVM can use.

Make data members of CallGraph private and add the appropriate accessor methods.

(cherry picked from FBD5143468)
  • Loading branch information
Bill Nell authored and maksfb committed May 26, 2017
1 parent 95ab659 commit 5feee9f
Show file tree
Hide file tree
Showing 16 changed files with 656 additions and 439 deletions.
195 changes: 195 additions & 0 deletions bolt/Passes/BinaryFunctionCallGraph.cpp
@@ -0,0 +1,195 @@
//===--- Passes/BinaryFunctionCallGraph.cpp -------------------------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
//===----------------------------------------------------------------------===//

#include "BinaryFunctionCallGraph.h"
#include "BinaryFunction.h"
#include "BinaryContext.h"

#define DEBUG_TYPE "callgraph"

namespace llvm {
namespace bolt {

CallGraph::NodeId BinaryFunctionCallGraph::addNode(BinaryFunction *BF,
uint32_t Size,
uint64_t Samples) {
auto 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() {
std::deque<BinaryFunction *> TopologicalOrder;
enum NodeStatus { NEW, VISITING, VISITED };
std::vector<NodeStatus> NodeStatus(Funcs.size());
std::stack<NodeId> Worklist;

for (auto *Func : Funcs) {
const auto Id = FuncToNodeId.at(Func);
Worklist.push(Id);
NodeStatus[Id] = NEW;
}

while (!Worklist.empty()) {
const auto 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 auto Callee : successors(FuncId)) {
if (NodeStatus[Callee] == VISITING || NodeStatus[Callee] == VISITED)
continue;
Worklist.push(Callee);
}
}

return TopologicalOrder;
}

BinaryFunctionCallGraph buildCallGraph(BinaryContext &BC,
std::map<uint64_t, BinaryFunction> &BFs,
CgFilterFunction Filter,
bool IncludeColdCalls,
bool UseFunctionHotSize,
bool UseEdgeCounts) {
BinaryFunctionCallGraph Cg;

// Add call graph nodes.
auto lookupNode = [&](BinaryFunction *Function) {
const auto Id = Cg.maybeGetNodeId(Function);
if (Id == CallGraph::InvalidId) {
// It's ok to use the hot size here when the function is split. This is
// because emitFunctions will emit the hot part first in the order that is
// computed by ReorderFunctions. The cold part will be emitted with the
// rest of the cold functions and code.
const auto Size = UseFunctionHotSize && Function->isSplit()
? Function->estimateHotSize()
: Function->estimateSize();
// NOTE: for functions without a profile, we set the number of samples
// to zero. This will keep these functions from appearing in the hot
// section. This is a little weird because we wouldn't be trying to
// create a node for a function unless it was the target of a call from
// a hot block. The alternative would be to set the count to one or
// accumulate the number of calls from the callsite into the function
// samples. Results from perfomance testing seem to favor the zero
// count though, so I'm leaving it this way for now.
const auto Samples =
Function->hasProfile() ? Function->getExecutionCount() : 0;
return Cg.addNode(Function, Size, Samples);
} else {
return Id;
}
};

// Add call graph edges.
uint64_t NotProcessed = 0;
uint64_t TotalCalls = 0;
for (auto &It : BFs) {
auto *Function = &It.second;

if(Filter(*Function)) {
continue;
}

auto BranchDataOrErr = BC.DR.getFuncBranchData(Function->getNames());
const auto SrcId = lookupNode(Function);
uint64_t Offset = Function->getAddress();

auto recordCall = [&](const MCSymbol *DestSymbol, const uint64_t Count) {
if (auto *DstFunc = BC.getFunctionForSymbol(DestSymbol)) {
const auto DstId = lookupNode(DstFunc);
const auto AvgDelta = !UseEdgeCounts ? Offset - DstFunc->getAddress() : 0;
Cg.incArcWeight(SrcId, DstId, Count, AvgDelta);
DEBUG(dbgs() << "BOLT-DEBUG: buildCallGraph: call " << *Function
<< " -> " << *DstFunc << " @ " << Offset << "\n");
return true;
}
return false;
};

for (auto *BB : Function->layout()) {
// Don't count calls from cold blocks
if (BB->isCold() && !IncludeColdCalls)
continue;

for (auto &Inst : *BB) {
// Find call instructions and extract target symbols from each one.
if (!BC.MIA->isCall(Inst))
continue;

++TotalCalls;
if (const auto *DstSym = BC.MIA->getTargetSymbol(Inst)) {
// For direct calls, just use the BB execution count.
const auto Count = UseEdgeCounts && BB->hasProfile()
? BB->getExecutionCount() : 1;
if (!recordCall(DstSym, Count))
++NotProcessed;
} else if (BC.MIA->hasAnnotation(Inst, "EdgeCountData")) {
// For indirect calls and jump tables, use branch data.
if (!BranchDataOrErr) {
++NotProcessed;
continue;
}
const FuncBranchData &BranchData = BranchDataOrErr.get();
const auto DataOffset =
BC.MIA->getAnnotationAs<uint64_t>(Inst, "EdgeCountData");

for (const auto &BI : BranchData.getBranchRange(DataOffset)) {
// Count each target as a separate call.
++TotalCalls;

if (!BI.To.IsSymbol) {
++NotProcessed;
continue;
}

auto Itr = BC.GlobalSymbols.find(BI.To.Name);
if (Itr == BC.GlobalSymbols.end()) {
++NotProcessed;
continue;
}

const auto *DstSym =
BC.getOrCreateGlobalSymbol(Itr->second, "FUNCat");

if (!recordCall(DstSym, UseEdgeCounts ? BI.Branches : 1))
++NotProcessed;
}
}

if (!UseEdgeCounts) {
Offset += BC.computeCodeSize(&Inst, &Inst + 1);
}
}
}
}

outs() << "BOLT-WARNING: buildCallGraph: " << NotProcessed
<< " callsites not processed out of " << TotalCalls << "\n";

return Cg;
}

}
}
80 changes: 80 additions & 0 deletions bolt/Passes/BinaryFunctionCallGraph.h
@@ -0,0 +1,80 @@
//===--- Passes/CallGraph.h -----------------------------------------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
//===----------------------------------------------------------------------===//

#ifndef LLVM_TOOLS_LLVM_BOLT_PASSES_BINARY_FUNCTION_CALLGRAPH_H
#define LLVM_TOOLS_LLVM_BOLT_PASSES_BINARY_FUNCTION_CALLGRAPH_H

#include "CallGraph.h"

#include <unordered_map>
#include <functional>
#include <deque>
#include <map>

namespace llvm {
namespace bolt {

class BinaryFunction;
class BinaryContext;

class BinaryFunctionCallGraph : public CallGraph {
public:
NodeId maybeGetNodeId(const BinaryFunction *BF) const {
auto Itr = FuncToNodeId.find(BF);
return Itr != FuncToNodeId.end() ? Itr->second : InvalidId;
}
NodeId getNodeId(const BinaryFunction *BF) const {
auto Itr = FuncToNodeId.find(BF);
assert(Itr != FuncToNodeId.end());
return Itr->second;
}
BinaryFunction *nodeIdToFunc(NodeId Id) {
assert(Id < Funcs.size());
return Funcs[Id];
}
const BinaryFunction *nodeIdToFunc(NodeId Id) const {
assert(Id < Funcs.size());
return Funcs[Id];
}
NodeId addNode(BinaryFunction *BF, uint32_t Size, uint64_t Samples = 0);

/// Compute a DFS traversal of the call graph.
std::deque<BinaryFunction *> buildTraversalOrder();

private:
std::unordered_map<const BinaryFunction *, NodeId> FuncToNodeId;
std::vector<BinaryFunction *> Funcs;
};

using CgFilterFunction = std::function<bool (const BinaryFunction &BF)>;
inline bool NoFilter(const BinaryFunction &) { return false; }

/// Builds a call graph from the map of BinaryFunctions provided in BFs.
/// The arguments control how the graph is constructed.
/// Filter is called on each function, any function that it returns true for
/// is omitted from the graph.
/// If IncludeColdCalls is true, then calls from cold BBs are considered for the
/// graph, otherwise they are ignored.
/// UseFunctionHotSize controls whether the hot size of a function is used when
/// filling in the Size attribute of new Nodes.
/// UseEdgeCounts is used to control if the AvgCallOffset attribute on Arcs is
/// computed using the offsets of call instructions.
BinaryFunctionCallGraph buildCallGraph(BinaryContext &BC,
std::map<uint64_t, BinaryFunction> &BFs,
CgFilterFunction Filter = NoFilter,
bool IncludeColdCalls = true,
bool UseFunctionHotSize = false,
bool UseEdgeCounts = false);

}
}

#endif
1 change: 1 addition & 0 deletions bolt/Passes/CMakeLists.txt
@@ -1,5 +1,6 @@
add_llvm_library(LLVMBOLTPasses
BinaryPasses.cpp
BinaryFunctionCallGraph.cpp
CallGraph.cpp
DataflowAnalysis.cpp
DataflowInfoManager.cpp
Expand Down

0 comments on commit 5feee9f

Please sign in to comment.