LLVM 20.0.0git
MemoryProfileInfo.cpp
Go to the documentation of this file.
1//===-- MemoryProfileInfo.cpp - memory profile info ------------------------==//
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
14#include "llvm/IR/Constants.h"
16
17using namespace llvm;
18using namespace llvm::memprof;
19
20#define DEBUG_TYPE "memory-profile-info"
21
22// Upper bound on lifetime access density (accesses per byte per lifetime sec)
23// for marking an allocation cold.
25 "memprof-lifetime-access-density-cold-threshold", cl::init(0.05),
27 cl::desc("The threshold the lifetime access density (accesses per byte per "
28 "lifetime sec) must be under to consider an allocation cold"));
29
30// Lower bound on lifetime to mark an allocation cold (in addition to accesses
31// per byte per sec above). This is to avoid pessimizing short lived objects.
33 "memprof-ave-lifetime-cold-threshold", cl::init(200), cl::Hidden,
34 cl::desc("The average lifetime (s) for an allocation to be considered "
35 "cold"));
36
37// Lower bound on average lifetime accesses density (total life time access
38// density / alloc count) for marking an allocation hot.
40 "memprof-min-ave-lifetime-access-density-hot-threshold", cl::init(1000),
42 cl::desc("The minimum TotalLifetimeAccessDensity / AllocCount for an "
43 "allocation to be considered hot"));
44
46 MemProfUseHotHints("memprof-use-hot-hints", cl::init(false), cl::Hidden,
47 cl::desc("Enable use of hot hints (only supported for "
48 "unambigously hot allocations)"));
49
51 "memprof-report-hinted-sizes", cl::init(false), cl::Hidden,
52 cl::desc("Report total allocation sizes of hinted allocations"));
53
55 uint64_t AllocCount,
56 uint64_t TotalLifetime) {
57 // The access densities are multiplied by 100 to hold 2 decimal places of
58 // precision, so need to divide by 100.
59 if (((float)TotalLifetimeAccessDensity) / AllocCount / 100 <
61 // Lifetime is expected to be in ms, so convert the threshold to ms.
62 && ((float)TotalLifetime) / AllocCount >=
64 return AllocationType::Cold;
65
66 // The access densities are multiplied by 100 to hold 2 decimal places of
67 // precision, so need to divide by 100.
69 ((float)TotalLifetimeAccessDensity) / AllocCount / 100 >
71 return AllocationType::Hot;
72
73 return AllocationType::NotCold;
74}
75
77 LLVMContext &Ctx) {
79 StackVals.reserve(CallStack.size());
80 for (auto Id : CallStack) {
81 auto *StackValMD =
82 ValueAsMetadata::get(ConstantInt::get(Type::getInt64Ty(Ctx), Id));
83 StackVals.push_back(StackValMD);
84 }
85 return MDNode::get(Ctx, StackVals);
86}
87
89 assert(MIB->getNumOperands() >= 2);
90 // The stack metadata is the first operand of each memprof MIB metadata.
91 return cast<MDNode>(MIB->getOperand(0));
92}
93
95 assert(MIB->getNumOperands() >= 2);
96 // The allocation type is currently the second operand of each memprof
97 // MIB metadata. This will need to change as we add additional allocation
98 // types that can be applied based on the allocation profile data.
99 auto *MDS = dyn_cast<MDString>(MIB->getOperand(1));
100 assert(MDS);
101 if (MDS->getString() == "cold") {
102 return AllocationType::Cold;
103 } else if (MDS->getString() == "hot") {
104 return AllocationType::Hot;
105 }
106 return AllocationType::NotCold;
107}
108
110 switch (Type) {
111 case AllocationType::NotCold:
112 return "notcold";
113 break;
114 case AllocationType::Cold:
115 return "cold";
116 break;
117 case AllocationType::Hot:
118 return "hot";
119 break;
120 default:
121 assert(false && "Unexpected alloc type");
122 }
123 llvm_unreachable("invalid alloc type");
124}
125
128 auto AllocTypeString = getAllocTypeAttributeString(AllocType);
129 auto A = llvm::Attribute::get(Ctx, "memprof", AllocTypeString);
130 CI->addFnAttr(A);
131}
132
134 const unsigned NumAllocTypes = llvm::popcount(AllocTypes);
135 assert(NumAllocTypes != 0);
136 return NumAllocTypes == 1;
137}
138
141 std::vector<ContextTotalSize> ContextSizeInfo) {
142 bool First = true;
143 CallStackTrieNode *Curr = nullptr;
144 for (auto StackId : StackIds) {
145 // If this is the first stack frame, add or update alloc node.
146 if (First) {
147 First = false;
148 if (Alloc) {
149 assert(AllocStackId == StackId);
150 Alloc->addAllocType(AllocType);
151 } else {
152 AllocStackId = StackId;
153 Alloc = new CallStackTrieNode(AllocType);
154 }
155 Curr = Alloc;
156 continue;
157 }
158 // Update existing caller node if it exists.
159 auto Next = Curr->Callers.find(StackId);
160 if (Next != Curr->Callers.end()) {
161 Curr = Next->second;
162 Curr->addAllocType(AllocType);
163 continue;
164 }
165 // Otherwise add a new caller node.
166 auto *New = new CallStackTrieNode(AllocType);
167 Curr->Callers[StackId] = New;
168 Curr = New;
169 }
170 assert(Curr);
171 Curr->ContextSizeInfo.insert(Curr->ContextSizeInfo.end(),
172 ContextSizeInfo.begin(), ContextSizeInfo.end());
173}
174
176 MDNode *StackMD = getMIBStackNode(MIB);
177 assert(StackMD);
178 std::vector<uint64_t> CallStack;
179 CallStack.reserve(StackMD->getNumOperands());
180 for (const auto &MIBStackIter : StackMD->operands()) {
181 auto *StackId = mdconst::dyn_extract<ConstantInt>(MIBStackIter);
182 assert(StackId);
183 CallStack.push_back(StackId->getZExtValue());
184 }
185 std::vector<ContextTotalSize> ContextSizeInfo;
186 // Collect the context size information if it exists.
187 if (MIB->getNumOperands() > 2) {
188 for (unsigned I = 2; I < MIB->getNumOperands(); I++) {
189 MDNode *ContextSizePair = dyn_cast<MDNode>(MIB->getOperand(I));
190 assert(ContextSizePair->getNumOperands() == 2);
191 uint64_t FullStackId =
192 mdconst::dyn_extract<ConstantInt>(ContextSizePair->getOperand(0))
193 ->getZExtValue();
194 uint64_t TotalSize =
195 mdconst::dyn_extract<ConstantInt>(ContextSizePair->getOperand(1))
196 ->getZExtValue();
197 ContextSizeInfo.push_back({FullStackId, TotalSize});
198 }
199 }
200 addCallStack(getMIBAllocType(MIB), CallStack, std::move(ContextSizeInfo));
201}
202
205 ArrayRef<ContextTotalSize> ContextSizeInfo) {
206 SmallVector<Metadata *> MIBPayload(
207 {buildCallstackMetadata(MIBCallStack, Ctx)});
208 MIBPayload.push_back(
210 if (!ContextSizeInfo.empty()) {
211 for (const auto &[FullStackId, TotalSize] : ContextSizeInfo) {
212 auto *FullStackIdMD = ValueAsMetadata::get(
213 ConstantInt::get(Type::getInt64Ty(Ctx), FullStackId));
214 auto *TotalSizeMD = ValueAsMetadata::get(
215 ConstantInt::get(Type::getInt64Ty(Ctx), TotalSize));
216 auto *ContextSizeMD = MDNode::get(Ctx, {FullStackIdMD, TotalSizeMD});
217 MIBPayload.push_back(ContextSizeMD);
218 }
219 }
220 return MDNode::get(Ctx, MIBPayload);
221}
222
223void CallStackTrie::collectContextSizeInfo(
224 CallStackTrieNode *Node, std::vector<ContextTotalSize> &ContextSizeInfo) {
225 ContextSizeInfo.insert(ContextSizeInfo.end(), Node->ContextSizeInfo.begin(),
226 Node->ContextSizeInfo.end());
227 for (auto &Caller : Node->Callers)
228 collectContextSizeInfo(Caller.second, ContextSizeInfo);
229}
230
231void CallStackTrie::convertHotToNotCold(CallStackTrieNode *Node) {
232 if (Node->hasAllocType(AllocationType::Hot)) {
233 Node->removeAllocType(AllocationType::Hot);
234 Node->addAllocType(AllocationType::NotCold);
235 }
236 for (auto &Caller : Node->Callers)
237 convertHotToNotCold(Caller.second);
238}
239
240// Recursive helper to trim contexts and create metadata nodes.
241// Caller should have pushed Node's loc to MIBCallStack. Doing this in the
242// caller makes it simpler to handle the many early returns in this method.
243bool CallStackTrie::buildMIBNodes(CallStackTrieNode *Node, LLVMContext &Ctx,
244 std::vector<uint64_t> &MIBCallStack,
245 std::vector<Metadata *> &MIBNodes,
246 bool CalleeHasAmbiguousCallerContext) {
247 // Trim context below the first node in a prefix with a single alloc type.
248 // Add an MIB record for the current call stack prefix.
249 if (hasSingleAllocType(Node->AllocTypes)) {
250 std::vector<ContextTotalSize> ContextSizeInfo;
251 collectContextSizeInfo(Node, ContextSizeInfo);
252 MIBNodes.push_back(createMIBNode(
253 Ctx, MIBCallStack, (AllocationType)Node->AllocTypes, ContextSizeInfo));
254 return true;
255 }
256
257 // We don't have a single allocation for all the contexts sharing this prefix,
258 // so recursively descend into callers in trie.
259 if (!Node->Callers.empty()) {
260 bool NodeHasAmbiguousCallerContext = Node->Callers.size() > 1;
261 bool AddedMIBNodesForAllCallerContexts = true;
262 for (auto &Caller : Node->Callers) {
263 MIBCallStack.push_back(Caller.first);
264 AddedMIBNodesForAllCallerContexts &=
265 buildMIBNodes(Caller.second, Ctx, MIBCallStack, MIBNodes,
266 NodeHasAmbiguousCallerContext);
267 // Remove Caller.
268 MIBCallStack.pop_back();
269 }
270 if (AddedMIBNodesForAllCallerContexts)
271 return true;
272 // We expect that the callers should be forced to add MIBs to disambiguate
273 // the context in this case (see below).
274 assert(!NodeHasAmbiguousCallerContext);
275 }
276
277 // If we reached here, then this node does not have a single allocation type,
278 // and we didn't add metadata for a longer call stack prefix including any of
279 // Node's callers. That means we never hit a single allocation type along all
280 // call stacks with this prefix. This can happen due to recursion collapsing
281 // or the stack being deeper than tracked by the profiler runtime, leading to
282 // contexts with different allocation types being merged. In that case, we
283 // trim the context just below the deepest context split, which is this
284 // node if the callee has an ambiguous caller context (multiple callers),
285 // since the recursive calls above returned false. Conservatively give it
286 // non-cold allocation type.
287 if (!CalleeHasAmbiguousCallerContext)
288 return false;
289 std::vector<ContextTotalSize> ContextSizeInfo;
290 collectContextSizeInfo(Node, ContextSizeInfo);
291 MIBNodes.push_back(createMIBNode(Ctx, MIBCallStack, AllocationType::NotCold,
292 ContextSizeInfo));
293 return true;
294}
295
297 StringRef Descriptor) {
298 addAllocTypeAttribute(CI->getContext(), CI, AT);
300 std::vector<ContextTotalSize> ContextSizeInfo;
301 collectContextSizeInfo(Alloc, ContextSizeInfo);
302 for (const auto &[FullStackId, TotalSize] : ContextSizeInfo) {
303 errs() << "MemProf hinting: Total size for full allocation context hash "
304 << FullStackId << " and " << Descriptor << " alloc type "
305 << getAllocTypeAttributeString(AT) << ": " << TotalSize << "\n";
306 }
307 }
308}
309
310// Build and attach the minimal necessary MIB metadata. If the alloc has a
311// single allocation type, add a function attribute instead. Returns true if
312// memprof metadata attached, false if not (attribute added).
314 if (hasSingleAllocType(Alloc->AllocTypes)) {
315 addSingleAllocTypeAttribute(CI, (AllocationType)Alloc->AllocTypes,
316 "single");
317 return false;
318 }
319 // If there were any hot allocation contexts, the Alloc trie node would have
320 // the Hot type set. If so, because we don't currently support cloning for hot
321 // contexts, they should be converted to NotCold. This happens in the cloning
322 // support anyway, however, doing this now enables more aggressive context
323 // trimming when building the MIB metadata (and possibly may make the
324 // allocation have a single NotCold allocation type), greatly reducing
325 // overheads in bitcode, cloning memory and cloning time.
326 if (Alloc->hasAllocType(AllocationType::Hot)) {
327 convertHotToNotCold(Alloc);
328 // Check whether we now have a single alloc type.
329 if (hasSingleAllocType(Alloc->AllocTypes)) {
330 addSingleAllocTypeAttribute(CI, (AllocationType)Alloc->AllocTypes,
331 "single");
332 return false;
333 }
334 }
335 auto &Ctx = CI->getContext();
336 std::vector<uint64_t> MIBCallStack;
337 MIBCallStack.push_back(AllocStackId);
338 std::vector<Metadata *> MIBNodes;
339 assert(!Alloc->Callers.empty() && "addCallStack has not been called yet");
340 // The last parameter is meant to say whether the callee of the given node
341 // has more than one caller. Here the node being passed in is the alloc
342 // and it has no callees. So it's false.
343 if (buildMIBNodes(Alloc, Ctx, MIBCallStack, MIBNodes, false)) {
344 assert(MIBCallStack.size() == 1 &&
345 "Should only be left with Alloc's location in stack");
346 CI->setMetadata(LLVMContext::MD_memprof, MDNode::get(Ctx, MIBNodes));
347 return true;
348 }
349 // If there exists corner case that CallStackTrie has one chain to leaf
350 // and all node in the chain have multi alloc type, conservatively give
351 // it non-cold allocation type.
352 // FIXME: Avoid this case before memory profile created. Alternatively, select
353 // hint based on fraction cold.
355 return false;
356}
357
358template <>
360 const MDNode *N, bool End)
361 : N(N) {
362 if (!N)
363 return;
364 Iter = End ? N->op_end() : N->op_begin();
365}
366
367template <>
370 assert(Iter != N->op_end());
371 ConstantInt *StackIdCInt = mdconst::dyn_extract<ConstantInt>(*Iter);
372 assert(StackIdCInt);
373 return StackIdCInt->getZExtValue();
374}
375
377 assert(N);
378 return mdconst::dyn_extract<ConstantInt>(N->operands().back())
379 ->getZExtValue();
380}
381
383 // TODO: Support more sophisticated merging, such as selecting the one with
384 // more bytes allocated, or implement support for carrying multiple allocation
385 // leaf contexts. For now, keep the first one.
386 if (A)
387 return A;
388 return B;
389}
390
392 // TODO: Support more sophisticated merging, which will require support for
393 // carrying multiple contexts. For now, keep the first one.
394 if (A)
395 return A;
396 return B;
397}
static GCRegistry::Add< OcamlGC > B("ocaml", "ocaml 3.10-compatible GC")
static GCRegistry::Add< ErlangGC > A("erlang", "erlang-compatible garbage collector")
This file contains the declarations for the subclasses of Constant, which represent the different fla...
bool End
Definition: ELF_riscv.cpp:480
#define I(x, y, z)
Definition: MD5.cpp:58
AllocType
cl::opt< float > MemProfLifetimeAccessDensityColdThreshold("memprof-lifetime-access-density-cold-threshold", cl::init(0.05), cl::Hidden, cl::desc("The threshold the lifetime access density (accesses per byte per " "lifetime sec) must be under to consider an allocation cold"))
cl::opt< unsigned > MemProfMinAveLifetimeAccessDensityHotThreshold("memprof-min-ave-lifetime-access-density-hot-threshold", cl::init(1000), cl::Hidden, cl::desc("The minimum TotalLifetimeAccessDensity / AllocCount for an " "allocation to be considered hot"))
cl::opt< bool > MemProfUseHotHints("memprof-use-hot-hints", cl::init(false), cl::Hidden, cl::desc("Enable use of hot hints (only supported for " "unambigously hot allocations)"))
cl::opt< bool > MemProfReportHintedSizes("memprof-report-hinted-sizes", cl::init(false), cl::Hidden, cl::desc("Report total allocation sizes of hinted allocations"))
static MDNode * createMIBNode(LLVMContext &Ctx, ArrayRef< uint64_t > MIBCallStack, AllocationType AllocType, ArrayRef< ContextTotalSize > ContextSizeInfo)
cl::opt< unsigned > MemProfAveLifetimeColdThreshold("memprof-ave-lifetime-cold-threshold", cl::init(200), cl::Hidden, cl::desc("The average lifetime (s) for an allocation to be considered " "cold"))
static void addAllocTypeAttribute(LLVMContext &Ctx, CallBase *CI, AllocationType AllocType)
assert(ImpDefSCC.getReg()==AMDGPU::SCC &&ImpDefSCC.isDef())
ArrayRef - Represent a constant reference to an array (0 or more elements consecutively in memory),...
Definition: ArrayRef.h:41
bool empty() const
empty - Check if the array is empty.
Definition: ArrayRef.h:163
static Attribute get(LLVMContext &Context, AttrKind Kind, uint64_t Val=0)
Return a uniquified Attribute object.
Definition: Attributes.cpp:95
Base class for all callable instructions (InvokeInst and CallInst) Holds everything related to callin...
Definition: InstrTypes.h:1112
void addFnAttr(Attribute::AttrKind Kind)
Adds the attribute to the function.
Definition: InstrTypes.h:1474
This is the shared class of boolean and integer constants.
Definition: Constants.h:83
uint64_t getZExtValue() const
Return the constant as a 64-bit unsigned integer value after it has been zero extended as appropriate...
Definition: Constants.h:157
void setMetadata(unsigned KindID, MDNode *Node)
Set the metadata of the specified kind to the specified node.
Definition: Metadata.cpp:1679
This is an important class for using LLVM in a threaded context.
Definition: LLVMContext.h:67
Metadata node.
Definition: Metadata.h:1073
static MDNode * getMergedCallsiteMetadata(MDNode *A, MDNode *B)
const MDOperand & getOperand(unsigned I) const
Definition: Metadata.h:1434
ArrayRef< MDOperand > operands() const
Definition: Metadata.h:1432
static MDTuple * get(LLVMContext &Context, ArrayRef< Metadata * > MDs)
Definition: Metadata.h:1549
unsigned getNumOperands() const
Return number of MDNode operands.
Definition: Metadata.h:1440
static MDNode * getMergedMemProfMetadata(MDNode *A, MDNode *B)
static MDString * get(LLVMContext &Context, StringRef Str)
Definition: Metadata.cpp:606
void push_back(Metadata *MD)
Append an element to the tuple. This will resize the node.
Definition: Metadata.h:1535
void reserve(size_type N)
Definition: SmallVector.h:663
void push_back(const T &Elt)
Definition: SmallVector.h:413
This is a 'vector' (really, a variable-sized array), optimized for the case when the array is small.
Definition: SmallVector.h:1196
StringRef - Represent a constant reference to a string, i.e.
Definition: StringRef.h:51
The instances of the Type class are immutable: once they are created, they are never changed.
Definition: Type.h:45
static IntegerType * getInt64Ty(LLVMContext &C)
static ValueAsMetadata * get(Value *V)
Definition: Metadata.cpp:501
LLVMContext & getContext() const
All values hold a context through their type.
Definition: Value.cpp:1075
void addCallStack(AllocationType AllocType, ArrayRef< uint64_t > StackIds, std::vector< ContextTotalSize > ContextSizeInfo={})
Add a call stack context with the given allocation type to the Trie.
void addSingleAllocTypeAttribute(CallBase *CI, AllocationType AT, StringRef Descriptor)
Add an attribute for the given allocation type to the call instruction.
bool buildAndAttachMIBMetadata(CallBase *CI)
Build and attach the minimal necessary MIB metadata.
Helper class to iterate through stack ids in both metadata (memprof MIB and callsite) and the corresp...
#define llvm_unreachable(msg)
Marks that the current location is not supposed to be reachable.
initializer< Ty > init(const Ty &Val)
Definition: CommandLine.h:443
MDNode * buildCallstackMetadata(ArrayRef< uint64_t > CallStack, LLVMContext &Ctx)
Build callstack metadata from the provided list of call stack ids.
AllocationType getAllocType(uint64_t TotalLifetimeAccessDensity, uint64_t AllocCount, uint64_t TotalLifetime)
Return the allocation type for a given set of memory profile values.
AllocationType getMIBAllocType(const MDNode *MIB)
Returns the allocation type from an MIB metadata node.
bool hasSingleAllocType(uint8_t AllocTypes)
True if the AllocTypes bitmask contains just a single type.
std::string getAllocTypeAttributeString(AllocationType Type)
Returns the string to use in attributes with the given type.
MDNode * getMIBStackNode(const MDNode *MIB)
Returns the stack node from an MIB metadata node.
This is an optimization pass for GlobalISel generic memory operations.
Definition: AddressRanges.h:18
int popcount(T Value) noexcept
Count the number of set bits in a value.
Definition: bit.h:385
raw_fd_ostream & errs()
This returns a reference to a raw_ostream for standard error.
@ First
Helpers to iterate all locations in the MemoryEffectsBase class.
#define N