1 #ifndef LLVM_PROFILEDATA_MEMPROF_H_
2 #define LLVM_PROFILEDATA_MEMPROF_H_
3 
4 #include "llvm/ADT/DenseMap.h"
5 #include "llvm/ADT/STLFunctionalExtras.h"
6 #include "llvm/ADT/SmallVector.h"
7 #include "llvm/IR/GlobalValue.h"
8 #include "llvm/ProfileData/MemProfData.inc"
9 #include "llvm/Support/Endian.h"
10 #include "llvm/Support/EndianStream.h"
11 #include "llvm/Support/raw_ostream.h"
12 
13 #include <cstdint>
14 #include <optional>
15 
16 namespace llvm {
17 namespace memprof {
18 
19 enum class Meta : uint64_t {
20   Start = 0,
21 #define MIBEntryDef(NameTag, Name, Type) NameTag,
22 #include "llvm/ProfileData/MIBEntryDef.inc"
23 #undef MIBEntryDef
24   Size
25 };
26 
27 using MemProfSchema = llvm::SmallVector<Meta, static_cast<int>(Meta::Size)>;
28 
29 // Holds the actual MemInfoBlock data with all fields. Contents may be read or
30 // written partially by providing an appropriate schema to the serialize and
31 // deserialize methods.
32 struct PortableMemInfoBlock {
33   PortableMemInfoBlock() = default;
34   explicit PortableMemInfoBlock(const MemInfoBlock &Block) {
35 #define MIBEntryDef(NameTag, Name, Type) Name = Block.Name;
36 #include "llvm/ProfileData/MIBEntryDef.inc"
37 #undef MIBEntryDef
38   }
39 
40   PortableMemInfoBlock(const MemProfSchema &Schema, const unsigned char *Ptr) {
41     deserialize(Schema, Ptr);
42   }
43 
44   // Read the contents of \p Ptr based on the \p Schema to populate the
45   // MemInfoBlock member.
46   void deserialize(const MemProfSchema &Schema, const unsigned char *Ptr) {
47     using namespace support;
48 
49     for (const Meta Id : Schema) {
50       switch (Id) {
51 #define MIBEntryDef(NameTag, Name, Type)                                       \
52   case Meta::Name: {                                                           \
53     Name = endian::readNext<Type, little, unaligned>(Ptr);                     \
54   } break;
55 #include "llvm/ProfileData/MIBEntryDef.inc"
56 #undef MIBEntryDef
57       default:
58         llvm_unreachable("Unknown meta type id, is the profile collected from "
59                          "a newer version of the runtime?");
60       }
61     }
62   }
63 
64   // Write the contents of the MemInfoBlock based on the \p Schema provided to
65   // the raw_ostream \p OS.
66   void serialize(const MemProfSchema &Schema, raw_ostream &OS) const {
67     using namespace support;
68 
69     endian::Writer LE(OS, little);
70     for (const Meta Id : Schema) {
71       switch (Id) {
72 #define MIBEntryDef(NameTag, Name, Type)                                       \
73   case Meta::Name: {                                                           \
74     LE.write<Type>(Name);                                                      \
75   } break;
76 #include "llvm/ProfileData/MIBEntryDef.inc"
77 #undef MIBEntryDef
78       default:
79         llvm_unreachable("Unknown meta type id, invalid input?");
80       }
81     }
82   }
83 
84   // Print out the contents of the MemInfoBlock in YAML format.
85   void printYAML(raw_ostream &OS) const {
86     OS << "      MemInfoBlock:\n";
87 #define MIBEntryDef(NameTag, Name, Type)                                       \
88   OS << "        " << #Name << ": " << Name << "\n";
89 #include "llvm/ProfileData/MIBEntryDef.inc"
90 #undef MIBEntryDef
91   }
92 
93   // Define getters for each type which can be called by analyses.
94 #define MIBEntryDef(NameTag, Name, Type)                                       \
95   Type get##Name() const { return Name; }
96 #include "llvm/ProfileData/MIBEntryDef.inc"
97 #undef MIBEntryDef
98 
99   void clear() { *this = PortableMemInfoBlock(); }
100 
101   // Returns the full schema currently in use.
102   static MemProfSchema getSchema() {
103     MemProfSchema List;
104 #define MIBEntryDef(NameTag, Name, Type) List.push_back(Meta::Name);
105 #include "llvm/ProfileData/MIBEntryDef.inc"
106 #undef MIBEntryDef
107     return List;
108   }
109 
110   bool operator==(const PortableMemInfoBlock &Other) const {
111 #define MIBEntryDef(NameTag, Name, Type)                                       \
112   if (Other.get##Name() != get##Name())                                        \
113     return false;
114 #include "llvm/ProfileData/MIBEntryDef.inc"
115 #undef MIBEntryDef
116     return true;
117   }
118 
119   bool operator!=(const PortableMemInfoBlock &Other) const {
120     return !operator==(Other);
121   }
122 
123   static constexpr size_t serializedSize() {
124     size_t Result = 0;
125 #define MIBEntryDef(NameTag, Name, Type) Result += sizeof(Type);
126 #include "llvm/ProfileData/MIBEntryDef.inc"
127 #undef MIBEntryDef
128     return Result;
129   }
130 
131 private:
132 #define MIBEntryDef(NameTag, Name, Type) Type Name = Type();
133 #include "llvm/ProfileData/MIBEntryDef.inc"
134 #undef MIBEntryDef
135 };
136 
137 // A type representing the id generated by hashing the contents of the Frame.
138 using FrameId = uint64_t;
139 // Describes a call frame for a dynamic allocation context. The contents of
140 // the frame are populated by symbolizing the stack depot call frame from the
141 // compiler runtime.
142 struct Frame {
143   // A uuid (uint64_t) identifying the function. It is obtained by
144   // llvm::md5(FunctionName) which returns the lower 64 bits.
145   GlobalValue::GUID Function;
146   // The symbol name for the function. Only populated in the Frame by the reader
147   // if requested during initialization. This field should not be serialized.
148   std::optional<std::string> SymbolName;
149   // The source line offset of the call from the beginning of parent function.
150   uint32_t LineOffset;
151   // The source column number of the call to help distinguish multiple calls
152   // on the same line.
153   uint32_t Column;
154   // Whether the current frame is inlined.
155   bool IsInlineFrame;
156 
157   Frame(const Frame &Other) {
158     Function = Other.Function;
159     SymbolName = Other.SymbolName;
160     LineOffset = Other.LineOffset;
161     Column = Other.Column;
162     IsInlineFrame = Other.IsInlineFrame;
163   }
164 
165   Frame(uint64_t Hash, uint32_t Off, uint32_t Col, bool Inline)
166       : Function(Hash), LineOffset(Off), Column(Col), IsInlineFrame(Inline) {}
167 
168   bool operator==(const Frame &Other) const {
169     // Ignore the SymbolName field to avoid a string compare. Comparing the
170     // function hash serves the same purpose.
171     return Other.Function == Function && Other.LineOffset == LineOffset &&
172            Other.Column == Column && Other.IsInlineFrame == IsInlineFrame;
173   }
174 
175   Frame &operator=(const Frame &Other) {
176     Function = Other.Function;
177     SymbolName = Other.SymbolName;
178     LineOffset = Other.LineOffset;
179     Column = Other.Column;
180     IsInlineFrame = Other.IsInlineFrame;
181     return *this;
182   }
183 
184   bool operator!=(const Frame &Other) const { return !operator==(Other); }
185 
186   // Write the contents of the frame to the ostream \p OS.
187   void serialize(raw_ostream &OS) const {
188     using namespace support;
189 
190     endian::Writer LE(OS, little);
191 
192     // If the type of the GlobalValue::GUID changes, then we need to update
193     // the reader and the writer.
194     static_assert(std::is_same<GlobalValue::GUID, uint64_t>::value,
195                   "Expect GUID to be uint64_t.");
196     LE.write<uint64_t>(Function);
197 
198     LE.write<uint32_t>(LineOffset);
199     LE.write<uint32_t>(Column);
200     LE.write<bool>(IsInlineFrame);
201   }
202 
203   // Read a frame from char data which has been serialized as little endian.
204   static Frame deserialize(const unsigned char *Ptr) {
205     using namespace support;
206 
207     const uint64_t F = endian::readNext<uint64_t, little, unaligned>(Ptr);
208     const uint32_t L = endian::readNext<uint32_t, little, unaligned>(Ptr);
209     const uint32_t C = endian::readNext<uint32_t, little, unaligned>(Ptr);
210     const bool I = endian::readNext<bool, little, unaligned>(Ptr);
211     return Frame(/*Function=*/F, /*LineOffset=*/L, /*Column=*/C,
212                  /*IsInlineFrame=*/I);
213   }
214 
215   // Returns the size of the frame information.
216   static constexpr size_t serializedSize() {
217     return sizeof(Frame::Function) + sizeof(Frame::LineOffset) +
218            sizeof(Frame::Column) + sizeof(Frame::IsInlineFrame);
219   }
220 
221   // Print the frame information in YAML format.
222   void printYAML(raw_ostream &OS) const {
223     OS << "      -\n"
224        << "        Function: " << Function << "\n"
225        << "        SymbolName: " << SymbolName.value_or("<None>") << "\n"
226        << "        LineOffset: " << LineOffset << "\n"
227        << "        Column: " << Column << "\n"
228        << "        Inline: " << IsInlineFrame << "\n";
229   }
230 
231   // Return a hash value based on the contents of the frame. Here we don't use
232   // hashing from llvm ADT since we are going to persist the hash id, the hash
233   // combine algorithm in ADT uses a new randomized seed each time.
234   inline FrameId hash() const {
235     auto HashCombine = [](auto Value, size_t Seed) {
236       std::hash<decltype(Value)> Hasher;
237       // The constant used below is the 64 bit representation of the fractional
238       // part of the golden ratio. Used here for the randomness in their bit
239       // pattern.
240       return Hasher(Value) + 0x9e3779b97f4a7c15 + (Seed << 6) + (Seed >> 2);
241     };
242 
243     size_t Result = 0;
244     Result ^= HashCombine(Function, Result);
245     Result ^= HashCombine(LineOffset, Result);
246     Result ^= HashCombine(Column, Result);
247     Result ^= HashCombine(IsInlineFrame, Result);
248     return static_cast<FrameId>(Result);
249   }
250 };
251 
252 // Holds allocation information in a space efficient format where frames are
253 // represented using unique identifiers.
254 struct IndexedAllocationInfo {
255   // The dynamic calling context for the allocation in bottom-up (leaf-to-root)
256   // order. Frame contents are stored out-of-line.
257   llvm::SmallVector<FrameId> CallStack;
258   // The statistics obtained from the runtime for the allocation.
259   PortableMemInfoBlock Info;
260 
261   IndexedAllocationInfo() = default;
262   IndexedAllocationInfo(ArrayRef<FrameId> CS, const MemInfoBlock &MB)
263       : CallStack(CS.begin(), CS.end()), Info(MB) {}
264 
265   // Returns the size in bytes when this allocation info struct is serialized.
266   size_t serializedSize() const {
267     return sizeof(uint64_t) + // The number of frames to serialize.
268            sizeof(FrameId) * CallStack.size() +    // The callstack frame ids.
269            PortableMemInfoBlock::serializedSize(); // The size of the payload.
270   }
271 
272   bool operator==(const IndexedAllocationInfo &Other) const {
273     if (Other.Info != Info)
274       return false;
275 
276     if (Other.CallStack.size() != CallStack.size())
277       return false;
278 
279     for (size_t J = 0; J < Other.CallStack.size(); J++) {
280       if (Other.CallStack[J] != CallStack[J])
281         return false;
282     }
283     return true;
284   }
285 
286   bool operator!=(const IndexedAllocationInfo &Other) const {
287     return !operator==(Other);
288   }
289 };
290 
291 // Holds allocation information with frame contents inline. The type should
292 // be used for temporary in-memory instances.
293 struct AllocationInfo {
294   // Same as IndexedAllocationInfo::CallStack with the frame contents inline.
295   llvm::SmallVector<Frame> CallStack;
296   // Same as IndexedAllocationInfo::Info;
297   PortableMemInfoBlock Info;
298 
299   AllocationInfo() = default;
300   AllocationInfo(
301       const IndexedAllocationInfo &IndexedAI,
302       llvm::function_ref<const Frame(const FrameId)> IdToFrameCallback) {
303     for (const FrameId &Id : IndexedAI.CallStack) {
304       CallStack.push_back(IdToFrameCallback(Id));
305     }
306     Info = IndexedAI.Info;
307   }
308 
309   void printYAML(raw_ostream &OS) const {
310     OS << "    -\n";
311     OS << "      Callstack:\n";
312     // TODO: Print out the frame on one line with to make it easier for deep
313     // callstacks once we have a test to check valid YAML is generated.
314     for (const Frame &F : CallStack) {
315       F.printYAML(OS);
316     }
317     Info.printYAML(OS);
318   }
319 };
320 
321 // Holds the memprof profile information for a function. The internal
322 // representation stores frame ids for efficiency. This representation should
323 // be used in the profile conversion and manipulation tools.
324 struct IndexedMemProfRecord {
325   // Memory allocation sites in this function for which we have memory
326   // profiling data.
327   llvm::SmallVector<IndexedAllocationInfo> AllocSites;
328   // Holds call sites in this function which are part of some memory
329   // allocation context. We store this as a list of locations, each with its
330   // list of inline locations in bottom-up order i.e. from leaf to root. The
331   // inline location list may include additional entries, users should pick
332   // the last entry in the list with the same function GUID.
333   llvm::SmallVector<llvm::SmallVector<FrameId>> CallSites;
334 
335   void clear() {
336     AllocSites.clear();
337     CallSites.clear();
338   }
339 
340   void merge(const IndexedMemProfRecord &Other) {
341     // TODO: Filter out duplicates which may occur if multiple memprof
342     // profiles are merged together using llvm-profdata.
343     AllocSites.append(Other.AllocSites);
344     CallSites.append(Other.CallSites);
345   }
346 
347   size_t serializedSize() const {
348     size_t Result = sizeof(GlobalValue::GUID);
349     for (const IndexedAllocationInfo &N : AllocSites)
350       Result += N.serializedSize();
351 
352     // The number of callsites we have information for.
353     Result += sizeof(uint64_t);
354     for (const auto &Frames : CallSites) {
355       // The number of frame ids to serialize.
356       Result += sizeof(uint64_t);
357       Result += Frames.size() * sizeof(FrameId);
358     }
359     return Result;
360   }
361 
362   bool operator==(const IndexedMemProfRecord &Other) const {
363     if (Other.AllocSites.size() != AllocSites.size())
364       return false;
365 
366     if (Other.CallSites.size() != CallSites.size())
367       return false;
368 
369     for (size_t I = 0; I < AllocSites.size(); I++) {
370       if (AllocSites[I] != Other.AllocSites[I])
371         return false;
372     }
373 
374     for (size_t I = 0; I < CallSites.size(); I++) {
375       if (CallSites[I] != Other.CallSites[I])
376         return false;
377     }
378     return true;
379   }
380 
381   // Serializes the memprof records in \p Records to the ostream \p OS based
382   // on the schema provided in \p Schema.
383   void serialize(const MemProfSchema &Schema, raw_ostream &OS);
384 
385   // Deserializes memprof records from the Buffer.
386   static IndexedMemProfRecord deserialize(const MemProfSchema &Schema,
387                                           const unsigned char *Buffer);
388 
389   // Returns the GUID for the function name after canonicalization. For
390   // memprof, we remove any .llvm suffix added by LTO. MemProfRecords are
391   // mapped to functions using this GUID.
392   static GlobalValue::GUID getGUID(const StringRef FunctionName);
393 };
394 
395 // Holds the memprof profile information for a function. The internal
396 // representation stores frame contents inline. This representation should
397 // be used for small amount of temporary, in memory instances.
398 struct MemProfRecord {
399   // Same as IndexedMemProfRecord::AllocSites with frame contents inline.
400   llvm::SmallVector<AllocationInfo> AllocSites;
401   // Same as IndexedMemProfRecord::CallSites with frame contents inline.
402   llvm::SmallVector<llvm::SmallVector<Frame>> CallSites;
403 
404   MemProfRecord() = default;
405   MemProfRecord(
406       const IndexedMemProfRecord &Record,
407       llvm::function_ref<const Frame(const FrameId Id)> IdToFrameCallback) {
408     for (const IndexedAllocationInfo &IndexedAI : Record.AllocSites) {
409       AllocSites.emplace_back(IndexedAI, IdToFrameCallback);
410     }
411     for (const ArrayRef<FrameId> Site : Record.CallSites) {
412       llvm::SmallVector<Frame> Frames;
413       for (const FrameId Id : Site) {
414         Frames.push_back(IdToFrameCallback(Id));
415       }
416       CallSites.push_back(Frames);
417     }
418   }
419 
420   // Prints out the contents of the memprof record in YAML.
421   void print(llvm::raw_ostream &OS) const {
422     if (!AllocSites.empty()) {
423       OS << "    AllocSites:\n";
424       for (const AllocationInfo &N : AllocSites)
425         N.printYAML(OS);
426     }
427 
428     if (!CallSites.empty()) {
429       OS << "    CallSites:\n";
430       for (const llvm::SmallVector<Frame> &Frames : CallSites) {
431         for (const Frame &F : Frames) {
432           OS << "    -\n";
433           F.printYAML(OS);
434         }
435       }
436     }
437   }
438 };
439 
440 // Reads a memprof schema from a buffer. All entries in the buffer are
441 // interpreted as uint64_t. The first entry in the buffer denotes the number of
442 // ids in the schema. Subsequent entries are integers which map to memprof::Meta
443 // enum class entries. After successfully reading the schema, the pointer is one
444 // byte past the schema contents.
445 Expected<MemProfSchema> readMemProfSchema(const unsigned char *&Buffer);
446 
447 // Trait for reading IndexedMemProfRecord data from the on-disk hash table.
448 class RecordLookupTrait {
449 public:
450   using data_type = const IndexedMemProfRecord &;
451   using internal_key_type = uint64_t;
452   using external_key_type = uint64_t;
453   using hash_value_type = uint64_t;
454   using offset_type = uint64_t;
455 
456   RecordLookupTrait() = delete;
457   RecordLookupTrait(const MemProfSchema &S) : Schema(S) {}
458 
459   static bool EqualKey(uint64_t A, uint64_t B) { return A == B; }
460   static uint64_t GetInternalKey(uint64_t K) { return K; }
461   static uint64_t GetExternalKey(uint64_t K) { return K; }
462 
463   hash_value_type ComputeHash(uint64_t K) { return K; }
464 
465   static std::pair<offset_type, offset_type>
466   ReadKeyDataLength(const unsigned char *&D) {
467     using namespace support;
468 
469     offset_type KeyLen = endian::readNext<offset_type, little, unaligned>(D);
470     offset_type DataLen = endian::readNext<offset_type, little, unaligned>(D);
471     return std::make_pair(KeyLen, DataLen);
472   }
473 
474   uint64_t ReadKey(const unsigned char *D, offset_type /*Unused*/) {
475     using namespace support;
476     return endian::readNext<external_key_type, little, unaligned>(D);
477   }
478 
479   data_type ReadData(uint64_t K, const unsigned char *D,
480                      offset_type /*Unused*/) {
481     Record = IndexedMemProfRecord::deserialize(Schema, D);
482     return Record;
483   }
484 
485 private:
486   // Holds the memprof schema used to deserialize records.
487   MemProfSchema Schema;
488   // Holds the records from one function deserialized from the indexed format.
489   IndexedMemProfRecord Record;
490 };
491 
492 // Trait for writing IndexedMemProfRecord data to the on-disk hash table.
493 class RecordWriterTrait {
494 public:
495   using key_type = uint64_t;
496   using key_type_ref = uint64_t;
497 
498   using data_type = IndexedMemProfRecord;
499   using data_type_ref = IndexedMemProfRecord &;
500 
501   using hash_value_type = uint64_t;
502   using offset_type = uint64_t;
503 
504   // Pointer to the memprof schema to use for the generator. Unlike the reader
505   // we must use a default constructor with no params for the writer trait so we
506   // have a public member which must be initialized by the user.
507   MemProfSchema *Schema = nullptr;
508 
509   RecordWriterTrait() = default;
510 
511   static hash_value_type ComputeHash(key_type_ref K) { return K; }
512 
513   static std::pair<offset_type, offset_type>
514   EmitKeyDataLength(raw_ostream &Out, key_type_ref K, data_type_ref V) {
515     using namespace support;
516 
517     endian::Writer LE(Out, little);
518     offset_type N = sizeof(K);
519     LE.write<offset_type>(N);
520     offset_type M = V.serializedSize();
521     LE.write<offset_type>(M);
522     return std::make_pair(N, M);
523   }
524 
525   void EmitKey(raw_ostream &Out, key_type_ref K, offset_type /*Unused*/) {
526     using namespace support;
527     endian::Writer LE(Out, little);
528     LE.write<uint64_t>(K);
529   }
530 
531   void EmitData(raw_ostream &Out, key_type_ref /*Unused*/, data_type_ref V,
532                 offset_type /*Unused*/) {
533     assert(Schema != nullptr && "MemProf schema is not initialized!");
534     V.serialize(*Schema, Out);
535   }
536 };
537 
538 // Trait for writing frame mappings to the on-disk hash table.
539 class FrameWriterTrait {
540 public:
541   using key_type = FrameId;
542   using key_type_ref = FrameId;
543 
544   using data_type = Frame;
545   using data_type_ref = Frame &;
546 
547   using hash_value_type = FrameId;
548   using offset_type = uint64_t;
549 
550   static hash_value_type ComputeHash(key_type_ref K) { return K; }
551 
552   static std::pair<offset_type, offset_type>
553   EmitKeyDataLength(raw_ostream &Out, key_type_ref K, data_type_ref V) {
554     using namespace support;
555     endian::Writer LE(Out, little);
556     offset_type N = sizeof(K);
557     LE.write<offset_type>(N);
558     offset_type M = V.serializedSize();
559     LE.write<offset_type>(M);
560     return std::make_pair(N, M);
561   }
562 
563   void EmitKey(raw_ostream &Out, key_type_ref K, offset_type /*Unused*/) {
564     using namespace support;
565     endian::Writer LE(Out, little);
566     LE.write<key_type>(K);
567   }
568 
569   void EmitData(raw_ostream &Out, key_type_ref /*Unused*/, data_type_ref V,
570                 offset_type /*Unused*/) {
571     V.serialize(Out);
572   }
573 };
574 
575 // Trait for reading frame mappings from the on-disk hash table.
576 class FrameLookupTrait {
577 public:
578   using data_type = const Frame;
579   using internal_key_type = FrameId;
580   using external_key_type = FrameId;
581   using hash_value_type = FrameId;
582   using offset_type = uint64_t;
583 
584   static bool EqualKey(internal_key_type A, internal_key_type B) {
585     return A == B;
586   }
587   static uint64_t GetInternalKey(internal_key_type K) { return K; }
588   static uint64_t GetExternalKey(external_key_type K) { return K; }
589 
590   hash_value_type ComputeHash(internal_key_type K) { return K; }
591 
592   static std::pair<offset_type, offset_type>
593   ReadKeyDataLength(const unsigned char *&D) {
594     using namespace support;
595 
596     offset_type KeyLen = endian::readNext<offset_type, little, unaligned>(D);
597     offset_type DataLen = endian::readNext<offset_type, little, unaligned>(D);
598     return std::make_pair(KeyLen, DataLen);
599   }
600 
601   uint64_t ReadKey(const unsigned char *D, offset_type /*Unused*/) {
602     using namespace support;
603     return endian::readNext<external_key_type, little, unaligned>(D);
604   }
605 
606   data_type ReadData(uint64_t K, const unsigned char *D,
607                      offset_type /*Unused*/) {
608     return Frame::deserialize(D);
609   }
610 };
611 } // namespace memprof
612 } // namespace llvm
613 
614 #endif // LLVM_PROFILEDATA_MEMPROF_H_
615