521 lines
18 KiB
C++
521 lines
18 KiB
C++
//===- bolt/Profile/DataReader.h - Perf data reader -------------*- 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
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
//
|
|
// This family of functions reads profile data written by the perf2bolt
|
|
// utility and stores it in memory for llvm-bolt consumption.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#ifndef BOLT_PROFILE_DATA_READER_H
|
|
#define BOLT_PROFILE_DATA_READER_H
|
|
|
|
#include "bolt/Profile/ProfileReaderBase.h"
|
|
#include "llvm/ADT/DenseMap.h"
|
|
#include "llvm/ADT/StringMap.h"
|
|
#include "llvm/ADT/StringSet.h"
|
|
#include "llvm/Support/ErrorOr.h"
|
|
#include "llvm/Support/MemoryBuffer.h"
|
|
#include "llvm/Support/raw_ostream.h"
|
|
#include <map>
|
|
#include <unordered_map>
|
|
#include <vector>
|
|
|
|
namespace llvm {
|
|
class MCSymbol;
|
|
|
|
namespace bolt {
|
|
|
|
class BinaryFunction;
|
|
|
|
struct Location {
|
|
bool IsSymbol;
|
|
StringRef Name;
|
|
uint64_t Offset;
|
|
|
|
explicit Location(uint64_t Offset)
|
|
: IsSymbol(false), Name("[unknown]"), Offset(Offset) {}
|
|
|
|
Location(bool IsSymbol, StringRef Name, uint64_t Offset)
|
|
: IsSymbol(IsSymbol), Name(Name), Offset(Offset) {}
|
|
|
|
bool operator==(const Location &RHS) const {
|
|
return IsSymbol == RHS.IsSymbol && Name == RHS.Name &&
|
|
(Name == "[heap]" || Offset == RHS.Offset);
|
|
}
|
|
|
|
bool operator<(const Location &RHS) const {
|
|
if (IsSymbol != RHS.IsSymbol)
|
|
return IsSymbol < RHS.IsSymbol;
|
|
|
|
if (Name != RHS.Name)
|
|
return Name < RHS.Name;
|
|
|
|
return Name != "[heap]" && Offset < RHS.Offset;
|
|
}
|
|
|
|
friend raw_ostream &operator<<(raw_ostream &OS, const Location &Loc);
|
|
};
|
|
|
|
typedef std::vector<std::pair<Location, Location>> BranchContext;
|
|
|
|
struct BranchInfo {
|
|
Location From;
|
|
Location To;
|
|
int64_t Mispreds;
|
|
int64_t Branches;
|
|
|
|
BranchInfo(Location From, Location To, int64_t Mispreds, int64_t Branches)
|
|
: From(std::move(From)), To(std::move(To)), Mispreds(Mispreds),
|
|
Branches(Branches) {}
|
|
|
|
bool operator==(const BranchInfo &RHS) const {
|
|
return From == RHS.From && To == RHS.To;
|
|
}
|
|
|
|
bool operator<(const BranchInfo &RHS) const {
|
|
return std::tie(From, To) < std::tie(RHS.From, RHS.To);
|
|
}
|
|
|
|
/// Merges branch and misprediction counts of \p BI with those of this object.
|
|
void mergeWith(const BranchInfo &BI);
|
|
|
|
void print(raw_ostream &OS) const;
|
|
};
|
|
|
|
struct FuncBranchData {
|
|
typedef std::vector<BranchInfo> ContainerTy;
|
|
|
|
StringRef Name;
|
|
ContainerTy Data;
|
|
ContainerTy EntryData;
|
|
|
|
/// Total execution count for the function.
|
|
int64_t ExecutionCount{0};
|
|
|
|
/// Indicate if the data was used.
|
|
bool Used{false};
|
|
|
|
FuncBranchData() {}
|
|
|
|
FuncBranchData(StringRef Name, ContainerTy Data = ContainerTy(),
|
|
ContainerTy EntryData = ContainerTy())
|
|
: Name(Name), Data(std::move(Data)), EntryData(std::move(EntryData)) {}
|
|
|
|
ErrorOr<const BranchInfo &> getBranch(uint64_t From, uint64_t To) const;
|
|
|
|
/// Returns the branch info object associated with a direct call originating
|
|
/// from the given offset. If no branch info object is found, an error is
|
|
/// returned. If the offset corresponds to an indirect call the behavior is
|
|
/// undefined.
|
|
ErrorOr<const BranchInfo &> getDirectCallBranch(uint64_t From) const;
|
|
|
|
/// Append the branch data of another function located \p Offset bytes away
|
|
/// from the entry of this function.
|
|
void appendFrom(const FuncBranchData &FBD, uint64_t Offset);
|
|
|
|
/// Returns the total number of executed branches in this function
|
|
/// by counting the number of executed branches for each BranchInfo
|
|
uint64_t getNumExecutedBranches() const;
|
|
|
|
/// Aggregation helpers
|
|
DenseMap<uint64_t, DenseMap<uint64_t, size_t>> IntraIndex;
|
|
DenseMap<uint64_t, DenseMap<Location, size_t>> InterIndex;
|
|
DenseMap<uint64_t, DenseMap<Location, size_t>> EntryIndex;
|
|
|
|
void bumpBranchCount(uint64_t OffsetFrom, uint64_t OffsetTo, uint64_t Count,
|
|
uint64_t Mispreds);
|
|
void bumpCallCount(uint64_t OffsetFrom, const Location &To, uint64_t Count,
|
|
uint64_t Mispreds);
|
|
void bumpEntryCount(const Location &From, uint64_t OffsetTo, uint64_t Count,
|
|
uint64_t Mispreds);
|
|
};
|
|
|
|
/// MemInfo represents a single memory load from an address \p Addr at an \p
|
|
/// Offset within a function. \p Count represents how many times a particular
|
|
/// address was seen.
|
|
struct MemInfo {
|
|
Location Offset;
|
|
Location Addr;
|
|
uint64_t Count;
|
|
|
|
bool operator==(const MemInfo &RHS) const {
|
|
return Offset == RHS.Offset && Addr == RHS.Addr;
|
|
}
|
|
|
|
bool operator<(const MemInfo &RHS) const {
|
|
if (Offset < RHS.Offset)
|
|
return true;
|
|
|
|
if (Offset == RHS.Offset)
|
|
return (Addr < RHS.Addr);
|
|
|
|
return false;
|
|
}
|
|
|
|
void mergeWith(const MemInfo &MI) { Count += MI.Count; }
|
|
|
|
void print(raw_ostream &OS) const;
|
|
void prettyPrint(raw_ostream &OS) const;
|
|
|
|
MemInfo(const Location &Offset, const Location &Addr, uint64_t Count = 0)
|
|
: Offset(Offset), Addr(Addr), Count(Count) {}
|
|
|
|
friend raw_ostream &operator<<(raw_ostream &OS, const MemInfo &MI) {
|
|
MI.prettyPrint(OS);
|
|
return OS;
|
|
}
|
|
};
|
|
|
|
/// Helper class to store memory load events recorded in the address space of
|
|
/// a given function, analogous to FuncBranchData but for memory load events
|
|
/// instead of branches.
|
|
struct FuncMemData {
|
|
typedef std::vector<MemInfo> ContainerTy;
|
|
|
|
StringRef Name;
|
|
ContainerTy Data;
|
|
|
|
/// Indicate if the data was used.
|
|
bool Used{false};
|
|
|
|
DenseMap<uint64_t, DenseMap<Location, size_t>> EventIndex;
|
|
|
|
/// Update \p Data with a memory event. Events with the same
|
|
/// \p Offset and \p Addr will be coalesced.
|
|
void update(const Location &Offset, const Location &Addr);
|
|
|
|
FuncMemData() {}
|
|
|
|
FuncMemData(StringRef Name, ContainerTy Data = ContainerTy())
|
|
: Name(Name), Data(std::move(Data)) {}
|
|
};
|
|
|
|
/// Similar to BranchInfo, but instead of recording from-to address (an edge),
|
|
/// it records the address of a perf event and the number of times samples hit
|
|
/// this address.
|
|
struct BasicSampleInfo {
|
|
Location Loc;
|
|
int64_t Hits;
|
|
|
|
BasicSampleInfo(Location Loc, int64_t Hits)
|
|
: Loc(std::move(Loc)), Hits(Hits) {}
|
|
|
|
bool operator==(const BasicSampleInfo &RHS) const { return Loc == RHS.Loc; }
|
|
|
|
bool operator<(const BasicSampleInfo &RHS) const {
|
|
if (Loc < RHS.Loc)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
void print(raw_ostream &OS) const;
|
|
|
|
void mergeWith(const BasicSampleInfo &SI);
|
|
};
|
|
|
|
/// Helper class to store samples recorded in the address space of a given
|
|
/// function, analogous to FuncBranchData but for samples instead of branches.
|
|
struct FuncBasicSampleData {
|
|
typedef std::vector<BasicSampleInfo> ContainerTy;
|
|
|
|
StringRef Name;
|
|
ContainerTy Data;
|
|
|
|
FuncBasicSampleData(StringRef Name, ContainerTy Data = ContainerTy())
|
|
: Name(Name), Data(std::move(Data)) {}
|
|
|
|
/// Get the number of samples recorded in [Start, End)
|
|
uint64_t getSamples(uint64_t Start, uint64_t End) const;
|
|
|
|
/// Returns the total number of samples recorded in this function.
|
|
uint64_t getSamples() const;
|
|
|
|
/// Aggregation helper
|
|
DenseMap<uint64_t, size_t> Index;
|
|
|
|
void bumpCount(uint64_t Offset, uint64_t Count);
|
|
};
|
|
|
|
/// DataReader Class
|
|
///
|
|
class DataReader : public ProfileReaderBase {
|
|
public:
|
|
explicit DataReader(StringRef Filename)
|
|
: ProfileReaderBase(Filename), Diag(errs()) {}
|
|
|
|
StringRef getReaderName() const override { return "branch profile reader"; }
|
|
|
|
bool isTrustedSource() const override { return false; }
|
|
|
|
Error preprocessProfile(BinaryContext &BC) override;
|
|
|
|
Error readProfilePreCFG(BinaryContext &BC) override;
|
|
|
|
Error readProfile(BinaryContext &BC) override;
|
|
|
|
bool hasLocalsWithFileName() const override;
|
|
|
|
bool mayHaveProfileData(const BinaryFunction &BF) override;
|
|
|
|
/// Return all event names used to collect this profile
|
|
StringSet<> getEventNames() const override { return EventNames; }
|
|
|
|
protected:
|
|
/// Read profile information available for the function.
|
|
void readProfile(BinaryFunction &BF);
|
|
|
|
/// In functions with multiple entry points, the profile collection records
|
|
/// data for other entry points in a different function entry. This function
|
|
/// attempts to fetch extra profile data for each secondary entry point.
|
|
bool fetchProfileForOtherEntryPoints(BinaryFunction &BF);
|
|
|
|
/// Find the best matching profile for a function after the creation of basic
|
|
/// blocks.
|
|
void matchProfileData(BinaryFunction &BF);
|
|
|
|
/// Find the best matching memory data profile for a function before the
|
|
/// creation of basic blocks.
|
|
void matchProfileMemData(BinaryFunction &BF);
|
|
|
|
/// Check how closely \p BranchData matches the function \p BF.
|
|
/// Return accuracy (ranging from 0.0 to 1.0) of the matching.
|
|
float evaluateProfileData(BinaryFunction &BF,
|
|
const FuncBranchData &BranchData) const;
|
|
|
|
/// If our profile data comes from sample addresses instead of LBR entries,
|
|
/// collect sample count for all addresses in this function address space,
|
|
/// aggregating them per basic block and assigning an execution count to each
|
|
/// basic block based on the number of samples recorded at those addresses.
|
|
/// The last step is to infer edge counts based on BB execution count. Note
|
|
/// this is the opposite of the LBR way, where we infer BB execution count
|
|
/// based on edge counts.
|
|
void readBasicSampleData(BinaryFunction &BF);
|
|
|
|
/// Convert function-level branch data into instruction annotations.
|
|
void convertBranchData(BinaryFunction &BF) const;
|
|
|
|
/// Update function \p BF profile with a taken branch.
|
|
/// \p Count could be 0 if verification of the branch is required.
|
|
///
|
|
/// Return true if the branch is valid, false otherwise.
|
|
bool recordBranch(BinaryFunction &BF, uint64_t From, uint64_t To,
|
|
uint64_t Count = 1, uint64_t Mispreds = 0) const;
|
|
|
|
/// Parses the input bolt data file into internal data structures. We expect
|
|
/// the file format to follow the syntax below.
|
|
///
|
|
/// <is symbol?> <closest elf symbol or DSO name> <relative FROM address>
|
|
/// <is symbol?> <closest elf symbol or DSO name> <relative TO address>
|
|
/// <number of mispredictions> <number of branches>
|
|
///
|
|
/// In <is symbol?> field we record 0 if our closest address is a DSO load
|
|
/// address or 1 if our closest address is an ELF symbol.
|
|
///
|
|
/// Examples:
|
|
///
|
|
/// 1 main 3fb 0 /lib/ld-2.21.so 12 4 221
|
|
///
|
|
/// The example records branches from symbol main, offset 3fb, to DSO ld-2.21,
|
|
/// offset 12, with 4 mispredictions and 221 branches.
|
|
///
|
|
/// 2 t2.c/func 11 1 globalfunc 1d 0 1775 2
|
|
/// 0 1002 2
|
|
/// 2 t2.c/func 31 2 t2.c/func d
|
|
/// 2 t2.c/func 18 2 t2.c/func 20
|
|
/// 0 773 2
|
|
/// 2 t2.c/func 71 2 t2.c/func d
|
|
/// 2 t2.c/func 18 2 t2.c/func 60
|
|
///
|
|
/// The examples records branches from local symbol func (from t2.c), offset
|
|
/// 11, to global symbol globalfunc, offset 1d, with 1775 branches, no
|
|
/// mispreds. Of these branches, 1002 were preceded by a sequence of
|
|
/// branches from func, offset 18 to offset 20 and then from offset 31 to
|
|
/// offset d. The rest 773 branches were preceded by a different sequence
|
|
/// of branches, from func, offset 18 to offset 60 and then from offset 71 to
|
|
/// offset d.
|
|
std::error_code parse();
|
|
|
|
/// When no_lbr is the first line of the file, activate No LBR mode. In this
|
|
/// mode we read the addresses where samples were recorded directly instead of
|
|
/// LBR entries. The line format is almost the same, except for a missing <to>
|
|
/// triple and a missing mispredictions field:
|
|
///
|
|
/// no_lbr
|
|
/// <is symbol?> <closest elf symbol or DSO name> <relative address> <count>
|
|
/// ...
|
|
///
|
|
/// Example:
|
|
///
|
|
/// no_lbr # First line of fdata file
|
|
/// 1 BZ2_compressBlock 466c 3
|
|
/// 1 BZ2_hbMakeCodeLengths 29c 1
|
|
///
|
|
std::error_code parseInNoLBRMode();
|
|
|
|
/// Return branch data matching one of the names in \p FuncNames.
|
|
FuncBranchData *
|
|
getBranchDataForNames(const std::vector<StringRef> &FuncNames);
|
|
|
|
/// Return branch data matching one of the \p Symbols.
|
|
FuncBranchData *
|
|
getBranchDataForSymbols(const std::vector<MCSymbol *> &Symbols);
|
|
|
|
/// Return mem data matching one of the names in \p FuncNames.
|
|
FuncMemData *getMemDataForNames(const std::vector<StringRef> &FuncNames);
|
|
|
|
FuncBasicSampleData *
|
|
getFuncBasicSampleData(const std::vector<StringRef> &FuncNames);
|
|
|
|
/// Return a vector of all FuncBranchData matching the list of names.
|
|
/// Internally use fuzzy matching to match special names like LTO-generated
|
|
/// function names.
|
|
std::vector<FuncBranchData *>
|
|
getBranchDataForNamesRegex(const std::vector<StringRef> &FuncNames);
|
|
|
|
/// Return a vector of all FuncMemData matching the list of names.
|
|
/// Internally use fuzzy matching to match special names like LTO-generated
|
|
/// function names.
|
|
std::vector<FuncMemData *>
|
|
getMemDataForNamesRegex(const std::vector<StringRef> &FuncNames);
|
|
|
|
/// Return branch data profile associated with function \p BF or nullptr
|
|
/// if the function has no associated profile.
|
|
FuncBranchData *getBranchData(const BinaryFunction &BF) const {
|
|
auto FBDI = FuncsToBranches.find(&BF);
|
|
if (FBDI == FuncsToBranches.end())
|
|
return nullptr;
|
|
return FBDI->second;
|
|
}
|
|
|
|
/// Updates branch profile data associated with function \p BF.
|
|
void setBranchData(const BinaryFunction &BF, FuncBranchData *FBD) {
|
|
FuncsToBranches[&BF] = FBD;
|
|
}
|
|
|
|
/// Return memory profile data associated with function \p BF, or nullptr
|
|
/// if the function has no associated profile.
|
|
FuncMemData *getMemData(const BinaryFunction &BF) const {
|
|
auto FMDI = FuncsToMemData.find(&BF);
|
|
if (FMDI == FuncsToMemData.end())
|
|
return nullptr;
|
|
return FMDI->second;
|
|
}
|
|
|
|
/// Updates the memory profile data associated with function \p BF.
|
|
void setMemData(const BinaryFunction &BF, FuncMemData *FMD) {
|
|
FuncsToMemData[&BF] = FMD;
|
|
}
|
|
|
|
using NamesToBranchesMapTy = std::map<StringRef, FuncBranchData>;
|
|
using NamesToBasicSamplesMapTy = std::map<StringRef, FuncBasicSampleData>;
|
|
using NamesToMemEventsMapTy = std::map<StringRef, FuncMemData>;
|
|
using FuncsToBranchesMapTy =
|
|
std::unordered_map<const BinaryFunction *, FuncBranchData *>;
|
|
using FuncsToMemDataMapTy =
|
|
std::unordered_map<const BinaryFunction *, FuncMemData *>;
|
|
|
|
/// Dumps the entire data structures parsed. Used for debugging.
|
|
void dump() const;
|
|
|
|
/// Return false only if we are running with profiling data that lacks LBR.
|
|
bool hasLBR() const { return !NoLBRMode; }
|
|
|
|
/// Return true if the profiling data was collected in a bolted binary. This
|
|
/// means we lose the ability to identify stale data at some branch locations,
|
|
/// since we have to be more permissive in some cases.
|
|
bool collectedInBoltedBinary() const { return BATMode; }
|
|
|
|
/// Return true if event named \p Name was used to collect this profile data.
|
|
bool usesEvent(StringRef Name) const {
|
|
for (auto I = EventNames.begin(), E = EventNames.end(); I != E; ++I) {
|
|
StringRef Event = I->getKey();
|
|
if (Event.contains(Name))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/// Open the file and parse the contents.
|
|
std::error_code parseInput();
|
|
|
|
/// Build suffix map once the profile data is parsed.
|
|
void buildLTONameMaps();
|
|
|
|
void reportError(StringRef ErrorMsg);
|
|
bool expectAndConsumeFS();
|
|
void consumeAllRemainingFS();
|
|
bool checkAndConsumeNewLine();
|
|
ErrorOr<StringRef> parseString(char EndChar, bool EndNl = false);
|
|
ErrorOr<int64_t> parseNumberField(char EndChar, bool EndNl = false);
|
|
ErrorOr<uint64_t> parseHexField(char EndChar, bool EndNl = false);
|
|
ErrorOr<Location> parseLocation(char EndChar, bool EndNl, bool ExpectMemLoc);
|
|
ErrorOr<Location> parseLocation(char EndChar, bool EndNl = false) {
|
|
return parseLocation(EndChar, EndNl, false);
|
|
}
|
|
ErrorOr<Location> parseMemLocation(char EndChar, bool EndNl = false) {
|
|
return parseLocation(EndChar, EndNl, true);
|
|
}
|
|
ErrorOr<BranchInfo> parseBranchInfo();
|
|
ErrorOr<BasicSampleInfo> parseSampleInfo();
|
|
ErrorOr<MemInfo> parseMemInfo();
|
|
ErrorOr<bool> maybeParseNoLBRFlag();
|
|
ErrorOr<bool> maybeParseBATFlag();
|
|
bool hasBranchData();
|
|
bool hasMemData();
|
|
|
|
/// An in-memory copy of the input data file - owns strings used in reader.
|
|
std::unique_ptr<MemoryBuffer> FileBuf;
|
|
raw_ostream &Diag;
|
|
StringRef ParsingBuf;
|
|
unsigned Line{0};
|
|
unsigned Col{0};
|
|
NamesToBranchesMapTy NamesToBranches;
|
|
NamesToBasicSamplesMapTy NamesToBasicSamples;
|
|
NamesToMemEventsMapTy NamesToMemEvents;
|
|
FuncsToBranchesMapTy FuncsToBranches;
|
|
FuncsToMemDataMapTy FuncsToMemData;
|
|
bool NoLBRMode{false};
|
|
bool BATMode{false};
|
|
StringSet<> EventNames;
|
|
static const char FieldSeparator = ' ';
|
|
|
|
/// Maps of common LTO names to possible matching profiles.
|
|
StringMap<std::vector<FuncBranchData *>> LTOCommonNameMap;
|
|
StringMap<std::vector<FuncMemData *>> LTOCommonNameMemMap;
|
|
|
|
public:
|
|
void setParsingBuffer(StringRef Buffer) { ParsingBuf = Buffer; }
|
|
};
|
|
|
|
} // namespace bolt
|
|
|
|
/// DenseMapInfo allows us to use the DenseMap LLVM data structure to store
|
|
/// Locations
|
|
template <> struct DenseMapInfo<bolt::Location> {
|
|
static inline bolt::Location getEmptyKey() {
|
|
return bolt::Location(true, StringRef(), static_cast<uint64_t>(-1LL));
|
|
}
|
|
static inline bolt::Location getTombstoneKey() {
|
|
return bolt::Location(true, StringRef(), static_cast<uint64_t>(-2LL));
|
|
;
|
|
}
|
|
static unsigned getHashValue(const bolt::Location &L) {
|
|
return (unsigned(DenseMapInfo<StringRef>::getHashValue(L.Name)) >> 4) ^
|
|
(unsigned(L.Offset));
|
|
}
|
|
static bool isEqual(const bolt::Location &LHS, const bolt::Location &RHS) {
|
|
return LHS.IsSymbol == RHS.IsSymbol && LHS.Name == RHS.Name &&
|
|
LHS.Offset == RHS.Offset;
|
|
}
|
|
};
|
|
|
|
} // namespace llvm
|
|
|
|
#endif
|