xref: /freebsd/contrib/llvm-project/llvm/include/llvm/ProfileData/PGOCtxProfReader.h (revision 700637cbb5e582861067a11aaca4d053546871d2)
1 //===--- PGOCtxProfReader.h - Contextual profile reader ---------*- C++ -*-===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 ///
9 /// \file
10 ///
11 /// Reader for contextual iFDO profile, which comes in bitstream format.
12 ///
13 //===----------------------------------------------------------------------===//
14 
15 #ifndef LLVM_PROFILEDATA_CTXINSTRPROFILEREADER_H
16 #define LLVM_PROFILEDATA_CTXINSTRPROFILEREADER_H
17 
18 #include "llvm/Bitstream/BitstreamReader.h"
19 #include "llvm/IR/GlobalValue.h"
20 #include "llvm/ProfileData/PGOCtxProfWriter.h"
21 #include "llvm/Support/Compiler.h"
22 #include "llvm/Support/Error.h"
23 #include <map>
24 
25 namespace llvm {
26 class PGOContextualProfile;
27 class PGOCtxProfContext;
28 
29 namespace internal {
30 // When we traverse the contextual profile, we typically want to visit contexts
31 // pertaining to a specific function. To avoid traversing the whole tree, we
32 // want to keep a per-function list - which will be in preorder - of that
33 // function's contexts. This happens in PGOContextualProfile. For memory use
34 // efficiency, we want to make PGOCtxProfContext an intrusive double-linked list
35 // node. We need to handle the cases where PGOCtxProfContext nodes are moved and
36 // deleted: in both cases, we need to update the index (==list). We can do that
37 // directly from the node in the list, without knowing who the "parent" of the
38 // list is. That makes the ADT ilist overkill here. Finally, IndexNode is meant
39 // to be an implementation detail of PGOCtxProfContext, and the only reason it's
40 // factored out is to avoid implementing move semantics for all its members.
41 class IndexNode {
42   // This class' members are intentionally private - it's a convenience
43   // implementation detail.
44   friend class ::llvm::PGOCtxProfContext;
45   friend class ::llvm::PGOContextualProfile;
46 
47   IndexNode *Previous = nullptr;
48   IndexNode *Next = nullptr;
49 
~IndexNode()50   ~IndexNode() {
51     if (Next)
52       Next->Previous = Previous;
53     if (Previous)
54       Previous->Next = Next;
55   }
56 
57   IndexNode(const IndexNode &Other) = delete;
58 
IndexNode(IndexNode && Other)59   IndexNode(IndexNode &&Other) {
60     // Copy the neighbor info
61     Next = Other.Next;
62     Previous = Other.Previous;
63 
64     // Update the neighbors to point to this object
65     if (Other.Next)
66       Other.Next->Previous = this;
67     if (Other.Previous)
68       Other.Previous->Next = this;
69 
70     // Make sure the dtor is a noop
71     Other.Next = nullptr;
72     Other.Previous = nullptr;
73   }
74   IndexNode() = default;
75 };
76 } // namespace internal
77 
78 // Setting initial capacity to 1 because all contexts must have at least 1
79 // counter, and then, because all contexts belonging to a function have the same
80 // size, there'll be at most one other heap allocation.
81 using CtxProfFlatProfile =
82     std::map<GlobalValue::GUID, SmallVector<uint64_t, 1>>;
83 
84 /// A node (context) in the loaded contextual profile, suitable for mutation
85 /// during IPO passes. We generally expect a fraction of counters and
86 /// callsites to be populated. We continue to model counters as vectors, but
87 /// callsites are modeled as a map of a map. The expectation is that, typically,
88 /// there is a small number of indirect targets (usually, 1 for direct calls);
89 /// but potentially a large number of callsites, and, as inlining progresses,
90 /// the callsite count of a caller will grow.
91 class PGOCtxProfContext final : public internal::IndexNode {
92 public:
93   using CallTargetMapTy = std::map<GlobalValue::GUID, PGOCtxProfContext>;
94   using CallsiteMapTy = std::map<uint32_t, CallTargetMapTy>;
95 
96 private:
97   friend class PGOCtxProfileReader;
98   friend class PGOContextualProfile;
99 
100   GlobalValue::GUID GUID = 0;
101   SmallVector<uint64_t, 16> Counters;
102   const std::optional<uint64_t> RootEntryCount{};
103   std::optional<CtxProfFlatProfile> Unhandled{};
104   CallsiteMapTy Callsites;
105 
106   PGOCtxProfContext(
107       GlobalValue::GUID G, SmallVectorImpl<uint64_t> &&Counters,
108       std::optional<uint64_t> RootEntryCount = std::nullopt,
109       std::optional<CtxProfFlatProfile> &&Unhandled = std::nullopt)
GUID(G)110       : GUID(G), Counters(std::move(Counters)), RootEntryCount(RootEntryCount),
111         Unhandled(std::move(Unhandled)) {
112     assert(RootEntryCount.has_value() == Unhandled.has_value());
113   }
114 
115   Expected<PGOCtxProfContext &>
116   getOrEmplace(uint32_t Index, GlobalValue::GUID G,
117                SmallVectorImpl<uint64_t> &&Counters);
118 
119   // Create a bogus context object, used for anchoring the index double linked
120   // list - see IndexNode
121   PGOCtxProfContext() = default;
122 
123 public:
124   PGOCtxProfContext(const PGOCtxProfContext &) = delete;
125   PGOCtxProfContext &operator=(const PGOCtxProfContext &) = delete;
126   PGOCtxProfContext(PGOCtxProfContext &&) = default;
127   PGOCtxProfContext &operator=(PGOCtxProfContext &&) = delete;
128 
guid()129   GlobalValue::GUID guid() const { return GUID; }
counters()130   const SmallVectorImpl<uint64_t> &counters() const { return Counters; }
counters()131   SmallVectorImpl<uint64_t> &counters() { return Counters; }
132 
isRoot()133   bool isRoot() const { return RootEntryCount.has_value(); }
getTotalRootEntryCount()134   uint64_t getTotalRootEntryCount() const { return RootEntryCount.value(); }
135 
getUnhandled()136   const CtxProfFlatProfile &getUnhandled() const { return Unhandled.value(); }
137 
getEntrycount()138   uint64_t getEntrycount() const {
139     assert(!Counters.empty() &&
140            "Functions are expected to have at their entry BB instrumented, so "
141            "there should always be at least 1 counter.");
142     return Counters[0];
143   }
144 
callsites()145   const CallsiteMapTy &callsites() const { return Callsites; }
callsites()146   CallsiteMapTy &callsites() { return Callsites; }
147 
ingestContext(uint32_t CSId,PGOCtxProfContext && Other)148   void ingestContext(uint32_t CSId, PGOCtxProfContext &&Other) {
149     callsites()[CSId].emplace(Other.guid(), std::move(Other));
150   }
151 
ingestAllContexts(uint32_t CSId,CallTargetMapTy && Other)152   void ingestAllContexts(uint32_t CSId, CallTargetMapTy &&Other) {
153     auto [_, Inserted] = callsites().try_emplace(CSId, std::move(Other));
154     (void)Inserted;
155     assert(Inserted &&
156            "CSId was expected to be newly created as result of e.g. inlining");
157   }
158 
resizeCounters(uint32_t Size)159   void resizeCounters(uint32_t Size) { Counters.resize(Size); }
160 
hasCallsite(uint32_t I)161   bool hasCallsite(uint32_t I) const {
162     return Callsites.find(I) != Callsites.end();
163   }
164 
callsite(uint32_t I)165   const CallTargetMapTy &callsite(uint32_t I) const {
166     assert(hasCallsite(I) && "Callsite not found");
167     return Callsites.find(I)->second;
168   }
169 
callsite(uint32_t I)170   CallTargetMapTy &callsite(uint32_t I) {
171     assert(hasCallsite(I) && "Callsite not found");
172     return Callsites.find(I)->second;
173   }
174 
175   /// Insert this node's GUID as well as the GUIDs of the transitive closure of
176   /// child nodes, into the provided set (technically, all that is required of
177   /// `TSetOfGUIDs` is to have an `insert(GUID)` member)
178   template <class TSetOfGUIDs>
getContainedGuids(TSetOfGUIDs & Guids)179   void getContainedGuids(TSetOfGUIDs &Guids) const {
180     Guids.insert(GUID);
181     for (const auto &[_, Callsite] : Callsites)
182       for (const auto &[_, Callee] : Callsite)
183         Callee.getContainedGuids(Guids);
184   }
185 };
186 
187 using CtxProfContextualProfiles =
188     std::map<GlobalValue::GUID, PGOCtxProfContext>;
189 struct PGOCtxProfile {
190   CtxProfContextualProfiles Contexts;
191   CtxProfFlatProfile FlatProfiles;
192 
193   PGOCtxProfile() = default;
194   PGOCtxProfile(const PGOCtxProfile &) = delete;
195   PGOCtxProfile(PGOCtxProfile &&) = default;
196   PGOCtxProfile &operator=(PGOCtxProfile &&) = default;
197 };
198 
199 class PGOCtxProfileReader final {
200   StringRef Magic;
201   BitstreamCursor Cursor;
202   Expected<BitstreamEntry> advance();
203   Error readMetadata();
204   Error wrongValue(const Twine &Msg);
205   Error unsupported(const Twine &Msg);
206 
207   Expected<std::pair<std::optional<uint32_t>, PGOCtxProfContext>>
208   readProfile(PGOCtxProfileBlockIDs Kind);
209 
210   bool tryGetNextKnownBlockID(PGOCtxProfileBlockIDs &ID);
211   bool canEnterBlockWithID(PGOCtxProfileBlockIDs ID);
212   Error enterBlockWithID(PGOCtxProfileBlockIDs ID);
213 
214   Error loadContexts(CtxProfContextualProfiles &P);
215   Error loadFlatProfiles(CtxProfFlatProfile &P);
216   Error loadFlatProfileList(CtxProfFlatProfile &P);
217 
218 public:
PGOCtxProfileReader(StringRef Buffer)219   PGOCtxProfileReader(StringRef Buffer)
220       : Magic(Buffer.substr(0, PGOCtxProfileWriter::ContainerMagic.size())),
221         Cursor(Buffer.substr(PGOCtxProfileWriter::ContainerMagic.size())) {}
222 
223   LLVM_ABI Expected<PGOCtxProfile> loadProfiles();
224 };
225 
226 LLVM_ABI void convertCtxProfToYaml(raw_ostream &OS,
227                                    const PGOCtxProfile &Profile);
228 } // namespace llvm
229 #endif
230