xref: /llvm-project/llvm/unittests/Analysis/MemoryProfileInfoTest.cpp (revision 98ed423361de2f9dc0113a31be2aa04524489ca9)
1 //===- MemoryProfileInfoTest.cpp - Memory Profile Info Unit Tests-===//
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 #include "llvm/Analysis/MemoryProfileInfo.h"
10 #include "llvm/AsmParser/Parser.h"
11 #include "llvm/IR/Instructions.h"
12 #include "llvm/IR/LLVMContext.h"
13 #include "llvm/IR/Module.h"
14 #include "llvm/IR/ModuleSummaryIndex.h"
15 #include "llvm/Support/CommandLine.h"
16 #include "llvm/Support/SourceMgr.h"
17 #include "gtest/gtest.h"
18 #include <cstring>
19 
20 using namespace llvm;
21 using namespace llvm::memprof;
22 
23 extern cl::opt<float> MemProfAccessesPerByteColdThreshold;
24 extern cl::opt<unsigned> MemProfMinLifetimeColdThreshold;
25 
26 namespace {
27 
28 class MemoryProfileInfoTest : public testing::Test {
29 protected:
30   std::unique_ptr<Module> makeLLVMModule(LLVMContext &C, const char *IR) {
31     SMDiagnostic Err;
32     std::unique_ptr<Module> Mod = parseAssemblyString(IR, Err, C);
33     if (!Mod)
34       Err.print("MemoryProfileInfoTest", errs());
35     return Mod;
36   }
37 
38   std::unique_ptr<ModuleSummaryIndex> makeLLVMIndex(const char *Summary) {
39     SMDiagnostic Err;
40     std::unique_ptr<ModuleSummaryIndex> Index =
41         parseSummaryIndexAssemblyString(Summary, Err);
42     if (!Index)
43       Err.print("MemoryProfileInfoTest", errs());
44     return Index;
45   }
46 
47   // This looks for a call that has the given value name, which
48   // is the name of the value being assigned the call return value.
49   CallBase *findCall(Function &F, const char *Name = nullptr) {
50     for (auto &BB : F)
51       for (auto &I : BB)
52         if (auto *CB = dyn_cast<CallBase>(&I))
53           if (!Name || CB->getName() == Name)
54             return CB;
55     return nullptr;
56   }
57 };
58 
59 // Test getAllocType helper.
60 // Basic checks on the allocation type for values just above and below
61 // the thresholds.
62 TEST_F(MemoryProfileInfoTest, GetAllocType) {
63   // Long lived with more accesses per byte than threshold is not cold.
64   EXPECT_EQ(
65       getAllocType(/*MaxAccessCount=*/MemProfAccessesPerByteColdThreshold + 1,
66                    /*MinSize=*/1,
67                    /*MinLifetime=*/MemProfMinLifetimeColdThreshold * 1000 + 1),
68       AllocationType::NotCold);
69   // Long lived with less accesses per byte than threshold is cold.
70   EXPECT_EQ(
71       getAllocType(/*MaxAccessCount=*/MemProfAccessesPerByteColdThreshold - 1,
72                    /*MinSize=*/1,
73                    /*MinLifetime=*/MemProfMinLifetimeColdThreshold * 1000 + 1),
74       AllocationType::Cold);
75   // Short lived with more accesses per byte than threshold is not cold.
76   EXPECT_EQ(
77       getAllocType(/*MaxAccessCount=*/MemProfAccessesPerByteColdThreshold + 1,
78                    /*MinSize=*/1,
79                    /*MinLifetime=*/MemProfMinLifetimeColdThreshold * 1000 - 1),
80       AllocationType::NotCold);
81   // Short lived with less accesses per byte than threshold is not cold.
82   EXPECT_EQ(
83       getAllocType(/*MaxAccessCount=*/MemProfAccessesPerByteColdThreshold - 1,
84                    /*MinSize=*/1,
85                    /*MinLifetime=*/MemProfMinLifetimeColdThreshold * 1000 - 1),
86       AllocationType::NotCold);
87 }
88 
89 // Test buildCallstackMetadata helper.
90 TEST_F(MemoryProfileInfoTest, BuildCallStackMD) {
91   LLVMContext C;
92   MDNode *CallStack = buildCallstackMetadata({1, 2, 3}, C);
93   ASSERT_EQ(CallStack->getNumOperands(), 3u);
94   unsigned ExpectedId = 1;
95   for (auto &Op : CallStack->operands()) {
96     auto *StackId = mdconst::dyn_extract<ConstantInt>(Op);
97     EXPECT_EQ(StackId->getZExtValue(), ExpectedId++);
98   }
99 }
100 
101 // Test CallStackTrie::addCallStack interface taking allocation type and list of
102 // call stack ids.
103 // Check that allocations with a single allocation type along all call stacks
104 // get an attribute instead of memprof metadata.
105 TEST_F(MemoryProfileInfoTest, Attribute) {
106   LLVMContext C;
107   std::unique_ptr<Module> M = makeLLVMModule(C,
108                                              R"IR(
109 target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
110 target triple = "x86_64-pc-linux-gnu"
111 define i32* @test() {
112 entry:
113   %call1 = call noalias dereferenceable_or_null(40) i8* @malloc(i64 noundef 40)
114   %0 = bitcast i8* %call1 to i32*
115   %call2 = call noalias dereferenceable_or_null(40) i8* @malloc(i64 noundef 40)
116   %1 = bitcast i8* %call2 to i32*
117   ret i32* %1
118 }
119 declare dso_local noalias noundef i8* @malloc(i64 noundef)
120 )IR");
121 
122   Function *Func = M->getFunction("test");
123 
124   // First call has all cold contexts.
125   CallStackTrie Trie1;
126   Trie1.addCallStack(AllocationType::Cold, {1, 2});
127   Trie1.addCallStack(AllocationType::Cold, {1, 3, 4});
128   CallBase *Call1 = findCall(*Func, "call1");
129   Trie1.buildAndAttachMIBMetadata(Call1);
130 
131   EXPECT_FALSE(Call1->hasMetadata(LLVMContext::MD_memprof));
132   EXPECT_TRUE(Call1->hasFnAttr("memprof"));
133   EXPECT_EQ(Call1->getFnAttr("memprof").getValueAsString(), "cold");
134 
135   // Second call has all non-cold contexts.
136   CallStackTrie Trie2;
137   Trie2.addCallStack(AllocationType::NotCold, {5, 6});
138   Trie2.addCallStack(AllocationType::NotCold, {5, 7, 8});
139   CallBase *Call2 = findCall(*Func, "call2");
140   Trie2.buildAndAttachMIBMetadata(Call2);
141 
142   EXPECT_FALSE(Call2->hasMetadata(LLVMContext::MD_memprof));
143   EXPECT_TRUE(Call2->hasFnAttr("memprof"));
144   EXPECT_EQ(Call2->getFnAttr("memprof").getValueAsString(), "notcold");
145 }
146 
147 // Test CallStackTrie::addCallStack interface taking allocation type and list of
148 // call stack ids.
149 // Test that an allocation call reached by both cold and non cold call stacks
150 // gets memprof metadata representing the different allocation type contexts.
151 TEST_F(MemoryProfileInfoTest, ColdAndNotColdMIB) {
152   LLVMContext C;
153   std::unique_ptr<Module> M = makeLLVMModule(C,
154                                              R"IR(
155 target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
156 target triple = "x86_64-pc-linux-gnu"
157 define i32* @test() {
158 entry:
159   %call = call noalias dereferenceable_or_null(40) i8* @malloc(i64 noundef 40)
160   %0 = bitcast i8* %call to i32*
161   ret i32* %0
162 }
163 declare dso_local noalias noundef i8* @malloc(i64 noundef)
164 )IR");
165 
166   Function *Func = M->getFunction("test");
167 
168   CallStackTrie Trie;
169   Trie.addCallStack(AllocationType::Cold, {1, 2});
170   Trie.addCallStack(AllocationType::NotCold, {1, 3});
171 
172   CallBase *Call = findCall(*Func, "call");
173   Trie.buildAndAttachMIBMetadata(Call);
174 
175   EXPECT_FALSE(Call->hasFnAttr("memprof"));
176   EXPECT_TRUE(Call->hasMetadata(LLVMContext::MD_memprof));
177   MDNode *MemProfMD = Call->getMetadata(LLVMContext::MD_memprof);
178   ASSERT_EQ(MemProfMD->getNumOperands(), 2u);
179   for (auto &MIBOp : MemProfMD->operands()) {
180     MDNode *MIB = dyn_cast<MDNode>(MIBOp);
181     MDNode *StackMD = getMIBStackNode(MIB);
182     ASSERT_NE(StackMD, nullptr);
183     ASSERT_EQ(StackMD->getNumOperands(), 2u);
184     auto *StackId = mdconst::dyn_extract<ConstantInt>(StackMD->getOperand(0));
185     ASSERT_EQ(StackId->getZExtValue(), 1u);
186     StackId = mdconst::dyn_extract<ConstantInt>(StackMD->getOperand(1));
187     if (StackId->getZExtValue() == 2u)
188       EXPECT_EQ(getMIBAllocType(MIB), AllocationType::Cold);
189     else {
190       ASSERT_EQ(StackId->getZExtValue(), 3u);
191       EXPECT_EQ(getMIBAllocType(MIB), AllocationType::NotCold);
192     }
193   }
194 }
195 
196 // Test CallStackTrie::addCallStack interface taking allocation type and list of
197 // call stack ids.
198 // Test that an allocation call reached by multiple call stacks has memprof
199 // metadata with the contexts trimmed to the minimum context required to
200 // identify the allocation type.
201 TEST_F(MemoryProfileInfoTest, TrimmedMIBContext) {
202   LLVMContext C;
203   std::unique_ptr<Module> M = makeLLVMModule(C,
204                                              R"IR(
205 target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
206 target triple = "x86_64-pc-linux-gnu"
207 define i32* @test() {
208 entry:
209   %call = call noalias dereferenceable_or_null(40) i8* @malloc(i64 noundef 40)
210   %0 = bitcast i8* %call to i32*
211   ret i32* %0
212 }
213 declare dso_local noalias noundef i8* @malloc(i64 noundef)
214 )IR");
215 
216   Function *Func = M->getFunction("test");
217 
218   CallStackTrie Trie;
219   // We should be able to trim the following two and combine into a single MIB
220   // with the cold context {1, 2}.
221   Trie.addCallStack(AllocationType::Cold, {1, 2, 3});
222   Trie.addCallStack(AllocationType::Cold, {1, 2, 4});
223   // We should be able to trim the following two and combine into a single MIB
224   // with the non-cold context {1, 5}.
225   Trie.addCallStack(AllocationType::NotCold, {1, 5, 6});
226   Trie.addCallStack(AllocationType::NotCold, {1, 5, 7});
227 
228   CallBase *Call = findCall(*Func, "call");
229   Trie.buildAndAttachMIBMetadata(Call);
230 
231   EXPECT_FALSE(Call->hasFnAttr("memprof"));
232   EXPECT_TRUE(Call->hasMetadata(LLVMContext::MD_memprof));
233   MDNode *MemProfMD = Call->getMetadata(LLVMContext::MD_memprof);
234   ASSERT_EQ(MemProfMD->getNumOperands(), 2u);
235   for (auto &MIBOp : MemProfMD->operands()) {
236     MDNode *MIB = dyn_cast<MDNode>(MIBOp);
237     MDNode *StackMD = getMIBStackNode(MIB);
238     ASSERT_NE(StackMD, nullptr);
239     ASSERT_EQ(StackMD->getNumOperands(), 2u);
240     auto *StackId = mdconst::dyn_extract<ConstantInt>(StackMD->getOperand(0));
241     EXPECT_EQ(StackId->getZExtValue(), 1u);
242     StackId = mdconst::dyn_extract<ConstantInt>(StackMD->getOperand(1));
243     if (StackId->getZExtValue() == 2u)
244       EXPECT_EQ(getMIBAllocType(MIB), AllocationType::Cold);
245     else {
246       ASSERT_EQ(StackId->getZExtValue(), 5u);
247       EXPECT_EQ(getMIBAllocType(MIB), AllocationType::NotCold);
248     }
249   }
250 }
251 
252 // Test CallStackTrie::addCallStack interface taking memprof MIB metadata.
253 // Check that allocations annotated with memprof metadata with a single
254 // allocation type get simplified to an attribute.
255 TEST_F(MemoryProfileInfoTest, SimplifyMIBToAttribute) {
256   LLVMContext C;
257   std::unique_ptr<Module> M = makeLLVMModule(C,
258                                              R"IR(
259 target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
260 target triple = "x86_64-pc-linux-gnu"
261 define i32* @test() {
262 entry:
263   %call1 = call noalias dereferenceable_or_null(40) i8* @malloc(i64 noundef 40), !memprof !0
264   %0 = bitcast i8* %call1 to i32*
265   %call2 = call noalias dereferenceable_or_null(40) i8* @malloc(i64 noundef 40), !memprof !3
266   %1 = bitcast i8* %call2 to i32*
267   ret i32* %1
268 }
269 declare dso_local noalias noundef i8* @malloc(i64 noundef)
270 !0 = !{!1}
271 !1 = !{!2, !"cold"}
272 !2 = !{i64 1, i64 2, i64 3}
273 !3 = !{!4}
274 !4 = !{!5, !"notcold"}
275 !5 = !{i64 4, i64 5, i64 6, i64 7}
276 )IR");
277 
278   Function *Func = M->getFunction("test");
279 
280   // First call has all cold contexts.
281   CallStackTrie Trie1;
282   CallBase *Call1 = findCall(*Func, "call1");
283   MDNode *MemProfMD1 = Call1->getMetadata(LLVMContext::MD_memprof);
284   ASSERT_EQ(MemProfMD1->getNumOperands(), 1u);
285   MDNode *MIB1 = dyn_cast<MDNode>(MemProfMD1->getOperand(0));
286   Trie1.addCallStack(MIB1);
287   Trie1.buildAndAttachMIBMetadata(Call1);
288 
289   EXPECT_TRUE(Call1->hasFnAttr("memprof"));
290   EXPECT_EQ(Call1->getFnAttr("memprof").getValueAsString(), "cold");
291 
292   // Second call has all non-cold contexts.
293   CallStackTrie Trie2;
294   CallBase *Call2 = findCall(*Func, "call2");
295   MDNode *MemProfMD2 = Call2->getMetadata(LLVMContext::MD_memprof);
296   ASSERT_EQ(MemProfMD2->getNumOperands(), 1u);
297   MDNode *MIB2 = dyn_cast<MDNode>(MemProfMD2->getOperand(0));
298   Trie2.addCallStack(MIB2);
299   Trie2.buildAndAttachMIBMetadata(Call2);
300 
301   EXPECT_TRUE(Call2->hasFnAttr("memprof"));
302   EXPECT_EQ(Call2->getFnAttr("memprof").getValueAsString(), "notcold");
303 }
304 
305 // Test CallStackTrie::addCallStack interface taking memprof MIB metadata.
306 // Test that allocations annotated with memprof metadata with multiple call
307 // stacks gets new memprof metadata with the contexts trimmed to the minimum
308 // context required to identify the allocation type.
309 TEST_F(MemoryProfileInfoTest, ReTrimMIBContext) {
310   LLVMContext C;
311   std::unique_ptr<Module> M = makeLLVMModule(C,
312                                              R"IR(
313 target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
314 target triple = "x86_64-pc-linux-gnu"
315 define i32* @test() {
316 entry:
317   %call = call noalias dereferenceable_or_null(40) i8* @malloc(i64 noundef 40), !memprof !0
318   %0 = bitcast i8* %call to i32*
319   ret i32* %0
320 }
321 declare dso_local noalias noundef i8* @malloc(i64 noundef)
322 !0 = !{!1, !3, !5, !7}
323 !1 = !{!2, !"cold"}
324 !2 = !{i64 1, i64 2, i64 3}
325 !3 = !{!4, !"cold"}
326 !4 = !{i64 1, i64 2, i64 4}
327 !5 = !{!6, !"notcold"}
328 !6 = !{i64 1, i64 5, i64 6}
329 !7 = !{!8, !"notcold"}
330 !8 = !{i64 1, i64 5, i64 7}
331 )IR");
332 
333   Function *Func = M->getFunction("test");
334 
335   CallStackTrie Trie;
336   ASSERT_TRUE(Trie.empty());
337   CallBase *Call = findCall(*Func, "call");
338   MDNode *MemProfMD = Call->getMetadata(LLVMContext::MD_memprof);
339   for (auto &MIBOp : MemProfMD->operands()) {
340     MDNode *MIB = dyn_cast<MDNode>(MIBOp);
341     Trie.addCallStack(MIB);
342   }
343   ASSERT_FALSE(Trie.empty());
344   Trie.buildAndAttachMIBMetadata(Call);
345 
346   // We should be able to trim the first two and combine into a single MIB
347   // with the cold context {1, 2}.
348   // We should be able to trim the second two and combine into a single MIB
349   // with the non-cold context {1, 5}.
350 
351   EXPECT_FALSE(Call->hasFnAttr("memprof"));
352   EXPECT_TRUE(Call->hasMetadata(LLVMContext::MD_memprof));
353   MemProfMD = Call->getMetadata(LLVMContext::MD_memprof);
354   ASSERT_EQ(MemProfMD->getNumOperands(), 2u);
355   for (auto &MIBOp : MemProfMD->operands()) {
356     MDNode *MIB = dyn_cast<MDNode>(MIBOp);
357     MDNode *StackMD = getMIBStackNode(MIB);
358     ASSERT_NE(StackMD, nullptr);
359     ASSERT_EQ(StackMD->getNumOperands(), 2u);
360     auto *StackId = mdconst::dyn_extract<ConstantInt>(StackMD->getOperand(0));
361     EXPECT_EQ(StackId->getZExtValue(), 1u);
362     StackId = mdconst::dyn_extract<ConstantInt>(StackMD->getOperand(1));
363     if (StackId->getZExtValue() == 2u)
364       EXPECT_EQ(getMIBAllocType(MIB), AllocationType::Cold);
365     else {
366       ASSERT_EQ(StackId->getZExtValue(), 5u);
367       EXPECT_EQ(getMIBAllocType(MIB), AllocationType::NotCold);
368     }
369   }
370 }
371 
372 TEST_F(MemoryProfileInfoTest, CallStackTestIR) {
373   LLVMContext C;
374   std::unique_ptr<Module> M = makeLLVMModule(C,
375                                              R"IR(
376 target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
377 target triple = "x86_64-pc-linux-gnu"
378 define ptr @test() {
379 entry:
380   %call = call noalias noundef nonnull dereferenceable(10) ptr @_Znam(i64 noundef 10), !memprof !1, !callsite !6
381   ret ptr %call
382 }
383 declare noundef nonnull ptr @_Znam(i64 noundef)
384 !1 = !{!2, !4}
385 !2 = !{!3, !"notcold"}
386 !3 = !{i64 1, i64 2, i64 3, i64 4}
387 !4 = !{!5, !"cold"}
388 !5 = !{i64 1, i64 2, i64 3, i64 5}
389 !6 = !{i64 1}
390 )IR");
391 
392   Function *Func = M->getFunction("test");
393   CallBase *Call = findCall(*Func, "call");
394 
395   CallStack<MDNode, MDNode::op_iterator> InstCallsite(
396       Call->getMetadata(LLVMContext::MD_callsite));
397 
398   MDNode *MemProfMD = Call->getMetadata(LLVMContext::MD_memprof);
399   bool First = true;
400   for (auto &MIBOp : MemProfMD->operands()) {
401     auto *MIBMD = cast<const MDNode>(MIBOp);
402     MDNode *StackNode = getMIBStackNode(MIBMD);
403     CallStack<MDNode, MDNode::op_iterator> StackContext(StackNode);
404     std::vector<uint64_t> StackIds;
405     for (auto ContextIter = StackContext.beginAfterSharedPrefix(InstCallsite);
406          ContextIter != StackContext.end(); ++ContextIter)
407       StackIds.push_back(*ContextIter);
408     if (First) {
409       std::vector<uint64_t> Expected = {2, 3, 4};
410       EXPECT_EQ(makeArrayRef(StackIds), makeArrayRef(Expected));
411     } else {
412       std::vector<uint64_t> Expected = {2, 3, 5};
413       EXPECT_EQ(makeArrayRef(StackIds), makeArrayRef(Expected));
414     }
415     First = false;
416   }
417 }
418 
419 TEST_F(MemoryProfileInfoTest, CallStackTestSummary) {
420   std::unique_ptr<ModuleSummaryIndex> Index = makeLLVMIndex(R"Summary(
421 ^0 = module: (path: "test.o", hash: (0, 0, 0, 0, 0))
422 ^1 = gv: (guid: 23, summaries: (function: (module: ^0, flags: (linkage: external, visibility: default, notEligibleToImport: 0, live: 0, dsoLocal: 1, canAutoHide: 0), insts: 2, funcFlags: (readNone: 0, readOnly: 0, noRecurse: 0, returnDoesNotAlias: 0, noInline: 1, alwaysInline: 0, noUnwind: 0, mayThrow: 0, hasUnknownCall: 0, mustBeUnreachable: 0), allocs: ((versions: (none), memProf: ((type: notcold, stackIds: (1, 2, 3, 4)), (type: cold, stackIds: (1, 2, 3, 5))))))))
423 ^2 = gv: (guid: 25, summaries: (function: (module: ^0, flags: (linkage: external, visibility: default, notEligibleToImport: 0, live: 0, dsoLocal: 1, canAutoHide: 0), insts: 22, funcFlags: (readNone: 0, readOnly: 0, noRecurse: 1, returnDoesNotAlias: 0, noInline: 1, alwaysInline: 0, noUnwind: 0, mayThrow: 0, hasUnknownCall: 0, mustBeUnreachable: 0), calls: ((callee: ^1)), callsites: ((callee: ^1, clones: (0), stackIds: (3, 4)), (callee: ^1, clones: (0), stackIds: (3, 5))))))
424 )Summary");
425 
426   ASSERT_NE(Index, nullptr);
427   auto *CallsiteSummary =
428       cast<FunctionSummary>(Index->getGlobalValueSummary(/*guid=*/25));
429   bool First = true;
430   for (auto &CI : CallsiteSummary->callsites()) {
431     CallStack<CallsiteInfo, SmallVector<unsigned>::const_iterator> InstCallsite(
432         &CI);
433     std::vector<uint64_t> StackIds;
434     for (auto StackIdIndex : InstCallsite)
435       StackIds.push_back(Index->getStackIdAtIndex(StackIdIndex));
436     if (First) {
437       std::vector<uint64_t> Expected = {3, 4};
438       EXPECT_EQ(makeArrayRef(StackIds), makeArrayRef(Expected));
439     } else {
440       std::vector<uint64_t> Expected = {3, 5};
441       EXPECT_EQ(makeArrayRef(StackIds), makeArrayRef(Expected));
442     }
443     First = false;
444   }
445 
446   auto *AllocSummary =
447       cast<FunctionSummary>(Index->getGlobalValueSummary(/*guid=*/23));
448   for (auto &AI : AllocSummary->allocs()) {
449     bool First = true;
450     for (auto &MIB : AI.MIBs) {
451       CallStack<MIBInfo, SmallVector<unsigned>::const_iterator> StackContext(
452           &MIB);
453       std::vector<uint64_t> StackIds;
454       for (auto StackIdIndex : StackContext)
455         StackIds.push_back(Index->getStackIdAtIndex(StackIdIndex));
456       if (First) {
457         std::vector<uint64_t> Expected = {1, 2, 3, 4};
458         EXPECT_EQ(makeArrayRef(StackIds), makeArrayRef(Expected));
459       } else {
460         std::vector<uint64_t> Expected = {1, 2, 3, 5};
461         EXPECT_EQ(makeArrayRef(StackIds), makeArrayRef(Expected));
462       }
463       First = false;
464     }
465   }
466 }
467 } // end anonymous namespace
468