//===- 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 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::getOrEmplace(uint32_t Index, GlobalValue::GUID G, SmallVectorImpl &&Counters) { auto [Iter, Inserted] = Callsites[Index].insert({G, PGOCtxProfContext(G, std::move(Counters))}); if (!Inserted) return make_error(instrprof_error::invalid_prof, "Duplicate GUID for same callsite."); return Iter->second; } Expected PGOCtxProfileReader::advance() { return Cursor.advance(BitstreamCursor::AF_DontAutoprocessAbbrevs); } Error PGOCtxProfileReader::wrongValue(const Twine &Msg) { return make_error(instrprof_error::invalid_prof, Msg); } Error PGOCtxProfileReader::unsupported(const Twine &Msg) { return make_error(instrprof_error::unsupported_version, Msg); } bool PGOCtxProfileReader::tryGetNextKnownBlockID(PGOCtxProfileBlockIDs &ID) { auto Blk = advance(); if (!Blk) { consumeError(Blk.takeError()); return false; } if (Blk->Kind != BitstreamEntry::SubBlock) return false; if (PGOCtxProfileBlockIDs::FIRST_VALID > Blk->ID || PGOCtxProfileBlockIDs::LAST_VALID < Blk->ID) return false; ID = static_cast(Blk->ID); return true; } bool PGOCtxProfileReader::canEnterBlockWithID(PGOCtxProfileBlockIDs ID) { PGOCtxProfileBlockIDs Test = {}; return tryGetNextKnownBlockID(Test) && Test == ID; } Error PGOCtxProfileReader::enterBlockWithID(PGOCtxProfileBlockIDs ID) { RET_ON_ERR(Cursor.EnterSubBlock(ID)); return Error::success(); } // Note: we use PGOCtxProfContext for flat profiles also, as the latter are // structurally similar. Alternative modeling here seems a bit overkill at the // moment. Expected, PGOCtxProfContext>> PGOCtxProfileReader::readProfile(PGOCtxProfileBlockIDs Kind) { assert((Kind == PGOCtxProfileBlockIDs::ContextRootBlockID || Kind == PGOCtxProfileBlockIDs::ContextNodeBlockID || Kind == PGOCtxProfileBlockIDs::FlatProfileBlockID) && "Unexpected profile kind"); RET_ON_ERR(enterBlockWithID(Kind)); std::optional Guid; std::optional> Counters; std::optional CallsiteIndex; std::optional TotalEntryCount; std::optional Unhandled; SmallVector RecordValues; const bool ExpectIndex = Kind == PGOCtxProfileBlockIDs::ContextNodeBlockID; const bool IsRoot = Kind == PGOCtxProfileBlockIDs::ContextRootBlockID; // 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()) && (!IsRoot || TotalEntryCount.has_value()) && (!IsRoot || Unhandled.has_value()); }; while (!GotAllWeNeed()) { RecordValues.clear(); EXPECT_OR_RET(Entry, advance()); if (Entry->Kind != BitstreamEntry::Record) { if (IsRoot && Entry->Kind == BitstreamEntry::SubBlock && Entry->ID == PGOCtxProfileBlockIDs::UnhandledBlockID) { RET_ON_ERR(enterBlockWithID(PGOCtxProfileBlockIDs::UnhandledBlockID)); Unhandled = CtxProfFlatProfile(); RET_ON_ERR(loadFlatProfileList(*Unhandled)); continue; } 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::CallsiteIndex: 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; case PGOCtxProfileRecords::TotalRootEntryCount: if (!IsRoot) return wrongValue("Non-root has a total entry count record"); if (RecordValues.size() != 1) return wrongValue( "The root total entry count record should have exactly one value"); TotalEntryCount = 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), TotalEntryCount, std::move(Unhandled)); 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(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 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) { 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(); } Error PGOCtxProfileReader::loadFlatProfileList(CtxProfFlatProfile &P) { while (canEnterBlockWithID(PGOCtxProfileBlockIDs::FlatProfileBlockID)) { EXPECT_OR_RET(E, readProfile(PGOCtxProfileBlockIDs::FlatProfileBlockID)); auto Guid = E->second.guid(); if (!P.insert({Guid, std::move(E->second.counters())}).second) return wrongValue("Duplicate flat profile entries"); } return Error::success(); } Error PGOCtxProfileReader::loadFlatProfiles(CtxProfFlatProfile &P) { RET_ON_ERR( enterBlockWithID(PGOCtxProfileBlockIDs::FlatProfilesSectionBlockID)); return loadFlatProfileList(P); } Expected PGOCtxProfileReader::loadProfiles() { RET_ON_ERR(readMetadata()); PGOCtxProfile Ret; PGOCtxProfileBlockIDs Test = {}; for (auto I = 0; I < 2; ++I) { if (!tryGetNextKnownBlockID(Test)) break; if (Test == PGOCtxProfileBlockIDs::ContextsSectionBlockID) { RET_ON_ERR(loadContexts(Ret.Contexts)); } else if (Test == PGOCtxProfileBlockIDs::FlatProfilesSectionBlockID) { RET_ON_ERR(loadFlatProfiles(Ret.FlatProfiles)); } else { return wrongValue("Unexpected section"); } } 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, const CtxProfFlatProfile &Flat); void toYaml(yaml::Output &Out, GlobalValue::GUID Guid, const SmallVectorImpl &Counters, const PGOCtxProfContext::CallsiteMapTy &Callsites, std::optional TotalRootEntryCount = std::nullopt, CtxProfFlatProfile Unhandled = {}) { 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); } if (TotalRootEntryCount) { Out.preflightKey("TotalRootEntryCount", true, false, UseDefault, SaveInfo); yaml::yamlize(Out, *TotalRootEntryCount, 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 (!Unhandled.empty()) { assert(TotalRootEntryCount.has_value()); Out.preflightKey("Unhandled", false, false, UseDefault, SaveInfo); toYaml(Out, Unhandled); 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 CtxProfFlatProfile &Flat) { void *SaveInfo = nullptr; Out.beginSequence(); size_t ElemID = 0; for (const auto &[Guid, Counters] : Flat) { Out.preflightElement(ElemID++, SaveInfo); toYaml(Out, Guid, Counters, {}); Out.postflightElement(nullptr); } Out.endSequence(); } void toYaml(yaml::Output &Out, const PGOCtxProfContext &Ctx) { if (Ctx.isRoot()) toYaml(Out, Ctx.guid(), Ctx.counters(), Ctx.callsites(), Ctx.getTotalRootEntryCount(), Ctx.getUnhandled()); else toYaml(Out, Ctx.guid(), Ctx.counters(), Ctx.callsites()); } } // namespace void llvm::convertCtxProfToYaml(raw_ostream &OS, const PGOCtxProfile &Profile) { yaml::Output Out(OS); void *SaveInfo = nullptr; bool UseDefault = false; Out.beginMapping(); if (!Profile.Contexts.empty()) { Out.preflightKey("Contexts", false, false, UseDefault, SaveInfo); toYaml(Out, Profile.Contexts); Out.postflightKey(nullptr); } if (!Profile.FlatProfiles.empty()) { Out.preflightKey("FlatProfiles", false, false, UseDefault, SaveInfo); toYaml(Out, Profile.FlatProfiles); Out.postflightKey(nullptr); } Out.endMapping(); }