mirror of
https://github.com/RPCS3/llvm-mirror.git
synced 2024-11-26 04:32:44 +01:00
ba7695d4ea
This change compresses the context string by removing cycles due to recursive function for CS profile generation. Removing recursion cycles is a way to normalize the calling context which will be better for the sample aggregation and also make the context promoting deterministic. Specifically for implementation, we recognize adjacent repeated frames as cycles and deduplicated them through multiple round of iteration. For example: Considering a input context string stack: [“a”, “a”, “b”, “c”, “a”, “b”, “c”, “b”, “c”, “d”] For first iteration,, it removed all adjacent repeated frames of size 1: [“a”, “b”, “c”, “a”, “b”, “c”, “b”, “c”, “d”] For second iteration, it removed all adjacent repeated frames of size 2: [“a”, “b”, “c”, “a”, “b”, “c”, “d”] So in the end, we get compressed output: [“a”, “b”, “c”, “d”] Compression will be called in two place: one for sample's context key right after unwinding, one is for the eventual context string id in the ProfileGenerator. Added a switch `compress-recursion` to control the size of duplicated frames, default -1 means no size limit. Added unit tests and regression test for this. Differential Revision: https://reviews.llvm.org/D93556
345 lines
12 KiB
C++
345 lines
12 KiB
C++
//===--- PseudoProbe.cpp - Pseudo probe decoding utilities ------*- 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 "PseudoProbe.h"
|
|
#include "ErrorHandling.h"
|
|
#include "llvm/Support/Endian.h"
|
|
#include "llvm/Support/LEB128.h"
|
|
#include "llvm/Support/raw_ostream.h"
|
|
#include <limits>
|
|
#include <memory>
|
|
|
|
using namespace llvm;
|
|
using namespace sampleprof;
|
|
using namespace support;
|
|
|
|
namespace llvm {
|
|
namespace sampleprof {
|
|
|
|
static StringRef getProbeFNameForGUID(const GUIDProbeFunctionMap &GUID2FuncMAP,
|
|
uint64_t GUID) {
|
|
auto It = GUID2FuncMAP.find(GUID);
|
|
assert(It != GUID2FuncMAP.end() &&
|
|
"Probe function must exist for a valid GUID");
|
|
return It->second.FuncName;
|
|
}
|
|
|
|
void PseudoProbeFuncDesc::print(raw_ostream &OS) {
|
|
OS << "GUID: " << FuncGUID << " Name: " << FuncName << "\n";
|
|
OS << "Hash: " << FuncHash << "\n";
|
|
}
|
|
|
|
void PseudoProbe::getInlineContext(SmallVectorImpl<std::string> &ContextStack,
|
|
const GUIDProbeFunctionMap &GUID2FuncMAP,
|
|
bool ShowName) const {
|
|
uint32_t Begin = ContextStack.size();
|
|
PseudoProbeInlineTree *Cur = InlineTree;
|
|
// It will add the string of each node's inline site during iteration.
|
|
// Note that it won't include the probe's belonging function(leaf location)
|
|
while (Cur->hasInlineSite()) {
|
|
std::string ContextStr;
|
|
if (ShowName) {
|
|
StringRef FuncName =
|
|
getProbeFNameForGUID(GUID2FuncMAP, std::get<0>(Cur->ISite));
|
|
ContextStr += FuncName.str();
|
|
} else {
|
|
ContextStr += Twine(std::get<0>(Cur->ISite)).str();
|
|
}
|
|
ContextStr += ":";
|
|
ContextStr += Twine(std::get<1>(Cur->ISite)).str();
|
|
ContextStack.emplace_back(ContextStr);
|
|
Cur = Cur->Parent;
|
|
}
|
|
// Make the ContextStack in caller-callee order
|
|
std::reverse(ContextStack.begin() + Begin, ContextStack.end());
|
|
}
|
|
|
|
std::string
|
|
PseudoProbe::getInlineContextStr(const GUIDProbeFunctionMap &GUID2FuncMAP,
|
|
bool ShowName) const {
|
|
std::ostringstream OContextStr;
|
|
SmallVector<std::string, 16> ContextStack;
|
|
getInlineContext(ContextStack, GUID2FuncMAP, ShowName);
|
|
for (auto &CxtStr : ContextStack) {
|
|
if (OContextStr.str().size())
|
|
OContextStr << " @ ";
|
|
OContextStr << CxtStr;
|
|
}
|
|
return OContextStr.str();
|
|
}
|
|
|
|
static const char *PseudoProbeTypeStr[3] = {"Block", "IndirectCall",
|
|
"DirectCall"};
|
|
|
|
void PseudoProbe::print(raw_ostream &OS,
|
|
const GUIDProbeFunctionMap &GUID2FuncMAP,
|
|
bool ShowName) {
|
|
OS << "FUNC: ";
|
|
if (ShowName) {
|
|
StringRef FuncName = getProbeFNameForGUID(GUID2FuncMAP, GUID);
|
|
OS << FuncName.str() << " ";
|
|
} else {
|
|
OS << GUID << " ";
|
|
}
|
|
OS << "Index: " << Index << " ";
|
|
OS << "Type: " << PseudoProbeTypeStr[static_cast<uint8_t>(Type)] << " ";
|
|
if (isDangling()) {
|
|
OS << "Dangling ";
|
|
}
|
|
if (isTailCall()) {
|
|
OS << "TailCall ";
|
|
}
|
|
std::string InlineContextStr = getInlineContextStr(GUID2FuncMAP, ShowName);
|
|
if (InlineContextStr.size()) {
|
|
OS << "Inlined: @ ";
|
|
OS << InlineContextStr;
|
|
}
|
|
OS << "\n";
|
|
}
|
|
|
|
template <typename T> T PseudoProbeDecoder::readUnencodedNumber() {
|
|
if (Data + sizeof(T) > End) {
|
|
exitWithError("Decode unencoded number error in " + SectionName +
|
|
" section");
|
|
}
|
|
T Val = endian::readNext<T, little, unaligned>(Data);
|
|
return Val;
|
|
}
|
|
|
|
template <typename T> T PseudoProbeDecoder::readUnsignedNumber() {
|
|
unsigned NumBytesRead = 0;
|
|
uint64_t Val = decodeULEB128(Data, &NumBytesRead);
|
|
if (Val > std::numeric_limits<T>::max() || (Data + NumBytesRead > End)) {
|
|
exitWithError("Decode number error in " + SectionName + " section");
|
|
}
|
|
Data += NumBytesRead;
|
|
return static_cast<T>(Val);
|
|
}
|
|
|
|
template <typename T> T PseudoProbeDecoder::readSignedNumber() {
|
|
unsigned NumBytesRead = 0;
|
|
int64_t Val = decodeSLEB128(Data, &NumBytesRead);
|
|
if (Val > std::numeric_limits<T>::max() || (Data + NumBytesRead > End)) {
|
|
exitWithError("Decode number error in " + SectionName + " section");
|
|
}
|
|
Data += NumBytesRead;
|
|
return static_cast<T>(Val);
|
|
}
|
|
|
|
StringRef PseudoProbeDecoder::readString(uint32_t Size) {
|
|
StringRef Str(reinterpret_cast<const char *>(Data), Size);
|
|
if (Data + Size > End) {
|
|
exitWithError("Decode string error in " + SectionName + " section");
|
|
}
|
|
Data += Size;
|
|
return Str;
|
|
}
|
|
|
|
void PseudoProbeDecoder::buildGUID2FuncDescMap(const uint8_t *Start,
|
|
std::size_t Size) {
|
|
// The pseudo_probe_desc section has a format like:
|
|
// .section .pseudo_probe_desc,"",@progbits
|
|
// .quad -5182264717993193164 // GUID
|
|
// .quad 4294967295 // Hash
|
|
// .uleb 3 // Name size
|
|
// .ascii "foo" // Name
|
|
// .quad -2624081020897602054
|
|
// .quad 174696971957
|
|
// .uleb 34
|
|
// .ascii "main"
|
|
#ifndef NDEBUG
|
|
SectionName = "pseudo_probe_desc";
|
|
#endif
|
|
Data = Start;
|
|
End = Data + Size;
|
|
|
|
while (Data < End) {
|
|
uint64_t GUID = readUnencodedNumber<uint64_t>();
|
|
uint64_t Hash = readUnencodedNumber<uint64_t>();
|
|
uint32_t NameSize = readUnsignedNumber<uint32_t>();
|
|
StringRef Name = readString(NameSize);
|
|
|
|
// Initialize PseudoProbeFuncDesc and populate it into GUID2FuncDescMap
|
|
GUID2FuncDescMap.emplace(GUID, PseudoProbeFuncDesc(GUID, Hash, Name));
|
|
}
|
|
assert(Data == End && "Have unprocessed data in pseudo_probe_desc section");
|
|
}
|
|
|
|
void PseudoProbeDecoder::buildAddress2ProbeMap(const uint8_t *Start,
|
|
std::size_t Size) {
|
|
// The pseudo_probe section encodes an inline forest and each tree has a
|
|
// format like:
|
|
// FUNCTION BODY (one for each uninlined function present in the text
|
|
// section)
|
|
// GUID (uint64)
|
|
// GUID of the function
|
|
// NPROBES (ULEB128)
|
|
// Number of probes originating from this function.
|
|
// NUM_INLINED_FUNCTIONS (ULEB128)
|
|
// Number of callees inlined into this function, aka number of
|
|
// first-level inlinees
|
|
// PROBE RECORDS
|
|
// A list of NPROBES entries. Each entry contains:
|
|
// INDEX (ULEB128)
|
|
// TYPE (uint4)
|
|
// 0 - block probe, 1 - indirect call, 2 - direct call
|
|
// ATTRIBUTE (uint3)
|
|
// 1 - tail call, 2 - dangling
|
|
// ADDRESS_TYPE (uint1)
|
|
// 0 - code address, 1 - address delta
|
|
// CODE_ADDRESS (uint64 or ULEB128)
|
|
// code address or address delta, depending on Flag
|
|
// INLINED FUNCTION RECORDS
|
|
// A list of NUM_INLINED_FUNCTIONS entries describing each of the
|
|
// inlined callees. Each record contains:
|
|
// INLINE SITE
|
|
// GUID of the inlinee (uint64)
|
|
// Index of the callsite probe (ULEB128)
|
|
// FUNCTION BODY
|
|
// A FUNCTION BODY entry describing the inlined function.
|
|
#ifndef NDEBUG
|
|
SectionName = "pseudo_probe";
|
|
#endif
|
|
Data = Start;
|
|
End = Data + Size;
|
|
|
|
PseudoProbeInlineTree *Root = &DummyInlineRoot;
|
|
PseudoProbeInlineTree *Cur = &DummyInlineRoot;
|
|
uint64_t LastAddr = 0;
|
|
uint32_t Index = 0;
|
|
// A DFS-based decoding
|
|
while (Data < End) {
|
|
// Read inline site for inlinees
|
|
if (Root != Cur) {
|
|
Index = readUnsignedNumber<uint32_t>();
|
|
}
|
|
// Switch/add to a new tree node(inlinee)
|
|
Cur = Cur->getOrAddNode(std::make_tuple(Cur->GUID, Index));
|
|
// Read guid
|
|
Cur->GUID = readUnencodedNumber<uint64_t>();
|
|
// Read number of probes in the current node.
|
|
uint32_t NodeCount = readUnsignedNumber<uint32_t>();
|
|
// Read number of direct inlinees
|
|
Cur->ChildrenToProcess = readUnsignedNumber<uint32_t>();
|
|
// Read all probes in this node
|
|
for (std::size_t I = 0; I < NodeCount; I++) {
|
|
// Read index
|
|
uint32_t Index = readUnsignedNumber<uint32_t>();
|
|
// Read type | flag.
|
|
uint8_t Value = readUnencodedNumber<uint8_t>();
|
|
uint8_t Kind = Value & 0xf;
|
|
uint8_t Attr = (Value & 0x70) >> 4;
|
|
// Read address
|
|
uint64_t Addr = 0;
|
|
if (Value & 0x80) {
|
|
int64_t Offset = readSignedNumber<int64_t>();
|
|
Addr = LastAddr + Offset;
|
|
} else {
|
|
Addr = readUnencodedNumber<int64_t>();
|
|
}
|
|
// Populate Address2ProbesMap
|
|
std::vector<PseudoProbe> &ProbeVec = Address2ProbesMap[Addr];
|
|
ProbeVec.emplace_back(Addr, Cur->GUID, Index, PseudoProbeType(Kind), Attr,
|
|
Cur);
|
|
Cur->addProbes(&ProbeVec.back());
|
|
LastAddr = Addr;
|
|
}
|
|
|
|
// Look for the parent for the next node by subtracting the current
|
|
// node count from tree counts along the parent chain. The first node
|
|
// in the chain that has a non-zero tree count is the target.
|
|
while (Cur != Root) {
|
|
if (Cur->ChildrenToProcess == 0) {
|
|
Cur = Cur->Parent;
|
|
if (Cur != Root) {
|
|
assert(Cur->ChildrenToProcess > 0 &&
|
|
"Should have some unprocessed nodes");
|
|
Cur->ChildrenToProcess -= 1;
|
|
}
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
assert(Data == End && "Have unprocessed data in pseudo_probe section");
|
|
assert(Cur == Root &&
|
|
" Cur should point to root when the forest is fully built up");
|
|
}
|
|
|
|
void PseudoProbeDecoder::printGUID2FuncDescMap(raw_ostream &OS) {
|
|
OS << "Pseudo Probe Desc:\n";
|
|
// Make the output deterministic
|
|
std::map<uint64_t, PseudoProbeFuncDesc> OrderedMap(GUID2FuncDescMap.begin(),
|
|
GUID2FuncDescMap.end());
|
|
for (auto &I : OrderedMap) {
|
|
I.second.print(OS);
|
|
}
|
|
}
|
|
|
|
void PseudoProbeDecoder::printProbeForAddress(raw_ostream &OS,
|
|
uint64_t Address) {
|
|
auto It = Address2ProbesMap.find(Address);
|
|
if (It != Address2ProbesMap.end()) {
|
|
for (auto &Probe : It->second) {
|
|
OS << " [Probe]:\t";
|
|
Probe.print(OS, GUID2FuncDescMap, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
const PseudoProbe *
|
|
PseudoProbeDecoder::getCallProbeForAddr(uint64_t Address) const {
|
|
auto It = Address2ProbesMap.find(Address);
|
|
if (It == Address2ProbesMap.end())
|
|
return nullptr;
|
|
const std::vector<PseudoProbe> &Probes = It->second;
|
|
|
|
const PseudoProbe *CallProbe = nullptr;
|
|
for (const auto &Probe : Probes) {
|
|
if (Probe.isCall()) {
|
|
assert(!CallProbe &&
|
|
"There should be only one call probe corresponding to address "
|
|
"which is a callsite.");
|
|
CallProbe = &Probe;
|
|
}
|
|
}
|
|
return CallProbe;
|
|
}
|
|
|
|
const PseudoProbeFuncDesc *
|
|
PseudoProbeDecoder::getFuncDescForGUID(uint64_t GUID) const {
|
|
auto It = GUID2FuncDescMap.find(GUID);
|
|
assert(It != GUID2FuncDescMap.end() && "Function descriptor doesn't exist");
|
|
return &It->second;
|
|
}
|
|
|
|
void PseudoProbeDecoder::getInlineContextForProbe(
|
|
const PseudoProbe *Probe, SmallVectorImpl<std::string> &InlineContextStack,
|
|
bool IncludeLeaf) const {
|
|
Probe->getInlineContext(InlineContextStack, GUID2FuncDescMap, true);
|
|
if (!IncludeLeaf)
|
|
return;
|
|
// Note that the context from probe doesn't include leaf frame,
|
|
// hence we need to retrieve and prepend leaf if requested.
|
|
const auto *FuncDesc = getFuncDescForGUID(Probe->GUID);
|
|
InlineContextStack.emplace_back(FuncDesc->FuncName + ":" +
|
|
Twine(Probe->Index).str());
|
|
}
|
|
|
|
const PseudoProbeFuncDesc *
|
|
PseudoProbeDecoder::getInlinerDescForProbe(const PseudoProbe *Probe) const {
|
|
PseudoProbeInlineTree *InlinerNode = Probe->InlineTree;
|
|
if (!InlinerNode->hasInlineSite())
|
|
return nullptr;
|
|
return getFuncDescForGUID(std::get<0>(InlinerNode->ISite));
|
|
}
|
|
|
|
} // end namespace sampleprof
|
|
} // end namespace llvm
|