| //===- IndexedMemProfData.h - MemProf format support ------------*- C++ -*-===// | |
| // | |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | |
| // See https://llvm.org/LICENSE.txt for license information. | |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | |
| // | |
| //===----------------------------------------------------------------------===// | |
| // | |
| // MemProf data is serialized in writeMemProf provided in this file. | |
| // | |
| //===----------------------------------------------------------------------===// | |
| #include "llvm/ProfileData/DataAccessProf.h" | |
| #include "llvm/ProfileData/InstrProf.h" | |
| #include "llvm/ProfileData/InstrProfReader.h" | |
| #include "llvm/ProfileData/MemProf.h" | |
| #include "llvm/ProfileData/MemProfRadixTree.h" | |
| #include "llvm/ProfileData/MemProfSummary.h" | |
| #include "llvm/Support/FormatVariadic.h" | |
| #include "llvm/Support/OnDiskHashTable.h" | |
| namespace llvm { | |
| // Serialize Schema. | |
| static void writeMemProfSchema(ProfOStream &OS, | |
| const memprof::MemProfSchema &Schema) { | |
| OS.write(static_cast<uint64_t>(Schema.size())); | |
| for (const auto Id : Schema) | |
| OS.write(static_cast<uint64_t>(Id)); | |
| } | |
| // Serialize MemProfRecordData. Return RecordTableOffset. | |
| static uint64_t writeMemProfRecords( | |
| ProfOStream &OS, | |
| llvm::MapVector<GlobalValue::GUID, memprof::IndexedMemProfRecord> | |
| &MemProfRecordData, | |
| memprof::MemProfSchema *Schema, memprof::IndexedVersion Version, | |
| llvm::DenseMap<memprof::CallStackId, memprof::LinearCallStackId> | |
| *MemProfCallStackIndexes = nullptr) { | |
| memprof::RecordWriterTrait RecordWriter(Schema, Version, | |
| MemProfCallStackIndexes); | |
| OnDiskChainedHashTableGenerator<memprof::RecordWriterTrait> | |
| RecordTableGenerator; | |
| for (auto &[GUID, Record] : MemProfRecordData) { | |
| // Insert the key (func hash) and value (memprof record). | |
| RecordTableGenerator.insert(GUID, Record, RecordWriter); | |
| } | |
| // Release the memory of this MapVector as it is no longer needed. | |
| MemProfRecordData.clear(); | |
| // The call to Emit invokes RecordWriterTrait::EmitData which destructs | |
| // the memprof record copies owned by the RecordTableGenerator. This works | |
| // because the RecordTableGenerator is not used after this point. | |
| return RecordTableGenerator.Emit(OS.OS, RecordWriter); | |
| } | |
| // Serialize MemProfFrameData. Return the mapping from FrameIds to their | |
| // indexes within the frame array. | |
| static llvm::DenseMap<memprof::FrameId, memprof::LinearFrameId> | |
| writeMemProfFrameArray( | |
| ProfOStream &OS, | |
| llvm::MapVector<memprof::FrameId, memprof::Frame> &MemProfFrameData, | |
| llvm::DenseMap<memprof::FrameId, memprof::FrameStat> &FrameHistogram) { | |
| // Mappings from FrameIds to array indexes. | |
| llvm::DenseMap<memprof::FrameId, memprof::LinearFrameId> MemProfFrameIndexes; | |
| // Compute the order in which we serialize Frames. The order does not matter | |
| // in terms of correctness, but we still compute it for deserialization | |
| // performance. Specifically, if we serialize frequently used Frames one | |
| // after another, we have better cache utilization. For two Frames that | |
| // appear equally frequently, we break a tie by serializing the one that tends | |
| // to appear earlier in call stacks. We implement the tie-breaking mechanism | |
| // by computing the sum of indexes within call stacks for each Frame. If we | |
| // still have a tie, then we just resort to compare two FrameIds, which is | |
| // just for stability of output. | |
| std::vector<std::pair<memprof::FrameId, const memprof::Frame *>> FrameIdOrder; | |
| FrameIdOrder.reserve(MemProfFrameData.size()); | |
| for (const auto &[Id, Frame] : MemProfFrameData) | |
| FrameIdOrder.emplace_back(Id, &Frame); | |
| assert(MemProfFrameData.size() == FrameIdOrder.size()); | |
| llvm::sort(FrameIdOrder, | |
| [&](const std::pair<memprof::FrameId, const memprof::Frame *> &L, | |
| const std::pair<memprof::FrameId, const memprof::Frame *> &R) { | |
| const auto &SL = FrameHistogram[L.first]; | |
| const auto &SR = FrameHistogram[R.first]; | |
| // Popular FrameIds should come first. | |
| if (SL.Count != SR.Count) | |
| return SL.Count > SR.Count; | |
| // If they are equally popular, then the one that tends to appear | |
| // earlier in call stacks should come first. | |
| if (SL.PositionSum != SR.PositionSum) | |
| return SL.PositionSum < SR.PositionSum; | |
| // Compare their FrameIds for sort stability. | |
| return L.first < R.first; | |
| }); | |
| // Serialize all frames while creating mappings from linear IDs to FrameIds. | |
| uint64_t Index = 0; | |
| MemProfFrameIndexes.reserve(FrameIdOrder.size()); | |
| for (const auto &[Id, F] : FrameIdOrder) { | |
| F->serialize(OS.OS); | |
| MemProfFrameIndexes.insert({Id, Index}); | |
| ++Index; | |
| } | |
| assert(MemProfFrameData.size() == Index); | |
| assert(MemProfFrameData.size() == MemProfFrameIndexes.size()); | |
| // Release the memory of this MapVector as it is no longer needed. | |
| MemProfFrameData.clear(); | |
| return MemProfFrameIndexes; | |
| } | |
| static llvm::DenseMap<memprof::CallStackId, memprof::LinearCallStackId> | |
| writeMemProfCallStackArray( | |
| ProfOStream &OS, | |
| llvm::MapVector<memprof::CallStackId, llvm::SmallVector<memprof::FrameId>> | |
| &MemProfCallStackData, | |
| llvm::DenseMap<memprof::FrameId, memprof::LinearFrameId> | |
| &MemProfFrameIndexes, | |
| llvm::DenseMap<memprof::FrameId, memprof::FrameStat> &FrameHistogram, | |
| unsigned &NumElements) { | |
| llvm::DenseMap<memprof::CallStackId, memprof::LinearCallStackId> | |
| MemProfCallStackIndexes; | |
| memprof::CallStackRadixTreeBuilder<memprof::FrameId> Builder; | |
| Builder.build(std::move(MemProfCallStackData), &MemProfFrameIndexes, | |
| FrameHistogram); | |
| for (auto I : Builder.getRadixArray()) | |
| OS.write32(I); | |
| NumElements = Builder.getRadixArray().size(); | |
| MemProfCallStackIndexes = Builder.takeCallStackPos(); | |
| // Release the memory of this vector as it is no longer needed. | |
| MemProfCallStackData.clear(); | |
| return MemProfCallStackIndexes; | |
| } | |
| static Error writeMemProfRadixTreeBased( | |
| ProfOStream &OS, memprof::IndexedMemProfData &MemProfData, | |
| memprof::IndexedVersion Version, bool MemProfFullSchema, | |
| std::unique_ptr<memprof::DataAccessProfData> DataAccessProfileData = | |
| nullptr, | |
| std::unique_ptr<memprof::MemProfSummary> MemProfSum = nullptr) { | |
| assert((Version == memprof::Version3 || Version == memprof::Version4) && | |
| "Unsupported version for radix tree format"); | |
| OS.write(Version); // Write the specific version (V3 or V4) | |
| uint64_t HeaderUpdatePos = OS.tell(); | |
| OS.write(0ULL); // Reserve space for the memprof call stack payload offset. | |
| OS.write(0ULL); // Reserve space for the memprof record payload offset. | |
| OS.write(0ULL); // Reserve space for the memprof record table offset. | |
| if (Version >= memprof::Version4) { | |
| OS.write(0ULL); // Reserve space for the data access profile offset. | |
| MemProfSum->write(OS); | |
| } | |
| auto Schema = memprof::getHotColdSchema(); | |
| if (MemProfFullSchema) | |
| Schema = memprof::getFullSchema(); | |
| writeMemProfSchema(OS, Schema); | |
| llvm::DenseMap<memprof::FrameId, memprof::FrameStat> FrameHistogram = | |
| memprof::computeFrameHistogram(MemProfData.CallStacks); | |
| assert(MemProfData.Frames.size() == FrameHistogram.size()); | |
| llvm::DenseMap<memprof::FrameId, memprof::LinearFrameId> MemProfFrameIndexes = | |
| writeMemProfFrameArray(OS, MemProfData.Frames, FrameHistogram); | |
| uint64_t CallStackPayloadOffset = OS.tell(); | |
| // The number of elements in the call stack array. | |
| unsigned NumElements = 0; | |
| llvm::DenseMap<memprof::CallStackId, memprof::LinearCallStackId> | |
| MemProfCallStackIndexes = | |
| writeMemProfCallStackArray(OS, MemProfData.CallStacks, | |
| MemProfFrameIndexes, FrameHistogram, | |
| NumElements); | |
| uint64_t RecordPayloadOffset = OS.tell(); | |
| uint64_t RecordTableOffset = writeMemProfRecords( | |
| OS, MemProfData.Records, &Schema, Version, &MemProfCallStackIndexes); | |
| uint64_t DataAccessProfOffset = 0; | |
| if (DataAccessProfileData != nullptr) { | |
| assert(Version >= memprof::Version4 && | |
| "Data access profiles are added starting from v4"); | |
| DataAccessProfOffset = OS.tell(); | |
| if (Error E = DataAccessProfileData->serialize(OS)) | |
| return E; | |
| } | |
| // Verify that the computation for the number of elements in the call stack | |
| // array works. | |
| assert(CallStackPayloadOffset + | |
| NumElements * sizeof(memprof::LinearFrameId) == | |
| RecordPayloadOffset); | |
| SmallVector<uint64_t, 4> Header = { | |
| CallStackPayloadOffset, | |
| RecordPayloadOffset, | |
| RecordTableOffset, | |
| }; | |
| if (Version >= memprof::Version4) | |
| Header.push_back(DataAccessProfOffset); | |
| OS.patch({{HeaderUpdatePos, Header}}); | |
| return Error::success(); | |
| } | |
| // Write out MemProf Version3 | |
| static Error writeMemProfV3(ProfOStream &OS, | |
| memprof::IndexedMemProfData &MemProfData, | |
| bool MemProfFullSchema) { | |
| return writeMemProfRadixTreeBased(OS, MemProfData, memprof::Version3, | |
| MemProfFullSchema); | |
| } | |
| // Write out MemProf Version4 | |
| static Error writeMemProfV4( | |
| ProfOStream &OS, memprof::IndexedMemProfData &MemProfData, | |
| bool MemProfFullSchema, | |
| std::unique_ptr<memprof::DataAccessProfData> DataAccessProfileData, | |
| std::unique_ptr<memprof::MemProfSummary> MemProfSum) { | |
| return writeMemProfRadixTreeBased( | |
| OS, MemProfData, memprof::Version4, MemProfFullSchema, | |
| std::move(DataAccessProfileData), std::move(MemProfSum)); | |
| } | |
| // Write out the MemProf data in a requested version. | |
| Error writeMemProf( | |
| ProfOStream &OS, memprof::IndexedMemProfData &MemProfData, | |
| memprof::IndexedVersion MemProfVersionRequested, bool MemProfFullSchema, | |
| std::unique_ptr<memprof::DataAccessProfData> DataAccessProfileData, | |
| std::unique_ptr<memprof::MemProfSummary> MemProfSum) { | |
| switch (MemProfVersionRequested) { | |
| case memprof::Version3: | |
| return writeMemProfV3(OS, MemProfData, MemProfFullSchema); | |
| case memprof::Version4: | |
| return writeMemProfV4(OS, MemProfData, MemProfFullSchema, | |
| std::move(DataAccessProfileData), | |
| std::move(MemProfSum)); | |
| } | |
| return make_error<InstrProfError>( | |
| instrprof_error::unsupported_version, | |
| formatv("MemProf version {} not supported; " | |
| "requires version between {} and {}, inclusive", | |
| MemProfVersionRequested, memprof::MinimumSupportedVersion, | |
| memprof::MaximumSupportedVersion)); | |
| } | |
| Error IndexedMemProfReader::deserializeRadixTreeBased( | |
| const unsigned char *Start, const unsigned char *Ptr, | |
| memprof::IndexedVersion Version) { | |
| assert((Version == memprof::Version3 || Version == memprof::Version4) && | |
| "Unsupported version for radix tree format"); | |
| // The offset in the stream right before invoking | |
| // CallStackTableGenerator.Emit. | |
| const uint64_t CallStackPayloadOffset = | |
| support::endian::readNext<uint64_t, llvm::endianness::little>(Ptr); | |
| // The offset in the stream right before invoking RecordTableGenerator.Emit. | |
| const uint64_t RecordPayloadOffset = | |
| support::endian::readNext<uint64_t, llvm::endianness::little>(Ptr); | |
| // The value returned from RecordTableGenerator.Emit. | |
| const uint64_t RecordTableOffset = | |
| support::endian::readNext<uint64_t, llvm::endianness::little>(Ptr); | |
| uint64_t DataAccessProfOffset = 0; | |
| if (Version >= memprof::Version4) { | |
| DataAccessProfOffset = | |
| support::endian::readNext<uint64_t, llvm::endianness::little>(Ptr); | |
| MemProfSum = memprof::MemProfSummary::deserialize(Ptr); | |
| if (DataAccessProfOffset > RecordTableOffset) { | |
| DataAccessProfileData = std::make_unique<memprof::DataAccessProfData>(); | |
| const unsigned char *DAPPtr = Start + DataAccessProfOffset; | |
| if (Error E = DataAccessProfileData->deserialize(DAPPtr)) | |
| return E; | |
| MemProfSum->buildDataAccessSummary(*DataAccessProfileData); | |
| } | |
| } | |
| assert((!DataAccessProfOffset || DataAccessProfOffset > RecordTableOffset) && | |
| "Data access profile is either empty or after the record table"); | |
| // Read the schema. | |
| auto SchemaOr = memprof::readMemProfSchema(Ptr); | |
| if (!SchemaOr) | |
| return SchemaOr.takeError(); | |
| Schema = SchemaOr.get(); | |
| FrameBase = Ptr; | |
| CallStackBase = Start + CallStackPayloadOffset; | |
| // Compute the number of elements in the radix tree array. Since we use this | |
| // to reserve enough bits in a BitVector, it's totally OK if we overestimate | |
| // this number a little bit because of padding just before the next section. | |
| RadixTreeSize = (RecordPayloadOffset - CallStackPayloadOffset) / | |
| sizeof(memprof::LinearFrameId); | |
| // Now initialize the table reader with a pointer into data buffer. | |
| MemProfRecordTable.reset(MemProfRecordHashTable::Create( | |
| /*Buckets=*/Start + RecordTableOffset, | |
| /*Payload=*/Start + RecordPayloadOffset, | |
| /*Base=*/Start, memprof::RecordLookupTrait(Version, Schema))); | |
| return Error::success(); | |
| } | |
| Error IndexedMemProfReader::deserialize(const unsigned char *Start, | |
| uint64_t MemProfOffset) { | |
| const unsigned char *Ptr = Start + MemProfOffset; | |
| // Read the MemProf version number. | |
| const uint64_t FirstWord = | |
| support::endian::readNext<uint64_t, llvm::endianness::little>(Ptr); | |
| // Check if the version is supported | |
| if (FirstWord >= memprof::MinimumSupportedVersion && | |
| FirstWord <= memprof::MaximumSupportedVersion) { | |
| // Everything is good. We can proceed to deserialize the rest. | |
| Version = static_cast<memprof::IndexedVersion>(FirstWord); | |
| } else { | |
| return make_error<InstrProfError>( | |
| instrprof_error::unsupported_version, | |
| formatv("MemProf version {} not supported; " | |
| "requires version between {} and {}, inclusive", | |
| FirstWord, memprof::MinimumSupportedVersion, | |
| memprof::MaximumSupportedVersion)); | |
| } | |
| switch (Version) { | |
| case memprof::Version3: | |
| case memprof::Version4: | |
| // V3 and V4 share the same high-level structure (radix tree, linear IDs). | |
| if (Error E = deserializeRadixTreeBased(Start, Ptr, Version)) | |
| return E; | |
| break; | |
| } | |
| return Error::success(); | |
| } | |
| } // namespace llvm |