blob: d3753d1e8a99bb8210f9189ec69ab8dcf09567df [file] [log] [blame]
//===- SampleProfReader.cpp - Read LLVM sample profile data ---------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
//
// This file implements the class that reads LLVM sample profiles. It
// supports three file formats: text, binary and gcov.
//
// The textual representation is useful for debugging and testing purposes. The
// binary representation is more compact, resulting in smaller file sizes.
//
// The gcov encoding is the one generated by GCC's AutoFDO profile creation
// tool (https://github.com/google/autofdo)
//
// All three encodings can be used interchangeably as an input sample profile.
//
//===----------------------------------------------------------------------===//
#include "llvm/ProfileData/SampleProfReader.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/IR/Module.h"
#include "llvm/IR/ProfileSummary.h"
#include "llvm/ProfileData/ProfileCommon.h"
#include "llvm/ProfileData/SampleProf.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/Compression.h"
#include "llvm/Support/ErrorOr.h"
#include "llvm/Support/JSON.h"
#include "llvm/Support/LEB128.h"
#include "llvm/Support/LineIterator.h"
#include "llvm/Support/MD5.h"
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/raw_ostream.h"
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <limits>
#include <memory>
#include <system_error>
#include <vector>
using namespace llvm;
using namespace sampleprof;
#define DEBUG_TYPE "samplepgo-reader"
// This internal option specifies if the profile uses FS discriminators.
// It only applies to text, binary and compact binary format profiles.
// For ext-binary format profiles, the flag is set in the summary.
static cl::opt<bool> ProfileIsFSDisciminator(
"profile-isfs", cl::Hidden, cl::init(false),
cl::desc("Profile uses flow sensitive discriminators"));
/// Dump the function profile for \p FName.
///
/// \param FContext Name + context of the function to print.
/// \param OS Stream to emit the output to.
void SampleProfileReader::dumpFunctionProfile(SampleContext FContext,
raw_ostream &OS) {
OS << "Function: " << FContext.toString() << ": " << Profiles[FContext];
}
/// Dump all the function profiles found on stream \p OS.
void SampleProfileReader::dump(raw_ostream &OS) {
std::vector<NameFunctionSamples> V;
sortFuncProfiles(Profiles, V);
for (const auto &I : V)
dumpFunctionProfile(I.first, OS);
}
static void dumpFunctionProfileJson(const FunctionSamples &S,
json::OStream &JOS, bool TopLevel = false) {
auto DumpBody = [&](const BodySampleMap &BodySamples) {
for (const auto &I : BodySamples) {
const LineLocation &Loc = I.first;
const SampleRecord &Sample = I.second;
JOS.object([&] {
JOS.attribute("line", Loc.LineOffset);
if (Loc.Discriminator)
JOS.attribute("discriminator", Loc.Discriminator);
JOS.attribute("samples", Sample.getSamples());
auto CallTargets = Sample.getSortedCallTargets();
if (!CallTargets.empty()) {
JOS.attributeArray("calls", [&] {
for (const auto &J : CallTargets) {
JOS.object([&] {
JOS.attribute("function", J.first);
JOS.attribute("samples", J.second);
});
}
});
}
});
}
};
auto DumpCallsiteSamples = [&](const CallsiteSampleMap &CallsiteSamples) {
for (const auto &I : CallsiteSamples)
for (const auto &FS : I.second) {
const LineLocation &Loc = I.first;
const FunctionSamples &CalleeSamples = FS.second;
JOS.object([&] {
JOS.attribute("line", Loc.LineOffset);
if (Loc.Discriminator)
JOS.attribute("discriminator", Loc.Discriminator);
JOS.attributeArray(
"samples", [&] { dumpFunctionProfileJson(CalleeSamples, JOS); });
});
}
};
JOS.object([&] {
JOS.attribute("name", S.getName());
JOS.attribute("total", S.getTotalSamples());
if (TopLevel)
JOS.attribute("head", S.getHeadSamples());
const auto &BodySamples = S.getBodySamples();
if (!BodySamples.empty())
JOS.attributeArray("body", [&] { DumpBody(BodySamples); });
const auto &CallsiteSamples = S.getCallsiteSamples();
if (!CallsiteSamples.empty())
JOS.attributeArray("callsites",
[&] { DumpCallsiteSamples(CallsiteSamples); });
});
}
/// Dump all the function profiles found on stream \p OS in the JSON format.
void SampleProfileReader::dumpJson(raw_ostream &OS) {
std::vector<NameFunctionSamples> V;
sortFuncProfiles(Profiles, V);
json::OStream JOS(OS, 2);
JOS.arrayBegin();
for (const auto &F : V)
dumpFunctionProfileJson(*F.second, JOS, true);
JOS.arrayEnd();
// Emit a newline character at the end as json::OStream doesn't emit one.
OS << "\n";
}
/// Parse \p Input as function head.
///
/// Parse one line of \p Input, and update function name in \p FName,
/// function's total sample count in \p NumSamples, function's entry
/// count in \p NumHeadSamples.
///
/// \returns true if parsing is successful.
static bool ParseHead(const StringRef &Input, StringRef &FName,
uint64_t &NumSamples, uint64_t &NumHeadSamples) {
if (Input[0] == ' ')
return false;
size_t n2 = Input.rfind(':');
size_t n1 = Input.rfind(':', n2 - 1);
FName = Input.substr(0, n1);
if (Input.substr(n1 + 1, n2 - n1 - 1).getAsInteger(10, NumSamples))
return false;
if (Input.substr(n2 + 1).getAsInteger(10, NumHeadSamples))
return false;
return true;
}
/// Returns true if line offset \p L is legal (only has 16 bits).
static bool isOffsetLegal(unsigned L) { return (L & 0xffff) == L; }
/// Parse \p Input that contains metadata.
/// Possible metadata:
/// - CFG Checksum information:
/// !CFGChecksum: 12345
/// - CFG Checksum information:
/// !Attributes: 1
/// Stores the FunctionHash (a.k.a. CFG Checksum) into \p FunctionHash.
static bool parseMetadata(const StringRef &Input, uint64_t &FunctionHash,
uint32_t &Attributes) {
if (Input.startswith("!CFGChecksum:")) {
StringRef CFGInfo = Input.substr(strlen("!CFGChecksum:")).trim();
return !CFGInfo.getAsInteger(10, FunctionHash);
}
if (Input.startswith("!Attributes:")) {
StringRef Attrib = Input.substr(strlen("!Attributes:")).trim();
return !Attrib.getAsInteger(10, Attributes);
}
return false;
}
enum class LineType {
CallSiteProfile,
BodyProfile,
Metadata,
};
/// Parse \p Input as line sample.
///
/// \param Input input line.
/// \param LineTy Type of this line.
/// \param Depth the depth of the inline stack.
/// \param NumSamples total samples of the line/inlined callsite.
/// \param LineOffset line offset to the start of the function.
/// \param Discriminator discriminator of the line.
/// \param TargetCountMap map from indirect call target to count.
/// \param FunctionHash the function's CFG hash, used by pseudo probe.
///
/// returns true if parsing is successful.
static bool ParseLine(const StringRef &Input, LineType &LineTy, uint32_t &Depth,
uint64_t &NumSamples, uint32_t &LineOffset,
uint32_t &Discriminator, StringRef &CalleeName,
DenseMap<StringRef, uint64_t> &TargetCountMap,
uint64_t &FunctionHash, uint32_t &Attributes) {
for (Depth = 0; Input[Depth] == ' '; Depth++)
;
if (Depth == 0)
return false;
if (Input[Depth] == '!') {
LineTy = LineType::Metadata;
return parseMetadata(Input.substr(Depth), FunctionHash, Attributes);
}
size_t n1 = Input.find(':');
StringRef Loc = Input.substr(Depth, n1 - Depth);
size_t n2 = Loc.find('.');
if (n2 == StringRef::npos) {
if (Loc.getAsInteger(10, LineOffset) || !isOffsetLegal(LineOffset))
return false;
Discriminator = 0;
} else {
if (Loc.substr(0, n2).getAsInteger(10, LineOffset))
return false;
if (Loc.substr(n2 + 1).getAsInteger(10, Discriminator))
return false;
}
StringRef Rest = Input.substr(n1 + 2);
if (isDigit(Rest[0])) {
LineTy = LineType::BodyProfile;
size_t n3 = Rest.find(' ');
if (n3 == StringRef::npos) {
if (Rest.getAsInteger(10, NumSamples))
return false;
} else {
if (Rest.substr(0, n3).getAsInteger(10, NumSamples))
return false;
}
// Find call targets and their sample counts.
// Note: In some cases, there are symbols in the profile which are not
// mangled. To accommodate such cases, use colon + integer pairs as the
// anchor points.
// An example:
// _M_construct<char *>:1000 string_view<std::allocator<char> >:437
// ":1000" and ":437" are used as anchor points so the string above will
// be interpreted as
// target: _M_construct<char *>
// count: 1000
// target: string_view<std::allocator<char> >
// count: 437
while (n3 != StringRef::npos) {
n3 += Rest.substr(n3).find_first_not_of(' ');
Rest = Rest.substr(n3);
n3 = Rest.find_first_of(':');
if (n3 == StringRef::npos || n3 == 0)
return false;
StringRef Target;
uint64_t count, n4;
while (true) {
// Get the segment after the current colon.
StringRef AfterColon = Rest.substr(n3 + 1);
// Get the target symbol before the current colon.
Target = Rest.substr(0, n3);
// Check if the word after the current colon is an integer.
n4 = AfterColon.find_first_of(' ');
n4 = (n4 != StringRef::npos) ? n3 + n4 + 1 : Rest.size();
StringRef WordAfterColon = Rest.substr(n3 + 1, n4 - n3 - 1);
if (!WordAfterColon.getAsInteger(10, count))
break;
// Try to find the next colon.
uint64_t n5 = AfterColon.find_first_of(':');
if (n5 == StringRef::npos)
return false;
n3 += n5 + 1;
}
// An anchor point is found. Save the {target, count} pair
TargetCountMap[Target] = count;
if (n4 == Rest.size())
break;
// Change n3 to the next blank space after colon + integer pair.
n3 = n4;
}
} else {
LineTy = LineType::CallSiteProfile;
size_t n3 = Rest.find_last_of(':');
CalleeName = Rest.substr(0, n3);
if (Rest.substr(n3 + 1).getAsInteger(10, NumSamples))
return false;
}
return true;
}
/// Load samples from a text file.
///
/// See the documentation at the top of the file for an explanation of
/// the expected format.
///
/// \returns true if the file was loaded successfully, false otherwise.
std::error_code SampleProfileReaderText::readImpl() {
line_iterator LineIt(*Buffer, /*SkipBlanks=*/true, '#');
sampleprof_error Result = sampleprof_error::success;
InlineCallStack InlineStack;
uint32_t TopLevelProbeProfileCount = 0;
// DepthMetadata tracks whether we have processed metadata for the current
// top-level or nested function profile.
uint32_t DepthMetadata = 0;
ProfileIsFS = ProfileIsFSDisciminator;
FunctionSamples::ProfileIsFS = ProfileIsFS;
for (; !LineIt.is_at_eof(); ++LineIt) {
if ((*LineIt)[(*LineIt).find_first_not_of(' ')] == '#')
continue;
// Read the header of each function.
//
// Note that for function identifiers we are actually expecting
// mangled names, but we may not always get them. This happens when
// the compiler decides not to emit the function (e.g., it was inlined
// and removed). In this case, the binary will not have the linkage
// name for the function, so the profiler will emit the function's
// unmangled name, which may contain characters like ':' and '>' in its
// name (member functions, templates, etc).
//
// The only requirement we place on the identifier, then, is that it
// should not begin with a number.
if ((*LineIt)[0] != ' ') {
uint64_t NumSamples, NumHeadSamples;
StringRef FName;
if (!ParseHead(*LineIt, FName, NumSamples, NumHeadSamples)) {
reportError(LineIt.line_number(),
"Expected 'mangled_name:NUM:NUM', found " + *LineIt);
return sampleprof_error::malformed;
}
DepthMetadata = 0;
SampleContext FContext(FName, CSNameTable);
if (FContext.hasContext())
++CSProfileCount;
Profiles[FContext] = FunctionSamples();
FunctionSamples &FProfile = Profiles[FContext];
FProfile.setContext(FContext);
MergeResult(Result, FProfile.addTotalSamples(NumSamples));
MergeResult(Result, FProfile.addHeadSamples(NumHeadSamples));
InlineStack.clear();
InlineStack.push_back(&FProfile);
} else {
uint64_t NumSamples;
StringRef FName;
DenseMap<StringRef, uint64_t> TargetCountMap;
uint32_t Depth, LineOffset, Discriminator;
LineType LineTy;
uint64_t FunctionHash = 0;
uint32_t Attributes = 0;
if (!ParseLine(*LineIt, LineTy, Depth, NumSamples, LineOffset,
Discriminator, FName, TargetCountMap, FunctionHash,
Attributes)) {
reportError(LineIt.line_number(),
"Expected 'NUM[.NUM]: NUM[ mangled_name:NUM]*', found " +
*LineIt);
return sampleprof_error::malformed;
}
if (LineTy != LineType::Metadata && Depth == DepthMetadata) {
// Metadata must be put at the end of a function profile.
reportError(LineIt.line_number(),
"Found non-metadata after metadata: " + *LineIt);
return sampleprof_error::malformed;
}
// Here we handle FS discriminators.
Discriminator &= getDiscriminatorMask();
while (InlineStack.size() > Depth) {
InlineStack.pop_back();
}
switch (LineTy) {
case LineType::CallSiteProfile: {
FunctionSamples &FSamples = InlineStack.back()->functionSamplesAt(
LineLocation(LineOffset, Discriminator))[std::string(FName)];
FSamples.setName(FName);
MergeResult(Result, FSamples.addTotalSamples(NumSamples));
InlineStack.push_back(&FSamples);
DepthMetadata = 0;
break;
}
case LineType::BodyProfile: {
while (InlineStack.size() > Depth) {
InlineStack.pop_back();
}
FunctionSamples &FProfile = *InlineStack.back();
for (const auto &name_count : TargetCountMap) {
MergeResult(Result, FProfile.addCalledTargetSamples(
LineOffset, Discriminator, name_count.first,
name_count.second));
}
MergeResult(Result, FProfile.addBodySamples(LineOffset, Discriminator,
NumSamples));
break;
}
case LineType::Metadata: {
FunctionSamples &FProfile = *InlineStack.back();
if (FunctionHash) {
FProfile.setFunctionHash(FunctionHash);
if (Depth == 1)
++TopLevelProbeProfileCount;
}
FProfile.getContext().setAllAttributes(Attributes);
if (Attributes & (uint32_t)ContextShouldBeInlined)
ProfileIsPreInlined = true;
DepthMetadata = Depth;
break;
}
}
}
}
assert((CSProfileCount == 0 || CSProfileCount == Profiles.size()) &&
"Cannot have both context-sensitive and regular profile");
ProfileIsCS = (CSProfileCount > 0);
assert((TopLevelProbeProfileCount == 0 ||
TopLevelProbeProfileCount == Profiles.size()) &&
"Cannot have both probe-based profiles and regular profiles");
ProfileIsProbeBased = (TopLevelProbeProfileCount > 0);
FunctionSamples::ProfileIsProbeBased = ProfileIsProbeBased;
FunctionSamples::ProfileIsCS = ProfileIsCS;
FunctionSamples::ProfileIsPreInlined = ProfileIsPreInlined;
if (Result == sampleprof_error::success)
computeSummary();
return Result;
}
bool SampleProfileReaderText::hasFormat(const MemoryBuffer &Buffer) {
bool result = false;
// Check that the first non-comment line is a valid function header.
line_iterator LineIt(Buffer, /*SkipBlanks=*/true, '#');
if (!LineIt.is_at_eof()) {
if ((*LineIt)[0] != ' ') {
uint64_t NumSamples, NumHeadSamples;
StringRef FName;
result = ParseHead(*LineIt, FName, NumSamples, NumHeadSamples);
}
}
return result;
}
template <typename T> ErrorOr<T> SampleProfileReaderBinary::readNumber() {
unsigned NumBytesRead = 0;
std::error_code EC;
uint64_t Val = decodeULEB128(Data, &NumBytesRead);
if (Val > std::numeric_limits<T>::max())
EC = sampleprof_error::malformed;
else if (Data + NumBytesRead > End)
EC = sampleprof_error::truncated;
else
EC = sampleprof_error::success;
if (EC) {
reportError(0, EC.message());
return EC;
}
Data += NumBytesRead;
return static_cast<T>(Val);
}
ErrorOr<StringRef> SampleProfileReaderBinary::readString() {
std::error_code EC;
StringRef Str(reinterpret_cast<const char *>(Data));
if (Data + Str.size() + 1 > End) {
EC = sampleprof_error::truncated;
reportError(0, EC.message());
return EC;
}
Data += Str.size() + 1;
return Str;
}
template <typename T>
ErrorOr<T> SampleProfileReaderBinary::readUnencodedNumber() {
std::error_code EC;
if (Data + sizeof(T) > End) {
EC = sampleprof_error::truncated;
reportError(0, EC.message());
return EC;
}
using namespace support;
T Val = endian::readNext<T, little, unaligned>(Data);
return Val;
}
template <typename T>
inline ErrorOr<uint32_t> SampleProfileReaderBinary::readStringIndex(T &Table) {
std::error_code EC;
auto Idx = readNumber<uint32_t>();
if (std::error_code EC = Idx.getError())
return EC;
if (*Idx >= Table.size())
return sampleprof_error::truncated_name_table;
return *Idx;
}
ErrorOr<StringRef> SampleProfileReaderBinary::readStringFromTable() {
auto Idx = readStringIndex(NameTable);
if (std::error_code EC = Idx.getError())
return EC;
return NameTable[*Idx];
}
ErrorOr<SampleContext> SampleProfileReaderBinary::readSampleContextFromTable() {
auto FName(readStringFromTable());
if (std::error_code EC = FName.getError())
return EC;
return SampleContext(*FName);
}
ErrorOr<StringRef> SampleProfileReaderExtBinaryBase::readStringFromTable() {
if (!FixedLengthMD5)
return SampleProfileReaderBinary::readStringFromTable();
// read NameTable index.
auto Idx = readStringIndex(NameTable);
if (std::error_code EC = Idx.getError())
return EC;
// Check whether the name to be accessed has been accessed before,
// if not, read it from memory directly.
StringRef &SR = NameTable[*Idx];
if (SR.empty()) {
const uint8_t *SavedData = Data;
Data = MD5NameMemStart + ((*Idx) * sizeof(uint64_t));
auto FID = readUnencodedNumber<uint64_t>();
if (std::error_code EC = FID.getError())
return EC;
// Save the string converted from uint64_t in MD5StringBuf. All the
// references to the name are all StringRefs refering to the string
// in MD5StringBuf.
MD5StringBuf->push_back(std::to_string(*FID));
SR = MD5StringBuf->back();
Data = SavedData;
}
return SR;
}
ErrorOr<StringRef> SampleProfileReaderCompactBinary::readStringFromTable() {
auto Idx = readStringIndex(NameTable);
if (std::error_code EC = Idx.getError())
return EC;
return StringRef(NameTable[*Idx]);
}
std::error_code
SampleProfileReaderBinary::readProfile(FunctionSamples &FProfile) {
auto NumSamples = readNumber<uint64_t>();
if (std::error_code EC = NumSamples.getError())
return EC;
FProfile.addTotalSamples(*NumSamples);
// Read the samples in the body.
auto NumRecords = readNumber<uint32_t>();
if (std::error_code EC = NumRecords.getError())
return EC;
for (uint32_t I = 0; I < *NumRecords; ++I) {
auto LineOffset = readNumber<uint64_t>();
if (std::error_code EC = LineOffset.getError())
return EC;
if (!isOffsetLegal(*LineOffset)) {
return std::error_code();
}
auto Discriminator = readNumber<uint64_t>();
if (std::error_code EC = Discriminator.getError())
return EC;
auto NumSamples = readNumber<uint64_t>();
if (std::error_code EC = NumSamples.getError())
return EC;
auto NumCalls = readNumber<uint32_t>();
if (std::error_code EC = NumCalls.getError())
return EC;
// Here we handle FS discriminators:
uint32_t DiscriminatorVal = (*Discriminator) & getDiscriminatorMask();
for (uint32_t J = 0; J < *NumCalls; ++J) {
auto CalledFunction(readStringFromTable());
if (std::error_code EC = CalledFunction.getError())
return EC;
auto CalledFunctionSamples = readNumber<uint64_t>();
if (std::error_code EC = CalledFunctionSamples.getError())
return EC;
FProfile.addCalledTargetSamples(*LineOffset, DiscriminatorVal,
*CalledFunction, *CalledFunctionSamples);
}
FProfile.addBodySamples(*LineOffset, DiscriminatorVal, *NumSamples);
}
// Read all the samples for inlined function calls.
auto NumCallsites = readNumber<uint32_t>();
if (std::error_code EC = NumCallsites.getError())
return EC;
for (uint32_t J = 0; J < *NumCallsites; ++J) {
auto LineOffset = readNumber<uint64_t>();
if (std::error_code EC = LineOffset.getError())
return EC;
auto Discriminator = readNumber<uint64_t>();
if (std::error_code EC = Discriminator.getError())
return EC;
auto FName(readStringFromTable());
if (std::error_code EC = FName.getError())
return EC;
// Here we handle FS discriminators:
uint32_t DiscriminatorVal = (*Discriminator) & getDiscriminatorMask();
FunctionSamples &CalleeProfile = FProfile.functionSamplesAt(
LineLocation(*LineOffset, DiscriminatorVal))[std::string(*FName)];
CalleeProfile.setName(*FName);
if (std::error_code EC = readProfile(CalleeProfile))
return EC;
}
return sampleprof_error::success;
}
std::error_code
SampleProfileReaderBinary::readFuncProfile(const uint8_t *Start) {
Data = Start;
auto NumHeadSamples = readNumber<uint64_t>();
if (std::error_code EC = NumHeadSamples.getError())
return EC;
ErrorOr<SampleContext> FContext(readSampleContextFromTable());
if (std::error_code EC = FContext.getError())
return EC;
Profiles[*FContext] = FunctionSamples();
FunctionSamples &FProfile = Profiles[*FContext];
FProfile.setContext(*FContext);
FProfile.addHeadSamples(*NumHeadSamples);
if (FContext->hasContext())
CSProfileCount++;
if (std::error_code EC = readProfile(FProfile))
return EC;
return sampleprof_error::success;
}
std::error_code SampleProfileReaderBinary::readImpl() {
ProfileIsFS = ProfileIsFSDisciminator;
FunctionSamples::ProfileIsFS = ProfileIsFS;
while (!at_eof()) {
if (std::error_code EC = readFuncProfile(Data))
return EC;
}
return sampleprof_error::success;
}
ErrorOr<SampleContextFrames>
SampleProfileReaderExtBinaryBase::readContextFromTable() {
auto ContextIdx = readNumber<uint32_t>();
if (std::error_code EC = ContextIdx.getError())
return EC;
if (*ContextIdx >= CSNameTable->size())
return sampleprof_error::truncated_name_table;
return (*CSNameTable)[*ContextIdx];
}
ErrorOr<SampleContext>
SampleProfileReaderExtBinaryBase::readSampleContextFromTable() {
if (ProfileIsCS) {
auto FContext(readContextFromTable());
if (std::error_code EC = FContext.getError())
return EC;
return SampleContext(*FContext);
} else {
auto FName(readStringFromTable());
if (std::error_code EC = FName.getError())
return EC;
return SampleContext(*FName);
}
}
std::error_code SampleProfileReaderExtBinaryBase::readOneSection(
const uint8_t *Start, uint64_t Size, const SecHdrTableEntry &Entry) {
Data = Start;
End = Start + Size;
switch (Entry.Type) {
case SecProfSummary:
if (std::error_code EC = readSummary())
return EC;
if (hasSecFlag(Entry, SecProfSummaryFlags::SecFlagPartial))
Summary->setPartialProfile(true);
if (hasSecFlag(Entry, SecProfSummaryFlags::SecFlagFullContext))
FunctionSamples::ProfileIsCS = ProfileIsCS = true;
if (hasSecFlag(Entry, SecProfSummaryFlags::SecFlagIsPreInlined))
FunctionSamples::ProfileIsPreInlined = ProfileIsPreInlined = true;
if (hasSecFlag(Entry, SecProfSummaryFlags::SecFlagFSDiscriminator))
FunctionSamples::ProfileIsFS = ProfileIsFS = true;
break;
case SecNameTable: {
FixedLengthMD5 =
hasSecFlag(Entry, SecNameTableFlags::SecFlagFixedLengthMD5);
bool UseMD5 = hasSecFlag(Entry, SecNameTableFlags::SecFlagMD5Name);
assert((!FixedLengthMD5 || UseMD5) &&
"If FixedLengthMD5 is true, UseMD5 has to be true");
FunctionSamples::HasUniqSuffix =
hasSecFlag(Entry, SecNameTableFlags::SecFlagUniqSuffix);
if (std::error_code EC = readNameTableSec(UseMD5))
return EC;
break;
}
case SecCSNameTable: {
if (std::error_code EC = readCSNameTableSec())
return EC;
break;
}
case SecLBRProfile:
if (std::error_code EC = readFuncProfiles())
return EC;
break;
case SecFuncOffsetTable:
FuncOffsetsOrdered = hasSecFlag(Entry, SecFuncOffsetFlags::SecFlagOrdered);
if (std::error_code EC = readFuncOffsetTable())
return EC;
break;
case SecFuncMetadata: {
ProfileIsProbeBased =
hasSecFlag(Entry, SecFuncMetadataFlags::SecFlagIsProbeBased);
FunctionSamples::ProfileIsProbeBased = ProfileIsProbeBased;
bool HasAttribute =
hasSecFlag(Entry, SecFuncMetadataFlags::SecFlagHasAttribute);
if (std::error_code EC = readFuncMetadata(HasAttribute))
return EC;
break;
}
case SecProfileSymbolList:
if (std::error_code EC = readProfileSymbolList())
return EC;
break;
default:
if (std::error_code EC = readCustomSection(Entry))
return EC;
break;
}
return sampleprof_error::success;
}
bool SampleProfileReaderExtBinaryBase::collectFuncsFromModule() {
if (!M)
return false;
FuncsToUse.clear();
for (auto &F : *M)
FuncsToUse.insert(FunctionSamples::getCanonicalFnName(F));
return true;
}
std::error_code SampleProfileReaderExtBinaryBase::readFuncOffsetTable() {
// If there are more than one FuncOffsetTable, the profile read associated
// with previous FuncOffsetTable has to be done before next FuncOffsetTable
// is read.
FuncOffsetTable.clear();
auto Size = readNumber<uint64_t>();
if (std::error_code EC = Size.getError())
return EC;
FuncOffsetTable.reserve(*Size);
if (FuncOffsetsOrdered) {
OrderedFuncOffsets =
std::make_unique<std::vector<std::pair<SampleContext, uint64_t>>>();
OrderedFuncOffsets->reserve(*Size);
}
for (uint64_t I = 0; I < *Size; ++I) {
auto FContext(readSampleContextFromTable());
if (std::error_code EC = FContext.getError())
return EC;
auto Offset = readNumber<uint64_t>();
if (std::error_code EC = Offset.getError())
return EC;
FuncOffsetTable[*FContext] = *Offset;
if (FuncOffsetsOrdered)
OrderedFuncOffsets->emplace_back(*FContext, *Offset);
}
return sampleprof_error::success;
}
std::error_code SampleProfileReaderExtBinaryBase::readFuncProfiles() {
// Collect functions used by current module if the Reader has been
// given a module.
// collectFuncsFromModule uses FunctionSamples::getCanonicalFnName
// which will query FunctionSamples::HasUniqSuffix, so it has to be
// called after FunctionSamples::HasUniqSuffix is set, i.e. after
// NameTable section is read.
bool LoadFuncsToBeUsed = collectFuncsFromModule();
// When LoadFuncsToBeUsed is false, load all the function profiles.
const uint8_t *Start = Data;
if (!LoadFuncsToBeUsed) {
while (Data < End) {
if (std::error_code EC = readFuncProfile(Data))
return EC;
}
assert(Data == End && "More data is read than expected");
} else {
// Load function profiles on demand.
if (Remapper) {
for (auto Name : FuncsToUse) {
Remapper->insert(Name);
}
}
if (ProfileIsCS) {
DenseSet<uint64_t> FuncGuidsToUse;
if (useMD5()) {
for (auto Name : FuncsToUse)
FuncGuidsToUse.insert(Function::getGUID(Name));
}
// For each function in current module, load all context profiles for
// the function as well as their callee contexts which can help profile
// guided importing for ThinLTO. This can be achieved by walking
// through an ordered context container, where contexts are laid out
// as if they were walked in preorder of a context trie. While
// traversing the trie, a link to the highest common ancestor node is
// kept so that all of its decendants will be loaded.
assert(OrderedFuncOffsets.get() &&
"func offset table should always be sorted in CS profile");
const SampleContext *CommonContext = nullptr;
for (const auto &NameOffset : *OrderedFuncOffsets) {
const auto &FContext = NameOffset.first;
auto FName = FContext.getName();
// For function in the current module, keep its farthest ancestor
// context. This can be used to load itself and its child and
// sibling contexts.
if ((useMD5() && FuncGuidsToUse.count(std::stoull(FName.data()))) ||
(!useMD5() && (FuncsToUse.count(FName) ||
(Remapper && Remapper->exist(FName))))) {
if (!CommonContext || !CommonContext->IsPrefixOf(FContext))
CommonContext = &FContext;
}
if (CommonContext == &FContext ||
(CommonContext && CommonContext->IsPrefixOf(FContext))) {
// Load profile for the current context which originated from
// the common ancestor.
const uint8_t *FuncProfileAddr = Start + NameOffset.second;
assert(FuncProfileAddr < End && "out of LBRProfile section");
if (std::error_code EC = readFuncProfile(FuncProfileAddr))
return EC;
}
}
} else {
if (useMD5()) {
for (auto Name : FuncsToUse) {
auto GUID = std::to_string(MD5Hash(Name));
auto iter = FuncOffsetTable.find(StringRef(GUID));
if (iter == FuncOffsetTable.end())
continue;
const uint8_t *FuncProfileAddr = Start + iter->second;
assert(FuncProfileAddr < End && "out of LBRProfile section");
if (std::error_code EC = readFuncProfile(FuncProfileAddr))
return EC;
}
} else {
for (auto NameOffset : FuncOffsetTable) {
SampleContext FContext(NameOffset.first);
auto FuncName = FContext.getName();
if (!FuncsToUse.count(FuncName) &&
(!Remapper || !Remapper->exist(FuncName)))
continue;
const uint8_t *FuncProfileAddr = Start + NameOffset.second;
assert(FuncProfileAddr < End && "out of LBRProfile section");
if (std::error_code EC = readFuncProfile(FuncProfileAddr))
return EC;
}
}
}
Data = End;
}
assert((CSProfileCount == 0 || CSProfileCount == Profiles.size()) &&
"Cannot have both context-sensitive and regular profile");
assert((!CSProfileCount || ProfileIsCS) &&
"Section flag should be consistent with actual profile");
return sampleprof_error::success;
}
std::error_code SampleProfileReaderExtBinaryBase::readProfileSymbolList() {
if (!ProfSymList)
ProfSymList = std::make_unique<ProfileSymbolList>();
if (std::error_code EC = ProfSymList->read(Data, End - Data))
return EC;
Data = End;
return sampleprof_error::success;
}
std::error_code SampleProfileReaderExtBinaryBase::decompressSection(
const uint8_t *SecStart, const uint64_t SecSize,
const uint8_t *&DecompressBuf, uint64_t &DecompressBufSize) {
Data = SecStart;
End = SecStart + SecSize;
auto DecompressSize = readNumber<uint64_t>();
if (std::error_code EC = DecompressSize.getError())
return EC;
DecompressBufSize = *DecompressSize;
auto CompressSize = readNumber<uint64_t>();
if (std::error_code EC = CompressSize.getError())
return EC;
if (!llvm::compression::zlib::isAvailable())
return sampleprof_error::zlib_unavailable;
uint8_t *Buffer = Allocator.Allocate<uint8_t>(DecompressBufSize);
size_t UCSize = DecompressBufSize;
llvm::Error E = compression::zlib::decompress(ArrayRef(Data, *CompressSize),
Buffer, UCSize);
if (E)
return sampleprof_error::uncompress_failed;
DecompressBuf = reinterpret_cast<const uint8_t *>(Buffer);
return sampleprof_error::success;
}
std::error_code SampleProfileReaderExtBinaryBase::readImpl() {
const uint8_t *BufStart =
reinterpret_cast<const uint8_t *>(Buffer->getBufferStart());
for (auto &Entry : SecHdrTable) {
// Skip empty section.
if (!Entry.Size)
continue;
// Skip sections without context when SkipFlatProf is true.
if (SkipFlatProf && hasSecFlag(Entry, SecCommonFlags::SecFlagFlat))
continue;
const uint8_t *SecStart = BufStart + Entry.Offset;
uint64_t SecSize = Entry.Size;
// If the section is compressed, decompress it into a buffer
// DecompressBuf before reading the actual data. The pointee of
// 'Data' will be changed to buffer hold by DecompressBuf
// temporarily when reading the actual data.
bool isCompressed = hasSecFlag(Entry, SecCommonFlags::SecFlagCompress);
if (isCompressed) {
const uint8_t *DecompressBuf;
uint64_t DecompressBufSize;
if (std::error_code EC = decompressSection(
SecStart, SecSize, DecompressBuf, DecompressBufSize))
return EC;
SecStart = DecompressBuf;
SecSize = DecompressBufSize;
}
if (std::error_code EC = readOneSection(SecStart, SecSize, Entry))
return EC;
if (Data != SecStart + SecSize)
return sampleprof_error::malformed;
// Change the pointee of 'Data' from DecompressBuf to original Buffer.
if (isCompressed) {
Data = BufStart + Entry.Offset;
End = BufStart + Buffer->getBufferSize();
}
}
return sampleprof_error::success;
}
std::error_code SampleProfileReaderCompactBinary::readImpl() {
// Collect functions used by current module if the Reader has been
// given a module.
bool LoadFuncsToBeUsed = collectFuncsFromModule();
ProfileIsFS = ProfileIsFSDisciminator;
FunctionSamples::ProfileIsFS = ProfileIsFS;
std::vector<uint64_t> OffsetsToUse;
if (!LoadFuncsToBeUsed) {
// load all the function profiles.
for (auto FuncEntry : FuncOffsetTable) {
OffsetsToUse.push_back(FuncEntry.second);
}
} else {
// load function profiles on demand.
for (auto Name : FuncsToUse) {
auto GUID = std::to_string(MD5Hash(Name));
auto iter = FuncOffsetTable.find(StringRef(GUID));
if (iter == FuncOffsetTable.end())
continue;
OffsetsToUse.push_back(iter->second);
}
}
for (auto Offset : OffsetsToUse) {
const uint8_t *SavedData = Data;
if (std::error_code EC = readFuncProfile(
reinterpret_cast<const uint8_t *>(Buffer->getBufferStart()) +
Offset))
return EC;
Data = SavedData;
}
return sampleprof_error::success;
}
std::error_code SampleProfileReaderRawBinary::verifySPMagic(uint64_t Magic) {
if (Magic == SPMagic())
return sampleprof_error::success;
return sampleprof_error::bad_magic;
}
std::error_code SampleProfileReaderExtBinary::verifySPMagic(uint64_t Magic) {
if (Magic == SPMagic(SPF_Ext_Binary))
return sampleprof_error::success;
return sampleprof_error::bad_magic;
}
std::error_code
SampleProfileReaderCompactBinary::verifySPMagic(uint64_t Magic) {
if (Magic == SPMagic(SPF_Compact_Binary))
return sampleprof_error::success;
return sampleprof_error::bad_magic;
}
std::error_code SampleProfileReaderBinary::readNameTable() {
auto Size = readNumber<uint32_t>();
if (std::error_code EC = Size.getError())
return EC;
NameTable.reserve(*Size + NameTable.size());
for (uint32_t I = 0; I < *Size; ++I) {
auto Name(readString());
if (std::error_code EC = Name.getError())
return EC;
NameTable.push_back(*Name);
}
return sampleprof_error::success;
}
std::error_code SampleProfileReaderExtBinaryBase::readMD5NameTable() {
auto Size = readNumber<uint64_t>();
if (std::error_code EC = Size.getError())
return EC;
MD5StringBuf = std::make_unique<std::vector<std::string>>();
MD5StringBuf->reserve(*Size);
if (FixedLengthMD5) {
// Preallocate and initialize NameTable so we can check whether a name
// index has been read before by checking whether the element in the
// NameTable is empty, meanwhile readStringIndex can do the boundary
// check using the size of NameTable.
NameTable.resize(*Size + NameTable.size());
MD5NameMemStart = Data;
Data = Data + (*Size) * sizeof(uint64_t);
return sampleprof_error::success;
}
NameTable.reserve(*Size);
for (uint64_t I = 0; I < *Size; ++I) {
auto FID = readNumber<uint64_t>();
if (std::error_code EC = FID.getError())
return EC;
MD5StringBuf->push_back(std::to_string(*FID));
// NameTable is a vector of StringRef. Here it is pushing back a
// StringRef initialized with the last string in MD5stringBuf.
NameTable.push_back(MD5StringBuf->back());
}
return sampleprof_error::success;
}
std::error_code SampleProfileReaderExtBinaryBase::readNameTableSec(bool IsMD5) {
if (IsMD5)
return readMD5NameTable();
return SampleProfileReaderBinary::readNameTable();
}
// Read in the CS name table section, which basically contains a list of context
// vectors. Each element of a context vector, aka a frame, refers to the
// underlying raw function names that are stored in the name table, as well as
// a callsite identifier that only makes sense for non-leaf frames.
std::error_code SampleProfileReaderExtBinaryBase::readCSNameTableSec() {
auto Size = readNumber<uint32_t>();
if (std::error_code EC = Size.getError())
return EC;
std::vector<SampleContextFrameVector> *PNameVec =
new std::vector<SampleContextFrameVector>();
PNameVec->reserve(*Size);
for (uint32_t I = 0; I < *Size; ++I) {
PNameVec->emplace_back(SampleContextFrameVector());
auto ContextSize = readNumber<uint32_t>();
if (std::error_code EC = ContextSize.getError())
return EC;
for (uint32_t J = 0; J < *ContextSize; ++J) {
auto FName(readStringFromTable());
if (std::error_code EC = FName.getError())
return EC;
auto LineOffset = readNumber<uint64_t>();
if (std::error_code EC = LineOffset.getError())
return EC;
if (!isOffsetLegal(*LineOffset))
return std::error_code();
auto Discriminator = readNumber<uint64_t>();
if (std::error_code EC = Discriminator.getError())
return EC;
PNameVec->back().emplace_back(
FName.get(), LineLocation(LineOffset.get(), Discriminator.get()));
}
}
// From this point the underlying object of CSNameTable should be immutable.
CSNameTable.reset(PNameVec);
return sampleprof_error::success;
}
std::error_code
SampleProfileReaderExtBinaryBase::readFuncMetadata(bool ProfileHasAttribute,
FunctionSamples *FProfile) {
if (Data < End) {
if (ProfileIsProbeBased) {
auto Checksum = readNumber<uint64_t>();
if (std::error_code EC = Checksum.getError())
return EC;
if (FProfile)
FProfile->setFunctionHash(*Checksum);
}
if (ProfileHasAttribute) {
auto Attributes = readNumber<uint32_t>();
if (std::error_code EC = Attributes.getError())
return EC;
if (FProfile)
FProfile->getContext().setAllAttributes(*Attributes);
}
if (!ProfileIsCS) {
// Read all the attributes for inlined function calls.
auto NumCallsites = readNumber<uint32_t>();
if (std::error_code EC = NumCallsites.getError())
return EC;
for (uint32_t J = 0; J < *NumCallsites; ++J) {
auto LineOffset = readNumber<uint64_t>();
if (std::error_code EC = LineOffset.getError())
return EC;
auto Discriminator = readNumber<uint64_t>();
if (std::error_code EC = Discriminator.getError())
return EC;
auto FContext(readSampleContextFromTable());
if (std::error_code EC = FContext.getError())
return EC;
FunctionSamples *CalleeProfile = nullptr;
if (FProfile) {
CalleeProfile = const_cast<FunctionSamples *>(
&FProfile->functionSamplesAt(LineLocation(
*LineOffset,
*Discriminator))[std::string(FContext.get().getName())]);
}
if (std::error_code EC =
readFuncMetadata(ProfileHasAttribute, CalleeProfile))
return EC;
}
}
}
return sampleprof_error::success;
}
std::error_code
SampleProfileReaderExtBinaryBase::readFuncMetadata(bool ProfileHasAttribute) {
while (Data < End) {
auto FContext(readSampleContextFromTable());
if (std::error_code EC = FContext.getError())
return EC;
FunctionSamples *FProfile = nullptr;
auto It = Profiles.find(*FContext);
if (It != Profiles.end())
FProfile = &It->second;
if (std::error_code EC = readFuncMetadata(ProfileHasAttribute, FProfile))
return EC;
}
assert(Data == End && "More data is read than expected");
return sampleprof_error::success;
}
std::error_code SampleProfileReaderCompactBinary::readNameTable() {
auto Size = readNumber<uint64_t>();
if (std::error_code EC = Size.getError())
return EC;
NameTable.reserve(*Size);
for (uint64_t I = 0; I < *Size; ++I) {
auto FID = readNumber<uint64_t>();
if (std::error_code EC = FID.getError())
return EC;
NameTable.push_back(std::to_string(*FID));
}
return sampleprof_error::success;
}
std::error_code
SampleProfileReaderExtBinaryBase::readSecHdrTableEntry(uint32_t Idx) {
SecHdrTableEntry Entry;
auto Type = readUnencodedNumber<uint64_t>();
if (std::error_code EC = Type.getError())
return EC;
Entry.Type = static_cast<SecType>(*Type);
auto Flags = readUnencodedNumber<uint64_t>();
if (std::error_code EC = Flags.getError())
return EC;
Entry.Flags = *Flags;
auto Offset = readUnencodedNumber<uint64_t>();
if (std::error_code EC = Offset.getError())
return EC;
Entry.Offset = *Offset;
auto Size = readUnencodedNumber<uint64_t>();
if (std::error_code EC = Size.getError())
return EC;
Entry.Size = *Size;
Entry.LayoutIndex = Idx;
SecHdrTable.push_back(std::move(Entry));
return sampleprof_error::success;
}
std::error_code SampleProfileReaderExtBinaryBase::readSecHdrTable() {
auto EntryNum = readUnencodedNumber<uint64_t>();
if (std::error_code EC = EntryNum.getError())
return EC;
for (uint64_t i = 0; i < (*EntryNum); i++)
if (std::error_code EC = readSecHdrTableEntry(i))
return EC;
return sampleprof_error::success;
}
std::error_code SampleProfileReaderExtBinaryBase::readHeader() {
const uint8_t *BufStart =
reinterpret_cast<const uint8_t *>(Buffer->getBufferStart());
Data = BufStart;
End = BufStart + Buffer->getBufferSize();
if (std::error_code EC = readMagicIdent())
return EC;
if (std::error_code EC = readSecHdrTable())
return EC;
return sampleprof_error::success;
}
uint64_t SampleProfileReaderExtBinaryBase::getSectionSize(SecType Type) {
uint64_t Size = 0;
for (auto &Entry : SecHdrTable) {
if (Entry.Type == Type)
Size += Entry.Size;
}
return Size;
}
uint64_t SampleProfileReaderExtBinaryBase::getFileSize() {
// Sections in SecHdrTable is not necessarily in the same order as
// sections in the profile because section like FuncOffsetTable needs
// to be written after section LBRProfile but needs to be read before
// section LBRProfile, so we cannot simply use the last entry in
// SecHdrTable to calculate the file size.
uint64_t FileSize = 0;
for (auto &Entry : SecHdrTable) {
FileSize = std::max(Entry.Offset + Entry.Size, FileSize);
}
return FileSize;
}
static std::string getSecFlagsStr(const SecHdrTableEntry &Entry) {
std::string Flags;
if (hasSecFlag(Entry, SecCommonFlags::SecFlagCompress))
Flags.append("{compressed,");
else
Flags.append("{");
if (hasSecFlag(Entry, SecCommonFlags::SecFlagFlat))
Flags.append("flat,");
switch (Entry.Type) {
case SecNameTable:
if (hasSecFlag(Entry, SecNameTableFlags::SecFlagFixedLengthMD5))
Flags.append("fixlenmd5,");
else if (hasSecFlag(Entry, SecNameTableFlags::SecFlagMD5Name))
Flags.append("md5,");
if (hasSecFlag(Entry, SecNameTableFlags::SecFlagUniqSuffix))
Flags.append("uniq,");
break;
case SecProfSummary:
if (hasSecFlag(Entry, SecProfSummaryFlags::SecFlagPartial))
Flags.append("partial,");
if (hasSecFlag(Entry, SecProfSummaryFlags::SecFlagFullContext))
Flags.append("context,");
if (hasSecFlag(Entry, SecProfSummaryFlags::SecFlagIsPreInlined))
Flags.append("preInlined,");
if (hasSecFlag(Entry, SecProfSummaryFlags::SecFlagFSDiscriminator))
Flags.append("fs-discriminator,");
break;
case SecFuncOffsetTable:
if (hasSecFlag(Entry, SecFuncOffsetFlags::SecFlagOrdered))
Flags.append("ordered,");
break;
case SecFuncMetadata:
if (hasSecFlag(Entry, SecFuncMetadataFlags::SecFlagIsProbeBased))
Flags.append("probe,");
if (hasSecFlag(Entry, SecFuncMetadataFlags::SecFlagHasAttribute))
Flags.append("attr,");
break;
default:
break;
}
char &last = Flags.back();
if (last == ',')
last = '}';
else
Flags.append("}");
return Flags;
}
bool SampleProfileReaderExtBinaryBase::dumpSectionInfo(raw_ostream &OS) {
uint64_t TotalSecsSize = 0;
for (auto &Entry : SecHdrTable) {
OS << getSecName(Entry.Type) << " - Offset: " << Entry.Offset
<< ", Size: " << Entry.Size << ", Flags: " << getSecFlagsStr(Entry)
<< "\n";
;
TotalSecsSize += Entry.Size;
}
uint64_t HeaderSize = SecHdrTable.front().Offset;
assert(HeaderSize + TotalSecsSize == getFileSize() &&
"Size of 'header + sections' doesn't match the total size of profile");
OS << "Header Size: " << HeaderSize << "\n";
OS << "Total Sections Size: " << TotalSecsSize << "\n";
OS << "File Size: " << getFileSize() << "\n";
return true;
}
std::error_code SampleProfileReaderBinary::readMagicIdent() {
// Read and check the magic identifier.
auto Magic = readNumber<uint64_t>();
if (std::error_code EC = Magic.getError())
return EC;
else if (std::error_code EC = verifySPMagic(*Magic))
return EC;
// Read the version number.
auto Version = readNumber<uint64_t>();
if (std::error_code EC = Version.getError())
return EC;
else if (*Version != SPVersion())
return sampleprof_error::unsupported_version;
return sampleprof_error::success;
}
std::error_code SampleProfileReaderBinary::readHeader() {
Data = reinterpret_cast<const uint8_t *>(Buffer->getBufferStart());
End = Data + Buffer->getBufferSize();
if (std::error_code EC = readMagicIdent())
return EC;
if (std::error_code EC = readSummary())
return EC;
if (std::error_code EC = readNameTable())
return EC;
return sampleprof_error::success;
}
std::error_code SampleProfileReaderCompactBinary::readHeader() {
SampleProfileReaderBinary::readHeader();
if (std::error_code EC = readFuncOffsetTable())
return EC;
return sampleprof_error::success;
}
std::error_code SampleProfileReaderCompactBinary::readFuncOffsetTable() {
auto TableOffset = readUnencodedNumber<uint64_t>();
if (std::error_code EC = TableOffset.getError())
return EC;
const uint8_t *SavedData = Data;
const uint8_t *TableStart =
reinterpret_cast<const uint8_t *>(Buffer->getBufferStart()) +
*TableOffset;
Data = TableStart;
auto Size = readNumber<uint64_t>();
if (std::error_code EC = Size.getError())
return EC;
FuncOffsetTable.reserve(*Size);
for (uint64_t I = 0; I < *Size; ++I) {
auto FName(readStringFromTable());
if (std::error_code EC = FName.getError())
return EC;
auto Offset = readNumber<uint64_t>();
if (std::error_code EC = Offset.getError())
return EC;
FuncOffsetTable[*FName] = *Offset;
}
End = TableStart;
Data = SavedData;
return sampleprof_error::success;
}
bool SampleProfileReaderCompactBinary::collectFuncsFromModule() {
if (!M)
return false;
FuncsToUse.clear();
for (auto &F : *M)
FuncsToUse.insert(FunctionSamples::getCanonicalFnName(F));
return true;
}
std::error_code SampleProfileReaderBinary::readSummaryEntry(
std::vector<ProfileSummaryEntry> &Entries) {
auto Cutoff = readNumber<uint64_t>();
if (std::error_code EC = Cutoff.getError())
return EC;
auto MinBlockCount = readNumber<uint64_t>();
if (std::error_code EC = MinBlockCount.getError())
return EC;
auto NumBlocks = readNumber<uint64_t>();
if (std::error_code EC = NumBlocks.getError())
return EC;
Entries.emplace_back(*Cutoff, *MinBlockCount, *NumBlocks);
return sampleprof_error::success;
}
std::error_code SampleProfileReaderBinary::readSummary() {
auto TotalCount = readNumber<uint64_t>();
if (std::error_code EC = TotalCount.getError())
return EC;
auto MaxBlockCount = readNumber<uint64_t>();
if (std::error_code EC = MaxBlockCount.getError())
return EC;
auto MaxFunctionCount = readNumber<uint64_t>();
if (std::error_code EC = MaxFunctionCount.getError())
return EC;
auto NumBlocks = readNumber<uint64_t>();
if (std::error_code EC = NumBlocks.getError())
return EC;
auto NumFunctions = readNumber<uint64_t>();
if (std::error_code EC = NumFunctions.getError())
return EC;
auto NumSummaryEntries = readNumber<uint64_t>();
if (std::error_code EC = NumSummaryEntries.getError())
return EC;
std::vector<ProfileSummaryEntry> Entries;
for (unsigned i = 0; i < *NumSummaryEntries; i++) {
std::error_code EC = readSummaryEntry(Entries);
if (EC != sampleprof_error::success)
return EC;
}
Summary = std::make_unique<ProfileSummary>(
ProfileSummary::PSK_Sample, Entries, *TotalCount, *MaxBlockCount, 0,
*MaxFunctionCount, *NumBlocks, *NumFunctions);
return sampleprof_error::success;
}
bool SampleProfileReaderRawBinary::hasFormat(const MemoryBuffer &Buffer) {
const uint8_t *Data =
reinterpret_cast<const uint8_t *>(Buffer.getBufferStart());
uint64_t Magic = decodeULEB128(Data);
return Magic == SPMagic();
}
bool SampleProfileReaderExtBinary::hasFormat(const MemoryBuffer &Buffer) {
const uint8_t *Data =
reinterpret_cast<const uint8_t *>(Buffer.getBufferStart());
uint64_t Magic = decodeULEB128(Data);
return Magic == SPMagic(SPF_Ext_Binary);
}
bool SampleProfileReaderCompactBinary::hasFormat(const MemoryBuffer &Buffer) {
const uint8_t *Data =
reinterpret_cast<const uint8_t *>(Buffer.getBufferStart());
uint64_t Magic = decodeULEB128(Data);
return Magic == SPMagic(SPF_Compact_Binary);
}
std::error_code SampleProfileReaderGCC::skipNextWord() {
uint32_t dummy;
if (!GcovBuffer.readInt(dummy))
return sampleprof_error::truncated;
return sampleprof_error::success;
}
template <typename T> ErrorOr<T> SampleProfileReaderGCC::readNumber() {
if (sizeof(T) <= sizeof(uint32_t)) {
uint32_t Val;
if (GcovBuffer.readInt(Val) && Val <= std::numeric_limits<T>::max())
return static_cast<T>(Val);
} else if (sizeof(T) <= sizeof(uint64_t)) {
uint64_t Val;
if (GcovBuffer.readInt64(Val) && Val <= std::numeric_limits<T>::max())
return static_cast<T>(Val);
}
std::error_code EC = sampleprof_error::malformed;
reportError(0, EC.message());
return EC;
}
ErrorOr<StringRef> SampleProfileReaderGCC::readString() {
StringRef Str;
if (!GcovBuffer.readString(Str))
return sampleprof_error::truncated;
return Str;
}
std::error_code SampleProfileReaderGCC::readHeader() {
// Read the magic identifier.
if (!GcovBuffer.readGCDAFormat())
return sampleprof_error::unrecognized_format;
// Read the version number. Note - the GCC reader does not validate this
// version, but the profile creator generates v704.
GCOV::GCOVVersion version;
if (!GcovBuffer.readGCOVVersion(version))
return sampleprof_error::unrecognized_format;
if (version != GCOV::V407)
return sampleprof_error::unsupported_version;
// Skip the empty integer.
if (std::error_code EC = skipNextWord())
return EC;
return sampleprof_error::success;
}
std::error_code SampleProfileReaderGCC::readSectionTag(uint32_t Expected) {
uint32_t Tag;
if (!GcovBuffer.readInt(Tag))
return sampleprof_error::truncated;
if (Tag != Expected)
return sampleprof_error::malformed;
if (std::error_code EC = skipNextWord())
return EC;
return sampleprof_error::success;
}
std::error_code SampleProfileReaderGCC::readNameTable() {
if (std::error_code EC = readSectionTag(GCOVTagAFDOFileNames))
return EC;
uint32_t Size;
if (!GcovBuffer.readInt(Size))
return sampleprof_error::truncated;
for (uint32_t I = 0; I < Size; ++I) {
StringRef Str;
if (!GcovBuffer.readString(Str))
return sampleprof_error::truncated;
Names.push_back(std::string(Str));
}
return sampleprof_error::success;
}
std::error_code SampleProfileReaderGCC::readFunctionProfiles() {
if (std::error_code EC = readSectionTag(GCOVTagAFDOFunction))
return EC;
uint32_t NumFunctions;
if (!GcovBuffer.readInt(NumFunctions))
return sampleprof_error::truncated;
InlineCallStack Stack;
for (uint32_t I = 0; I < NumFunctions; ++I)
if (std::error_code EC = readOneFunctionProfile(Stack, true, 0))
return EC;
computeSummary();
return sampleprof_error::success;
}
std::error_code SampleProfileReaderGCC::readOneFunctionProfile(
const InlineCallStack &InlineStack, bool Update, uint32_t Offset) {
uint64_t HeadCount = 0;
if (InlineStack.size() == 0)
if (!GcovBuffer.readInt64(HeadCount))
return sampleprof_error::truncated;
uint32_t NameIdx;
if (!GcovBuffer.readInt(NameIdx))
return sampleprof_error::truncated;
StringRef Name(Names[NameIdx]);
uint32_t NumPosCounts;
if (!GcovBuffer.readInt(NumPosCounts))
return sampleprof_error::truncated;
uint32_t NumCallsites;
if (!GcovBuffer.readInt(NumCallsites))
return sampleprof_error::truncated;
FunctionSamples *FProfile = nullptr;
if (InlineStack.size() == 0) {
// If this is a top function that we have already processed, do not
// update its profile again. This happens in the presence of
// function aliases. Since these aliases share the same function
// body, there will be identical replicated profiles for the
// original function. In this case, we simply not bother updating
// the profile of the original function.
FProfile = &Profiles[Name];
FProfile->addHeadSamples(HeadCount);
if (FProfile->getTotalSamples() > 0)
Update = false;
} else {
// Otherwise, we are reading an inlined instance. The top of the
// inline stack contains the profile of the caller. Insert this
// callee in the caller's CallsiteMap.
FunctionSamples *CallerProfile = InlineStack.front();
uint32_t LineOffset = Offset >> 16;
uint32_t Discriminator = Offset & 0xffff;
FProfile = &CallerProfile->functionSamplesAt(
LineLocation(LineOffset, Discriminator))[std::string(Name)];
}
FProfile->setName(Name);
for (uint32_t I = 0; I < NumPosCounts; ++I) {
uint32_t Offset;
if (!GcovBuffer.readInt(Offset))
return sampleprof_error::truncated;
uint32_t NumTargets;
if (!GcovBuffer.readInt(NumTargets))
return sampleprof_error::truncated;
uint64_t Count;
if (!GcovBuffer.readInt64(Count))
return sampleprof_error::truncated;
// The line location is encoded in the offset as:
// high 16 bits: line offset to the start of the function.
// low 16 bits: discriminator.
uint32_t LineOffset = Offset >> 16;
uint32_t Discriminator = Offset & 0xffff;
InlineCallStack NewStack;
NewStack.push_back(FProfile);
llvm::append_range(NewStack, InlineStack);
if (Update) {
// Walk up the inline stack, adding the samples on this line to
// the total sample count of the callers in the chain.
for (auto *CallerProfile : NewStack)
CallerProfile->addTotalSamples(Count);
// Update the body samples for the current profile.
FProfile->addBodySamples(LineOffset, Discriminator, Count);
}
// Process the list of functions called at an indirect call site.
// These are all the targets that a function pointer (or virtual
// function) resolved at runtime.
for (uint32_t J = 0; J < NumTargets; J++) {
uint32_t HistVal;
if (!GcovBuffer.readInt(HistVal))
return sampleprof_error::truncated;
if (HistVal != HIST_TYPE_INDIR_CALL_TOPN)
return sampleprof_error::malformed;
uint64_t TargetIdx;
if (!GcovBuffer.readInt64(TargetIdx))
return sampleprof_error::truncated;
StringRef TargetName(Names[TargetIdx]);
uint64_t TargetCount;
if (!GcovBuffer.readInt64(TargetCount))
return sampleprof_error::truncated;
if (Update)
FProfile->addCalledTargetSamples(LineOffset, Discriminator,
TargetName, TargetCount);
}
}
// Process all the inlined callers into the current function. These
// are all the callsites that were inlined into this function.
for (uint32_t I = 0; I < NumCallsites; I++) {
// The offset is encoded as:
// high 16 bits: line offset to the start of the function.
// low 16 bits: discriminator.
uint32_t Offset;
if (!GcovBuffer.readInt(Offset))
return sampleprof_error::truncated;
InlineCallStack NewStack;
NewStack.push_back(FProfile);
llvm::append_range(NewStack, InlineStack);
if (std::error_code EC = readOneFunctionProfile(NewStack, Update, Offset))
return EC;
}
return sampleprof_error::success;
}
/// Read a GCC AutoFDO profile.
///
/// This format is generated by the Linux Perf conversion tool at
/// https://github.com/google/autofdo.
std::error_code SampleProfileReaderGCC::readImpl() {
assert(!ProfileIsFSDisciminator && "Gcc profiles not support FSDisciminator");
// Read the string table.
if (std::error_code EC = readNameTable())
return EC;
// Read the source profile.
if (std::error_code EC = readFunctionProfiles())
return EC;
return sampleprof_error::success;
}
bool SampleProfileReaderGCC::hasFormat(const MemoryBuffer &Buffer) {
StringRef Magic(reinterpret_cast<const char *>(Buffer.getBufferStart()));
return Magic == "adcg*704";
}
void SampleProfileReaderItaniumRemapper::applyRemapping(LLVMContext &Ctx) {
// If the reader uses MD5 to represent string, we can't remap it because
// we don't know what the original function names were.
if (Reader.useMD5()) {
Ctx.diagnose(DiagnosticInfoSampleProfile(
Reader.getBuffer()->getBufferIdentifier(),
"Profile data remapping cannot be applied to profile data "
"in compact format (original mangled names are not available).",
DS_Warning));
return;
}
// CSSPGO-TODO: Remapper is not yet supported.
// We will need to remap the entire context string.
assert(Remappings && "should be initialized while creating remapper");
for (auto &Sample : Reader.getProfiles()) {
DenseSet<StringRef> NamesInSample;
Sample.second.findAllNames(NamesInSample);
for (auto &Name : NamesInSample)
if (auto Key = Remappings->insert(Name))
NameMap.insert({Key, Name});
}
RemappingApplied = true;
}
std::optional<StringRef>
SampleProfileReaderItaniumRemapper::lookUpNameInProfile(StringRef Fname) {
if (auto Key = Remappings->lookup(Fname))
return NameMap.lookup(Key);
return std::nullopt;
}
/// Prepare a memory buffer for the contents of \p Filename.
///
/// \returns an error code indicating the status of the buffer.
static ErrorOr<std::unique_ptr<MemoryBuffer>>
setupMemoryBuffer(const Twine &Filename) {
auto BufferOrErr = MemoryBuffer::getFileOrSTDIN(Filename, /*IsText=*/true);
if (std::error_code EC = BufferOrErr.getError())
return EC;
auto Buffer = std::move(BufferOrErr.get());
return std::move(Buffer);
}
/// Create a sample profile reader based on the format of the input file.
///
/// \param Filename The file to open.
///
/// \param C The LLVM context to use to emit diagnostics.
///
/// \param P The FSDiscriminatorPass.
///
/// \param RemapFilename The file used for profile remapping.
///
/// \returns an error code indicating the status of the created reader.
ErrorOr<std::unique_ptr<SampleProfileReader>>
SampleProfileReader::create(const std::string Filename, LLVMContext &C,
FSDiscriminatorPass P,
const std::string RemapFilename) {
auto BufferOrError = setupMemoryBuffer(Filename);
if (std::error_code EC = BufferOrError.getError())
return EC;
return create(BufferOrError.get(), C, P, RemapFilename);
}
/// Create a sample profile remapper from the given input, to remap the
/// function names in the given profile data.
///
/// \param Filename The file to open.
///
/// \param Reader The profile reader the remapper is going to be applied to.
///
/// \param C The LLVM context to use to emit diagnostics.
///
/// \returns an error code indicating the status of the created reader.
ErrorOr<std::unique_ptr<SampleProfileReaderItaniumRemapper>>
SampleProfileReaderItaniumRemapper::create(const std::string Filename,
SampleProfileReader &Reader,
LLVMContext &C) {
auto BufferOrError = setupMemoryBuffer(Filename);
if (std::error_code EC = BufferOrError.getError())
return EC;
return create(BufferOrError.get(), Reader, C);
}
/// Create a sample profile remapper from the given input, to remap the
/// function names in the given profile data.
///
/// \param B The memory buffer to create the reader from (assumes ownership).
///
/// \param C The LLVM context to use to emit diagnostics.
///
/// \param Reader The profile reader the remapper is going to be applied to.
///
/// \returns an error code indicating the status of the created reader.
ErrorOr<std::unique_ptr<SampleProfileReaderItaniumRemapper>>
SampleProfileReaderItaniumRemapper::create(std::unique_ptr<MemoryBuffer> &B,
SampleProfileReader &Reader,
LLVMContext &C) {
auto Remappings = std::make_unique<SymbolRemappingReader>();
if (Error E = Remappings->read(*B)) {
handleAllErrors(
std::move(E), [&](const SymbolRemappingParseError &ParseError) {
C.diagnose(DiagnosticInfoSampleProfile(B->getBufferIdentifier(),
ParseError.getLineNum(),
ParseError.getMessage()));
});
return sampleprof_error::malformed;
}
return std::make_unique<SampleProfileReaderItaniumRemapper>(
std::move(B), std::move(Remappings), Reader);
}
/// Create a sample profile reader based on the format of the input data.
///
/// \param B The memory buffer to create the reader from (assumes ownership).
///
/// \param C The LLVM context to use to emit diagnostics.
///
/// \param P The FSDiscriminatorPass.
///
/// \param RemapFilename The file used for profile remapping.
///
/// \returns an error code indicating the status of the created reader.
ErrorOr<std::unique_ptr<SampleProfileReader>>
SampleProfileReader::create(std::unique_ptr<MemoryBuffer> &B, LLVMContext &C,
FSDiscriminatorPass P,
const std::string RemapFilename) {
std::unique_ptr<SampleProfileReader> Reader;
if (SampleProfileReaderRawBinary::hasFormat(*B))
Reader.reset(new SampleProfileReaderRawBinary(std::move(B), C));
else if (SampleProfileReaderExtBinary::hasFormat(*B))
Reader.reset(new SampleProfileReaderExtBinary(std::move(B), C));
else if (SampleProfileReaderCompactBinary::hasFormat(*B))
Reader.reset(new SampleProfileReaderCompactBinary(std::move(B), C));
else if (SampleProfileReaderGCC::hasFormat(*B))
Reader.reset(new SampleProfileReaderGCC(std::move(B), C));
else if (SampleProfileReaderText::hasFormat(*B))
Reader.reset(new SampleProfileReaderText(std::move(B), C));
else
return sampleprof_error::unrecognized_format;
if (!RemapFilename.empty()) {
auto ReaderOrErr =
SampleProfileReaderItaniumRemapper::create(RemapFilename, *Reader, C);
if (std::error_code EC = ReaderOrErr.getError()) {
std::string Msg = "Could not create remapper: " + EC.message();
C.diagnose(DiagnosticInfoSampleProfile(RemapFilename, Msg));
return EC;
}
Reader->Remapper = std::move(ReaderOrErr.get());
}
if (std::error_code EC = Reader->readHeader()) {
return EC;
}
Reader->setDiscriminatorMaskedBitFrom(P);
return std::move(Reader);
}
// For text and GCC file formats, we compute the summary after reading the
// profile. Binary format has the profile summary in its header.
void SampleProfileReader::computeSummary() {
SampleProfileSummaryBuilder Builder(ProfileSummaryBuilder::DefaultCutoffs);
Summary = Builder.computeSummaryForProfiles(Profiles);
}