mirror of
https://github.com/llvm/llvm-project.git
synced 2025-04-26 14:36:08 +00:00
317 lines
11 KiB
C++
317 lines
11 KiB
C++
//===-- MissingFrameInferrer.cpp - Missing frame inferrer --------- C++ -*-===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "MissingFrameInferrer.h"
|
|
#include "PerfReader.h"
|
|
#include "ProfiledBinary.h"
|
|
#include "llvm/ADT/SCCIterator.h"
|
|
#include "llvm/ADT/Statistic.h"
|
|
#include <algorithm>
|
|
#include <cstdint>
|
|
#include <iterator>
|
|
#include <queue>
|
|
#include <sys/types.h>
|
|
|
|
#define DEBUG_TYPE "missing-frame-inferrer"
|
|
|
|
using namespace llvm;
|
|
using namespace sampleprof;
|
|
|
|
STATISTIC(TailCallUniReachable,
|
|
"Number of frame pairs reachable via a unique tail call path");
|
|
STATISTIC(TailCallMultiReachable,
|
|
"Number of frame pairs reachable via a multiple tail call paths");
|
|
STATISTIC(TailCallUnreachable,
|
|
"Number of frame pairs unreachable via any tail call path");
|
|
STATISTIC(TailCallFuncSingleTailCalls,
|
|
"Number of functions with single tail call site");
|
|
STATISTIC(TailCallFuncMultipleTailCalls,
|
|
"Number of functions with multiple tail call sites");
|
|
STATISTIC(TailCallMaxTailCallPath, "Length of the longest tail call path");
|
|
|
|
static cl::opt<uint32_t>
|
|
MaximumSearchDepth("max-search-depth", cl::init(UINT32_MAX - 1),
|
|
cl::desc("The maximum levels the DFS-based missing "
|
|
"frame search should go with"));
|
|
|
|
void MissingFrameInferrer::initialize(
|
|
const ContextSampleCounterMap *SampleCounters) {
|
|
// Refine call edges based on LBR samples.
|
|
if (SampleCounters) {
|
|
std::unordered_map<uint64_t, std::unordered_set<uint64_t>> SampledCalls;
|
|
std::unordered_map<uint64_t, std::unordered_set<uint64_t>> SampledTailCalls;
|
|
|
|
// Populate SampledCalls based on static call sites. Similarly to
|
|
// SampledTailCalls.
|
|
for (const auto &CI : *SampleCounters) {
|
|
for (auto Item : CI.second.BranchCounter) {
|
|
auto From = Item.first.first;
|
|
auto To = Item.first.second;
|
|
if (CallEdges.count(From)) {
|
|
assert(CallEdges[From].size() == 1 &&
|
|
"A callsite should only appear once with either a known or a "
|
|
"zero (unknown) target value at this point");
|
|
SampledCalls[From].insert(To);
|
|
}
|
|
if (TailCallEdges.count(From)) {
|
|
assert(TailCallEdges[From].size() == 1 &&
|
|
"A callsite should only appear once with either a known or a "
|
|
"zero (unknown) target value at this point");
|
|
FuncRange *FromFRange = Binary->findFuncRange(From);
|
|
FuncRange *ToFRange = Binary->findFuncRange(To);
|
|
if (FromFRange != ToFRange)
|
|
SampledTailCalls[From].insert(To);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Replace static edges with dynamic edges.
|
|
CallEdges = SampledCalls;
|
|
TailCallEdges = SampledTailCalls;
|
|
}
|
|
|
|
// Populate function-based edges. This is to speed up address to function
|
|
// translation.
|
|
for (auto Call : CallEdges)
|
|
for (auto Target : Call.second)
|
|
if (FuncRange *ToFRange = Binary->findFuncRange(Target))
|
|
CallEdgesF[Call.first].insert(ToFRange->Func);
|
|
|
|
for (auto Call : TailCallEdges) {
|
|
for (auto Target : Call.second) {
|
|
if (FuncRange *ToFRange = Binary->findFuncRange(Target)) {
|
|
TailCallEdgesF[Call.first].insert(ToFRange->Func);
|
|
TailCallTargetFuncs.insert(ToFRange->Func);
|
|
}
|
|
}
|
|
if (FuncRange *FromFRange = Binary->findFuncRange(Call.first))
|
|
FuncToTailCallMap[FromFRange->Func].push_back(Call.first);
|
|
}
|
|
|
|
#if LLVM_ENABLE_STATS
|
|
for (auto F : FuncToTailCallMap) {
|
|
assert(F.second.size() > 0 && "");
|
|
if (F.second.size() > 1)
|
|
TailCallFuncMultipleTailCalls++;
|
|
else
|
|
TailCallFuncSingleTailCalls++;
|
|
}
|
|
#endif
|
|
|
|
#ifndef NDEBUG
|
|
auto PrintCallTargets =
|
|
[&](const std::unordered_map<uint64_t, std::unordered_set<uint64_t>>
|
|
&CallTargets,
|
|
bool IsTailCall) {
|
|
for (const auto &Targets : CallTargets) {
|
|
for (const auto &Target : Targets.second) {
|
|
dbgs() << (IsTailCall ? "TailCall" : "Call");
|
|
dbgs() << " From " << format("%8" PRIx64, Targets.first) << " to "
|
|
<< format("%8" PRIx64, Target) << "\n";
|
|
}
|
|
}
|
|
};
|
|
|
|
LLVM_DEBUG(dbgs() << "============================\n ";
|
|
dbgs() << "Call targets:\n";
|
|
PrintCallTargets(CallEdges, false);
|
|
dbgs() << "\nTail call targets:\n";
|
|
PrintCallTargets(CallEdges, true);
|
|
dbgs() << "============================\n";);
|
|
#endif
|
|
}
|
|
|
|
uint64_t MissingFrameInferrer::computeUniqueTailCallPath(
|
|
BinaryFunction *From, BinaryFunction *To, SmallVectorImpl<uint64_t> &Path) {
|
|
// Search for a unique path comprised of only tail call edges for a given
|
|
// source and target frame address on the a tail call graph that consists of
|
|
// only tail call edges. Note that only a unique path counts. Multiple paths
|
|
// are treated unreachable.
|
|
if (From == To)
|
|
return 1;
|
|
|
|
// Ignore cyclic paths. Since we are doing a recursive DFS walk, if the source
|
|
// frame being visited is already in the stack, it means we are seeing a
|
|
// cycle. This is done before querying the cached result because the cached
|
|
// result may be computed based on the same path. Consider the following case:
|
|
// A -> B, B -> A, A -> D
|
|
// When computing unique reachablity from A to D, the cached result for (B,D)
|
|
// should not be counted since the unique path B->A->D is basically the same
|
|
// path as A->D. Counting that with invalidate the uniqueness from A to D.
|
|
if (Visiting.contains(From))
|
|
return 0;
|
|
|
|
// If already computed, return the cached result.
|
|
auto I = UniquePaths.find({From, To});
|
|
if (I != UniquePaths.end()) {
|
|
Path.append(I->second.begin(), I->second.end());
|
|
return 1;
|
|
}
|
|
|
|
auto J = NonUniquePaths.find({From, To});
|
|
if (J != NonUniquePaths.end()) {
|
|
return J->second;
|
|
}
|
|
|
|
uint64_t Pos = Path.size();
|
|
|
|
// DFS walk each outgoing tail call edges.
|
|
// Bail out if we are already at the the maximum searching depth.
|
|
if (CurSearchingDepth == MaximumSearchDepth)
|
|
return 0;
|
|
|
|
|
|
if (!FuncToTailCallMap.count(From))
|
|
return 0;
|
|
|
|
CurSearchingDepth++;
|
|
Visiting.insert(From);
|
|
uint64_t NumPaths = 0;
|
|
for (auto TailCall : FuncToTailCallMap[From]) {
|
|
NumPaths += computeUniqueTailCallPath(TailCall, To, Path);
|
|
// Stop analyzing the remaining if we are already seeing more than one
|
|
// reachable paths.
|
|
if (NumPaths > 1)
|
|
break;
|
|
}
|
|
CurSearchingDepth--;
|
|
Visiting.erase(From);
|
|
|
|
// Undo already-computed path if it is not unique.
|
|
if (NumPaths != 1) {
|
|
Path.pop_back_n(Path.size() - Pos);
|
|
}
|
|
|
|
// Cache the result.
|
|
if (NumPaths == 1) {
|
|
UniquePaths[{From, To}].assign(Path.begin() + Pos, Path.end());
|
|
#if LLVM_ENABLE_STATS
|
|
auto &LocalPath = UniquePaths[{From, To}];
|
|
assert((LocalPath.size() <= MaximumSearchDepth + 1) &&
|
|
"Path should not be longer than the maximum searching depth");
|
|
TailCallMaxTailCallPath = std::max(uint64_t(LocalPath.size()),
|
|
TailCallMaxTailCallPath.getValue());
|
|
#endif
|
|
} else {
|
|
NonUniquePaths[{From, To}] = NumPaths;
|
|
}
|
|
|
|
return NumPaths;
|
|
}
|
|
|
|
uint64_t MissingFrameInferrer::computeUniqueTailCallPath(
|
|
uint64_t From, BinaryFunction *To, SmallVectorImpl<uint64_t> &Path) {
|
|
if (!TailCallEdgesF.count(From))
|
|
return 0;
|
|
Path.push_back(From);
|
|
uint64_t NumPaths = 0;
|
|
for (auto Target : TailCallEdgesF[From]) {
|
|
NumPaths += computeUniqueTailCallPath(Target, To, Path);
|
|
// Stop analyzing the remaining if we are already seeing more than one
|
|
// reachable paths.
|
|
if (NumPaths > 1)
|
|
break;
|
|
}
|
|
|
|
// Undo already-computed path if it is not unique.
|
|
if (NumPaths != 1)
|
|
Path.pop_back();
|
|
return NumPaths;
|
|
}
|
|
|
|
bool MissingFrameInferrer::inferMissingFrames(
|
|
uint64_t From, uint64_t To, SmallVectorImpl<uint64_t> &UniquePath) {
|
|
assert(!TailCallEdgesF.count(From) &&
|
|
"transition between From and To cannot be via a tailcall otherwise "
|
|
"they would not show up at the same time");
|
|
UniquePath.push_back(From);
|
|
uint64_t Pos = UniquePath.size();
|
|
|
|
FuncRange *ToFRange = Binary->findFuncRange(To);
|
|
if (!ToFRange)
|
|
return false;
|
|
|
|
// Bail out if caller has no known outgoing call edges.
|
|
if (!CallEdgesF.count(From))
|
|
return false;
|
|
|
|
// Done with the inference if the calle is reachable via a single callsite.
|
|
// This may not be accurate but it improves the search throughput.
|
|
if (llvm::is_contained(CallEdgesF[From], ToFRange->Func))
|
|
return true;
|
|
|
|
// Bail out if callee is not tailcall reachable at all.
|
|
if (!TailCallTargetFuncs.contains(ToFRange->Func))
|
|
return false;
|
|
|
|
Visiting.clear();
|
|
CurSearchingDepth = 0;
|
|
uint64_t NumPaths = 0;
|
|
for (auto Target : CallEdgesF[From]) {
|
|
NumPaths +=
|
|
computeUniqueTailCallPath(Target, ToFRange->Func, UniquePath);
|
|
// Stop analyzing the remaining if we are already seeing more than one
|
|
// reachable paths.
|
|
if (NumPaths > 1)
|
|
break;
|
|
}
|
|
|
|
// Undo already-computed path if it is not unique.
|
|
if (NumPaths != 1) {
|
|
UniquePath.pop_back_n(UniquePath.size() - Pos);
|
|
assert(UniquePath.back() == From && "broken path");
|
|
}
|
|
|
|
#if LLVM_ENABLE_STATS
|
|
if (NumPaths == 1) {
|
|
if (ReachableViaUniquePaths.insert({From, ToFRange->StartAddress}).second)
|
|
TailCallUniReachable++;
|
|
} else if (NumPaths == 0) {
|
|
if (Unreachables.insert({From, ToFRange->StartAddress}).second) {
|
|
TailCallUnreachable++;
|
|
LLVM_DEBUG(dbgs() << "No path found from "
|
|
<< format("%8" PRIx64 ":", From) << " to "
|
|
<< format("%8" PRIx64 ":", ToFRange->StartAddress)
|
|
<< "\n");
|
|
}
|
|
} else if (NumPaths > 1) {
|
|
if (ReachableViaMultiPaths.insert({From, ToFRange->StartAddress})
|
|
.second) {
|
|
TailCallMultiReachable++;
|
|
LLVM_DEBUG(dbgs() << "Multiple paths found from "
|
|
<< format("%8" PRIx64 ":", From) << " to "
|
|
<< format("%8" PRIx64 ":", ToFRange->StartAddress)
|
|
<< "\n");
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return NumPaths == 1;
|
|
}
|
|
|
|
void MissingFrameInferrer::inferMissingFrames(
|
|
const SmallVectorImpl<uint64_t> &Context,
|
|
SmallVectorImpl<uint64_t> &NewContext) {
|
|
if (Context.size() == 1) {
|
|
NewContext = Context;
|
|
return;
|
|
}
|
|
|
|
NewContext.clear();
|
|
for (uint64_t I = 1; I < Context.size(); I++) {
|
|
inferMissingFrames(Context[I - 1], Context[I], NewContext);
|
|
}
|
|
NewContext.push_back(Context.back());
|
|
|
|
assert((NewContext.size() >= Context.size()) &&
|
|
"Inferred context should include all frames in the original context");
|
|
assert((NewContext.size() > Context.size() || NewContext == Context) &&
|
|
"Inferred context should be exactly the same "
|
|
"with the original context");
|
|
}
|