xref: /llvm-project/llvm/unittests/ProfileData/MemProfTest.cpp (revision 459a82e6890ff41e30d486f36c8c7ec22628bb7a)
1 //===- unittests/Support/MemProfTest.cpp ----------------------------------===//
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/ProfileData/MemProf.h"
10 #include "llvm/ADT/DenseMap.h"
11 #include "llvm/ADT/MapVector.h"
12 #include "llvm/ADT/STLForwardCompat.h"
13 #include "llvm/DebugInfo/DIContext.h"
14 #include "llvm/DebugInfo/Symbolize/SymbolizableModule.h"
15 #include "llvm/IR/Value.h"
16 #include "llvm/Object/ObjectFile.h"
17 #include "llvm/ProfileData/MemProfData.inc"
18 #include "llvm/ProfileData/MemProfReader.h"
19 #include "llvm/Support/raw_ostream.h"
20 #include "gmock/gmock.h"
21 #include "gtest/gtest.h"
22 
23 #include <initializer_list>
24 
25 namespace {
26 
27 using ::llvm::DIGlobal;
28 using ::llvm::DIInliningInfo;
29 using ::llvm::DILineInfo;
30 using ::llvm::DILineInfoSpecifier;
31 using ::llvm::DILocal;
32 using ::llvm::StringRef;
33 using ::llvm::memprof::CallStackId;
34 using ::llvm::memprof::CallStackMap;
35 using ::llvm::memprof::Frame;
36 using ::llvm::memprof::FrameId;
37 using ::llvm::memprof::IndexedAllocationInfo;
38 using ::llvm::memprof::IndexedMemProfRecord;
39 using ::llvm::memprof::MemInfoBlock;
40 using ::llvm::memprof::MemProfReader;
41 using ::llvm::memprof::MemProfRecord;
42 using ::llvm::memprof::MemProfSchema;
43 using ::llvm::memprof::Meta;
44 using ::llvm::memprof::PortableMemInfoBlock;
45 using ::llvm::memprof::RawMemProfReader;
46 using ::llvm::memprof::SegmentEntry;
47 using ::llvm::object::SectionedAddress;
48 using ::llvm::symbolize::SymbolizableModule;
49 using ::testing::Return;
50 using ::testing::SizeIs;
51 
52 class MockSymbolizer : public SymbolizableModule {
53 public:
54   MOCK_CONST_METHOD3(symbolizeInlinedCode,
55                      DIInliningInfo(SectionedAddress, DILineInfoSpecifier,
56                                     bool));
57   // Most of the methods in the interface are unused. We only mock the
58   // method that we expect to be called from the memprof reader.
59   virtual DILineInfo symbolizeCode(SectionedAddress, DILineInfoSpecifier,
60                                    bool) const {
61     llvm_unreachable("unused");
62   }
63   virtual DIGlobal symbolizeData(SectionedAddress) const {
64     llvm_unreachable("unused");
65   }
66   virtual std::vector<DILocal> symbolizeFrame(SectionedAddress) const {
67     llvm_unreachable("unused");
68   }
69   virtual std::vector<SectionedAddress> findSymbol(StringRef Symbol,
70                                                    uint64_t Offset) const {
71     llvm_unreachable("unused");
72   }
73   virtual bool isWin32Module() const { llvm_unreachable("unused"); }
74   virtual uint64_t getModulePreferredBase() const {
75     llvm_unreachable("unused");
76   }
77 };
78 
79 struct MockInfo {
80   std::string FunctionName;
81   uint32_t Line;
82   uint32_t StartLine;
83   uint32_t Column;
84   std::string FileName = "valid/path.cc";
85 };
86 DIInliningInfo makeInliningInfo(std::initializer_list<MockInfo> MockFrames) {
87   DIInliningInfo Result;
88   for (const auto &Item : MockFrames) {
89     DILineInfo Frame;
90     Frame.FunctionName = Item.FunctionName;
91     Frame.Line = Item.Line;
92     Frame.StartLine = Item.StartLine;
93     Frame.Column = Item.Column;
94     Frame.FileName = Item.FileName;
95     Result.addFrame(Frame);
96   }
97   return Result;
98 }
99 
100 llvm::SmallVector<SegmentEntry, 4> makeSegments() {
101   llvm::SmallVector<SegmentEntry, 4> Result;
102   // Mimic an entry for a non position independent executable.
103   Result.emplace_back(0x0, 0x40000, 0x0);
104   return Result;
105 }
106 
107 const DILineInfoSpecifier specifier() {
108   return DILineInfoSpecifier(
109       DILineInfoSpecifier::FileLineInfoKind::RawValue,
110       DILineInfoSpecifier::FunctionNameKind::LinkageName);
111 }
112 
113 MATCHER_P4(FrameContains, FunctionName, LineOffset, Column, Inline, "") {
114   const Frame &F = arg;
115 
116   const uint64_t ExpectedHash = IndexedMemProfRecord::getGUID(FunctionName);
117   if (F.Function != ExpectedHash) {
118     *result_listener << "Hash mismatch";
119     return false;
120   }
121   if (F.SymbolName && *F.SymbolName != FunctionName) {
122     *result_listener << "SymbolName mismatch\nWant: " << FunctionName
123                      << "\nGot: " << *F.SymbolName;
124     return false;
125   }
126   if (F.LineOffset == LineOffset && F.Column == Column &&
127       F.IsInlineFrame == Inline) {
128     return true;
129   }
130   *result_listener << "LineOffset, Column or Inline mismatch";
131   return false;
132 }
133 
134 TEST(MemProf, FillsValue) {
135   std::unique_ptr<MockSymbolizer> Symbolizer(new MockSymbolizer());
136 
137   EXPECT_CALL(*Symbolizer, symbolizeInlinedCode(SectionedAddress{0x1000},
138                                                 specifier(), false))
139       .Times(1) // Only once since we remember invalid PCs.
140       .WillRepeatedly(Return(makeInliningInfo({
141           {"new", 70, 57, 3, "memprof/memprof_new_delete.cpp"},
142       })));
143 
144   EXPECT_CALL(*Symbolizer, symbolizeInlinedCode(SectionedAddress{0x2000},
145                                                 specifier(), false))
146       .Times(1) // Only once since we cache the result for future lookups.
147       .WillRepeatedly(Return(makeInliningInfo({
148           {"foo", 10, 5, 30},
149           {"bar", 201, 150, 20},
150       })));
151 
152   EXPECT_CALL(*Symbolizer, symbolizeInlinedCode(SectionedAddress{0x3000},
153                                                 specifier(), false))
154       .Times(1)
155       .WillRepeatedly(Return(makeInliningInfo({
156           {"xyz.llvm.123", 10, 5, 30},
157           {"abc", 10, 5, 30},
158       })));
159 
160   CallStackMap CSM;
161   CSM[0x1] = {0x1000, 0x2000, 0x3000};
162 
163   llvm::MapVector<uint64_t, MemInfoBlock> Prof;
164   Prof[0x1].AllocCount = 1;
165 
166   auto Seg = makeSegments();
167 
168   RawMemProfReader Reader(std::move(Symbolizer), Seg, Prof, CSM,
169                           /*KeepName=*/true);
170 
171   llvm::DenseMap<llvm::GlobalValue::GUID, MemProfRecord> Records;
172   for (const auto &Pair : Reader) {
173     Records.insert({Pair.first, Pair.second});
174   }
175 
176   // Mock program pseudocode and expected memprof record contents.
177   //
178   //                              AllocSite       CallSite
179   // inline foo() { new(); }         Y               N
180   // bar() { foo(); }                Y               Y
181   // inline xyz() { bar(); }         N               Y
182   // abc() { xyz(); }                N               Y
183 
184   // We expect 4 records. We attach alloc site data to foo and bar, i.e.
185   // all frames bottom up until we find a non-inline frame. We attach call site
186   // data to bar, xyz and abc.
187   ASSERT_THAT(Records, SizeIs(4));
188 
189   // Check the memprof record for foo.
190   const llvm::GlobalValue::GUID FooId = IndexedMemProfRecord::getGUID("foo");
191   ASSERT_TRUE(Records.contains(FooId));
192   const MemProfRecord &Foo = Records[FooId];
193   ASSERT_THAT(Foo.AllocSites, SizeIs(1));
194   EXPECT_EQ(Foo.AllocSites[0].Info.getAllocCount(), 1U);
195   EXPECT_THAT(Foo.AllocSites[0].CallStack[0],
196               FrameContains("foo", 5U, 30U, true));
197   EXPECT_THAT(Foo.AllocSites[0].CallStack[1],
198               FrameContains("bar", 51U, 20U, false));
199   EXPECT_THAT(Foo.AllocSites[0].CallStack[2],
200               FrameContains("xyz", 5U, 30U, true));
201   EXPECT_THAT(Foo.AllocSites[0].CallStack[3],
202               FrameContains("abc", 5U, 30U, false));
203   EXPECT_TRUE(Foo.CallSites.empty());
204 
205   // Check the memprof record for bar.
206   const llvm::GlobalValue::GUID BarId = IndexedMemProfRecord::getGUID("bar");
207   ASSERT_TRUE(Records.contains(BarId));
208   const MemProfRecord &Bar = Records[BarId];
209   ASSERT_THAT(Bar.AllocSites, SizeIs(1));
210   EXPECT_EQ(Bar.AllocSites[0].Info.getAllocCount(), 1U);
211   EXPECT_THAT(Bar.AllocSites[0].CallStack[0],
212               FrameContains("foo", 5U, 30U, true));
213   EXPECT_THAT(Bar.AllocSites[0].CallStack[1],
214               FrameContains("bar", 51U, 20U, false));
215   EXPECT_THAT(Bar.AllocSites[0].CallStack[2],
216               FrameContains("xyz", 5U, 30U, true));
217   EXPECT_THAT(Bar.AllocSites[0].CallStack[3],
218               FrameContains("abc", 5U, 30U, false));
219 
220   ASSERT_THAT(Bar.CallSites, SizeIs(1));
221   ASSERT_THAT(Bar.CallSites[0], SizeIs(2));
222   EXPECT_THAT(Bar.CallSites[0][0], FrameContains("foo", 5U, 30U, true));
223   EXPECT_THAT(Bar.CallSites[0][1], FrameContains("bar", 51U, 20U, false));
224 
225   // Check the memprof record for xyz.
226   const llvm::GlobalValue::GUID XyzId = IndexedMemProfRecord::getGUID("xyz");
227   ASSERT_TRUE(Records.contains(XyzId));
228   const MemProfRecord &Xyz = Records[XyzId];
229   ASSERT_THAT(Xyz.CallSites, SizeIs(1));
230   ASSERT_THAT(Xyz.CallSites[0], SizeIs(2));
231   // Expect the entire frame even though in practice we only need the first
232   // entry here.
233   EXPECT_THAT(Xyz.CallSites[0][0], FrameContains("xyz", 5U, 30U, true));
234   EXPECT_THAT(Xyz.CallSites[0][1], FrameContains("abc", 5U, 30U, false));
235 
236   // Check the memprof record for abc.
237   const llvm::GlobalValue::GUID AbcId = IndexedMemProfRecord::getGUID("abc");
238   ASSERT_TRUE(Records.contains(AbcId));
239   const MemProfRecord &Abc = Records[AbcId];
240   EXPECT_TRUE(Abc.AllocSites.empty());
241   ASSERT_THAT(Abc.CallSites, SizeIs(1));
242   ASSERT_THAT(Abc.CallSites[0], SizeIs(2));
243   EXPECT_THAT(Abc.CallSites[0][0], FrameContains("xyz", 5U, 30U, true));
244   EXPECT_THAT(Abc.CallSites[0][1], FrameContains("abc", 5U, 30U, false));
245 }
246 
247 TEST(MemProf, PortableWrapper) {
248   MemInfoBlock Info(/*size=*/16, /*access_count=*/7, /*alloc_timestamp=*/1000,
249                     /*dealloc_timestamp=*/2000, /*alloc_cpu=*/3,
250                     /*dealloc_cpu=*/4, /*Histogram=*/0, /*HistogramSize=*/0);
251 
252   const auto Schema = llvm::memprof::getFullSchema();
253   PortableMemInfoBlock WriteBlock(Info, Schema);
254 
255   std::string Buffer;
256   llvm::raw_string_ostream OS(Buffer);
257   WriteBlock.serialize(Schema, OS);
258 
259   PortableMemInfoBlock ReadBlock(
260       Schema, reinterpret_cast<const unsigned char *>(Buffer.data()));
261 
262   EXPECT_EQ(ReadBlock, WriteBlock);
263   // Here we compare directly with the actual counts instead of MemInfoBlock
264   // members. Since the MemInfoBlock struct is packed and the EXPECT_EQ macros
265   // take a reference to the params, this results in unaligned accesses.
266   EXPECT_EQ(1UL, ReadBlock.getAllocCount());
267   EXPECT_EQ(7ULL, ReadBlock.getTotalAccessCount());
268   EXPECT_EQ(3UL, ReadBlock.getAllocCpuId());
269 }
270 
271 // Version0 and Version1 serialize IndexedMemProfRecord in the same format, so
272 // we share one test.
273 TEST(MemProf, RecordSerializationRoundTripVersion0And1) {
274   const auto Schema = llvm::memprof::getFullSchema();
275 
276   MemInfoBlock Info(/*size=*/16, /*access_count=*/7, /*alloc_timestamp=*/1000,
277                     /*dealloc_timestamp=*/2000, /*alloc_cpu=*/3,
278                     /*dealloc_cpu=*/4, /*Histogram=*/0, /*HistogramSize=*/0);
279 
280   llvm::SmallVector<llvm::SmallVector<FrameId>> AllocCallStacks = {
281       {0x123, 0x345}, {0x123, 0x567}};
282 
283   llvm::SmallVector<llvm::SmallVector<FrameId>> CallSites = {{0x333, 0x777}};
284 
285   IndexedMemProfRecord Record;
286   for (const auto &ACS : AllocCallStacks) {
287     // Use the same info block for both allocation sites.
288     Record.AllocSites.emplace_back(ACS, llvm::memprof::hashCallStack(ACS),
289                                    Info);
290   }
291   Record.CallSites.assign(CallSites);
292   for (const auto &CS : CallSites)
293     Record.CallSiteIds.push_back(llvm::memprof::hashCallStack(CS));
294 
295   std::string Buffer;
296   llvm::raw_string_ostream OS(Buffer);
297   Record.serialize(Schema, OS, llvm::memprof::Version0);
298 
299   const IndexedMemProfRecord GotRecord = IndexedMemProfRecord::deserialize(
300       Schema, reinterpret_cast<const unsigned char *>(Buffer.data()),
301       llvm::memprof::Version0);
302 
303   EXPECT_EQ(Record, GotRecord);
304 }
305 
306 TEST(MemProf, RecordSerializationRoundTripVerion2) {
307   const auto Schema = llvm::memprof::getFullSchema();
308 
309   MemInfoBlock Info(/*size=*/16, /*access_count=*/7, /*alloc_timestamp=*/1000,
310                     /*dealloc_timestamp=*/2000, /*alloc_cpu=*/3,
311                     /*dealloc_cpu=*/4, /*Histogram=*/0, /*HistogramSize=*/0);
312 
313   llvm::SmallVector<llvm::memprof::CallStackId> CallStackIds = {0x123, 0x456};
314 
315   llvm::SmallVector<llvm::memprof::CallStackId> CallSiteIds = {0x333, 0x444};
316 
317   IndexedMemProfRecord Record;
318   for (const auto &CSId : CallStackIds) {
319     // Use the same info block for both allocation sites.
320     Record.AllocSites.emplace_back(llvm::SmallVector<FrameId>(), CSId, Info);
321   }
322   Record.CallSiteIds.assign(CallSiteIds);
323 
324   std::string Buffer;
325   llvm::raw_string_ostream OS(Buffer);
326   Record.serialize(Schema, OS, llvm::memprof::Version2);
327 
328   const IndexedMemProfRecord GotRecord = IndexedMemProfRecord::deserialize(
329       Schema, reinterpret_cast<const unsigned char *>(Buffer.data()),
330       llvm::memprof::Version2);
331 
332   EXPECT_EQ(Record, GotRecord);
333 }
334 
335 TEST(MemProf, RecordSerializationRoundTripVersion2HotColdSchema) {
336   const auto Schema = llvm::memprof::getHotColdSchema();
337 
338   MemInfoBlock Info;
339   Info.AllocCount = 11;
340   Info.TotalSize = 22;
341   Info.TotalLifetime = 33;
342   Info.TotalLifetimeAccessDensity = 44;
343 
344   llvm::SmallVector<llvm::memprof::CallStackId> CallStackIds = {0x123, 0x456};
345 
346   llvm::SmallVector<llvm::memprof::CallStackId> CallSiteIds = {0x333, 0x444};
347 
348   IndexedMemProfRecord Record;
349   for (const auto &CSId : CallStackIds) {
350     // Use the same info block for both allocation sites.
351     Record.AllocSites.emplace_back(llvm::SmallVector<FrameId>(), CSId, Info,
352                                    Schema);
353   }
354   Record.CallSiteIds.assign(CallSiteIds);
355 
356   std::bitset<llvm::to_underlying(Meta::Size)> SchemaBitSet;
357   for (auto Id : Schema)
358     SchemaBitSet.set(llvm::to_underlying(Id));
359 
360   // Verify that SchemaBitSet has the fields we expect and nothing else, which
361   // we check with count().
362   EXPECT_EQ(SchemaBitSet.count(), 4U);
363   EXPECT_TRUE(SchemaBitSet[llvm::to_underlying(Meta::AllocCount)]);
364   EXPECT_TRUE(SchemaBitSet[llvm::to_underlying(Meta::TotalSize)]);
365   EXPECT_TRUE(SchemaBitSet[llvm::to_underlying(Meta::TotalLifetime)]);
366   EXPECT_TRUE(
367       SchemaBitSet[llvm::to_underlying(Meta::TotalLifetimeAccessDensity)]);
368 
369   // Verify that Schema has propagated all the way to the Info field in each
370   // IndexedAllocationInfo.
371   ASSERT_THAT(Record.AllocSites, ::SizeIs(2));
372   EXPECT_EQ(Record.AllocSites[0].Info.getSchema(), SchemaBitSet);
373   EXPECT_EQ(Record.AllocSites[1].Info.getSchema(), SchemaBitSet);
374 
375   std::string Buffer;
376   llvm::raw_string_ostream OS(Buffer);
377   Record.serialize(Schema, OS, llvm::memprof::Version2);
378 
379   const IndexedMemProfRecord GotRecord = IndexedMemProfRecord::deserialize(
380       Schema, reinterpret_cast<const unsigned char *>(Buffer.data()),
381       llvm::memprof::Version2);
382 
383   // Verify that Schema comes back correctly after deserialization. Technically,
384   // the comparison between Record and GotRecord below includes the comparison
385   // of their Schemas, but we'll verify the Schemas on our own.
386   ASSERT_THAT(GotRecord.AllocSites, ::SizeIs(2));
387   EXPECT_EQ(GotRecord.AllocSites[0].Info.getSchema(), SchemaBitSet);
388   EXPECT_EQ(GotRecord.AllocSites[1].Info.getSchema(), SchemaBitSet);
389 
390   EXPECT_EQ(Record, GotRecord);
391 }
392 
393 TEST(MemProf, SymbolizationFilter) {
394   std::unique_ptr<MockSymbolizer> Symbolizer(new MockSymbolizer());
395 
396   EXPECT_CALL(*Symbolizer, symbolizeInlinedCode(SectionedAddress{0x1000},
397                                                 specifier(), false))
398       .Times(1) // once since we don't lookup invalid PCs repeatedly.
399       .WillRepeatedly(Return(makeInliningInfo({
400           {"malloc", 70, 57, 3, "memprof/memprof_malloc_linux.cpp"},
401       })));
402 
403   EXPECT_CALL(*Symbolizer, symbolizeInlinedCode(SectionedAddress{0x2000},
404                                                 specifier(), false))
405       .Times(1) // once since we don't lookup invalid PCs repeatedly.
406       .WillRepeatedly(Return(makeInliningInfo({
407           {"new", 70, 57, 3, "memprof/memprof_new_delete.cpp"},
408       })));
409 
410   EXPECT_CALL(*Symbolizer, symbolizeInlinedCode(SectionedAddress{0x3000},
411                                                 specifier(), false))
412       .Times(1) // once since we don't lookup invalid PCs repeatedly.
413       .WillRepeatedly(Return(makeInliningInfo({
414           {DILineInfo::BadString, 0, 0, 0},
415       })));
416 
417   EXPECT_CALL(*Symbolizer, symbolizeInlinedCode(SectionedAddress{0x4000},
418                                                 specifier(), false))
419       .Times(1)
420       .WillRepeatedly(Return(makeInliningInfo({
421           {"foo", 10, 5, 30, "memprof/memprof_test_file.cpp"},
422       })));
423 
424   EXPECT_CALL(*Symbolizer, symbolizeInlinedCode(SectionedAddress{0x5000},
425                                                 specifier(), false))
426       .Times(1)
427       .WillRepeatedly(Return(makeInliningInfo({
428           // Depending on how the runtime was compiled, only the filename
429           // may be present in the debug information.
430           {"malloc", 70, 57, 3, "memprof_malloc_linux.cpp"},
431       })));
432 
433   CallStackMap CSM;
434   CSM[0x1] = {0x1000, 0x2000, 0x3000, 0x4000};
435   // This entry should be dropped since all PCs are either not
436   // symbolizable or belong to the runtime.
437   CSM[0x2] = {0x1000, 0x2000, 0x5000};
438 
439   llvm::MapVector<uint64_t, MemInfoBlock> Prof;
440   Prof[0x1].AllocCount = 1;
441   Prof[0x2].AllocCount = 1;
442 
443   auto Seg = makeSegments();
444 
445   RawMemProfReader Reader(std::move(Symbolizer), Seg, Prof, CSM);
446 
447   llvm::SmallVector<MemProfRecord, 1> Records;
448   for (const auto &KeyRecordPair : Reader) {
449     Records.push_back(KeyRecordPair.second);
450   }
451 
452   ASSERT_THAT(Records, SizeIs(1));
453   ASSERT_THAT(Records[0].AllocSites, SizeIs(1));
454   ASSERT_THAT(Records[0].AllocSites[0].CallStack, SizeIs(1));
455   EXPECT_THAT(Records[0].AllocSites[0].CallStack[0],
456               FrameContains("foo", 5U, 30U, false));
457 }
458 
459 TEST(MemProf, BaseMemProfReader) {
460   llvm::DenseMap<FrameId, Frame> FrameIdMap;
461   Frame F1(/*Hash=*/IndexedMemProfRecord::getGUID("foo"), /*LineOffset=*/20,
462            /*Column=*/5, /*IsInlineFrame=*/true);
463   Frame F2(/*Hash=*/IndexedMemProfRecord::getGUID("bar"), /*LineOffset=*/10,
464            /*Column=*/2, /*IsInlineFrame=*/false);
465   FrameIdMap.insert({F1.hash(), F1});
466   FrameIdMap.insert({F2.hash(), F2});
467 
468   llvm::MapVector<llvm::GlobalValue::GUID, IndexedMemProfRecord> ProfData;
469   IndexedMemProfRecord FakeRecord;
470   MemInfoBlock Block;
471   Block.AllocCount = 1U, Block.TotalAccessDensity = 4,
472   Block.TotalLifetime = 200001;
473   std::array<FrameId, 2> CallStack{F1.hash(), F2.hash()};
474   FakeRecord.AllocSites.emplace_back(
475       /*CS=*/CallStack, /*CSId=*/llvm::memprof::hashCallStack(CallStack),
476       /*MB=*/Block);
477   ProfData.insert({F1.hash(), FakeRecord});
478 
479   MemProfReader Reader(FrameIdMap, ProfData);
480 
481   llvm::SmallVector<MemProfRecord, 1> Records;
482   for (const auto &KeyRecordPair : Reader) {
483     Records.push_back(KeyRecordPair.second);
484   }
485 
486   ASSERT_THAT(Records, SizeIs(1));
487   ASSERT_THAT(Records[0].AllocSites, SizeIs(1));
488   ASSERT_THAT(Records[0].AllocSites[0].CallStack, SizeIs(2));
489   EXPECT_THAT(Records[0].AllocSites[0].CallStack[0],
490               FrameContains("foo", 20U, 5U, true));
491   EXPECT_THAT(Records[0].AllocSites[0].CallStack[1],
492               FrameContains("bar", 10U, 2U, false));
493 }
494 
495 TEST(MemProf, BaseMemProfReaderWithCSIdMap) {
496   llvm::DenseMap<FrameId, Frame> FrameIdMap;
497   Frame F1(/*Hash=*/IndexedMemProfRecord::getGUID("foo"), /*LineOffset=*/20,
498            /*Column=*/5, /*IsInlineFrame=*/true);
499   Frame F2(/*Hash=*/IndexedMemProfRecord::getGUID("bar"), /*LineOffset=*/10,
500            /*Column=*/2, /*IsInlineFrame=*/false);
501   FrameIdMap.insert({F1.hash(), F1});
502   FrameIdMap.insert({F2.hash(), F2});
503 
504   llvm::DenseMap<CallStackId, llvm::SmallVector<FrameId>> CSIdMap;
505   llvm::SmallVector<FrameId> CallStack = {F1.hash(), F2.hash()};
506   CallStackId CSId = llvm::memprof::hashCallStack(CallStack);
507   CSIdMap.insert({CSId, CallStack});
508 
509   llvm::MapVector<llvm::GlobalValue::GUID, IndexedMemProfRecord> ProfData;
510   IndexedMemProfRecord FakeRecord;
511   MemInfoBlock Block;
512   Block.AllocCount = 1U, Block.TotalAccessDensity = 4,
513   Block.TotalLifetime = 200001;
514   FakeRecord.AllocSites.emplace_back(
515       /*CS=*/llvm::SmallVector<FrameId>(),
516       /*CSId=*/llvm::memprof::hashCallStack(CallStack),
517       /*MB=*/Block);
518   ProfData.insert({F1.hash(), FakeRecord});
519 
520   MemProfReader Reader(FrameIdMap, CSIdMap, ProfData);
521 
522   llvm::SmallVector<MemProfRecord, 1> Records;
523   for (const auto &KeyRecordPair : Reader) {
524     Records.push_back(KeyRecordPair.second);
525   }
526 
527   ASSERT_THAT(Records, SizeIs(1));
528   ASSERT_THAT(Records[0].AllocSites, SizeIs(1));
529   ASSERT_THAT(Records[0].AllocSites[0].CallStack, SizeIs(2));
530   EXPECT_THAT(Records[0].AllocSites[0].CallStack[0],
531               FrameContains("foo", 20U, 5U, true));
532   EXPECT_THAT(Records[0].AllocSites[0].CallStack[1],
533               FrameContains("bar", 10U, 2U, false));
534 }
535 
536 TEST(MemProf, IndexedMemProfRecordToMemProfRecord) {
537   // Verify that MemProfRecord can be constructed from IndexedMemProfRecord with
538   // CallStackIds only.
539 
540   llvm::DenseMap<FrameId, Frame> FrameIdMap;
541   Frame F1(1, 0, 0, false);
542   Frame F2(2, 0, 0, false);
543   Frame F3(3, 0, 0, false);
544   Frame F4(4, 0, 0, false);
545   FrameIdMap.insert({F1.hash(), F1});
546   FrameIdMap.insert({F2.hash(), F2});
547   FrameIdMap.insert({F3.hash(), F3});
548   FrameIdMap.insert({F4.hash(), F4});
549 
550   llvm::DenseMap<CallStackId, llvm::SmallVector<FrameId>> CallStackIdMap;
551   llvm::SmallVector<FrameId> CS1 = {F1.hash(), F2.hash()};
552   llvm::SmallVector<FrameId> CS2 = {F1.hash(), F3.hash()};
553   llvm::SmallVector<FrameId> CS3 = {F2.hash(), F3.hash()};
554   llvm::SmallVector<FrameId> CS4 = {F2.hash(), F4.hash()};
555   CallStackIdMap.insert({llvm::memprof::hashCallStack(CS1), CS1});
556   CallStackIdMap.insert({llvm::memprof::hashCallStack(CS2), CS2});
557   CallStackIdMap.insert({llvm::memprof::hashCallStack(CS3), CS3});
558   CallStackIdMap.insert({llvm::memprof::hashCallStack(CS4), CS4});
559 
560   IndexedMemProfRecord IndexedRecord;
561   IndexedAllocationInfo AI;
562   AI.CSId = llvm::memprof::hashCallStack(CS1);
563   IndexedRecord.AllocSites.push_back(AI);
564   AI.CSId = llvm::memprof::hashCallStack(CS2);
565   IndexedRecord.AllocSites.push_back(AI);
566   IndexedRecord.CallSiteIds.push_back(llvm::memprof::hashCallStack(CS3));
567   IndexedRecord.CallSiteIds.push_back(llvm::memprof::hashCallStack(CS4));
568 
569   llvm::memprof::FrameIdConverter<decltype(FrameIdMap)> FrameIdConv(FrameIdMap);
570   llvm::memprof::CallStackIdConverter<decltype(CallStackIdMap)> CSIdConv(
571       CallStackIdMap, FrameIdConv);
572 
573   MemProfRecord Record = IndexedRecord.toMemProfRecord(CSIdConv);
574 
575   // Make sure that all lookups are successful.
576   ASSERT_EQ(FrameIdConv.LastUnmappedId, std::nullopt);
577   ASSERT_EQ(CSIdConv.LastUnmappedId, std::nullopt);
578 
579   // Verify the contents of Record.
580   ASSERT_THAT(Record.AllocSites, SizeIs(2));
581   ASSERT_THAT(Record.AllocSites[0].CallStack, SizeIs(2));
582   EXPECT_EQ(Record.AllocSites[0].CallStack[0].hash(), F1.hash());
583   EXPECT_EQ(Record.AllocSites[0].CallStack[1].hash(), F2.hash());
584   ASSERT_THAT(Record.AllocSites[1].CallStack, SizeIs(2));
585   EXPECT_EQ(Record.AllocSites[1].CallStack[0].hash(), F1.hash());
586   EXPECT_EQ(Record.AllocSites[1].CallStack[1].hash(), F3.hash());
587   ASSERT_THAT(Record.CallSites, SizeIs(2));
588   ASSERT_THAT(Record.CallSites[0], SizeIs(2));
589   EXPECT_EQ(Record.CallSites[0][0].hash(), F2.hash());
590   EXPECT_EQ(Record.CallSites[0][1].hash(), F3.hash());
591   ASSERT_THAT(Record.CallSites[1], SizeIs(2));
592   EXPECT_EQ(Record.CallSites[1][0].hash(), F2.hash());
593   EXPECT_EQ(Record.CallSites[1][1].hash(), F4.hash());
594 }
595 
596 using FrameIdMapTy =
597     llvm::DenseMap<::llvm::memprof::FrameId, ::llvm::memprof::Frame>;
598 using CallStackIdMapTy =
599     llvm::DenseMap<::llvm::memprof::CallStackId,
600                    ::llvm::SmallVector<::llvm::memprof::FrameId>>;
601 
602 // Populate those fields returned by getHotColdSchema.
603 MemInfoBlock makePartialMIB() {
604   MemInfoBlock MIB;
605   MIB.AllocCount = 1;
606   MIB.TotalSize = 5;
607   MIB.TotalLifetime = 10;
608   MIB.TotalLifetimeAccessDensity = 23;
609   return MIB;
610 }
611 
612 TEST(MemProf, MissingCallStackId) {
613   // Use a non-existent CallStackId to trigger a mapping error in
614   // toMemProfRecord.
615   llvm::memprof::IndexedAllocationInfo AI({}, 0xdeadbeefU, makePartialMIB(),
616                                           llvm::memprof::getHotColdSchema());
617 
618   IndexedMemProfRecord IndexedMR;
619   IndexedMR.AllocSites.push_back(AI);
620 
621   // Create empty maps.
622   const FrameIdMapTy IdToFrameMap;
623   const CallStackIdMapTy CSIdToCallStackMap;
624   llvm::memprof::FrameIdConverter<decltype(IdToFrameMap)> FrameIdConv(
625       IdToFrameMap);
626   llvm::memprof::CallStackIdConverter<decltype(CSIdToCallStackMap)> CSIdConv(
627       CSIdToCallStackMap, FrameIdConv);
628 
629   // We are only interested in errors, not the return value.
630   (void)IndexedMR.toMemProfRecord(CSIdConv);
631 
632   ASSERT_TRUE(CSIdConv.LastUnmappedId.has_value());
633   EXPECT_EQ(*CSIdConv.LastUnmappedId, 0xdeadbeefU);
634   EXPECT_EQ(FrameIdConv.LastUnmappedId, std::nullopt);
635 }
636 
637 TEST(MemProf, MissingFrameId) {
638   llvm::memprof::IndexedAllocationInfo AI({}, 0x222, makePartialMIB(),
639                                           llvm::memprof::getHotColdSchema());
640 
641   IndexedMemProfRecord IndexedMR;
642   IndexedMR.AllocSites.push_back(AI);
643 
644   // An empty map to trigger a mapping error.
645   const FrameIdMapTy IdToFrameMap;
646   CallStackIdMapTy CSIdToCallStackMap;
647   CSIdToCallStackMap.insert({0x222, {2, 3}});
648 
649   llvm::memprof::FrameIdConverter<decltype(IdToFrameMap)> FrameIdConv(
650       IdToFrameMap);
651   llvm::memprof::CallStackIdConverter<decltype(CSIdToCallStackMap)> CSIdConv(
652       CSIdToCallStackMap, FrameIdConv);
653 
654   // We are only interested in errors, not the return value.
655   (void)IndexedMR.toMemProfRecord(CSIdConv);
656 
657   EXPECT_EQ(CSIdConv.LastUnmappedId, std::nullopt);
658   ASSERT_TRUE(FrameIdConv.LastUnmappedId.has_value());
659   EXPECT_EQ(*FrameIdConv.LastUnmappedId, 3U);
660 }
661 
662 // Verify CallStackRadixTreeBuilder can handle empty inputs.
663 TEST(MemProf, RadixTreeBuilderEmpty) {
664   llvm::DenseMap<FrameId, llvm::memprof::LinearFrameId> MemProfFrameIndexes;
665   llvm::MapVector<CallStackId, llvm::SmallVector<FrameId>> MemProfCallStackData;
666   llvm::DenseMap<llvm::memprof::FrameId, llvm::memprof::FrameStat>
667       FrameHistogram =
668           llvm::memprof::computeFrameHistogram(MemProfCallStackData);
669   llvm::memprof::CallStackRadixTreeBuilder Builder;
670   Builder.build(std::move(MemProfCallStackData), MemProfFrameIndexes,
671                 FrameHistogram);
672   ASSERT_THAT(Builder.getRadixArray(), testing::IsEmpty());
673   const auto Mappings = Builder.takeCallStackPos();
674   ASSERT_THAT(Mappings, testing::IsEmpty());
675 }
676 
677 // Verify CallStackRadixTreeBuilder can handle one trivial call stack.
678 TEST(MemProf, RadixTreeBuilderOne) {
679   llvm::DenseMap<FrameId, llvm::memprof::LinearFrameId> MemProfFrameIndexes = {
680       {11, 1}, {12, 2}, {13, 3}};
681   llvm::SmallVector<llvm::memprof::FrameId> CS1 = {13, 12, 11};
682   llvm::MapVector<CallStackId, llvm::SmallVector<FrameId>> MemProfCallStackData;
683   MemProfCallStackData.insert({llvm::memprof::hashCallStack(CS1), CS1});
684   llvm::DenseMap<llvm::memprof::FrameId, llvm::memprof::FrameStat>
685       FrameHistogram =
686           llvm::memprof::computeFrameHistogram(MemProfCallStackData);
687   llvm::memprof::CallStackRadixTreeBuilder Builder;
688   Builder.build(std::move(MemProfCallStackData), MemProfFrameIndexes,
689                 FrameHistogram);
690   EXPECT_THAT(Builder.getRadixArray(), testing::ElementsAreArray({
691                                            3U, // Size of CS1,
692                                            3U, // MemProfFrameIndexes[13]
693                                            2U, // MemProfFrameIndexes[12]
694                                            1U  // MemProfFrameIndexes[11]
695                                        }));
696   const auto Mappings = Builder.takeCallStackPos();
697   ASSERT_THAT(Mappings, SizeIs(1));
698   EXPECT_THAT(Mappings, testing::Contains(testing::Pair(
699                             llvm::memprof::hashCallStack(CS1), 0U)));
700 }
701 
702 // Verify CallStackRadixTreeBuilder can form a link between two call stacks.
703 TEST(MemProf, RadixTreeBuilderTwo) {
704   llvm::DenseMap<FrameId, llvm::memprof::LinearFrameId> MemProfFrameIndexes = {
705       {11, 1}, {12, 2}, {13, 3}};
706   llvm::SmallVector<llvm::memprof::FrameId> CS1 = {12, 11};
707   llvm::SmallVector<llvm::memprof::FrameId> CS2 = {13, 12, 11};
708   llvm::MapVector<CallStackId, llvm::SmallVector<FrameId>> MemProfCallStackData;
709   MemProfCallStackData.insert({llvm::memprof::hashCallStack(CS1), CS1});
710   MemProfCallStackData.insert({llvm::memprof::hashCallStack(CS2), CS2});
711   llvm::DenseMap<llvm::memprof::FrameId, llvm::memprof::FrameStat>
712       FrameHistogram =
713           llvm::memprof::computeFrameHistogram(MemProfCallStackData);
714   llvm::memprof::CallStackRadixTreeBuilder Builder;
715   Builder.build(std::move(MemProfCallStackData), MemProfFrameIndexes,
716                 FrameHistogram);
717   EXPECT_THAT(Builder.getRadixArray(),
718               testing::ElementsAreArray({
719                   2U,                        // Size of CS1
720                   static_cast<uint32_t>(-3), // Jump 3 steps
721                   3U,                        // Size of CS2
722                   3U,                        // MemProfFrameIndexes[13]
723                   2U,                        // MemProfFrameIndexes[12]
724                   1U                         // MemProfFrameIndexes[11]
725               }));
726   const auto Mappings = Builder.takeCallStackPos();
727   ASSERT_THAT(Mappings, SizeIs(2));
728   EXPECT_THAT(Mappings, testing::Contains(testing::Pair(
729                             llvm::memprof::hashCallStack(CS1), 0U)));
730   EXPECT_THAT(Mappings, testing::Contains(testing::Pair(
731                             llvm::memprof::hashCallStack(CS2), 2U)));
732 }
733 
734 // Verify CallStackRadixTreeBuilder can form a jump to a prefix that itself has
735 // another jump to another prefix.
736 TEST(MemProf, RadixTreeBuilderSuccessiveJumps) {
737   llvm::DenseMap<FrameId, llvm::memprof::LinearFrameId> MemProfFrameIndexes = {
738       {11, 1}, {12, 2}, {13, 3}, {14, 4}, {15, 5}, {16, 6}, {17, 7}, {18, 8},
739   };
740   llvm::SmallVector<llvm::memprof::FrameId> CS1 = {14, 13, 12, 11};
741   llvm::SmallVector<llvm::memprof::FrameId> CS2 = {15, 13, 12, 11};
742   llvm::SmallVector<llvm::memprof::FrameId> CS3 = {17, 16, 12, 11};
743   llvm::SmallVector<llvm::memprof::FrameId> CS4 = {18, 16, 12, 11};
744   llvm::MapVector<CallStackId, llvm::SmallVector<FrameId>> MemProfCallStackData;
745   MemProfCallStackData.insert({llvm::memprof::hashCallStack(CS1), CS1});
746   MemProfCallStackData.insert({llvm::memprof::hashCallStack(CS2), CS2});
747   MemProfCallStackData.insert({llvm::memprof::hashCallStack(CS3), CS3});
748   MemProfCallStackData.insert({llvm::memprof::hashCallStack(CS4), CS4});
749   llvm::DenseMap<llvm::memprof::FrameId, llvm::memprof::FrameStat>
750       FrameHistogram =
751           llvm::memprof::computeFrameHistogram(MemProfCallStackData);
752   llvm::memprof::CallStackRadixTreeBuilder Builder;
753   Builder.build(std::move(MemProfCallStackData), MemProfFrameIndexes,
754                 FrameHistogram);
755   EXPECT_THAT(Builder.getRadixArray(),
756               testing::ElementsAreArray({
757                   4U,                        // Size of CS1
758                   4U,                        // MemProfFrameIndexes[14]
759                   static_cast<uint32_t>(-3), // Jump 3 steps
760                   4U,                        // Size of CS2
761                   5U,                        // MemProfFrameIndexes[15]
762                   3U,                        // MemProfFrameIndexes[13]
763                   static_cast<uint32_t>(-7), // Jump 7 steps
764                   4U,                        // Size of CS3
765                   7U,                        // MemProfFrameIndexes[17]
766                   static_cast<uint32_t>(-3), // Jump 3 steps
767                   4U,                        // Size of CS4
768                   8U,                        // MemProfFrameIndexes[18]
769                   6U,                        // MemProfFrameIndexes[16]
770                   2U,                        // MemProfFrameIndexes[12]
771                   1U                         // MemProfFrameIndexes[11]
772               }));
773   const auto Mappings = Builder.takeCallStackPos();
774   ASSERT_THAT(Mappings, SizeIs(4));
775   EXPECT_THAT(Mappings, testing::Contains(testing::Pair(
776                             llvm::memprof::hashCallStack(CS1), 0U)));
777   EXPECT_THAT(Mappings, testing::Contains(testing::Pair(
778                             llvm::memprof::hashCallStack(CS2), 3U)));
779   EXPECT_THAT(Mappings, testing::Contains(testing::Pair(
780                             llvm::memprof::hashCallStack(CS3), 7U)));
781   EXPECT_THAT(Mappings, testing::Contains(testing::Pair(
782                             llvm::memprof::hashCallStack(CS4), 10U)));
783 }
784 } // namespace
785