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