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