1 #ifndef LLVM_PROFILEDATA_MEMPROF_H_
2 #define LLVM_PROFILEDATA_MEMPROF_H_
3 
4 #include "llvm/ADT/MapVector.h"
5 #include "llvm/ADT/STLForwardCompat.h"
6 #include "llvm/ADT/STLFunctionalExtras.h"
7 #include "llvm/ADT/SmallVector.h"
8 #include "llvm/IR/GlobalValue.h"
9 #include "llvm/ProfileData/MemProfData.inc"
10 #include "llvm/Support/Endian.h"
11 #include "llvm/Support/EndianStream.h"
12 #include "llvm/Support/raw_ostream.h"
13 
14 #include <bitset>
15 #include <cstdint>
16 #include <optional>
17 
18 namespace llvm {
19 namespace memprof {
20 
21 struct MemProfRecord;
22 
23 // The versions of the indexed MemProf format
24 enum IndexedVersion : uint64_t {
25   // Version 0: This version didn't have a version field.
26   Version0 = 0,
27   // Version 1: Added a version field to the header.
28   Version1 = 1,
29   // Version 2: Added a call stack table.
30   Version2 = 2,
31 };
32 
33 constexpr uint64_t MinimumSupportedVersion = Version0;
34 constexpr uint64_t MaximumSupportedVersion = Version2;
35 
36 // Verify that the minimum and maximum satisfy the obvious constraint.
37 static_assert(MinimumSupportedVersion <= MaximumSupportedVersion);
38 
39 enum class Meta : uint64_t {
40   Start = 0,
41 #define MIBEntryDef(NameTag, Name, Type) NameTag,
42 #include "llvm/ProfileData/MIBEntryDef.inc"
43 #undef MIBEntryDef
44   Size
45 };
46 
47 using MemProfSchema = llvm::SmallVector<Meta, static_cast<int>(Meta::Size)>;
48 
49 // Returns the full schema currently in use.
50 MemProfSchema getFullSchema();
51 
52 // Returns the schema consisting of the fields used for hot cold memory hinting.
53 MemProfSchema getHotColdSchema();
54 
55 // Holds the actual MemInfoBlock data with all fields. Contents may be read or
56 // written partially by providing an appropriate schema to the serialize and
57 // deserialize methods.
58 struct PortableMemInfoBlock {
59   PortableMemInfoBlock() = default;
PortableMemInfoBlockPortableMemInfoBlock60   explicit PortableMemInfoBlock(const MemInfoBlock &Block,
61                                 const MemProfSchema &IncomingSchema) {
62     for (const Meta Id : IncomingSchema)
63       Schema.set(llvm::to_underlying(Id));
64 #define MIBEntryDef(NameTag, Name, Type) Name = Block.Name;
65 #include "llvm/ProfileData/MIBEntryDef.inc"
66 #undef MIBEntryDef
67   }
68 
PortableMemInfoBlockPortableMemInfoBlock69   PortableMemInfoBlock(const MemProfSchema &Schema, const unsigned char *Ptr) {
70     deserialize(Schema, Ptr);
71   }
72 
73   // Read the contents of \p Ptr based on the \p Schema to populate the
74   // MemInfoBlock member.
deserializePortableMemInfoBlock75   void deserialize(const MemProfSchema &IncomingSchema,
76                    const unsigned char *Ptr) {
77     using namespace support;
78 
79     Schema.reset();
80     for (const Meta Id : IncomingSchema) {
81       switch (Id) {
82 #define MIBEntryDef(NameTag, Name, Type)                                       \
83   case Meta::Name: {                                                           \
84     Name = endian::readNext<Type, llvm::endianness::little>(Ptr);              \
85   } break;
86 #include "llvm/ProfileData/MIBEntryDef.inc"
87 #undef MIBEntryDef
88       default:
89         llvm_unreachable("Unknown meta type id, is the profile collected from "
90                          "a newer version of the runtime?");
91       }
92 
93       Schema.set(llvm::to_underlying(Id));
94     }
95   }
96 
97   // Write the contents of the MemInfoBlock based on the \p Schema provided to
98   // the raw_ostream \p OS.
serializePortableMemInfoBlock99   void serialize(const MemProfSchema &Schema, raw_ostream &OS) const {
100     using namespace support;
101 
102     endian::Writer LE(OS, llvm::endianness::little);
103     for (const Meta Id : Schema) {
104       switch (Id) {
105 #define MIBEntryDef(NameTag, Name, Type)                                       \
106   case Meta::Name: {                                                           \
107     LE.write<Type>(Name);                                                      \
108   } break;
109 #include "llvm/ProfileData/MIBEntryDef.inc"
110 #undef MIBEntryDef
111       default:
112         llvm_unreachable("Unknown meta type id, invalid input?");
113       }
114     }
115   }
116 
117   // Print out the contents of the MemInfoBlock in YAML format.
printYAMLPortableMemInfoBlock118   void printYAML(raw_ostream &OS) const {
119     OS << "      MemInfoBlock:\n";
120 #define MIBEntryDef(NameTag, Name, Type)                                       \
121   OS << "        " << #Name << ": " << Name << "\n";
122 #include "llvm/ProfileData/MIBEntryDef.inc"
123 #undef MIBEntryDef
124   }
125 
126   // Return the schema, only for unit tests.
getSchemaPortableMemInfoBlock127   std::bitset<llvm::to_underlying(Meta::Size)> getSchema() const {
128     return Schema;
129   }
130 
131   // Define getters for each type which can be called by analyses.
132 #define MIBEntryDef(NameTag, Name, Type)                                       \
133   Type get##Name() const {                                                     \
134     assert(Schema[llvm::to_underlying(Meta::Name)]);                           \
135     return Name;                                                               \
136   }
137 #include "llvm/ProfileData/MIBEntryDef.inc"
138 #undef MIBEntryDef
139 
clearPortableMemInfoBlock140   void clear() { *this = PortableMemInfoBlock(); }
141 
142   bool operator==(const PortableMemInfoBlock &Other) const {
143     if (Other.Schema != Schema)
144       return false;
145 
146 #define MIBEntryDef(NameTag, Name, Type)                                       \
147   if (Schema[llvm::to_underlying(Meta::Name)] &&                               \
148       Other.get##Name() != get##Name())                                        \
149     return false;
150 #include "llvm/ProfileData/MIBEntryDef.inc"
151 #undef MIBEntryDef
152     return true;
153   }
154 
155   bool operator!=(const PortableMemInfoBlock &Other) const {
156     return !operator==(Other);
157   }
158 
serializedSizePortableMemInfoBlock159   static size_t serializedSize(const MemProfSchema &Schema) {
160     size_t Result = 0;
161 
162     for (const Meta Id : Schema) {
163       switch (Id) {
164 #define MIBEntryDef(NameTag, Name, Type)                                       \
165   case Meta::Name: {                                                           \
166     Result += sizeof(Type);                                                    \
167   } break;
168 #include "llvm/ProfileData/MIBEntryDef.inc"
169 #undef MIBEntryDef
170       default:
171         llvm_unreachable("Unknown meta type id, invalid input?");
172       }
173     }
174 
175     return Result;
176   }
177 
178 private:
179   // The set of available fields, indexed by Meta::Name.
180   std::bitset<llvm::to_underlying(Meta::Size)> Schema;
181 
182 #define MIBEntryDef(NameTag, Name, Type) Type Name = Type();
183 #include "llvm/ProfileData/MIBEntryDef.inc"
184 #undef MIBEntryDef
185 };
186 
187 // A type representing the id generated by hashing the contents of the Frame.
188 using FrameId = uint64_t;
189 // Describes a call frame for a dynamic allocation context. The contents of
190 // the frame are populated by symbolizing the stack depot call frame from the
191 // compiler runtime.
192 struct Frame {
193   // A uuid (uint64_t) identifying the function. It is obtained by
194   // llvm::md5(FunctionName) which returns the lower 64 bits.
195   GlobalValue::GUID Function;
196   // The symbol name for the function. Only populated in the Frame by the reader
197   // if requested during initialization. This field should not be serialized.
198   std::optional<std::string> SymbolName;
199   // The source line offset of the call from the beginning of parent function.
200   uint32_t LineOffset;
201   // The source column number of the call to help distinguish multiple calls
202   // on the same line.
203   uint32_t Column;
204   // Whether the current frame is inlined.
205   bool IsInlineFrame;
206 
FrameFrame207   Frame(const Frame &Other) {
208     Function = Other.Function;
209     SymbolName = Other.SymbolName;
210     LineOffset = Other.LineOffset;
211     Column = Other.Column;
212     IsInlineFrame = Other.IsInlineFrame;
213   }
214 
FrameFrame215   Frame(uint64_t Hash, uint32_t Off, uint32_t Col, bool Inline)
216       : Function(Hash), LineOffset(Off), Column(Col), IsInlineFrame(Inline) {}
217 
218   bool operator==(const Frame &Other) const {
219     // Ignore the SymbolName field to avoid a string compare. Comparing the
220     // function hash serves the same purpose.
221     return Other.Function == Function && Other.LineOffset == LineOffset &&
222            Other.Column == Column && Other.IsInlineFrame == IsInlineFrame;
223   }
224 
225   Frame &operator=(const Frame &Other) {
226     Function = Other.Function;
227     SymbolName = Other.SymbolName;
228     LineOffset = Other.LineOffset;
229     Column = Other.Column;
230     IsInlineFrame = Other.IsInlineFrame;
231     return *this;
232   }
233 
234   bool operator!=(const Frame &Other) const { return !operator==(Other); }
235 
236   // Write the contents of the frame to the ostream \p OS.
serializeFrame237   void serialize(raw_ostream &OS) const {
238     using namespace support;
239 
240     endian::Writer LE(OS, llvm::endianness::little);
241 
242     // If the type of the GlobalValue::GUID changes, then we need to update
243     // the reader and the writer.
244     static_assert(std::is_same<GlobalValue::GUID, uint64_t>::value,
245                   "Expect GUID to be uint64_t.");
246     LE.write<uint64_t>(Function);
247 
248     LE.write<uint32_t>(LineOffset);
249     LE.write<uint32_t>(Column);
250     LE.write<bool>(IsInlineFrame);
251   }
252 
253   // Read a frame from char data which has been serialized as little endian.
deserializeFrame254   static Frame deserialize(const unsigned char *Ptr) {
255     using namespace support;
256 
257     const uint64_t F =
258         endian::readNext<uint64_t, llvm::endianness::little>(Ptr);
259     const uint32_t L =
260         endian::readNext<uint32_t, llvm::endianness::little>(Ptr);
261     const uint32_t C =
262         endian::readNext<uint32_t, llvm::endianness::little>(Ptr);
263     const bool I = endian::readNext<bool, llvm::endianness::little>(Ptr);
264     return Frame(/*Function=*/F, /*LineOffset=*/L, /*Column=*/C,
265                  /*IsInlineFrame=*/I);
266   }
267 
268   // Returns the size of the frame information.
serializedSizeFrame269   static constexpr size_t serializedSize() {
270     return sizeof(Frame::Function) + sizeof(Frame::LineOffset) +
271            sizeof(Frame::Column) + sizeof(Frame::IsInlineFrame);
272   }
273 
274   // Print the frame information in YAML format.
printYAMLFrame275   void printYAML(raw_ostream &OS) const {
276     OS << "      -\n"
277        << "        Function: " << Function << "\n"
278        << "        SymbolName: " << SymbolName.value_or("<None>") << "\n"
279        << "        LineOffset: " << LineOffset << "\n"
280        << "        Column: " << Column << "\n"
281        << "        Inline: " << IsInlineFrame << "\n";
282   }
283 
284   // Return a hash value based on the contents of the frame. Here we don't use
285   // hashing from llvm ADT since we are going to persist the hash id, the hash
286   // combine algorithm in ADT uses a new randomized seed each time.
hashFrame287   inline FrameId hash() const {
288     auto HashCombine = [](auto Value, size_t Seed) {
289       std::hash<decltype(Value)> Hasher;
290       // The constant used below is the 64 bit representation of the fractional
291       // part of the golden ratio. Used here for the randomness in their bit
292       // pattern.
293       return Hasher(Value) + 0x9e3779b97f4a7c15 + (Seed << 6) + (Seed >> 2);
294     };
295 
296     size_t Result = 0;
297     Result ^= HashCombine(Function, Result);
298     Result ^= HashCombine(LineOffset, Result);
299     Result ^= HashCombine(Column, Result);
300     Result ^= HashCombine(IsInlineFrame, Result);
301     return static_cast<FrameId>(Result);
302   }
303 };
304 
305 // A type representing the index into the table of call stacks.
306 using CallStackId = uint64_t;
307 
308 // Holds allocation information in a space efficient format where frames are
309 // represented using unique identifiers.
310 struct IndexedAllocationInfo {
311   // The dynamic calling context for the allocation in bottom-up (leaf-to-root)
312   // order. Frame contents are stored out-of-line.
313   // TODO: Remove once we fully transition to CSId.
314   llvm::SmallVector<FrameId> CallStack;
315   // Conceptually the same as above.  We are going to keep both CallStack and
316   // CallStackId while we are transitioning from CallStack to CallStackId.
317   CallStackId CSId = 0;
318   // The statistics obtained from the runtime for the allocation.
319   PortableMemInfoBlock Info;
320 
321   IndexedAllocationInfo() = default;
322   IndexedAllocationInfo(ArrayRef<FrameId> CS, CallStackId CSId,
323                         const MemInfoBlock &MB,
324                         const MemProfSchema &Schema = getFullSchema())
325       : CallStack(CS.begin(), CS.end()), CSId(CSId), Info(MB, Schema) {}
326 
327   // Returns the size in bytes when this allocation info struct is serialized.
328   size_t serializedSize(const MemProfSchema &Schema,
329                         IndexedVersion Version) const;
330 
331   bool operator==(const IndexedAllocationInfo &Other) const {
332     if (Other.Info != Info)
333       return false;
334 
335     if (Other.CSId != CSId)
336       return false;
337     return true;
338   }
339 
340   bool operator!=(const IndexedAllocationInfo &Other) const {
341     return !operator==(Other);
342   }
343 };
344 
345 // Holds allocation information with frame contents inline. The type should
346 // be used for temporary in-memory instances.
347 struct AllocationInfo {
348   // Same as IndexedAllocationInfo::CallStack with the frame contents inline.
349   llvm::SmallVector<Frame> CallStack;
350   // Same as IndexedAllocationInfo::Info;
351   PortableMemInfoBlock Info;
352 
353   AllocationInfo() = default;
AllocationInfoAllocationInfo354   AllocationInfo(
355       const IndexedAllocationInfo &IndexedAI,
356       llvm::function_ref<const Frame(const FrameId)> IdToFrameCallback) {
357     for (const FrameId &Id : IndexedAI.CallStack) {
358       CallStack.push_back(IdToFrameCallback(Id));
359     }
360     Info = IndexedAI.Info;
361   }
362 
printYAMLAllocationInfo363   void printYAML(raw_ostream &OS) const {
364     OS << "    -\n";
365     OS << "      Callstack:\n";
366     // TODO: Print out the frame on one line with to make it easier for deep
367     // callstacks once we have a test to check valid YAML is generated.
368     for (const Frame &F : CallStack) {
369       F.printYAML(OS);
370     }
371     Info.printYAML(OS);
372   }
373 };
374 
375 // Holds the memprof profile information for a function. The internal
376 // representation stores frame ids for efficiency. This representation should
377 // be used in the profile conversion and manipulation tools.
378 struct IndexedMemProfRecord {
379   // Memory allocation sites in this function for which we have memory
380   // profiling data.
381   llvm::SmallVector<IndexedAllocationInfo> AllocSites;
382   // Holds call sites in this function which are part of some memory
383   // allocation context. We store this as a list of locations, each with its
384   // list of inline locations in bottom-up order i.e. from leaf to root. The
385   // inline location list may include additional entries, users should pick
386   // the last entry in the list with the same function GUID.
387   llvm::SmallVector<llvm::SmallVector<FrameId>> CallSites;
388   // Conceptually the same as above.  We are going to keep both CallSites and
389   // CallSiteIds while we are transitioning from CallSites to CallSiteIds.
390   llvm::SmallVector<CallStackId> CallSiteIds;
391 
clearIndexedMemProfRecord392   void clear() {
393     AllocSites.clear();
394     CallSites.clear();
395   }
396 
mergeIndexedMemProfRecord397   void merge(const IndexedMemProfRecord &Other) {
398     // TODO: Filter out duplicates which may occur if multiple memprof
399     // profiles are merged together using llvm-profdata.
400     AllocSites.append(Other.AllocSites);
401     CallSites.append(Other.CallSites);
402   }
403 
404   size_t serializedSize(const MemProfSchema &Schema,
405                         IndexedVersion Version) const;
406 
407   bool operator==(const IndexedMemProfRecord &Other) const {
408     if (Other.AllocSites != AllocSites)
409       return false;
410 
411     if (Other.CallSiteIds != CallSiteIds)
412       return false;
413     return true;
414   }
415 
416   // Serializes the memprof records in \p Records to the ostream \p OS based
417   // on the schema provided in \p Schema.
418   void serialize(const MemProfSchema &Schema, raw_ostream &OS,
419                  IndexedVersion Version);
420 
421   // Deserializes memprof records from the Buffer.
422   static IndexedMemProfRecord deserialize(const MemProfSchema &Schema,
423                                           const unsigned char *Buffer,
424                                           IndexedVersion Version);
425 
426   // Convert IndexedMemProfRecord to MemProfRecord.  Callback is used to
427   // translate CallStackId to call stacks with frames inline.
428   MemProfRecord toMemProfRecord(
429       std::function<const llvm::SmallVector<Frame>(const CallStackId)> Callback)
430       const;
431 
432   // Returns the GUID for the function name after canonicalization. For
433   // memprof, we remove any .llvm suffix added by LTO. MemProfRecords are
434   // mapped to functions using this GUID.
435   static GlobalValue::GUID getGUID(const StringRef FunctionName);
436 };
437 
438 // Holds the memprof profile information for a function. The internal
439 // representation stores frame contents inline. This representation should
440 // be used for small amount of temporary, in memory instances.
441 struct MemProfRecord {
442   // Same as IndexedMemProfRecord::AllocSites with frame contents inline.
443   llvm::SmallVector<AllocationInfo> AllocSites;
444   // Same as IndexedMemProfRecord::CallSites with frame contents inline.
445   llvm::SmallVector<llvm::SmallVector<Frame>> CallSites;
446 
447   MemProfRecord() = default;
MemProfRecordMemProfRecord448   MemProfRecord(
449       const IndexedMemProfRecord &Record,
450       llvm::function_ref<const Frame(const FrameId Id)> IdToFrameCallback) {
451     for (const IndexedAllocationInfo &IndexedAI : Record.AllocSites) {
452       AllocSites.emplace_back(IndexedAI, IdToFrameCallback);
453     }
454     for (const ArrayRef<FrameId> Site : Record.CallSites) {
455       llvm::SmallVector<Frame> Frames;
456       for (const FrameId Id : Site) {
457         Frames.push_back(IdToFrameCallback(Id));
458       }
459       CallSites.push_back(Frames);
460     }
461   }
462 
463   // Prints out the contents of the memprof record in YAML.
printMemProfRecord464   void print(llvm::raw_ostream &OS) const {
465     if (!AllocSites.empty()) {
466       OS << "    AllocSites:\n";
467       for (const AllocationInfo &N : AllocSites)
468         N.printYAML(OS);
469     }
470 
471     if (!CallSites.empty()) {
472       OS << "    CallSites:\n";
473       for (const llvm::SmallVector<Frame> &Frames : CallSites) {
474         for (const Frame &F : Frames) {
475           OS << "    -\n";
476           F.printYAML(OS);
477         }
478       }
479     }
480   }
481 };
482 
483 // Reads a memprof schema from a buffer. All entries in the buffer are
484 // interpreted as uint64_t. The first entry in the buffer denotes the number of
485 // ids in the schema. Subsequent entries are integers which map to memprof::Meta
486 // enum class entries. After successfully reading the schema, the pointer is one
487 // byte past the schema contents.
488 Expected<MemProfSchema> readMemProfSchema(const unsigned char *&Buffer);
489 
490 // Trait for reading IndexedMemProfRecord data from the on-disk hash table.
491 class RecordLookupTrait {
492 public:
493   using data_type = const IndexedMemProfRecord &;
494   using internal_key_type = uint64_t;
495   using external_key_type = uint64_t;
496   using hash_value_type = uint64_t;
497   using offset_type = uint64_t;
498 
499   RecordLookupTrait() = delete;
RecordLookupTrait(IndexedVersion V,const MemProfSchema & S)500   RecordLookupTrait(IndexedVersion V, const MemProfSchema &S)
501       : Version(V), Schema(S) {}
502 
EqualKey(uint64_t A,uint64_t B)503   static bool EqualKey(uint64_t A, uint64_t B) { return A == B; }
GetInternalKey(uint64_t K)504   static uint64_t GetInternalKey(uint64_t K) { return K; }
GetExternalKey(uint64_t K)505   static uint64_t GetExternalKey(uint64_t K) { return K; }
506 
ComputeHash(uint64_t K)507   hash_value_type ComputeHash(uint64_t K) { return K; }
508 
509   static std::pair<offset_type, offset_type>
ReadKeyDataLength(const unsigned char * & D)510   ReadKeyDataLength(const unsigned char *&D) {
511     using namespace support;
512 
513     offset_type KeyLen =
514         endian::readNext<offset_type, llvm::endianness::little>(D);
515     offset_type DataLen =
516         endian::readNext<offset_type, llvm::endianness::little>(D);
517     return std::make_pair(KeyLen, DataLen);
518   }
519 
ReadKey(const unsigned char * D,offset_type)520   uint64_t ReadKey(const unsigned char *D, offset_type /*Unused*/) {
521     using namespace support;
522     return endian::readNext<external_key_type, llvm::endianness::little>(D);
523   }
524 
ReadData(uint64_t K,const unsigned char * D,offset_type)525   data_type ReadData(uint64_t K, const unsigned char *D,
526                      offset_type /*Unused*/) {
527     Record = IndexedMemProfRecord::deserialize(Schema, D, Version);
528     return Record;
529   }
530 
531 private:
532   // Holds the MemProf version.
533   IndexedVersion Version;
534   // Holds the memprof schema used to deserialize records.
535   MemProfSchema Schema;
536   // Holds the records from one function deserialized from the indexed format.
537   IndexedMemProfRecord Record;
538 };
539 
540 // Trait for writing IndexedMemProfRecord data to the on-disk hash table.
541 class RecordWriterTrait {
542 public:
543   using key_type = uint64_t;
544   using key_type_ref = uint64_t;
545 
546   using data_type = IndexedMemProfRecord;
547   using data_type_ref = IndexedMemProfRecord &;
548 
549   using hash_value_type = uint64_t;
550   using offset_type = uint64_t;
551 
552 private:
553   // Pointer to the memprof schema to use for the generator.
554   const MemProfSchema *Schema;
555   // The MemProf version to use for the serialization.
556   IndexedVersion Version;
557 
558 public:
559   // We do not support the default constructor, which does not set Version.
560   RecordWriterTrait() = delete;
RecordWriterTrait(const MemProfSchema * Schema,IndexedVersion V)561   RecordWriterTrait(const MemProfSchema *Schema, IndexedVersion V)
562       : Schema(Schema), Version(V) {}
563 
ComputeHash(key_type_ref K)564   static hash_value_type ComputeHash(key_type_ref K) { return K; }
565 
566   std::pair<offset_type, offset_type>
EmitKeyDataLength(raw_ostream & Out,key_type_ref K,data_type_ref V)567   EmitKeyDataLength(raw_ostream &Out, key_type_ref K, data_type_ref V) {
568     using namespace support;
569 
570     endian::Writer LE(Out, llvm::endianness::little);
571     offset_type N = sizeof(K);
572     LE.write<offset_type>(N);
573     offset_type M = V.serializedSize(*Schema, Version);
574     LE.write<offset_type>(M);
575     return std::make_pair(N, M);
576   }
577 
EmitKey(raw_ostream & Out,key_type_ref K,offset_type)578   void EmitKey(raw_ostream &Out, key_type_ref K, offset_type /*Unused*/) {
579     using namespace support;
580     endian::Writer LE(Out, llvm::endianness::little);
581     LE.write<uint64_t>(K);
582   }
583 
EmitData(raw_ostream & Out,key_type_ref,data_type_ref V,offset_type)584   void EmitData(raw_ostream &Out, key_type_ref /*Unused*/, data_type_ref V,
585                 offset_type /*Unused*/) {
586     assert(Schema != nullptr && "MemProf schema is not initialized!");
587     V.serialize(*Schema, Out, Version);
588     // Clear the IndexedMemProfRecord which results in clearing/freeing its
589     // vectors of allocs and callsites. This is owned by the associated on-disk
590     // hash table, but unused after this point. See also the comment added to
591     // the client which constructs the on-disk hash table for this trait.
592     V.clear();
593   }
594 };
595 
596 // Trait for writing frame mappings to the on-disk hash table.
597 class FrameWriterTrait {
598 public:
599   using key_type = FrameId;
600   using key_type_ref = FrameId;
601 
602   using data_type = Frame;
603   using data_type_ref = Frame &;
604 
605   using hash_value_type = FrameId;
606   using offset_type = uint64_t;
607 
ComputeHash(key_type_ref K)608   static hash_value_type ComputeHash(key_type_ref K) { return K; }
609 
610   static std::pair<offset_type, offset_type>
EmitKeyDataLength(raw_ostream & Out,key_type_ref K,data_type_ref V)611   EmitKeyDataLength(raw_ostream &Out, key_type_ref K, data_type_ref V) {
612     using namespace support;
613     endian::Writer LE(Out, llvm::endianness::little);
614     offset_type N = sizeof(K);
615     LE.write<offset_type>(N);
616     offset_type M = V.serializedSize();
617     LE.write<offset_type>(M);
618     return std::make_pair(N, M);
619   }
620 
EmitKey(raw_ostream & Out,key_type_ref K,offset_type)621   void EmitKey(raw_ostream &Out, key_type_ref K, offset_type /*Unused*/) {
622     using namespace support;
623     endian::Writer LE(Out, llvm::endianness::little);
624     LE.write<key_type>(K);
625   }
626 
EmitData(raw_ostream & Out,key_type_ref,data_type_ref V,offset_type)627   void EmitData(raw_ostream &Out, key_type_ref /*Unused*/, data_type_ref V,
628                 offset_type /*Unused*/) {
629     V.serialize(Out);
630   }
631 };
632 
633 // Trait for reading frame mappings from the on-disk hash table.
634 class FrameLookupTrait {
635 public:
636   using data_type = const Frame;
637   using internal_key_type = FrameId;
638   using external_key_type = FrameId;
639   using hash_value_type = FrameId;
640   using offset_type = uint64_t;
641 
EqualKey(internal_key_type A,internal_key_type B)642   static bool EqualKey(internal_key_type A, internal_key_type B) {
643     return A == B;
644   }
GetInternalKey(internal_key_type K)645   static uint64_t GetInternalKey(internal_key_type K) { return K; }
GetExternalKey(external_key_type K)646   static uint64_t GetExternalKey(external_key_type K) { return K; }
647 
ComputeHash(internal_key_type K)648   hash_value_type ComputeHash(internal_key_type K) { return K; }
649 
650   static std::pair<offset_type, offset_type>
ReadKeyDataLength(const unsigned char * & D)651   ReadKeyDataLength(const unsigned char *&D) {
652     using namespace support;
653 
654     offset_type KeyLen =
655         endian::readNext<offset_type, llvm::endianness::little>(D);
656     offset_type DataLen =
657         endian::readNext<offset_type, llvm::endianness::little>(D);
658     return std::make_pair(KeyLen, DataLen);
659   }
660 
ReadKey(const unsigned char * D,offset_type)661   uint64_t ReadKey(const unsigned char *D, offset_type /*Unused*/) {
662     using namespace support;
663     return endian::readNext<external_key_type, llvm::endianness::little>(D);
664   }
665 
ReadData(uint64_t K,const unsigned char * D,offset_type)666   data_type ReadData(uint64_t K, const unsigned char *D,
667                      offset_type /*Unused*/) {
668     return Frame::deserialize(D);
669   }
670 };
671 
672 // Trait for writing call stacks to the on-disk hash table.
673 class CallStackWriterTrait {
674 public:
675   using key_type = CallStackId;
676   using key_type_ref = CallStackId;
677 
678   using data_type = llvm::SmallVector<FrameId>;
679   using data_type_ref = llvm::SmallVector<FrameId> &;
680 
681   using hash_value_type = CallStackId;
682   using offset_type = uint64_t;
683 
ComputeHash(key_type_ref K)684   static hash_value_type ComputeHash(key_type_ref K) { return K; }
685 
686   static std::pair<offset_type, offset_type>
EmitKeyDataLength(raw_ostream & Out,key_type_ref K,data_type_ref V)687   EmitKeyDataLength(raw_ostream &Out, key_type_ref K, data_type_ref V) {
688     using namespace support;
689     endian::Writer LE(Out, llvm::endianness::little);
690     // We do not explicitly emit the key length because it is a constant.
691     offset_type N = sizeof(K);
692     offset_type M = sizeof(FrameId) * V.size();
693     LE.write<offset_type>(M);
694     return std::make_pair(N, M);
695   }
696 
EmitKey(raw_ostream & Out,key_type_ref K,offset_type)697   void EmitKey(raw_ostream &Out, key_type_ref K, offset_type /*Unused*/) {
698     using namespace support;
699     endian::Writer LE(Out, llvm::endianness::little);
700     LE.write<key_type>(K);
701   }
702 
EmitData(raw_ostream & Out,key_type_ref,data_type_ref V,offset_type)703   void EmitData(raw_ostream &Out, key_type_ref /*Unused*/, data_type_ref V,
704                 offset_type /*Unused*/) {
705     using namespace support;
706     endian::Writer LE(Out, llvm::endianness::little);
707     // Emit the frames.  We do not explicitly emit the length of the vector
708     // because it can be inferred from the data length.
709     for (FrameId F : V)
710       LE.write<FrameId>(F);
711   }
712 };
713 
714 // Trait for reading call stack mappings from the on-disk hash table.
715 class CallStackLookupTrait {
716 public:
717   using data_type = const llvm::SmallVector<FrameId>;
718   using internal_key_type = CallStackId;
719   using external_key_type = CallStackId;
720   using hash_value_type = CallStackId;
721   using offset_type = uint64_t;
722 
EqualKey(internal_key_type A,internal_key_type B)723   static bool EqualKey(internal_key_type A, internal_key_type B) {
724     return A == B;
725   }
GetInternalKey(internal_key_type K)726   static uint64_t GetInternalKey(internal_key_type K) { return K; }
GetExternalKey(external_key_type K)727   static uint64_t GetExternalKey(external_key_type K) { return K; }
728 
ComputeHash(internal_key_type K)729   hash_value_type ComputeHash(internal_key_type K) { return K; }
730 
731   static std::pair<offset_type, offset_type>
ReadKeyDataLength(const unsigned char * & D)732   ReadKeyDataLength(const unsigned char *&D) {
733     using namespace support;
734 
735     // We do not explicitly read the key length because it is a constant.
736     offset_type KeyLen = sizeof(external_key_type);
737     offset_type DataLen =
738         endian::readNext<offset_type, llvm::endianness::little>(D);
739     return std::make_pair(KeyLen, DataLen);
740   }
741 
ReadKey(const unsigned char * D,offset_type)742   uint64_t ReadKey(const unsigned char *D, offset_type /*Unused*/) {
743     using namespace support;
744     return endian::readNext<external_key_type, llvm::endianness::little>(D);
745   }
746 
ReadData(uint64_t K,const unsigned char * D,offset_type Length)747   data_type ReadData(uint64_t K, const unsigned char *D, offset_type Length) {
748     using namespace support;
749     llvm::SmallVector<FrameId> CS;
750     // Derive the number of frames from the data length.
751     uint64_t NumFrames = Length / sizeof(FrameId);
752     assert(Length % sizeof(FrameId) == 0);
753     CS.reserve(NumFrames);
754     for (size_t I = 0; I != NumFrames; ++I) {
755       FrameId F = endian::readNext<FrameId, llvm::endianness::little>(D);
756       CS.push_back(F);
757     }
758     return CS;
759   }
760 };
761 
762 // Compute a CallStackId for a given call stack.
763 CallStackId hashCallStack(ArrayRef<FrameId> CS);
764 
765 namespace detail {
766 // "Dereference" the iterator from DenseMap or OnDiskChainedHashTable.  We have
767 // to do so in one of two different ways depending on the type of the hash
768 // table.
769 template <typename value_type, typename IterTy>
DerefIterator(IterTy Iter)770 value_type DerefIterator(IterTy Iter) {
771   using deref_type = llvm::remove_cvref_t<decltype(*Iter)>;
772   if constexpr (std::is_same_v<deref_type, value_type>)
773     return *Iter;
774   else
775     return Iter->second;
776 }
777 } // namespace detail
778 
779 // A function object that returns a frame for a given FrameId.
780 template <typename MapTy> struct FrameIdConverter {
781   std::optional<FrameId> LastUnmappedId;
782   MapTy &Map;
783 
784   FrameIdConverter() = delete;
FrameIdConverterFrameIdConverter785   FrameIdConverter(MapTy &Map) : Map(Map) {}
786 
operatorFrameIdConverter787   Frame operator()(FrameId Id) {
788     auto Iter = Map.find(Id);
789     if (Iter == Map.end()) {
790       LastUnmappedId = Id;
791       return Frame(0, 0, 0, false);
792     }
793     return detail::DerefIterator<Frame>(Iter);
794   }
795 };
796 
797 // A function object that returns a call stack for a given CallStackId.
798 template <typename MapTy> struct CallStackIdConverter {
799   std::optional<CallStackId> LastUnmappedId;
800   MapTy &Map;
801   std::function<Frame(FrameId)> FrameIdToFrame;
802 
803   CallStackIdConverter() = delete;
CallStackIdConverterCallStackIdConverter804   CallStackIdConverter(MapTy &Map, std::function<Frame(FrameId)> FrameIdToFrame)
805       : Map(Map), FrameIdToFrame(FrameIdToFrame) {}
806 
operatorCallStackIdConverter807   llvm::SmallVector<Frame> operator()(CallStackId CSId) {
808     llvm::SmallVector<Frame> Frames;
809     auto CSIter = Map.find(CSId);
810     if (CSIter == Map.end()) {
811       LastUnmappedId = CSId;
812     } else {
813       llvm::SmallVector<FrameId> CS =
814           detail::DerefIterator<llvm::SmallVector<FrameId>>(CSIter);
815       Frames.reserve(CS.size());
816       for (FrameId Id : CS)
817         Frames.push_back(FrameIdToFrame(Id));
818     }
819     return Frames;
820   }
821 };
822 
823 // Verify that each CallStackId is computed with hashCallStack.  This function
824 // is intended to help transition from CallStack to CSId in
825 // IndexedAllocationInfo.
826 void verifyIndexedMemProfRecord(const IndexedMemProfRecord &Record);
827 
828 // Verify that each CallStackId is computed with hashCallStack.  This function
829 // is intended to help transition from CallStack to CSId in
830 // IndexedAllocationInfo.
831 void verifyFunctionProfileData(
832     const llvm::MapVector<GlobalValue::GUID, IndexedMemProfRecord>
833         &FunctionProfileData);
834 } // namespace memprof
835 } // namespace llvm
836 
837 #endif // LLVM_PROFILEDATA_MEMPROF_H_
838