xref: /llvm-project/llvm/include/llvm/Analysis/MemoryProfileInfo.h (revision ae6d5dd58bcae9ced3b3c5b058876d3564017337)
1 //===- llvm/Analysis/MemoryProfileInfo.h - memory profile info ---*- 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 // This file contains utilities to analyze memory profile information.
10 //
11 //===----------------------------------------------------------------------===//
12 
13 #ifndef LLVM_ANALYSIS_MEMORYPROFILEINFO_H
14 #define LLVM_ANALYSIS_MEMORYPROFILEINFO_H
15 
16 #include "llvm/IR/Metadata.h"
17 #include "llvm/IR/ModuleSummaryIndex.h"
18 #include <map>
19 
20 namespace llvm {
21 namespace memprof {
22 
23 /// Return the allocation type for a given set of memory profile values.
24 AllocationType getAllocType(uint64_t TotalLifetimeAccessDensity,
25                             uint64_t AllocCount, uint64_t TotalLifetime);
26 
27 /// Build callstack metadata from the provided list of call stack ids. Returns
28 /// the resulting metadata node.
29 MDNode *buildCallstackMetadata(ArrayRef<uint64_t> CallStack, LLVMContext &Ctx);
30 
31 /// Build metadata from the provided list of full stack id and profiled size, to
32 /// use when reporting of hinted sizes is enabled.
33 MDNode *buildContextSizeMetadata(ArrayRef<ContextTotalSize> ContextSizeInfo,
34                                  LLVMContext &Ctx);
35 
36 /// Returns the stack node from an MIB metadata node.
37 MDNode *getMIBStackNode(const MDNode *MIB);
38 
39 /// Returns the allocation type from an MIB metadata node.
40 AllocationType getMIBAllocType(const MDNode *MIB);
41 
42 /// Returns the string to use in attributes with the given type.
43 std::string getAllocTypeAttributeString(AllocationType Type);
44 
45 /// True if the AllocTypes bitmask contains just a single type.
46 bool hasSingleAllocType(uint8_t AllocTypes);
47 
48 /// Class to build a trie of call stack contexts for a particular profiled
49 /// allocation call, along with their associated allocation types.
50 /// The allocation will be at the root of the trie, which is then used to
51 /// compute the minimum lists of context ids needed to associate a call context
52 /// with a single allocation type.
53 class CallStackTrie {
54 private:
55   struct CallStackTrieNode {
56     // Allocation types for call context sharing the context prefix at this
57     // node.
58     uint8_t AllocTypes;
59     // Updated as we add allocations to note if this is the deepest point in the
60     // trie that has an ambiguous allocation type (both Cold and NotCold). It is
61     // used to prune unneeded NotCold contexts, taking advantage of the fact
62     // that we later will only clone Cold contexts, as NotCold is the allocation
63     // default. We only need to keep as metadata the NotCold contexts that
64     // overlap the longest with Cold allocations, so that we know how deeply we
65     // need to clone. For example, assume we add the following contexts to the
66     // trie:
67     //    1 3 (notcold)
68     //    1 2 4 (cold)
69     //    1 2 5 (notcold)
70     //    1 2 6 (notcold)
71     // the trie looks like:
72     //         1
73     //        / \
74     //       2   3
75     //      /|\
76     //     4 5 6
77     //
78     // It is sufficient to prune all but one not cold contexts (either 1,2,5 or
79     // 1,2,6, we arbitrarily keep the first one we encounter which will be
80     // 1,2,5). We'll initially have DeepestAmbiguousAllocType set false for trie
81     // node 1 after the trie is built, and true for node 2. This indicates that
82     // the not cold context ending in 3 is not needed (its immediate callee has
83     // this value set false). The first notcold context we encounter when
84     // iterating the callers of node 2 will be the context ending in 5 (since
85     // std::map iteration is in sorted order of key), which will see that this
86     // field is true for its callee, so we will keep it. But at that point we
87     // set the callee's flag to false which prevents adding the not cold context
88     // ending in 6 unnecessarily.
89     bool DeepestAmbiguousAllocType = true;
90     // If the user has requested reporting of hinted sizes, keep track of the
91     // associated full stack id and profiled sizes. Can have more than one
92     // after trimming (e.g. when building from metadata). This is only placed on
93     // the last (root-most) trie node for each allocation context.
94     std::vector<ContextTotalSize> ContextSizeInfo;
95     // Map of caller stack id to the corresponding child Trie node.
96     std::map<uint64_t, CallStackTrieNode *> Callers;
97     CallStackTrieNode(AllocationType Type)
98         : AllocTypes(static_cast<uint8_t>(Type)) {}
99     void addAllocType(AllocationType AllocType) {
100       AllocTypes |= static_cast<uint8_t>(AllocType);
101     }
102     void removeAllocType(AllocationType AllocType) {
103       AllocTypes &= ~static_cast<uint8_t>(AllocType);
104     }
105     bool hasAllocType(AllocationType AllocType) const {
106       return AllocTypes & static_cast<uint8_t>(AllocType);
107     }
108   };
109 
110   // The node for the allocation at the root.
111   CallStackTrieNode *Alloc = nullptr;
112   // The allocation's leaf stack id.
113   uint64_t AllocStackId = 0;
114 
115   void deleteTrieNode(CallStackTrieNode *Node) {
116     if (!Node)
117       return;
118     for (auto C : Node->Callers)
119       deleteTrieNode(C.second);
120     delete Node;
121   }
122 
123   // Recursively build up a complete list of context size information from the
124   // trie nodes reached form the given Node, for hint size reporting.
125   void collectContextSizeInfo(CallStackTrieNode *Node,
126                               std::vector<ContextTotalSize> &ContextSizeInfo);
127 
128   // Recursively convert hot allocation types to notcold, since we don't
129   // actually do any cloning for hot contexts, to facilitate more aggressive
130   // pruning of contexts.
131   void convertHotToNotCold(CallStackTrieNode *Node);
132 
133   // Recursive helper to trim contexts and create metadata nodes.
134   bool buildMIBNodes(CallStackTrieNode *Node, LLVMContext &Ctx,
135                      std::vector<uint64_t> &MIBCallStack,
136                      std::vector<Metadata *> &MIBNodes,
137                      bool CalleeHasAmbiguousCallerContext,
138                      bool &CalleeDeepestAmbiguousAllocType);
139 
140 public:
141   CallStackTrie() = default;
142   ~CallStackTrie() { deleteTrieNode(Alloc); }
143 
144   bool empty() const { return Alloc == nullptr; }
145 
146   /// Add a call stack context with the given allocation type to the Trie.
147   /// The context is represented by the list of stack ids (computed during
148   /// matching via a debug location hash), expected to be in order from the
149   /// allocation call down to the bottom of the call stack (i.e. callee to
150   /// caller order).
151   void addCallStack(AllocationType AllocType, ArrayRef<uint64_t> StackIds,
152                     std::vector<ContextTotalSize> ContextSizeInfo = {});
153 
154   /// Add the call stack context along with its allocation type from the MIB
155   /// metadata to the Trie.
156   void addCallStack(MDNode *MIB);
157 
158   /// Build and attach the minimal necessary MIB metadata. If the alloc has a
159   /// single allocation type, add a function attribute instead. The reason for
160   /// adding an attribute in this case is that it matches how the behavior for
161   /// allocation calls will be communicated to lib call simplification after
162   /// cloning or another optimization to distinguish the allocation types,
163   /// which is lower overhead and more direct than maintaining this metadata.
164   /// Returns true if memprof metadata attached, false if not (attribute added).
165   bool buildAndAttachMIBMetadata(CallBase *CI);
166 
167   /// Add an attribute for the given allocation type to the call instruction.
168   /// If hinted by reporting is enabled, a message is emitted with the given
169   /// descriptor used to identify the category of single allocation type.
170   void addSingleAllocTypeAttribute(CallBase *CI, AllocationType AT,
171                                    StringRef Descriptor);
172 };
173 
174 /// Helper class to iterate through stack ids in both metadata (memprof MIB and
175 /// callsite) and the corresponding ThinLTO summary data structures
176 /// (CallsiteInfo and MIBInfo). This simplifies implementation of client code
177 /// which doesn't need to worry about whether we are operating with IR (Regular
178 /// LTO), or summary (ThinLTO).
179 template <class NodeT, class IteratorT> class CallStack {
180 public:
181   CallStack(const NodeT *N = nullptr) : N(N) {}
182 
183   // Implement minimum required methods for range-based for loop.
184   // The default implementation assumes we are operating on ThinLTO data
185   // structures, which have a vector of StackIdIndices. There are specialized
186   // versions provided to iterate through metadata.
187   struct CallStackIterator {
188     const NodeT *N = nullptr;
189     IteratorT Iter;
190     CallStackIterator(const NodeT *N, bool End);
191     uint64_t operator*();
192     bool operator==(const CallStackIterator &rhs) { return Iter == rhs.Iter; }
193     bool operator!=(const CallStackIterator &rhs) { return !(*this == rhs); }
194     void operator++() { ++Iter; }
195   };
196 
197   bool empty() const { return N == nullptr; }
198 
199   CallStackIterator begin() const;
200   CallStackIterator end() const { return CallStackIterator(N, /*End*/ true); }
201   CallStackIterator beginAfterSharedPrefix(CallStack &Other);
202   uint64_t back() const;
203 
204 private:
205   const NodeT *N = nullptr;
206 };
207 
208 template <class NodeT, class IteratorT>
209 CallStack<NodeT, IteratorT>::CallStackIterator::CallStackIterator(
210     const NodeT *N, bool End)
211     : N(N) {
212   if (!N) {
213     Iter = nullptr;
214     return;
215   }
216   Iter = End ? N->StackIdIndices.end() : N->StackIdIndices.begin();
217 }
218 
219 template <class NodeT, class IteratorT>
220 uint64_t CallStack<NodeT, IteratorT>::CallStackIterator::operator*() {
221   assert(Iter != N->StackIdIndices.end());
222   return *Iter;
223 }
224 
225 template <class NodeT, class IteratorT>
226 uint64_t CallStack<NodeT, IteratorT>::back() const {
227   assert(N);
228   return N->StackIdIndices.back();
229 }
230 
231 template <class NodeT, class IteratorT>
232 typename CallStack<NodeT, IteratorT>::CallStackIterator
233 CallStack<NodeT, IteratorT>::begin() const {
234   return CallStackIterator(N, /*End*/ false);
235 }
236 
237 template <class NodeT, class IteratorT>
238 typename CallStack<NodeT, IteratorT>::CallStackIterator
239 CallStack<NodeT, IteratorT>::beginAfterSharedPrefix(CallStack &Other) {
240   CallStackIterator Cur = begin();
241   for (CallStackIterator OtherCur = Other.begin();
242        Cur != end() && OtherCur != Other.end(); ++Cur, ++OtherCur)
243     assert(*Cur == *OtherCur);
244   return Cur;
245 }
246 
247 /// Specializations for iterating through IR metadata stack contexts.
248 template <>
249 CallStack<MDNode, MDNode::op_iterator>::CallStackIterator::CallStackIterator(
250     const MDNode *N, bool End);
251 template <>
252 uint64_t CallStack<MDNode, MDNode::op_iterator>::CallStackIterator::operator*();
253 template <> uint64_t CallStack<MDNode, MDNode::op_iterator>::back() const;
254 
255 } // end namespace memprof
256 } // end namespace llvm
257 
258 #endif
259