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