blob: bb912635879d220af751dd576d53ca94fcaf3066 [file] [log] [blame]
//===- PGOCtxProfReader.cpp - Contextual Instrumentation profile reader ---===//
//
// 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
//
//===----------------------------------------------------------------------===//
//
// Read a contextual profile into a datastructure suitable for maintenance
// throughout IPO
//
//===----------------------------------------------------------------------===//
#include "llvm/ProfileData/PGOCtxProfReader.h"
#include "llvm/Bitstream/BitCodeEnums.h"
#include "llvm/Bitstream/BitstreamReader.h"
#include "llvm/ProfileData/InstrProf.h"
#include "llvm/ProfileData/PGOCtxProfWriter.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/YAMLTraits.h"
#include <utility>
using namespace llvm;
// FIXME(#92054) - these Error handling macros are (re-)invented in a few
// places.
#define EXPECT_OR_RET(LHS, RHS) \
auto LHS = RHS; \
if (!LHS) \
return LHS.takeError();
#define RET_ON_ERR(EXPR) \
if (auto Err = (EXPR)) \
return Err;
Expected<PGOCtxProfContext &>
PGOCtxProfContext::getOrEmplace(uint32_t Index, GlobalValue::GUID G,
SmallVectorImpl<uint64_t> &&Counters) {
auto [Iter, Inserted] =
Callsites[Index].insert({G, PGOCtxProfContext(G, std::move(Counters))});
if (!Inserted)
return make_error<InstrProfError>(instrprof_error::invalid_prof,
"Duplicate GUID for same callsite.");
return Iter->second;
}
Expected<BitstreamEntry> PGOCtxProfileReader::advance() {
return Cursor.advance(BitstreamCursor::AF_DontAutoprocessAbbrevs);
}
Error PGOCtxProfileReader::wrongValue(const Twine &Msg) {
return make_error<InstrProfError>(instrprof_error::invalid_prof, Msg);
}
Error PGOCtxProfileReader::unsupported(const Twine &Msg) {
return make_error<InstrProfError>(instrprof_error::unsupported_version, Msg);
}
bool PGOCtxProfileReader::canEnterBlockWithID(PGOCtxProfileBlockIDs ID) {
auto Blk = advance();
if (!Blk) {
consumeError(Blk.takeError());
return false;
}
return Blk->Kind == BitstreamEntry::SubBlock && Blk->ID == ID;
}
Error PGOCtxProfileReader::enterBlockWithID(PGOCtxProfileBlockIDs ID) {
RET_ON_ERR(Cursor.EnterSubBlock(ID));
return Error::success();
}
Expected<std::pair<std::optional<uint32_t>, PGOCtxProfContext>>
PGOCtxProfileReader::readProfile(PGOCtxProfileBlockIDs Kind) {
assert((Kind == PGOCtxProfileBlockIDs::ContextRootBlockID ||
Kind == PGOCtxProfileBlockIDs::ContextNodeBlockID) &&
"Unexpected profile kind");
RET_ON_ERR(enterBlockWithID(Kind));
std::optional<ctx_profile::GUID> Guid;
std::optional<SmallVector<uint64_t, 16>> Counters;
std::optional<uint32_t> CallsiteIndex;
SmallVector<uint64_t, 1> RecordValues;
const bool ExpectIndex = Kind == PGOCtxProfileBlockIDs::ContextNodeBlockID;
// We don't prescribe the order in which the records come in, and we are ok
// if other unsupported records appear. We seek in the current subblock until
// we get all we know.
auto GotAllWeNeed = [&]() {
return Guid.has_value() && Counters.has_value() &&
(!ExpectIndex || CallsiteIndex.has_value());
};
while (!GotAllWeNeed()) {
RecordValues.clear();
EXPECT_OR_RET(Entry, advance());
if (Entry->Kind != BitstreamEntry::Record)
return wrongValue(
"Expected records before encountering more subcontexts");
EXPECT_OR_RET(ReadRecord,
Cursor.readRecord(bitc::UNABBREV_RECORD, RecordValues));
switch (*ReadRecord) {
case PGOCtxProfileRecords::Guid:
if (RecordValues.size() != 1)
return wrongValue("The GUID record should have exactly one value");
Guid = RecordValues[0];
break;
case PGOCtxProfileRecords::Counters:
Counters = std::move(RecordValues);
if (Counters->empty())
return wrongValue("Empty counters. At least the entry counter (one "
"value) was expected");
break;
case PGOCtxProfileRecords::CalleeIndex:
if (!ExpectIndex)
return wrongValue("The root context should not have a callee index");
if (RecordValues.size() != 1)
return wrongValue("The callee index should have exactly one value");
CallsiteIndex = RecordValues[0];
break;
default:
// OK if we see records we do not understand, like records (profile
// components) introduced later.
break;
}
}
PGOCtxProfContext Ret(*Guid, std::move(*Counters));
while (canEnterBlockWithID(PGOCtxProfileBlockIDs::ContextNodeBlockID)) {
EXPECT_OR_RET(SC, readProfile(PGOCtxProfileBlockIDs::ContextNodeBlockID));
auto &Targets = Ret.callsites()[*SC->first];
auto [_, Inserted] =
Targets.insert({SC->second.guid(), std::move(SC->second)});
if (!Inserted)
return wrongValue(
"Unexpected duplicate target (callee) at the same callsite.");
}
return std::make_pair(CallsiteIndex, std::move(Ret));
}
Error PGOCtxProfileReader::readMetadata() {
if (Magic.size() < PGOCtxProfileWriter::ContainerMagic.size() ||
Magic != PGOCtxProfileWriter::ContainerMagic)
return make_error<InstrProfError>(instrprof_error::invalid_prof,
"Invalid magic");
BitstreamEntry Entry;
RET_ON_ERR(Cursor.advance().moveInto(Entry));
if (Entry.Kind != BitstreamEntry::SubBlock ||
Entry.ID != bitc::BLOCKINFO_BLOCK_ID)
return unsupported("Expected Block ID");
// We don't need the blockinfo to read the rest, it's metadata usable for e.g.
// llvm-bcanalyzer.
RET_ON_ERR(Cursor.SkipBlock());
EXPECT_OR_RET(Blk, advance());
if (Blk->Kind != BitstreamEntry::SubBlock)
return unsupported("Expected Version record");
RET_ON_ERR(
Cursor.EnterSubBlock(PGOCtxProfileBlockIDs::ProfileMetadataBlockID));
EXPECT_OR_RET(MData, advance());
if (MData->Kind != BitstreamEntry::Record)
return unsupported("Expected Version record");
SmallVector<uint64_t, 1> Ver;
EXPECT_OR_RET(Code, Cursor.readRecord(bitc::UNABBREV_RECORD, Ver));
if (*Code != PGOCtxProfileRecords::Version)
return unsupported("Expected Version record");
if (Ver.size() != 1 || Ver[0] > PGOCtxProfileWriter::CurrentVersion)
return unsupported("Version " + Twine(*Code) +
" is higher than supported version " +
Twine(PGOCtxProfileWriter::CurrentVersion));
return Error::success();
}
Error PGOCtxProfileReader::loadContexts(CtxProfContextualProfiles &P) {
if (canEnterBlockWithID(PGOCtxProfileBlockIDs::ContextsSectionBlockID)) {
RET_ON_ERR(enterBlockWithID(PGOCtxProfileBlockIDs::ContextsSectionBlockID));
while (canEnterBlockWithID(PGOCtxProfileBlockIDs::ContextRootBlockID)) {
EXPECT_OR_RET(E, readProfile(PGOCtxProfileBlockIDs::ContextRootBlockID));
auto Key = E->second.guid();
if (!P.insert({Key, std::move(E->second)}).second)
return wrongValue("Duplicate roots");
}
}
return Error::success();
}
Expected<PGOCtxProfile> PGOCtxProfileReader::loadProfiles() {
RET_ON_ERR(readMetadata());
PGOCtxProfile Ret;
RET_ON_ERR(loadContexts(Ret.Contexts));
return std::move(Ret);
}
namespace {
// We want to pass `const` values PGOCtxProfContext references to the yaml
// converter, and the regular yaml mapping APIs are designed to handle both
// serialization and deserialization, which prevents using const for
// serialization. Using an intermediate datastructure is overkill, both
// space-wise and design complexity-wise. Instead, we use the lower-level APIs.
void toYaml(yaml::Output &Out, const PGOCtxProfContext &Ctx);
void toYaml(yaml::Output &Out,
const PGOCtxProfContext::CallTargetMapTy &CallTargets) {
Out.beginSequence();
size_t Index = 0;
void *SaveData = nullptr;
for (const auto &[_, Ctx] : CallTargets) {
Out.preflightElement(Index++, SaveData);
toYaml(Out, Ctx);
Out.postflightElement(nullptr);
}
Out.endSequence();
}
void toYaml(yaml::Output &Out,
const PGOCtxProfContext::CallsiteMapTy &Callsites) {
auto AllCS = ::llvm::make_first_range(Callsites);
auto MaxIt = ::llvm::max_element(AllCS);
assert(MaxIt != AllCS.end() && "We should have a max value because the "
"callsites collection is not empty.");
void *SaveData = nullptr;
Out.beginSequence();
for (auto I = 0U; I <= *MaxIt; ++I) {
Out.preflightElement(I, SaveData);
auto It = Callsites.find(I);
if (It == Callsites.end()) {
// This will produce a `[ ]` sequence, which is what we want here.
Out.beginFlowSequence();
Out.endFlowSequence();
} else {
toYaml(Out, It->second);
}
Out.postflightElement(nullptr);
}
Out.endSequence();
}
void toYaml(yaml::Output &Out, GlobalValue::GUID Guid,
const SmallVectorImpl<uint64_t> &Counters,
const PGOCtxProfContext::CallsiteMapTy &Callsites) {
yaml::EmptyContext Empty;
Out.beginMapping();
void *SaveInfo = nullptr;
bool UseDefault = false;
{
Out.preflightKey("Guid", /*Required=*/true, /*SameAsDefault=*/false,
UseDefault, SaveInfo);
yaml::yamlize(Out, Guid, true, Empty);
Out.postflightKey(nullptr);
}
{
Out.preflightKey("Counters", true, false, UseDefault, SaveInfo);
Out.beginFlowSequence();
for (size_t I = 0U, E = Counters.size(); I < E; ++I) {
Out.preflightFlowElement(I, SaveInfo);
uint64_t V = Counters[I];
yaml::yamlize(Out, V, true, Empty);
Out.postflightFlowElement(SaveInfo);
}
Out.endFlowSequence();
Out.postflightKey(nullptr);
}
if (!Callsites.empty()) {
Out.preflightKey("Callsites", true, false, UseDefault, SaveInfo);
toYaml(Out, Callsites);
Out.postflightKey(nullptr);
}
Out.endMapping();
}
void toYaml(yaml::Output &Out, const PGOCtxProfContext &Ctx) {
toYaml(Out, Ctx.guid(), Ctx.counters(), Ctx.callsites());
}
} // namespace
void llvm::convertCtxProfToYaml(raw_ostream &OS,
const PGOCtxProfile &Profiles) {
yaml::Output Out(OS);
void *SaveInfo = nullptr;
bool UseDefault = false;
Out.beginMapping();
if (!Profiles.Contexts.empty()) {
Out.preflightKey("Contexts", false, false, UseDefault, SaveInfo);
toYaml(Out, Profiles.Contexts);
Out.postflightKey(nullptr);
}
Out.endMapping();
}