//===--- 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 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>; /// 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; using CallsiteMapTy = std::map; private: friend class PGOCtxProfileReader; friend class PGOContextualProfile; GlobalValue::GUID GUID = 0; SmallVector Counters; const std::optional RootEntryCount{}; std::optional Unhandled{}; CallsiteMapTy Callsites; PGOCtxProfContext( GlobalValue::GUID G, SmallVectorImpl &&Counters, std::optional RootEntryCount = std::nullopt, std::optional &&Unhandled = std::nullopt) : GUID(G), Counters(std::move(Counters)), RootEntryCount(RootEntryCount), Unhandled(std::move(Unhandled)) { assert(RootEntryCount.has_value() == Unhandled.has_value()); } Expected getOrEmplace(uint32_t Index, GlobalValue::GUID G, SmallVectorImpl &&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 &counters() const { return Counters; } SmallVectorImpl &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 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; 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 advance(); Error readMetadata(); Error wrongValue(const Twine &Msg); Error unsupported(const Twine &Msg); Expected, 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 loadProfiles(); }; LLVM_ABI void convertCtxProfToYaml(raw_ostream &OS, const PGOCtxProfile &Profile); } // namespace llvm #endif