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