xref: /llvm-project/llvm/include/llvm/ProfileData/PGOCtxProfReader.h (revision b15845c0059b06f406e33f278127d7eb41ff5ab6)
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/Error.h"
22 #include <map>
23 
24 namespace llvm {
25 class PGOContextualProfile;
26 class PGOCtxProfContext;
27 
28 namespace internal {
29 // When we traverse the contextual profile, we typically want to visit contexts
30 // pertaining to a specific function. To avoid traversing the whole tree, we
31 // want to keep a per-function list - which will be in preorder - of that
32 // function's contexts. This happens in PGOContextualProfile. For memory use
33 // efficiency, we want to make PGOCtxProfContext an intrusive double-linked list
34 // node. We need to handle the cases where PGOCtxProfContext nodes are moved and
35 // deleted: in both cases, we need to update the index (==list). We can do that
36 // directly from the node in the list, without knowing who the "parent" of the
37 // list is. That makes the ADT ilist overkill here. Finally, IndexNode is meant
38 // to be an implementation detail of PGOCtxProfContext, and the only reason it's
39 // factored out is to avoid implementing move semantics for all its members.
40 class IndexNode {
41   // This class' members are intentionally private - it's a convenience
42   // implementation detail.
43   friend class ::llvm::PGOCtxProfContext;
44   friend class ::llvm::PGOContextualProfile;
45 
46   IndexNode *Previous = nullptr;
47   IndexNode *Next = nullptr;
48 
49   ~IndexNode() {
50     if (Next)
51       Next->Previous = Previous;
52     if (Previous)
53       Previous->Next = Next;
54   }
55 
56   IndexNode(const IndexNode &Other) = delete;
57 
58   IndexNode(IndexNode &&Other) {
59     // Copy the neighbor info
60     Next = Other.Next;
61     Previous = Other.Previous;
62 
63     // Update the neighbors to point to this object
64     if (Other.Next)
65       Other.Next->Previous = this;
66     if (Other.Previous)
67       Other.Previous->Next = this;
68 
69     // Make sure the dtor is a noop
70     Other.Next = nullptr;
71     Other.Previous = nullptr;
72   }
73   IndexNode() = default;
74 };
75 } // namespace internal
76 
77 /// A node (context) in the loaded contextual profile, suitable for mutation
78 /// during IPO passes. We generally expect a fraction of counters and
79 /// callsites to be populated. We continue to model counters as vectors, but
80 /// callsites are modeled as a map of a map. The expectation is that, typically,
81 /// there is a small number of indirect targets (usually, 1 for direct calls);
82 /// but potentially a large number of callsites, and, as inlining progresses,
83 /// the callsite count of a caller will grow.
84 class PGOCtxProfContext final : public internal::IndexNode {
85 public:
86   using CallTargetMapTy = std::map<GlobalValue::GUID, PGOCtxProfContext>;
87   using CallsiteMapTy = std::map<uint32_t, CallTargetMapTy>;
88 
89 private:
90   friend class PGOCtxProfileReader;
91   friend class PGOContextualProfile;
92 
93   GlobalValue::GUID GUID = 0;
94   SmallVector<uint64_t, 16> Counters;
95   CallsiteMapTy Callsites;
96 
97   PGOCtxProfContext(GlobalValue::GUID G, SmallVectorImpl<uint64_t> &&Counters)
98       : GUID(G), Counters(std::move(Counters)) {}
99 
100   Expected<PGOCtxProfContext &>
101   getOrEmplace(uint32_t Index, GlobalValue::GUID G,
102                SmallVectorImpl<uint64_t> &&Counters);
103 
104   // Create a bogus context object, used for anchoring the index double linked
105   // list - see IndexNode
106   PGOCtxProfContext() = default;
107 
108 public:
109   PGOCtxProfContext(const PGOCtxProfContext &) = delete;
110   PGOCtxProfContext &operator=(const PGOCtxProfContext &) = delete;
111   PGOCtxProfContext(PGOCtxProfContext &&) = default;
112   PGOCtxProfContext &operator=(PGOCtxProfContext &&) = delete;
113 
114   GlobalValue::GUID guid() const { return GUID; }
115   const SmallVectorImpl<uint64_t> &counters() const { return Counters; }
116   SmallVectorImpl<uint64_t> &counters() { return Counters; }
117 
118   uint64_t getEntrycount() const {
119     assert(!Counters.empty() &&
120            "Functions are expected to have at their entry BB instrumented, so "
121            "there should always be at least 1 counter.");
122     return Counters[0];
123   }
124 
125   const CallsiteMapTy &callsites() const { return Callsites; }
126   CallsiteMapTy &callsites() { return Callsites; }
127 
128   void ingestContext(uint32_t CSId, PGOCtxProfContext &&Other) {
129     callsites()[CSId].emplace(Other.guid(), std::move(Other));
130   }
131 
132   void ingestAllContexts(uint32_t CSId, CallTargetMapTy &&Other) {
133     auto [_, Inserted] = callsites().try_emplace(CSId, std::move(Other));
134     (void)Inserted;
135     assert(Inserted &&
136            "CSId was expected to be newly created as result of e.g. inlining");
137   }
138 
139   void resizeCounters(uint32_t Size) { Counters.resize(Size); }
140 
141   bool hasCallsite(uint32_t I) const {
142     return Callsites.find(I) != Callsites.end();
143   }
144 
145   const CallTargetMapTy &callsite(uint32_t I) const {
146     assert(hasCallsite(I) && "Callsite not found");
147     return Callsites.find(I)->second;
148   }
149 
150   CallTargetMapTy &callsite(uint32_t I) {
151     assert(hasCallsite(I) && "Callsite not found");
152     return Callsites.find(I)->second;
153   }
154 
155   /// Insert this node's GUID as well as the GUIDs of the transitive closure of
156   /// child nodes, into the provided set (technically, all that is required of
157   /// `TSetOfGUIDs` is to have an `insert(GUID)` member)
158   template <class TSetOfGUIDs>
159   void getContainedGuids(TSetOfGUIDs &Guids) const {
160     Guids.insert(GUID);
161     for (const auto &[_, Callsite] : Callsites)
162       for (const auto &[_, Callee] : Callsite)
163         Callee.getContainedGuids(Guids);
164   }
165 };
166 
167 class PGOCtxProfileReader final {
168   StringRef Magic;
169   BitstreamCursor Cursor;
170   Expected<BitstreamEntry> advance();
171   Error readMetadata();
172   Error wrongValue(const Twine &);
173   Error unsupported(const Twine &);
174 
175   Expected<std::pair<std::optional<uint32_t>, PGOCtxProfContext>>
176   readContext(bool ExpectIndex);
177   bool canReadContext();
178 
179 public:
180   PGOCtxProfileReader(StringRef Buffer)
181       : Magic(Buffer.substr(0, PGOCtxProfileWriter::ContainerMagic.size())),
182         Cursor(Buffer.substr(PGOCtxProfileWriter::ContainerMagic.size())) {}
183 
184   Expected<std::map<GlobalValue::GUID, PGOCtxProfContext>> loadContexts();
185 };
186 
187 void convertCtxProfToYaml(raw_ostream &OS,
188                           const PGOCtxProfContext::CallTargetMapTy &);
189 } // namespace llvm
190 #endif
191