230 lines
8.2 KiB
C++
230 lines
8.2 KiB
C++
//===--- PGOCtxProfReader.h - Contextual profile 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
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
///
|
|
/// \file
|
|
///
|
|
/// Reader for contextual iFDO profile, which comes in bitstream format.
|
|
///
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#ifndef LLVM_PROFILEDATA_CTXINSTRPROFILEREADER_H
|
|
#define LLVM_PROFILEDATA_CTXINSTRPROFILEREADER_H
|
|
|
|
#include "llvm/Bitstream/BitstreamReader.h"
|
|
#include "llvm/IR/GlobalValue.h"
|
|
#include "llvm/ProfileData/PGOCtxProfWriter.h"
|
|
#include "llvm/Support/Compiler.h"
|
|
#include "llvm/Support/Error.h"
|
|
#include <map>
|
|
|
|
namespace llvm {
|
|
class PGOContextualProfile;
|
|
class PGOCtxProfContext;
|
|
|
|
namespace internal {
|
|
// When we traverse the contextual profile, we typically want to visit contexts
|
|
// pertaining to a specific function. To avoid traversing the whole tree, we
|
|
// want to keep a per-function list - which will be in preorder - of that
|
|
// function's contexts. This happens in PGOContextualProfile. For memory use
|
|
// efficiency, we want to make PGOCtxProfContext an intrusive double-linked list
|
|
// node. We need to handle the cases where PGOCtxProfContext nodes are moved and
|
|
// deleted: in both cases, we need to update the index (==list). We can do that
|
|
// directly from the node in the list, without knowing who the "parent" of the
|
|
// list is. That makes the ADT ilist overkill here. Finally, IndexNode is meant
|
|
// to be an implementation detail of PGOCtxProfContext, and the only reason it's
|
|
// factored out is to avoid implementing move semantics for all its members.
|
|
class IndexNode {
|
|
// This class' members are intentionally private - it's a convenience
|
|
// implementation detail.
|
|
friend class ::llvm::PGOCtxProfContext;
|
|
friend class ::llvm::PGOContextualProfile;
|
|
|
|
IndexNode *Previous = nullptr;
|
|
IndexNode *Next = nullptr;
|
|
|
|
~IndexNode() {
|
|
if (Next)
|
|
Next->Previous = Previous;
|
|
if (Previous)
|
|
Previous->Next = Next;
|
|
}
|
|
|
|
IndexNode(const IndexNode &Other) = delete;
|
|
|
|
IndexNode(IndexNode &&Other) {
|
|
// Copy the neighbor info
|
|
Next = Other.Next;
|
|
Previous = Other.Previous;
|
|
|
|
// Update the neighbors to point to this object
|
|
if (Other.Next)
|
|
Other.Next->Previous = this;
|
|
if (Other.Previous)
|
|
Other.Previous->Next = this;
|
|
|
|
// Make sure the dtor is a noop
|
|
Other.Next = nullptr;
|
|
Other.Previous = nullptr;
|
|
}
|
|
IndexNode() = default;
|
|
};
|
|
} // namespace internal
|
|
|
|
// Setting initial capacity to 1 because all contexts must have at least 1
|
|
// counter, and then, because all contexts belonging to a function have the same
|
|
// size, there'll be at most one other heap allocation.
|
|
using CtxProfFlatProfile =
|
|
std::map<GlobalValue::GUID, SmallVector<uint64_t, 1>>;
|
|
|
|
/// A node (context) in the loaded contextual profile, suitable for mutation
|
|
/// during IPO passes. We generally expect a fraction of counters and
|
|
/// callsites to be populated. We continue to model counters as vectors, but
|
|
/// callsites are modeled as a map of a map. The expectation is that, typically,
|
|
/// there is a small number of indirect targets (usually, 1 for direct calls);
|
|
/// but potentially a large number of callsites, and, as inlining progresses,
|
|
/// the callsite count of a caller will grow.
|
|
class PGOCtxProfContext final : public internal::IndexNode {
|
|
public:
|
|
using CallTargetMapTy = std::map<GlobalValue::GUID, PGOCtxProfContext>;
|
|
using CallsiteMapTy = std::map<uint32_t, CallTargetMapTy>;
|
|
|
|
private:
|
|
friend class PGOCtxProfileReader;
|
|
friend class PGOContextualProfile;
|
|
|
|
GlobalValue::GUID GUID = 0;
|
|
SmallVector<uint64_t, 16> Counters;
|
|
const std::optional<uint64_t> RootEntryCount{};
|
|
std::optional<CtxProfFlatProfile> Unhandled{};
|
|
CallsiteMapTy Callsites;
|
|
|
|
PGOCtxProfContext(
|
|
GlobalValue::GUID G, SmallVectorImpl<uint64_t> &&Counters,
|
|
std::optional<uint64_t> RootEntryCount = std::nullopt,
|
|
std::optional<CtxProfFlatProfile> &&Unhandled = std::nullopt)
|
|
: GUID(G), Counters(std::move(Counters)), RootEntryCount(RootEntryCount),
|
|
Unhandled(std::move(Unhandled)) {
|
|
assert(RootEntryCount.has_value() == Unhandled.has_value());
|
|
}
|
|
|
|
Expected<PGOCtxProfContext &>
|
|
getOrEmplace(uint32_t Index, GlobalValue::GUID G,
|
|
SmallVectorImpl<uint64_t> &&Counters);
|
|
|
|
// Create a bogus context object, used for anchoring the index double linked
|
|
// list - see IndexNode
|
|
PGOCtxProfContext() = default;
|
|
|
|
public:
|
|
PGOCtxProfContext(const PGOCtxProfContext &) = delete;
|
|
PGOCtxProfContext &operator=(const PGOCtxProfContext &) = delete;
|
|
PGOCtxProfContext(PGOCtxProfContext &&) = default;
|
|
PGOCtxProfContext &operator=(PGOCtxProfContext &&) = delete;
|
|
|
|
GlobalValue::GUID guid() const { return GUID; }
|
|
const SmallVectorImpl<uint64_t> &counters() const { return Counters; }
|
|
SmallVectorImpl<uint64_t> &counters() { return Counters; }
|
|
|
|
bool isRoot() const { return RootEntryCount.has_value(); }
|
|
uint64_t getTotalRootEntryCount() const { return RootEntryCount.value(); }
|
|
|
|
const CtxProfFlatProfile &getUnhandled() const { return Unhandled.value(); }
|
|
|
|
uint64_t getEntrycount() const {
|
|
assert(!Counters.empty() &&
|
|
"Functions are expected to have at their entry BB instrumented, so "
|
|
"there should always be at least 1 counter.");
|
|
return Counters[0];
|
|
}
|
|
|
|
const CallsiteMapTy &callsites() const { return Callsites; }
|
|
CallsiteMapTy &callsites() { return Callsites; }
|
|
|
|
void ingestContext(uint32_t CSId, PGOCtxProfContext &&Other) {
|
|
callsites()[CSId].emplace(Other.guid(), std::move(Other));
|
|
}
|
|
|
|
void ingestAllContexts(uint32_t CSId, CallTargetMapTy &&Other) {
|
|
auto [_, Inserted] = callsites().try_emplace(CSId, std::move(Other));
|
|
(void)Inserted;
|
|
assert(Inserted &&
|
|
"CSId was expected to be newly created as result of e.g. inlining");
|
|
}
|
|
|
|
void resizeCounters(uint32_t Size) { Counters.resize(Size); }
|
|
|
|
bool hasCallsite(uint32_t I) const {
|
|
return Callsites.find(I) != Callsites.end();
|
|
}
|
|
|
|
const CallTargetMapTy &callsite(uint32_t I) const {
|
|
assert(hasCallsite(I) && "Callsite not found");
|
|
return Callsites.find(I)->second;
|
|
}
|
|
|
|
CallTargetMapTy &callsite(uint32_t I) {
|
|
assert(hasCallsite(I) && "Callsite not found");
|
|
return Callsites.find(I)->second;
|
|
}
|
|
|
|
/// Insert this node's GUID as well as the GUIDs of the transitive closure of
|
|
/// child nodes, into the provided set (technically, all that is required of
|
|
/// `TSetOfGUIDs` is to have an `insert(GUID)` member)
|
|
template <class TSetOfGUIDs>
|
|
void getContainedGuids(TSetOfGUIDs &Guids) const {
|
|
Guids.insert(GUID);
|
|
for (const auto &[_, Callsite] : Callsites)
|
|
for (const auto &[_, Callee] : Callsite)
|
|
Callee.getContainedGuids(Guids);
|
|
}
|
|
};
|
|
|
|
using CtxProfContextualProfiles =
|
|
std::map<GlobalValue::GUID, PGOCtxProfContext>;
|
|
struct PGOCtxProfile {
|
|
CtxProfContextualProfiles Contexts;
|
|
CtxProfFlatProfile FlatProfiles;
|
|
|
|
PGOCtxProfile() = default;
|
|
PGOCtxProfile(const PGOCtxProfile &) = delete;
|
|
PGOCtxProfile(PGOCtxProfile &&) = default;
|
|
PGOCtxProfile &operator=(PGOCtxProfile &&) = default;
|
|
};
|
|
|
|
class PGOCtxProfileReader final {
|
|
StringRef Magic;
|
|
BitstreamCursor Cursor;
|
|
Expected<BitstreamEntry> advance();
|
|
Error readMetadata();
|
|
Error wrongValue(const Twine &Msg);
|
|
Error unsupported(const Twine &Msg);
|
|
|
|
Expected<std::pair<std::optional<uint32_t>, PGOCtxProfContext>>
|
|
readProfile(PGOCtxProfileBlockIDs Kind);
|
|
|
|
bool tryGetNextKnownBlockID(PGOCtxProfileBlockIDs &ID);
|
|
bool canEnterBlockWithID(PGOCtxProfileBlockIDs ID);
|
|
Error enterBlockWithID(PGOCtxProfileBlockIDs ID);
|
|
|
|
Error loadContexts(CtxProfContextualProfiles &P);
|
|
Error loadFlatProfiles(CtxProfFlatProfile &P);
|
|
Error loadFlatProfileList(CtxProfFlatProfile &P);
|
|
|
|
public:
|
|
PGOCtxProfileReader(StringRef Buffer)
|
|
: Magic(Buffer.substr(0, PGOCtxProfileWriter::ContainerMagic.size())),
|
|
Cursor(Buffer.substr(PGOCtxProfileWriter::ContainerMagic.size())) {}
|
|
|
|
LLVM_ABI Expected<PGOCtxProfile> loadProfiles();
|
|
};
|
|
|
|
LLVM_ABI void convertCtxProfToYaml(raw_ostream &OS,
|
|
const PGOCtxProfile &Profile);
|
|
} // namespace llvm
|
|
#endif
|