xref: /llvm-project/llvm/include/llvm/Analysis/MemoryProfileInfo.h (revision ae6d5dd58bcae9ced3b3c5b058876d3564017337)
11dad6247STeresa Johnson //===- llvm/Analysis/MemoryProfileInfo.h - memory profile info ---*- C++ -*-==//
21dad6247STeresa Johnson //
31dad6247STeresa Johnson // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
41dad6247STeresa Johnson // See https://llvm.org/LICENSE.txt for license information.
51dad6247STeresa Johnson // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
61dad6247STeresa Johnson //
71dad6247STeresa Johnson //===----------------------------------------------------------------------===//
81dad6247STeresa Johnson //
91dad6247STeresa Johnson // This file contains utilities to analyze memory profile information.
101dad6247STeresa Johnson //
111dad6247STeresa Johnson //===----------------------------------------------------------------------===//
121dad6247STeresa Johnson 
131dad6247STeresa Johnson #ifndef LLVM_ANALYSIS_MEMORYPROFILEINFO_H
141dad6247STeresa Johnson #define LLVM_ANALYSIS_MEMORYPROFILEINFO_H
151dad6247STeresa Johnson 
161dad6247STeresa Johnson #include "llvm/IR/Metadata.h"
179eacbba2STeresa Johnson #include "llvm/IR/ModuleSummaryIndex.h"
181dad6247STeresa Johnson #include <map>
191dad6247STeresa Johnson 
201dad6247STeresa Johnson namespace llvm {
211dad6247STeresa Johnson namespace memprof {
221dad6247STeresa Johnson 
231dad6247STeresa Johnson /// Return the allocation type for a given set of memory profile values.
24a4bdb275STeresa Johnson AllocationType getAllocType(uint64_t TotalLifetimeAccessDensity,
25a4bdb275STeresa Johnson                             uint64_t AllocCount, uint64_t TotalLifetime);
261dad6247STeresa Johnson 
271dad6247STeresa Johnson /// Build callstack metadata from the provided list of call stack ids. Returns
281dad6247STeresa Johnson /// the resulting metadata node.
291dad6247STeresa Johnson MDNode *buildCallstackMetadata(ArrayRef<uint64_t> CallStack, LLVMContext &Ctx);
301dad6247STeresa Johnson 
319513f2fdSTeresa Johnson /// Build metadata from the provided list of full stack id and profiled size, to
329513f2fdSTeresa Johnson /// use when reporting of hinted sizes is enabled.
339513f2fdSTeresa Johnson MDNode *buildContextSizeMetadata(ArrayRef<ContextTotalSize> ContextSizeInfo,
349513f2fdSTeresa Johnson                                  LLVMContext &Ctx);
359513f2fdSTeresa Johnson 
361dad6247STeresa Johnson /// Returns the stack node from an MIB metadata node.
371dad6247STeresa Johnson MDNode *getMIBStackNode(const MDNode *MIB);
381dad6247STeresa Johnson 
391dad6247STeresa Johnson /// Returns the allocation type from an MIB metadata node.
401dad6247STeresa Johnson AllocationType getMIBAllocType(const MDNode *MIB);
411dad6247STeresa Johnson 
42a28261c7STeresa Johnson /// Returns the string to use in attributes with the given type.
43a28261c7STeresa Johnson std::string getAllocTypeAttributeString(AllocationType Type);
44a28261c7STeresa Johnson 
455fd82ca0STeresa Johnson /// True if the AllocTypes bitmask contains just a single type.
465fd82ca0STeresa Johnson bool hasSingleAllocType(uint8_t AllocTypes);
475fd82ca0STeresa Johnson 
481dad6247STeresa Johnson /// Class to build a trie of call stack contexts for a particular profiled
491dad6247STeresa Johnson /// allocation call, along with their associated allocation types.
501dad6247STeresa Johnson /// The allocation will be at the root of the trie, which is then used to
511dad6247STeresa Johnson /// compute the minimum lists of context ids needed to associate a call context
521dad6247STeresa Johnson /// with a single allocation type.
531dad6247STeresa Johnson class CallStackTrie {
541dad6247STeresa Johnson private:
551dad6247STeresa Johnson   struct CallStackTrieNode {
561dad6247STeresa Johnson     // Allocation types for call context sharing the context prefix at this
571dad6247STeresa Johnson     // node.
581dad6247STeresa Johnson     uint8_t AllocTypes;
59*ae6d5dd5STeresa Johnson     // Updated as we add allocations to note if this is the deepest point in the
60*ae6d5dd5STeresa Johnson     // trie that has an ambiguous allocation type (both Cold and NotCold). It is
61*ae6d5dd5STeresa Johnson     // used to prune unneeded NotCold contexts, taking advantage of the fact
62*ae6d5dd5STeresa Johnson     // that we later will only clone Cold contexts, as NotCold is the allocation
63*ae6d5dd5STeresa Johnson     // default. We only need to keep as metadata the NotCold contexts that
64*ae6d5dd5STeresa Johnson     // overlap the longest with Cold allocations, so that we know how deeply we
65*ae6d5dd5STeresa Johnson     // need to clone. For example, assume we add the following contexts to the
66*ae6d5dd5STeresa Johnson     // trie:
67*ae6d5dd5STeresa Johnson     //    1 3 (notcold)
68*ae6d5dd5STeresa Johnson     //    1 2 4 (cold)
69*ae6d5dd5STeresa Johnson     //    1 2 5 (notcold)
70*ae6d5dd5STeresa Johnson     //    1 2 6 (notcold)
71*ae6d5dd5STeresa Johnson     // the trie looks like:
72*ae6d5dd5STeresa Johnson     //         1
73*ae6d5dd5STeresa Johnson     //        / \
74*ae6d5dd5STeresa Johnson     //       2   3
75*ae6d5dd5STeresa Johnson     //      /|\
76*ae6d5dd5STeresa Johnson     //     4 5 6
77*ae6d5dd5STeresa Johnson     //
78*ae6d5dd5STeresa Johnson     // It is sufficient to prune all but one not cold contexts (either 1,2,5 or
79*ae6d5dd5STeresa Johnson     // 1,2,6, we arbitrarily keep the first one we encounter which will be
80*ae6d5dd5STeresa Johnson     // 1,2,5). We'll initially have DeepestAmbiguousAllocType set false for trie
81*ae6d5dd5STeresa Johnson     // node 1 after the trie is built, and true for node 2. This indicates that
82*ae6d5dd5STeresa Johnson     // the not cold context ending in 3 is not needed (its immediate callee has
83*ae6d5dd5STeresa Johnson     // this value set false). The first notcold context we encounter when
84*ae6d5dd5STeresa Johnson     // iterating the callers of node 2 will be the context ending in 5 (since
85*ae6d5dd5STeresa Johnson     // std::map iteration is in sorted order of key), which will see that this
86*ae6d5dd5STeresa Johnson     // field is true for its callee, so we will keep it. But at that point we
87*ae6d5dd5STeresa Johnson     // set the callee's flag to false which prevents adding the not cold context
88*ae6d5dd5STeresa Johnson     // ending in 6 unnecessarily.
89*ae6d5dd5STeresa Johnson     bool DeepestAmbiguousAllocType = true;
909513f2fdSTeresa Johnson     // If the user has requested reporting of hinted sizes, keep track of the
919513f2fdSTeresa Johnson     // associated full stack id and profiled sizes. Can have more than one
929513f2fdSTeresa Johnson     // after trimming (e.g. when building from metadata). This is only placed on
939513f2fdSTeresa Johnson     // the last (root-most) trie node for each allocation context.
949513f2fdSTeresa Johnson     std::vector<ContextTotalSize> ContextSizeInfo;
951dad6247STeresa Johnson     // Map of caller stack id to the corresponding child Trie node.
961dad6247STeresa Johnson     std::map<uint64_t, CallStackTrieNode *> Callers;
979513f2fdSTeresa Johnson     CallStackTrieNode(AllocationType Type)
989513f2fdSTeresa Johnson         : AllocTypes(static_cast<uint8_t>(Type)) {}
99c725a95eSTeresa Johnson     void addAllocType(AllocationType AllocType) {
100c725a95eSTeresa Johnson       AllocTypes |= static_cast<uint8_t>(AllocType);
101c725a95eSTeresa Johnson     }
102c725a95eSTeresa Johnson     void removeAllocType(AllocationType AllocType) {
103c725a95eSTeresa Johnson       AllocTypes &= ~static_cast<uint8_t>(AllocType);
104c725a95eSTeresa Johnson     }
105c725a95eSTeresa Johnson     bool hasAllocType(AllocationType AllocType) const {
106c725a95eSTeresa Johnson       return AllocTypes & static_cast<uint8_t>(AllocType);
107c725a95eSTeresa Johnson     }
1081dad6247STeresa Johnson   };
1091dad6247STeresa Johnson 
1101dad6247STeresa Johnson   // The node for the allocation at the root.
111ad74adbdSKazu Hirata   CallStackTrieNode *Alloc = nullptr;
1121dad6247STeresa Johnson   // The allocation's leaf stack id.
113ad74adbdSKazu Hirata   uint64_t AllocStackId = 0;
1141dad6247STeresa Johnson 
1151dad6247STeresa Johnson   void deleteTrieNode(CallStackTrieNode *Node) {
1161dad6247STeresa Johnson     if (!Node)
1171dad6247STeresa Johnson       return;
1181dad6247STeresa Johnson     for (auto C : Node->Callers)
1191dad6247STeresa Johnson       deleteTrieNode(C.second);
1201dad6247STeresa Johnson     delete Node;
1211dad6247STeresa Johnson   }
1221dad6247STeresa Johnson 
1239513f2fdSTeresa Johnson   // Recursively build up a complete list of context size information from the
1249513f2fdSTeresa Johnson   // trie nodes reached form the given Node, for hint size reporting.
1259513f2fdSTeresa Johnson   void collectContextSizeInfo(CallStackTrieNode *Node,
1269513f2fdSTeresa Johnson                               std::vector<ContextTotalSize> &ContextSizeInfo);
1279513f2fdSTeresa Johnson 
128c725a95eSTeresa Johnson   // Recursively convert hot allocation types to notcold, since we don't
129c725a95eSTeresa Johnson   // actually do any cloning for hot contexts, to facilitate more aggressive
130c725a95eSTeresa Johnson   // pruning of contexts.
131c725a95eSTeresa Johnson   void convertHotToNotCold(CallStackTrieNode *Node);
132c725a95eSTeresa Johnson 
1331dad6247STeresa Johnson   // Recursive helper to trim contexts and create metadata nodes.
1341dad6247STeresa Johnson   bool buildMIBNodes(CallStackTrieNode *Node, LLVMContext &Ctx,
1351dad6247STeresa Johnson                      std::vector<uint64_t> &MIBCallStack,
1361dad6247STeresa Johnson                      std::vector<Metadata *> &MIBNodes,
137*ae6d5dd5STeresa Johnson                      bool CalleeHasAmbiguousCallerContext,
138*ae6d5dd5STeresa Johnson                      bool &CalleeDeepestAmbiguousAllocType);
1391dad6247STeresa Johnson 
1401dad6247STeresa Johnson public:
141ad74adbdSKazu Hirata   CallStackTrie() = default;
1421dad6247STeresa Johnson   ~CallStackTrie() { deleteTrieNode(Alloc); }
1431dad6247STeresa Johnson 
1441dad6247STeresa Johnson   bool empty() const { return Alloc == nullptr; }
1451dad6247STeresa Johnson 
1461dad6247STeresa Johnson   /// Add a call stack context with the given allocation type to the Trie.
1471dad6247STeresa Johnson   /// The context is represented by the list of stack ids (computed during
1481dad6247STeresa Johnson   /// matching via a debug location hash), expected to be in order from the
1491dad6247STeresa Johnson   /// allocation call down to the bottom of the call stack (i.e. callee to
1501dad6247STeresa Johnson   /// caller order).
1518c1bd67dSTeresa Johnson   void addCallStack(AllocationType AllocType, ArrayRef<uint64_t> StackIds,
1529513f2fdSTeresa Johnson                     std::vector<ContextTotalSize> ContextSizeInfo = {});
1531dad6247STeresa Johnson 
1541dad6247STeresa Johnson   /// Add the call stack context along with its allocation type from the MIB
1551dad6247STeresa Johnson   /// metadata to the Trie.
1561dad6247STeresa Johnson   void addCallStack(MDNode *MIB);
1571dad6247STeresa Johnson 
1581dad6247STeresa Johnson   /// Build and attach the minimal necessary MIB metadata. If the alloc has a
1591dad6247STeresa Johnson   /// single allocation type, add a function attribute instead. The reason for
1601dad6247STeresa Johnson   /// adding an attribute in this case is that it matches how the behavior for
1611dad6247STeresa Johnson   /// allocation calls will be communicated to lib call simplification after
1621dad6247STeresa Johnson   /// cloning or another optimization to distinguish the allocation types,
1631dad6247STeresa Johnson   /// which is lower overhead and more direct than maintaining this metadata.
1641dad6247STeresa Johnson   /// Returns true if memprof metadata attached, false if not (attribute added).
1651dad6247STeresa Johnson   bool buildAndAttachMIBMetadata(CallBase *CI);
166d7d0e740STeresa Johnson 
167d7d0e740STeresa Johnson   /// Add an attribute for the given allocation type to the call instruction.
168d7d0e740STeresa Johnson   /// If hinted by reporting is enabled, a message is emitted with the given
169d7d0e740STeresa Johnson   /// descriptor used to identify the category of single allocation type.
170d7d0e740STeresa Johnson   void addSingleAllocTypeAttribute(CallBase *CI, AllocationType AT,
171d7d0e740STeresa Johnson                                    StringRef Descriptor);
1721dad6247STeresa Johnson };
1731dad6247STeresa Johnson 
1749eacbba2STeresa Johnson /// Helper class to iterate through stack ids in both metadata (memprof MIB and
1759eacbba2STeresa Johnson /// callsite) and the corresponding ThinLTO summary data structures
1769eacbba2STeresa Johnson /// (CallsiteInfo and MIBInfo). This simplifies implementation of client code
1779eacbba2STeresa Johnson /// which doesn't need to worry about whether we are operating with IR (Regular
1789eacbba2STeresa Johnson /// LTO), or summary (ThinLTO).
1799eacbba2STeresa Johnson template <class NodeT, class IteratorT> class CallStack {
1809eacbba2STeresa Johnson public:
1819eacbba2STeresa Johnson   CallStack(const NodeT *N = nullptr) : N(N) {}
1829eacbba2STeresa Johnson 
1839eacbba2STeresa Johnson   // Implement minimum required methods for range-based for loop.
1849eacbba2STeresa Johnson   // The default implementation assumes we are operating on ThinLTO data
1859eacbba2STeresa Johnson   // structures, which have a vector of StackIdIndices. There are specialized
1869eacbba2STeresa Johnson   // versions provided to iterate through metadata.
1879eacbba2STeresa Johnson   struct CallStackIterator {
1889eacbba2STeresa Johnson     const NodeT *N = nullptr;
1899eacbba2STeresa Johnson     IteratorT Iter;
1909eacbba2STeresa Johnson     CallStackIterator(const NodeT *N, bool End);
1919eacbba2STeresa Johnson     uint64_t operator*();
1929eacbba2STeresa Johnson     bool operator==(const CallStackIterator &rhs) { return Iter == rhs.Iter; }
1939eacbba2STeresa Johnson     bool operator!=(const CallStackIterator &rhs) { return !(*this == rhs); }
1949eacbba2STeresa Johnson     void operator++() { ++Iter; }
1959eacbba2STeresa Johnson   };
1969eacbba2STeresa Johnson 
1979eacbba2STeresa Johnson   bool empty() const { return N == nullptr; }
1989eacbba2STeresa Johnson 
1999eacbba2STeresa Johnson   CallStackIterator begin() const;
2009eacbba2STeresa Johnson   CallStackIterator end() const { return CallStackIterator(N, /*End*/ true); }
2019eacbba2STeresa Johnson   CallStackIterator beginAfterSharedPrefix(CallStack &Other);
2026827c4f0STeresa Johnson   uint64_t back() const;
2039eacbba2STeresa Johnson 
2049eacbba2STeresa Johnson private:
2059eacbba2STeresa Johnson   const NodeT *N = nullptr;
2069eacbba2STeresa Johnson };
2079eacbba2STeresa Johnson 
2089eacbba2STeresa Johnson template <class NodeT, class IteratorT>
2099eacbba2STeresa Johnson CallStack<NodeT, IteratorT>::CallStackIterator::CallStackIterator(
2109eacbba2STeresa Johnson     const NodeT *N, bool End)
2119eacbba2STeresa Johnson     : N(N) {
2126827c4f0STeresa Johnson   if (!N) {
2136827c4f0STeresa Johnson     Iter = nullptr;
2149eacbba2STeresa Johnson     return;
2156827c4f0STeresa Johnson   }
2169eacbba2STeresa Johnson   Iter = End ? N->StackIdIndices.end() : N->StackIdIndices.begin();
2179eacbba2STeresa Johnson }
2189eacbba2STeresa Johnson 
2199eacbba2STeresa Johnson template <class NodeT, class IteratorT>
2209eacbba2STeresa Johnson uint64_t CallStack<NodeT, IteratorT>::CallStackIterator::operator*() {
2219eacbba2STeresa Johnson   assert(Iter != N->StackIdIndices.end());
2229eacbba2STeresa Johnson   return *Iter;
2239eacbba2STeresa Johnson }
2249eacbba2STeresa Johnson 
2259eacbba2STeresa Johnson template <class NodeT, class IteratorT>
2266827c4f0STeresa Johnson uint64_t CallStack<NodeT, IteratorT>::back() const {
2276827c4f0STeresa Johnson   assert(N);
2286827c4f0STeresa Johnson   return N->StackIdIndices.back();
2296827c4f0STeresa Johnson }
2306827c4f0STeresa Johnson 
2316827c4f0STeresa Johnson template <class NodeT, class IteratorT>
2329eacbba2STeresa Johnson typename CallStack<NodeT, IteratorT>::CallStackIterator
2339eacbba2STeresa Johnson CallStack<NodeT, IteratorT>::begin() const {
2349eacbba2STeresa Johnson   return CallStackIterator(N, /*End*/ false);
2359eacbba2STeresa Johnson }
2369eacbba2STeresa Johnson 
2379eacbba2STeresa Johnson template <class NodeT, class IteratorT>
2389eacbba2STeresa Johnson typename CallStack<NodeT, IteratorT>::CallStackIterator
2399eacbba2STeresa Johnson CallStack<NodeT, IteratorT>::beginAfterSharedPrefix(CallStack &Other) {
2409eacbba2STeresa Johnson   CallStackIterator Cur = begin();
2419eacbba2STeresa Johnson   for (CallStackIterator OtherCur = Other.begin();
2429eacbba2STeresa Johnson        Cur != end() && OtherCur != Other.end(); ++Cur, ++OtherCur)
2439eacbba2STeresa Johnson     assert(*Cur == *OtherCur);
2449eacbba2STeresa Johnson   return Cur;
2459eacbba2STeresa Johnson }
2469eacbba2STeresa Johnson 
2479eacbba2STeresa Johnson /// Specializations for iterating through IR metadata stack contexts.
2489eacbba2STeresa Johnson template <>
2499eacbba2STeresa Johnson CallStack<MDNode, MDNode::op_iterator>::CallStackIterator::CallStackIterator(
2509eacbba2STeresa Johnson     const MDNode *N, bool End);
2519eacbba2STeresa Johnson template <>
2529eacbba2STeresa Johnson uint64_t CallStack<MDNode, MDNode::op_iterator>::CallStackIterator::operator*();
2536827c4f0STeresa Johnson template <> uint64_t CallStack<MDNode, MDNode::op_iterator>::back() const;
2549eacbba2STeresa Johnson 
2551dad6247STeresa Johnson } // end namespace memprof
2561dad6247STeresa Johnson } // end namespace llvm
2571dad6247STeresa Johnson 
2581dad6247STeresa Johnson #endif
259