1 #include "llvm/ProfileData/MemProf.h"
2 #include "llvm/ADT/DenseMap.h"
3 #include "llvm/ADT/MapVector.h"
4 #include "llvm/DebugInfo/DIContext.h"
5 #include "llvm/DebugInfo/Symbolize/SymbolizableModule.h"
6 #include "llvm/IR/Function.h"
7 #include "llvm/IR/Value.h"
8 #include "llvm/Object/ObjectFile.h"
9 #include "llvm/ProfileData/InstrProf.h"
10 #include "llvm/ProfileData/MemProfData.inc"
11 #include "llvm/ProfileData/RawMemProfReader.h"
12 #include "llvm/Support/Error.h"
13 #include "llvm/Support/MD5.h"
14 #include "llvm/Support/raw_ostream.h"
15 #include "gmock/gmock.h"
16 #include "gtest/gtest.h"
17 
18 #include <initializer_list>
19 
20 namespace {
21 
22 using ::llvm::DIGlobal;
23 using ::llvm::DIInliningInfo;
24 using ::llvm::DILineInfo;
25 using ::llvm::DILineInfoSpecifier;
26 using ::llvm::DILocal;
27 using ::llvm::memprof::CallStackMap;
28 using ::llvm::memprof::Frame;
29 using ::llvm::memprof::FrameId;
30 using ::llvm::memprof::IndexedMemProfRecord;
31 using ::llvm::memprof::MemInfoBlock;
32 using ::llvm::memprof::MemProfRecord;
33 using ::llvm::memprof::MemProfSchema;
34 using ::llvm::memprof::Meta;
35 using ::llvm::memprof::PortableMemInfoBlock;
36 using ::llvm::memprof::RawMemProfReader;
37 using ::llvm::memprof::SegmentEntry;
38 using ::llvm::object::SectionedAddress;
39 using ::llvm::symbolize::SymbolizableModule;
40 using ::testing::Return;
41 
42 class MockSymbolizer : public SymbolizableModule {
43 public:
44   MOCK_CONST_METHOD3(symbolizeInlinedCode,
45                      DIInliningInfo(SectionedAddress, DILineInfoSpecifier,
46                                     bool));
47   // Most of the methods in the interface are unused. We only mock the
48   // method that we expect to be called from the memprof reader.
49   virtual DILineInfo symbolizeCode(SectionedAddress, DILineInfoSpecifier,
50                                    bool) const {
51     llvm_unreachable("unused");
52   }
53   virtual DIGlobal symbolizeData(SectionedAddress) const {
54     llvm_unreachable("unused");
55   }
56   virtual std::vector<DILocal> symbolizeFrame(SectionedAddress) const {
57     llvm_unreachable("unused");
58   }
59   virtual bool isWin32Module() const { llvm_unreachable("unused"); }
60   virtual uint64_t getModulePreferredBase() const {
61     llvm_unreachable("unused");
62   }
63 };
64 
65 struct MockInfo {
66   std::string FunctionName;
67   uint32_t Line;
68   uint32_t StartLine;
69   uint32_t Column;
70   std::string FileName = "valid/path.cc";
71 };
72 DIInliningInfo makeInliningInfo(std::initializer_list<MockInfo> MockFrames) {
73   DIInliningInfo Result;
74   for (const auto &Item : MockFrames) {
75     DILineInfo Frame;
76     Frame.FunctionName = Item.FunctionName;
77     Frame.Line = Item.Line;
78     Frame.StartLine = Item.StartLine;
79     Frame.Column = Item.Column;
80     Frame.FileName = Item.FileName;
81     Result.addFrame(Frame);
82   }
83   return Result;
84 }
85 
86 llvm::SmallVector<SegmentEntry, 4> makeSegments() {
87   llvm::SmallVector<SegmentEntry, 4> Result;
88   // Mimic an entry for a non position independent executable.
89   Result.emplace_back(0x0, 0x40000, 0x0);
90   return Result;
91 }
92 
93 const DILineInfoSpecifier specifier() {
94   return DILineInfoSpecifier(
95       DILineInfoSpecifier::FileLineInfoKind::RawValue,
96       DILineInfoSpecifier::FunctionNameKind::LinkageName);
97 }
98 
99 MATCHER_P4(FrameContains, FunctionName, LineOffset, Column, Inline, "") {
100   const Frame &F = arg;
101 
102   const uint64_t ExpectedHash = llvm::Function::getGUID(FunctionName);
103   if (F.Function != ExpectedHash) {
104     *result_listener << "Hash mismatch";
105     return false;
106   }
107   if (F.LineOffset == LineOffset && F.Column == Column &&
108       F.IsInlineFrame == Inline) {
109     return true;
110   }
111   *result_listener << "LineOffset, Column or Inline mismatch";
112   return false;
113 }
114 
115 MemProfSchema getFullSchema() {
116   MemProfSchema Schema;
117 #define MIBEntryDef(NameTag, Name, Type) Schema.push_back(Meta::Name);
118 #include "llvm/ProfileData/MIBEntryDef.inc"
119 #undef MIBEntryDef
120   return Schema;
121 }
122 
123 TEST(MemProf, FillsValue) {
124   std::unique_ptr<MockSymbolizer> Symbolizer(new MockSymbolizer());
125 
126   EXPECT_CALL(*Symbolizer, symbolizeInlinedCode(SectionedAddress{0x1000},
127                                                 specifier(), false))
128       .Times(1) // Only once since we remember invalid PCs.
129       .WillRepeatedly(Return(makeInliningInfo({
130           {"new", 70, 57, 3, "memprof/memprof_new_delete.cpp"},
131       })));
132 
133   EXPECT_CALL(*Symbolizer, symbolizeInlinedCode(SectionedAddress{0x2000},
134                                                 specifier(), false))
135       .Times(1) // Only once since we cache the result for future lookups.
136       .WillRepeatedly(Return(makeInliningInfo({
137           {"foo", 10, 5, 30},
138           {"bar", 201, 150, 20},
139       })));
140 
141   EXPECT_CALL(*Symbolizer, symbolizeInlinedCode(SectionedAddress{0x3000},
142                                                 specifier(), false))
143       .Times(1)
144       .WillRepeatedly(Return(makeInliningInfo({
145           {"xyz", 10, 5, 30},
146           {"abc", 10, 5, 30},
147       })));
148 
149   CallStackMap CSM;
150   CSM[0x1] = {0x1000, 0x2000, 0x3000};
151 
152   llvm::MapVector<uint64_t, MemInfoBlock> Prof;
153   Prof[0x1].AllocCount = 1;
154 
155   auto Seg = makeSegments();
156 
157   RawMemProfReader Reader(std::move(Symbolizer), Seg, Prof, CSM);
158 
159   llvm::DenseMap<llvm::GlobalValue::GUID, MemProfRecord> Records;
160   for (const auto &Pair : Reader) {
161     Records.insert({Pair.first, Pair.second});
162   }
163 
164   // Mock program psuedocode and expected memprof record contents.
165   //
166   //                              AllocSite       CallSite
167   // inline foo() { new(); }         Y               N
168   // bar() { foo(); }                Y               Y
169   // inline xyz() { bar(); }         N               Y
170   // abc() { xyz(); }                N               Y
171 
172   // We expect 4 records. We attach alloc site data to foo and bar, i.e.
173   // all frames bottom up until we find a non-inline frame. We attach call site
174   // data to bar, xyz and abc.
175   ASSERT_EQ(Records.size(), 4U);
176 
177   // Check the memprof record for foo.
178   const llvm::GlobalValue::GUID FooId = IndexedMemProfRecord::getGUID("foo");
179   ASSERT_EQ(Records.count(FooId), 1U);
180   const MemProfRecord &Foo = Records[FooId];
181   ASSERT_EQ(Foo.AllocSites.size(), 1U);
182   EXPECT_EQ(Foo.AllocSites[0].Info.getAllocCount(), 1U);
183   EXPECT_THAT(Foo.AllocSites[0].CallStack[0],
184               FrameContains("foo", 5U, 30U, true));
185   EXPECT_THAT(Foo.AllocSites[0].CallStack[1],
186               FrameContains("bar", 51U, 20U, false));
187   EXPECT_THAT(Foo.AllocSites[0].CallStack[2],
188               FrameContains("xyz", 5U, 30U, true));
189   EXPECT_THAT(Foo.AllocSites[0].CallStack[3],
190               FrameContains("abc", 5U, 30U, false));
191   EXPECT_TRUE(Foo.CallSites.empty());
192 
193   // Check the memprof record for bar.
194   const llvm::GlobalValue::GUID BarId = IndexedMemProfRecord::getGUID("bar");
195   ASSERT_EQ(Records.count(BarId), 1U);
196   const MemProfRecord &Bar = Records[BarId];
197   ASSERT_EQ(Bar.AllocSites.size(), 1U);
198   EXPECT_EQ(Bar.AllocSites[0].Info.getAllocCount(), 1U);
199   EXPECT_THAT(Bar.AllocSites[0].CallStack[0],
200               FrameContains("foo", 5U, 30U, true));
201   EXPECT_THAT(Bar.AllocSites[0].CallStack[1],
202               FrameContains("bar", 51U, 20U, false));
203   EXPECT_THAT(Bar.AllocSites[0].CallStack[2],
204               FrameContains("xyz", 5U, 30U, true));
205   EXPECT_THAT(Bar.AllocSites[0].CallStack[3],
206               FrameContains("abc", 5U, 30U, false));
207 
208   ASSERT_EQ(Bar.CallSites.size(), 1U);
209   ASSERT_EQ(Bar.CallSites[0].size(), 2U);
210   EXPECT_THAT(Bar.CallSites[0][0], FrameContains("foo", 5U, 30U, true));
211   EXPECT_THAT(Bar.CallSites[0][1], FrameContains("bar", 51U, 20U, false));
212 
213   // Check the memprof record for xyz.
214   const llvm::GlobalValue::GUID XyzId = IndexedMemProfRecord::getGUID("xyz");
215   ASSERT_EQ(Records.count(XyzId), 1U);
216   const MemProfRecord &Xyz = Records[XyzId];
217   ASSERT_EQ(Xyz.CallSites.size(), 1U);
218   ASSERT_EQ(Xyz.CallSites[0].size(), 2U);
219   // Expect the entire frame even though in practice we only need the first
220   // entry here.
221   EXPECT_THAT(Xyz.CallSites[0][0], FrameContains("xyz", 5U, 30U, true));
222   EXPECT_THAT(Xyz.CallSites[0][1], FrameContains("abc", 5U, 30U, false));
223 
224   // Check the memprof record for abc.
225   const llvm::GlobalValue::GUID AbcId = IndexedMemProfRecord::getGUID("abc");
226   ASSERT_EQ(Records.count(AbcId), 1U);
227   const MemProfRecord &Abc = Records[AbcId];
228   EXPECT_TRUE(Abc.AllocSites.empty());
229   ASSERT_EQ(Abc.CallSites.size(), 1U);
230   ASSERT_EQ(Abc.CallSites[0].size(), 2U);
231   EXPECT_THAT(Abc.CallSites[0][0], FrameContains("xyz", 5U, 30U, true));
232   EXPECT_THAT(Abc.CallSites[0][1], FrameContains("abc", 5U, 30U, false));
233 }
234 
235 TEST(MemProf, PortableWrapper) {
236   MemInfoBlock Info(/*size=*/16, /*access_count=*/7, /*alloc_timestamp=*/1000,
237                     /*dealloc_timestamp=*/2000, /*alloc_cpu=*/3,
238                     /*dealloc_cpu=*/4);
239 
240   const auto Schema = getFullSchema();
241   PortableMemInfoBlock WriteBlock(Info);
242 
243   std::string Buffer;
244   llvm::raw_string_ostream OS(Buffer);
245   WriteBlock.serialize(Schema, OS);
246   OS.flush();
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, RecordSerializationRoundTrip) {
261   const MemProfSchema 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);
266 
267   llvm::SmallVector<llvm::SmallVector<FrameId>> AllocCallStacks = {
268       {0x123, 0x345}, {0x123, 0x567}};
269 
270   llvm::SmallVector<llvm::SmallVector<FrameId>> CallSites = {{0x333, 0x777}};
271 
272   IndexedMemProfRecord Record;
273   for (const auto &ACS : AllocCallStacks) {
274     // Use the same info block for both allocation sites.
275     Record.AllocSites.emplace_back(ACS, Info);
276   }
277   Record.CallSites.assign(CallSites);
278 
279   std::string Buffer;
280   llvm::raw_string_ostream OS(Buffer);
281   Record.serialize(Schema, OS);
282   OS.flush();
283 
284   const IndexedMemProfRecord GotRecord = IndexedMemProfRecord::deserialize(
285       Schema, reinterpret_cast<const unsigned char *>(Buffer.data()));
286 
287   EXPECT_EQ(Record, GotRecord);
288 }
289 
290 TEST(MemProf, SymbolizationFilter) {
291   std::unique_ptr<MockSymbolizer> Symbolizer(new MockSymbolizer());
292 
293   EXPECT_CALL(*Symbolizer, symbolizeInlinedCode(SectionedAddress{0x1000},
294                                                 specifier(), false))
295       .Times(1) // once since we don't lookup invalid PCs repeatedly.
296       .WillRepeatedly(Return(makeInliningInfo({
297           {"malloc", 70, 57, 3, "memprof/memprof_malloc_linux.cpp"},
298       })));
299 
300   EXPECT_CALL(*Symbolizer, symbolizeInlinedCode(SectionedAddress{0x2000},
301                                                 specifier(), false))
302       .Times(1) // once since we don't lookup invalid PCs repeatedly.
303       .WillRepeatedly(Return(makeInliningInfo({
304           {"new", 70, 57, 3, "memprof/memprof_new_delete.cpp"},
305       })));
306 
307   EXPECT_CALL(*Symbolizer, symbolizeInlinedCode(SectionedAddress{0x3000},
308                                                 specifier(), false))
309       .Times(1) // once since we don't lookup invalid PCs repeatedly.
310       .WillRepeatedly(Return(makeInliningInfo({
311           {DILineInfo::BadString, 0, 0, 0},
312       })));
313 
314   EXPECT_CALL(*Symbolizer, symbolizeInlinedCode(SectionedAddress{0x4000},
315                                                 specifier(), false))
316       .Times(1)
317       .WillRepeatedly(Return(makeInliningInfo({
318           {"foo", 10, 5, 30},
319       })));
320 
321   CallStackMap CSM;
322   CSM[0x1] = {0x1000, 0x2000, 0x3000, 0x4000};
323   // This entry should be dropped since all PCs are either not
324   // symbolizable or belong to the runtime.
325   CSM[0x2] = {0x1000, 0x2000};
326 
327   llvm::MapVector<uint64_t, MemInfoBlock> Prof;
328   Prof[0x1].AllocCount = 1;
329   Prof[0x2].AllocCount = 1;
330 
331   auto Seg = makeSegments();
332 
333   RawMemProfReader Reader(std::move(Symbolizer), Seg, Prof, CSM);
334 
335   llvm::SmallVector<MemProfRecord, 1> Records;
336   for (const auto &KeyRecordPair : Reader) {
337     Records.push_back(KeyRecordPair.second);
338   }
339 
340   ASSERT_EQ(Records.size(), 1U);
341   ASSERT_EQ(Records[0].AllocSites.size(), 1U);
342   ASSERT_EQ(Records[0].AllocSites[0].CallStack.size(), 1U);
343   EXPECT_THAT(Records[0].AllocSites[0].CallStack[0],
344               FrameContains("foo", 5U, 30U, false));
345 }
346 } // namespace
347