| //===- SampleProfWriter.cpp - Write LLVM sample profile data --------------===// | |
| // | |
| // 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 | |
| // | |
| //===----------------------------------------------------------------------===// | |
| // | |
| // This file implements the class that writes LLVM sample profiles. It | |
| // supports two file formats: text and binary. The textual representation | |
| // is useful for debugging and testing purposes. The binary representation | |
| // is more compact, resulting in smaller file sizes. However, they can | |
| // both be used interchangeably. | |
| // | |
| // See lib/ProfileData/SampleProfReader.cpp for documentation on each of the | |
| // supported formats. | |
| // | |
| //===----------------------------------------------------------------------===// | |
| #include "llvm/ProfileData/SampleProfWriter.h" | |
| #include "llvm/ADT/Eytzinger.h" | |
| #include "llvm/ADT/StringRef.h" | |
| #include "llvm/ProfileData/ProfileCommon.h" | |
| #include "llvm/ProfileData/SampleProf.h" | |
| #include "llvm/Support/Compression.h" | |
| #include "llvm/Support/EndianStream.h" | |
| #include "llvm/Support/ErrorOr.h" | |
| #include "llvm/Support/FileSystem.h" | |
| #include "llvm/Support/LEB128.h" | |
| #include "llvm/Support/MD5.h" | |
| #include "llvm/Support/SaveAndRestore.h" | |
| #include "llvm/Support/raw_ostream.h" | |
| #include <array> | |
| #include <cmath> | |
| #include <cstdint> | |
| #include <memory> | |
| #include <system_error> | |
| #include <utility> | |
| #include <vector> | |
| #define DEBUG_TYPE "llvm-profdata" | |
| using namespace llvm; | |
| using namespace sampleprof; | |
| // To begin with, make this option off by default. | |
| static cl::opt<bool> ExtBinaryWriteVTableTypeProf( | |
| "extbinary-write-vtable-type-prof", cl::init(false), cl::Hidden, | |
| cl::desc("Write vtable type profile in ext-binary sample profile writer")); | |
| static cl::opt<uint64_t> RequestedVersion( | |
| "sample-profile-format-version", cl::init(DefaultVersion), cl::Hidden, | |
| cl::desc("Format version to write for extensible binary profiles")); | |
| static cl::opt<bool> | |
| ExtBinaryCompositeProf("extbinary-composite-prof", cl::init(false), | |
| cl::Hidden, | |
| cl::desc("Use the composite profile format")); | |
| namespace llvm { | |
| namespace support { | |
| namespace endian { | |
| namespace { | |
| // Adapter class to llvm::support::endian::Writer for pwrite(). | |
| struct SeekableWriter { | |
| raw_pwrite_stream &OS; | |
| endianness Endian; | |
| SeekableWriter(raw_pwrite_stream &OS, endianness Endian) | |
| : OS(OS), Endian(Endian) {} | |
| template <typename ValueType> void pwrite(ValueType Val, size_t Offset) { | |
| std::string StringBuf; | |
| raw_string_ostream SStream(StringBuf); | |
| Writer(SStream, Endian).write(Val); | |
| OS.pwrite(StringBuf.data(), StringBuf.size(), Offset); | |
| } | |
| }; | |
| } // namespace | |
| } // namespace endian | |
| } // namespace support | |
| } // namespace llvm | |
| DefaultFunctionPruningStrategy::DefaultFunctionPruningStrategy( | |
| SampleProfileMap &ProfileMap, size_t OutputSizeLimit) | |
| : FunctionPruningStrategy(ProfileMap, OutputSizeLimit) { | |
| sortFuncProfiles(ProfileMap, SortedFunctions); | |
| } | |
| void DefaultFunctionPruningStrategy::Erase(size_t CurrentOutputSize) { | |
| double D = (double)OutputSizeLimit / CurrentOutputSize; | |
| size_t NewSize = (size_t)round(ProfileMap.size() * D * D); | |
| size_t NumToRemove = ProfileMap.size() - NewSize; | |
| if (NumToRemove < 1) | |
| NumToRemove = 1; | |
| assert(NumToRemove <= SortedFunctions.size()); | |
| for (const NameFunctionSamples &E : | |
| llvm::drop_begin(SortedFunctions, SortedFunctions.size() - NumToRemove)) | |
| ProfileMap.erase(E.first); | |
| SortedFunctions.resize(SortedFunctions.size() - NumToRemove); | |
| } | |
| std::error_code SampleProfileWriter::writeWithSizeLimitInternal( | |
| SampleProfileMap &ProfileMap, size_t OutputSizeLimit, | |
| FunctionPruningStrategy *Strategy) { | |
| if (OutputSizeLimit == 0) | |
| return write(ProfileMap); | |
| size_t OriginalFunctionCount = ProfileMap.size(); | |
| std::unique_ptr<raw_ostream> OriginalOutputStream; | |
| OutputStream.swap(OriginalOutputStream); | |
| size_t IterationCount = 0; | |
| size_t TotalSize; | |
| SmallVector<char> StringBuffer; | |
| do { | |
| StringBuffer.clear(); | |
| OutputStream.reset(new raw_svector_ostream(StringBuffer)); | |
| if (std::error_code EC = write(ProfileMap)) | |
| return EC; | |
| TotalSize = StringBuffer.size(); | |
| // On Windows every "\n" is actually written as "\r\n" to disk but not to | |
| // memory buffer, this difference should be added when considering the total | |
| // output size. | |
| #ifdef _WIN32 | |
| if (Format == SPF_Text) | |
| TotalSize += LineCount; | |
| #endif | |
| if (TotalSize <= OutputSizeLimit) | |
| break; | |
| Strategy->Erase(TotalSize); | |
| IterationCount++; | |
| } while (ProfileMap.size() != 0); | |
| if (ProfileMap.size() == 0) | |
| return sampleprof_error::too_large; | |
| OutputStream.swap(OriginalOutputStream); | |
| OutputStream->write(StringBuffer.data(), StringBuffer.size()); | |
| LLVM_DEBUG(dbgs() << "Profile originally has " << OriginalFunctionCount | |
| << " functions, reduced to " << ProfileMap.size() << " in " | |
| << IterationCount << " iterations\n"); | |
| // Silence warning on Release build. | |
| (void)OriginalFunctionCount; | |
| (void)IterationCount; | |
| return sampleprof_error::success; | |
| } | |
| std::error_code | |
| SampleProfileWriter::writeFuncProfiles(const SampleProfileMap &ProfileMap) { | |
| std::vector<NameFunctionSamples> V; | |
| sortFuncProfiles(ProfileMap, V); | |
| for (const auto &I : V) { | |
| if (std::error_code EC = writeSample(*I.second)) | |
| return EC; | |
| } | |
| return sampleprof_error::success; | |
| } | |
| std::error_code SampleProfileWriter::write(const SampleProfileMap &ProfileMap) { | |
| if (std::error_code EC = writeHeader(ProfileMap)) | |
| return EC; | |
| if (std::error_code EC = writeFuncProfiles(ProfileMap)) | |
| return EC; | |
| return sampleprof_error::success; | |
| } | |
| /// Return the current position and prepare to use it as the start | |
| /// position of a section given the section type \p Type and its position | |
| /// \p LayoutIdx in SectionHdrLayout. | |
| uint64_t | |
| SampleProfileWriterExtBinaryBase::markSectionStart(SecType Type, | |
| uint32_t LayoutIdx) { | |
| uint64_t SectionStart = OutputStream->tell(); | |
| assert(LayoutIdx < SectionHdrLayout.size() && "LayoutIdx out of range"); | |
| const auto &Entry = SectionHdrLayout[LayoutIdx]; | |
| assert(Entry.Type == Type && "Unexpected section type"); | |
| // Use LocalBuf as a temporary output for writing data. | |
| if (hasSecFlag(Entry, SecCommonFlags::SecFlagCompress)) | |
| LocalBufStream.swap(OutputStream); | |
| return SectionStart; | |
| } | |
| std::error_code SampleProfileWriterExtBinaryBase::compressAndOutput() { | |
| if (!llvm::compression::zlib::isAvailable()) | |
| return sampleprof_error::zlib_unavailable; | |
| std::string &UncompressedStrings = | |
| static_cast<raw_string_ostream *>(LocalBufStream.get())->str(); | |
| if (UncompressedStrings.empty()) | |
| return sampleprof_error::success; | |
| auto &OS = *OutputStream; | |
| SmallVector<uint8_t, 128> CompressedStrings; | |
| compression::zlib::compress(arrayRefFromStringRef(UncompressedStrings), | |
| CompressedStrings, | |
| compression::zlib::BestSizeCompression); | |
| encodeULEB128(UncompressedStrings.size(), OS); | |
| encodeULEB128(CompressedStrings.size(), OS); | |
| OS << toStringRef(CompressedStrings); | |
| UncompressedStrings.clear(); | |
| return sampleprof_error::success; | |
| } | |
| /// Add a new section into section header table given the section type | |
| /// \p Type, its position \p LayoutIdx in SectionHdrLayout and the | |
| /// location \p SectionStart where the section should be written to. | |
| std::error_code SampleProfileWriterExtBinaryBase::addNewSection( | |
| SecType Type, uint32_t LayoutIdx, uint64_t SectionStart) { | |
| assert(LayoutIdx < SectionHdrLayout.size() && "LayoutIdx out of range"); | |
| const auto &Entry = SectionHdrLayout[LayoutIdx]; | |
| assert(Entry.Type == Type && "Unexpected section type"); | |
| if (hasSecFlag(Entry, SecCommonFlags::SecFlagCompress)) { | |
| LocalBufStream.swap(OutputStream); | |
| if (std::error_code EC = compressAndOutput()) | |
| return EC; | |
| } | |
| SecHdrTable.push_back({Type, Entry.Flags, SectionStart - FileStart, | |
| OutputStream->tell() - SectionStart, LayoutIdx}); | |
| return sampleprof_error::success; | |
| } | |
| std::error_code | |
| SampleProfileWriterExtBinaryBase::write(const SampleProfileMap &ProfileMap) { | |
| // When calling write on a different profile map, existing states should be | |
| // cleared. | |
| NameTable.clear(); | |
| CSNameTable.clear(); | |
| SecHdrTable.clear(); | |
| if (std::error_code EC = writeHeader(ProfileMap)) | |
| return EC; | |
| std::string LocalBuf; | |
| LocalBufStream = std::make_unique<raw_string_ostream>(LocalBuf); | |
| if (std::error_code EC = writeSections(ProfileMap)) | |
| return EC; | |
| if (std::error_code EC = writeSecHdrTable()) | |
| return EC; | |
| return sampleprof_error::success; | |
| } | |
| std::error_code SampleProfileWriterExtBinaryBase::writeContextIdx( | |
| const SampleContext &Context) { | |
| if (Context.hasContext()) | |
| return writeCSNameIdx(Context); | |
| else | |
| return SampleProfileWriterBinary::writeNameIdx(Context.getFunction()); | |
| } | |
| std::error_code | |
| SampleProfileWriterExtBinaryBase::writeCSNameIdx(const SampleContext &Context) { | |
| const auto &Ret = CSNameTable.find(Context); | |
| if (Ret == CSNameTable.end()) | |
| return sampleprof_error::truncated_name_table; | |
| encodeULEB128(Ret->second, *OutputStream); | |
| return sampleprof_error::success; | |
| } | |
| std::error_code | |
| SampleProfileWriterExtBinaryBase::writeSample(const FunctionSamples &S) { | |
| uint64_t Offset = OutputStream->tell(); | |
| auto &Context = S.getContext(); | |
| FuncOffsetTable[Context] = Offset - SecLBRProfileStart; | |
| if (!WriteCompositeProf) | |
| encodeULEB128(S.getHeadSamples(), *OutputStream); | |
| return writeBody(S, /*IsNested=*/false); | |
| } | |
| std::error_code | |
| SampleProfileWriterExtBinaryBase::writeFuncOffsetTable(SecType Type, | |
| bool IsNested) { | |
| if (UseMD5IndexedTables) { | |
| // Eytzinger layout requires MD5 representation and does not support | |
| // multi-context Context-Sensitive profiles. | |
| if (!UseMD5 || FunctionSamples::ProfileIsCS) | |
| return sampleprof_error::unsupported_writing_format; | |
| return writeEytzingerFuncOffsetTable(Type, IsNested); | |
| } | |
| return writeLegacyFuncOffsetTable(Type); | |
| } | |
| std::error_code | |
| SampleProfileWriterExtBinaryBase::writeEytzingerFuncOffsetTable(SecType Type, | |
| bool IsNested) { | |
| assert((NumNested + NumFlat > 0 || FuncOffsetTable.empty()) && | |
| "SecNameTable must be written before SecFuncOffsetTable to establish " | |
| "Eytzinger indices!"); | |
| size_t SpanSize = IsNested ? NumNested : NumFlat; | |
| size_t BaseIdx = IsNested ? 0 : NumNested; | |
| std::vector<support::ulittle32_t> FuncOffsets( | |
| SpanSize, support::ulittle32_t(UINT32_MAX)); | |
| // Populate the function offset array parallel to the Eytzinger span. | |
| for (const auto &[Context, RelativeOffset] : FuncOffsetTable) { | |
| if (RelativeOffset >= UINT32_MAX) | |
| return sampleprof_error::too_large; | |
| FunctionId FId = Context.getFunction(); | |
| auto It = NameTable.find(FId); | |
| if (It == NameTable.end()) | |
| continue; | |
| size_t GlobalIdx = It->second; | |
| if (GlobalIdx < BaseIdx || (GlobalIdx - BaseIdx) >= SpanSize) | |
| continue; | |
| size_t LocalIdx = GlobalIdx - BaseIdx; | |
| assert( | |
| FuncOffsets[LocalIdx] == UINT32_MAX && | |
| "Function offset slot already populated; duplicate GUID or collision!"); | |
| FuncOffsets[LocalIdx] = static_cast<uint32_t>(RelativeOffset); | |
| } | |
| assert(!llvm::is_contained(FuncOffsets, support::ulittle32_t(UINT32_MAX)) && | |
| "Unpopulated slot in Eytzinger function offset array!"); | |
| OutputStream->write(reinterpret_cast<const char *>(FuncOffsets.data()), | |
| SpanSize * sizeof(support::ulittle32_t)); | |
| // Type is SecFuncOffsetTable or SecCompositeFuncOffsetTable. | |
| addSectionFlag(Type, SecFuncOffsetFlags::SecFlagEytzinger); | |
| FuncOffsetTable.clear(); | |
| return sampleprof_error::success; | |
| } | |
| std::error_code | |
| SampleProfileWriterExtBinaryBase::writeLegacyFuncOffsetTable(SecType Type) { | |
| auto &OS = *OutputStream; | |
| // Write out the table size. | |
| encodeULEB128(FuncOffsetTable.size(), OS); | |
| // Write out FuncOffsetTable. | |
| auto WriteItem = [&](const SampleContext &Context, uint64_t Offset) { | |
| if (std::error_code EC = writeContextIdx(Context)) | |
| return EC; | |
| encodeULEB128(Offset, OS); | |
| return (std::error_code)sampleprof_error::success; | |
| }; | |
| if (FunctionSamples::ProfileIsCS) { | |
| // Sort the contexts before writing them out. This is to help fast load all | |
| // context profiles for a function as well as their callee contexts which | |
| // can help profile-guided importing for ThinLTO. | |
| std::map<SampleContext, uint64_t> OrderedFuncOffsetTable( | |
| FuncOffsetTable.begin(), FuncOffsetTable.end()); | |
| for (const auto &Entry : OrderedFuncOffsetTable) { | |
| if (std::error_code EC = WriteItem(Entry.first, Entry.second)) | |
| return EC; | |
| } | |
| addSectionFlag(Type, SecFuncOffsetFlags::SecFlagOrdered); | |
| } else { | |
| for (const auto &Entry : FuncOffsetTable) { | |
| if (std::error_code EC = WriteItem(Entry.first, Entry.second)) | |
| return EC; | |
| } | |
| } | |
| FuncOffsetTable.clear(); | |
| return sampleprof_error::success; | |
| } | |
| std::error_code SampleProfileWriterExtBinaryBase::writeFuncMetadata( | |
| const FunctionSamples &FunctionProfile) { | |
| auto &OS = *OutputStream; | |
| if (std::error_code EC = writeContextIdx(FunctionProfile.getContext())) | |
| return EC; | |
| if (FunctionSamples::ProfileIsProbeBased) | |
| encodeULEB128(FunctionProfile.getFunctionHash(), OS); | |
| if (FunctionSamples::ProfileIsCS || FunctionSamples::ProfileIsPreInlined) { | |
| encodeULEB128(FunctionProfile.getContext().getAllAttributes(), OS); | |
| } | |
| if (!FunctionSamples::ProfileIsCS) { | |
| // Recursively emit attributes for all callee samples. | |
| uint64_t NumCallsites = 0; | |
| for (const auto &J : FunctionProfile.getCallsiteSamples()) | |
| NumCallsites += J.second.size(); | |
| encodeULEB128(NumCallsites, OS); | |
| for (const auto &J : FunctionProfile.getCallsiteSamples()) { | |
| for (const auto &FS : J.second) { | |
| LineLocation Loc = J.first; | |
| encodeULEB128(Loc.LineOffset, OS); | |
| encodeULEB128(Loc.Discriminator, OS); | |
| if (std::error_code EC = writeFuncMetadata(FS.second)) | |
| return EC; | |
| } | |
| } | |
| } | |
| return sampleprof_error::success; | |
| } | |
| std::error_code SampleProfileWriterExtBinaryBase::writeFuncMetadata( | |
| const SampleProfileMap &Profiles) { | |
| if (!FunctionSamples::ProfileIsProbeBased && !FunctionSamples::ProfileIsCS && | |
| !FunctionSamples::ProfileIsPreInlined) | |
| return sampleprof_error::success; | |
| for (const auto &Entry : Profiles) { | |
| if (std::error_code EC = writeFuncMetadata(Entry.second)) | |
| return EC; | |
| } | |
| return sampleprof_error::success; | |
| } | |
| template <class KeyT, class ValT> | |
| static SmallVector<std::pair<KeyT, ValT> *, 0> | |
| stabilizeTable(MapVector<KeyT, ValT> &Table) { | |
| SmallVector<std::pair<KeyT, ValT> *, 0> Entries( | |
| llvm::make_pointer_range(Table)); | |
| llvm::sort(Entries, | |
| [](const auto *L, const auto *R) { return L->first < R->first; }); | |
| for (const auto &[I, Entry] : llvm::enumerate(Entries)) | |
| Entry->second = I; | |
| return Entries; | |
| } | |
| std::error_code SampleProfileWriterExtBinaryBase::writeNameTable() { | |
| if (!UseMD5) | |
| return SampleProfileWriterBinary::writeNameTable(); | |
| auto &OS = *OutputStream; | |
| // Write out the MD5 name table. We wrote unencoded MD5 so reader can | |
| // retrieve the name using the name index without having to read the | |
| // whole name table. | |
| encodeULEB128(NameTable.size(), OS); | |
| support::endian::Writer Writer(OS, llvm::endianness::little); | |
| for (const auto *Entry : stabilizeTable(NameTable)) | |
| Writer.write(Entry->first.getHashCode()); | |
| return sampleprof_error::success; | |
| } | |
| std::error_code SampleProfileWriterExtBinaryBase::writeNameTableSection( | |
| const SampleProfileMap &ProfileMap) { | |
| for (const auto &I : ProfileMap) { | |
| addContext(I.second.getContext()); | |
| addNames(I.second); | |
| } | |
| // If NameTable contains ".__uniq." suffix, set SecFlagUniqSuffix flag | |
| // so compiler won't strip the suffix during profile matching after | |
| // seeing the flag in the profile. | |
| // Original names are unavailable if using MD5, so this option has no use. | |
| if (!UseMD5) { | |
| for (const auto &I : NameTable) { | |
| if (I.first.stringRef().contains(FunctionSamples::UniqSuffix)) { | |
| addSectionFlag(SecNameTable, SecNameTableFlags::SecFlagUniqSuffix); | |
| break; | |
| } | |
| } | |
| } | |
| if (UseMD5 && UseMD5IndexedTables) { | |
| // Eytzinger name tables do not support CSSPGO profiles | |
| // (FunctionSamples::ProfileIsCS). | |
| if (FunctionSamples::ProfileIsCS) | |
| return sampleprof_error::unsupported_writing_format; | |
| if (auto EC = writeEytzingerNameTableSection(ProfileMap)) | |
| return EC; | |
| return sampleprof_error::success; | |
| } | |
| if (auto EC = writeNameTable()) | |
| return EC; | |
| return sampleprof_error::success; | |
| } | |
| namespace { | |
| // Helper class to construct and write the SecNameTable section in Eytzinger | |
| // layout for ExtBinary MD5 profiles. | |
| // | |
| // The on-disk layout of the Eytzinger name table section consists of symbol | |
| // counts followed by three contiguous Eytzinger hash arrays: | |
| // - ULEB128 count of Nested top-level profile symbol keys | |
| // - ULEB128 count of Flat top-level profile symbol keys | |
| // - ULEB128 count of Inlinee and auxiliary profile symbol keys | |
| // - Array of 64-bit little-endian MD5 hash keys for Nested profiles in | |
| // Eytzinger order | |
| // - Array of 64-bit little-endian MD5 hash keys for Flat profiles in Eytzinger | |
| // order | |
| // - Array of 64-bit little-endian MD5 hash keys for Inlinees in Eytzinger order | |
| class EytzingerNameTable { | |
| using TableT = llvm::EytzingerTable<support::ulittle64_t>; | |
| std::array<TableT, static_cast<size_t>(EytzingerSpan::NumSpans)> Spans; | |
| public: | |
| EytzingerNameTable(std::vector<support::ulittle64_t> NestedKeys, | |
| std::vector<support::ulittle64_t> FlatKeys, | |
| std::vector<support::ulittle64_t> InlineeKeys) | |
| : Spans{TableT::create(std::move(NestedKeys)), | |
| TableT::create(std::move(FlatKeys)), | |
| TableT::create(std::move(InlineeKeys))} {} | |
| // Find the global index of GUID across the three Eytzinger table spans. | |
| uint64_t findGlobalIdx(uint64_t GUID) const { | |
| uint64_t BaseIdx = 0; | |
| for (const auto &Table : Spans) { | |
| if (std::optional<size_t> LocalIdx = Table.findIndex(GUID)) | |
| return BaseIdx + *LocalIdx; | |
| BaseIdx += Table.size(); | |
| } | |
| llvm_unreachable("Symbol in NameTable missing from Eytzinger spans"); | |
| } | |
| void write(raw_ostream &OS) const { | |
| for (const auto &Table : Spans) | |
| encodeULEB128(uint64_t(Table.size()), OS); | |
| for (const auto &Table : Spans) | |
| OS.write(reinterpret_cast<const char *>(Table.data()), | |
| Table.size() * sizeof(support::ulittle64_t)); | |
| } | |
| size_t size(EytzingerSpan S) const { | |
| return Spans[static_cast<size_t>(S)].size(); | |
| } | |
| }; | |
| } // end anonymous namespace | |
| std::error_code | |
| SampleProfileWriterExtBinaryBase::writeEytzingerNameTableSection( | |
| const SampleProfileMap &ProfileMap) { | |
| DenseSet<uint64_t> TopLevelGUIDs; | |
| std::vector<support::ulittle64_t> NestedKeys, FlatKeys, InlineeKeys; | |
| // Collect top-level Nested and Flat keys directly from ProfileMap. | |
| for (const auto &I : ProfileMap) { | |
| const SampleContext &Ctx = I.second.getContext(); | |
| uint64_t GUID = Ctx.getFunction().getHashCode(); | |
| if (TopLevelGUIDs.insert(GUID).second) { | |
| // In single-table default layouts, unify all top-level symbols in the | |
| // Nested partition so they match the single unflagged function offset | |
| // table. | |
| if (SecLayout != CtxSplitLayout || I.second.hasCallsiteSamples()) | |
| NestedKeys.emplace_back(GUID); | |
| else | |
| FlatKeys.emplace_back(GUID); | |
| } | |
| } | |
| // Collect remaining non-top-level symbols (inlinees, targets, vtables) from | |
| // NameTable. | |
| for (const auto &Entry : NameTable) { | |
| uint64_t GUID = Entry.first.getHashCode(); | |
| if (!TopLevelGUIDs.contains(GUID)) | |
| InlineeKeys.emplace_back(GUID); | |
| } | |
| EytzingerNameTable Tables(std::move(NestedKeys), std::move(FlatKeys), | |
| std::move(InlineeKeys)); | |
| // Assign each symbol its corresponding index in the Eytzinger layout. | |
| for (auto &[FId, Idx] : NameTable) | |
| Idx = Tables.findGlobalIdx(FId.getHashCode()); | |
| Tables.write(*OutputStream); | |
| NumNested = Tables.size(EytzingerSpan::Nested); | |
| NumFlat = Tables.size(EytzingerSpan::Flat); | |
| return sampleprof_error::success; | |
| } | |
| std::error_code SampleProfileWriterExtBinaryBase::writeCSNameTableSection() { | |
| auto &OS = *OutputStream; | |
| encodeULEB128(CSNameTable.size(), OS); | |
| support::endian::Writer Writer(OS, llvm::endianness::little); | |
| for (const auto *Entry : stabilizeTable(CSNameTable)) { | |
| auto Frames = Entry->first.getContextFrames(); | |
| encodeULEB128(Frames.size(), OS); | |
| for (auto &Callsite : Frames) { | |
| if (std::error_code EC = writeNameIdx(Callsite.Func)) | |
| return EC; | |
| encodeULEB128(Callsite.Location.LineOffset, OS); | |
| encodeULEB128(Callsite.Location.Discriminator, OS); | |
| } | |
| } | |
| return sampleprof_error::success; | |
| } | |
| std::error_code | |
| SampleProfileWriterExtBinaryBase::writeProfileSymbolListSection() { | |
| if (UseMD5ProfSymList) | |
| return writeMD5ProfileSymbolListSection(); | |
| return writeStringBasedProfileSymbolListSection(); | |
| } | |
| std::error_code | |
| SampleProfileWriterExtBinaryBase::writeStringBasedProfileSymbolListSection() { | |
| assert((!ProfSymList || !ProfSymList->isMD5()) && | |
| "Writing string-based ProfileSymbolListSection from MD5 table " | |
| "not yet implemented"); | |
| if (ProfSymList && ProfSymList->size() > 0) | |
| if (std::error_code EC = ProfSymList->write(*OutputStream)) | |
| return EC; | |
| return sampleprof_error::success; | |
| } | |
| std::error_code | |
| SampleProfileWriterExtBinaryBase::writeMD5ProfileSymbolListSection() { | |
| if (!ProfSymList || ProfSymList->size() == 0) | |
| return sampleprof_error::success; | |
| auto &OS = *OutputStream; | |
| std::vector<uint64_t> Keys = ProfSymList->collectGUIDs(); | |
| auto Table = | |
| llvm::EytzingerTable<support::ulittle64_t>::create(std::move(Keys)); | |
| OS.write(reinterpret_cast<const char *>(Table.data()), | |
| Table.size() * sizeof(support::ulittle64_t)); | |
| return sampleprof_error::success; | |
| } | |
| unsigned SampleProfileWriterExtBinaryBase::findUnwrittenEntry(SecType Type) { | |
| auto WrittenIndices = | |
| llvm::map_range(SecHdrTable, &SecHdrTableEntry::LayoutIndex); | |
| for (auto [I, Entry] : llvm::enumerate(SectionHdrLayout)) | |
| if (Entry.Type == Type && !llvm::is_contained(WrittenIndices, I)) | |
| return I; | |
| llvm_unreachable("Matching section not found in SectionHdrLayout"); | |
| } | |
| std::error_code SampleProfileWriterExtBinaryBase::writeOneSection( | |
| SecType Type, const SampleProfileMap &ProfileMap) { | |
| unsigned LayoutIdx = findUnwrittenEntry(Type); | |
| SecHdrTableEntry &Entry = SectionHdrLayout[LayoutIdx]; | |
| // The setting of SecFlagCompress should happen before markSectionStart. | |
| if (Type == SecFuncMetadata && FunctionSamples::ProfileIsProbeBased) | |
| addSectionFlag(SecFuncMetadata, SecFuncMetadataFlags::SecFlagIsProbeBased); | |
| if (Type == SecFuncMetadata && | |
| (FunctionSamples::ProfileIsCS || FunctionSamples::ProfileIsPreInlined)) | |
| addSectionFlag(SecFuncMetadata, SecFuncMetadataFlags::SecFlagHasAttribute); | |
| if (Type == SecProfSummary && FunctionSamples::ProfileIsCS) | |
| addSectionFlag(SecProfSummary, SecProfSummaryFlags::SecFlagFullContext); | |
| if (Type == SecProfSummary && FunctionSamples::ProfileIsPreInlined) | |
| addSectionFlag(SecProfSummary, SecProfSummaryFlags::SecFlagIsPreInlined); | |
| if (Type == SecProfSummary && FunctionSamples::ProfileIsFS) | |
| addSectionFlag(SecProfSummary, SecProfSummaryFlags::SecFlagFSDiscriminator); | |
| if (Type == SecProfSummary && ExtBinaryWriteVTableTypeProf) | |
| addSectionFlag(SecProfSummary, | |
| SecProfSummaryFlags::SecFlagHasVTableTypeProf); | |
| if (Type == SecProfileSymbolList && UseMD5ProfSymList) | |
| addSectionFlag(SecProfileSymbolList, SecProfileSymbolListFlags::SecFlagMD5); | |
| if (Type == SecNameTable && UseMD5IndexedTables && UseMD5) | |
| addSectionFlag(SecNameTable, SecNameTableFlags::SecFlagEytzinger); | |
| uint64_t SectionStart = markSectionStart(Type, LayoutIdx); | |
| switch (Type) { | |
| case SecProfSummary: | |
| computeSummary(ProfileMap); | |
| if (auto EC = writeSummary()) | |
| return EC; | |
| break; | |
| case SecNameTable: | |
| if (auto EC = writeNameTableSection(ProfileMap)) | |
| return EC; | |
| break; | |
| case SecCSNameTable: | |
| if (auto EC = writeCSNameTableSection()) | |
| return EC; | |
| break; | |
| case SecLBRProfile: | |
| case SecCompositeProfile: | |
| SecLBRProfileStart = OutputStream->tell(); | |
| if (std::error_code EC = writeFuncProfiles(ProfileMap)) | |
| return EC; | |
| break; | |
| case SecFuncOffsetTable: | |
| case SecCompositeFuncOffsetTable: { | |
| bool IsFlat = hasSecFlag(Entry, SecCommonFlags::SecFlagFlat); | |
| // An unflagged function offset table inherently indexes the primary | |
| // Nested symbol span. | |
| bool IsNested = !IsFlat; | |
| if (auto EC = writeFuncOffsetTable(Type, IsNested)) | |
| return EC; | |
| break; | |
| } | |
| case SecFuncMetadata: | |
| if (std::error_code EC = writeFuncMetadata(ProfileMap)) | |
| return EC; | |
| break; | |
| case SecProfileSymbolList: | |
| if (auto EC = writeProfileSymbolListSection()) | |
| return EC; | |
| break; | |
| default: | |
| if (auto EC = writeCustomSection(Type)) | |
| return EC; | |
| break; | |
| } | |
| if (std::error_code EC = addNewSection(Type, LayoutIdx, SectionStart)) | |
| return EC; | |
| return sampleprof_error::success; | |
| } | |
| SampleProfileWriterExtBinary::SampleProfileWriterExtBinary( | |
| std::unique_ptr<raw_ostream> &OS) | |
| : SampleProfileWriterExtBinaryBase(OS) { | |
| WriteVTableProf = ExtBinaryWriteVTableTypeProf; | |
| } | |
| std::error_code SampleProfileWriterExtBinary::writeDefaultLayout( | |
| const SampleProfileMap &ProfileMap) { | |
| // ProfSection / FuncOffsetSection are SecLBR* or SecComposite* after | |
| // configureCompositeProfile. | |
| const SecType Sections[] = { | |
| SecProfSummary, SecNameTable, SecCSNameTable, ProfSection, | |
| SecProfileSymbolList, FuncOffsetSection, SecFuncMetadata, | |
| }; | |
| for (SecType Type : Sections) | |
| if (std::error_code EC = writeOneSection(Type, ProfileMap)) | |
| return EC; | |
| return sampleprof_error::success; | |
| } | |
| static void splitProfileMapToTwo(const SampleProfileMap &ProfileMap, | |
| SampleProfileMap &NestedProfileMap, | |
| SampleProfileMap &FlatProfileMap) { | |
| for (const auto &I : ProfileMap) { | |
| if (I.second.hasCallsiteSamples()) | |
| NestedProfileMap.insert({I.first, I.second}); | |
| else | |
| FlatProfileMap.insert({I.first, I.second}); | |
| } | |
| } | |
| std::error_code SampleProfileWriterExtBinary::writeCtxSplitLayout( | |
| const SampleProfileMap &ProfileMap) { | |
| SampleProfileMap NestedProfileMap, FlatProfileMap; | |
| splitProfileMapToTwo(ProfileMap, NestedProfileMap, FlatProfileMap); | |
| // Flat SecFlag is pre-set in ExtBinaryHdrLayoutTable; findUnwrittenEntry | |
| // picks the matching unwritten ProfSection / FuncOffsetSection slot. | |
| const std::pair<SecType, const SampleProfileMap &> Sections[] = { | |
| {SecProfSummary, ProfileMap}, {SecNameTable, ProfileMap}, | |
| {ProfSection, NestedProfileMap}, {FuncOffsetSection, NestedProfileMap}, | |
| {ProfSection, FlatProfileMap}, {FuncOffsetSection, FlatProfileMap}, | |
| {SecProfileSymbolList, ProfileMap}, {SecFuncMetadata, ProfileMap}, | |
| }; | |
| for (const auto &[Type, Map] : Sections) | |
| if (std::error_code EC = writeOneSection(Type, Map)) | |
| return EC; | |
| return sampleprof_error::success; | |
| } | |
| void SampleProfileWriterExtBinary::configureCompositeProfile() { | |
| ProfSection = WriteCompositeProf ? SecCompositeProfile : SecLBRProfile; | |
| FuncOffsetSection = | |
| WriteCompositeProf ? SecCompositeFuncOffsetTable : SecFuncOffsetTable; | |
| // Change the section types in place to avoid duplicating the whole layout and | |
| // its handling. Rewrite both legacy and composite entries so repeated writes | |
| // can switch formats without losing configured flags. | |
| for (auto &Entry : SectionHdrLayout) { | |
| if (Entry.Type == SecFuncOffsetTable || | |
| Entry.Type == SecCompositeFuncOffsetTable) | |
| Entry.Type = FuncOffsetSection; | |
| else if (Entry.Type == SecLBRProfile || Entry.Type == SecCompositeProfile) | |
| Entry.Type = ProfSection; | |
| } | |
| } | |
| std::error_code SampleProfileWriterExtBinary::writeSections( | |
| const SampleProfileMap &ProfileMap) { | |
| // Rewrite the final configured layout immediately before its section types | |
| // are consumed. Earlier layout configuration may replace SectionHdrLayout. | |
| configureCompositeProfile(); | |
| std::error_code EC; | |
| if (SecLayout == DefaultLayout) | |
| EC = writeDefaultLayout(ProfileMap); | |
| else if (SecLayout == CtxSplitLayout) | |
| EC = writeCtxSplitLayout(ProfileMap); | |
| else | |
| llvm_unreachable("Unsupported layout"); | |
| return EC; | |
| } | |
| /// Write samples to a text file. | |
| /// | |
| /// Note: it may be tempting to implement this in terms of | |
| /// FunctionSamples::print(). Please don't. The dump functionality is intended | |
| /// for debugging and has no specified form. | |
| /// | |
| /// The format used here is more structured and deliberate because | |
| /// it needs to be parsed by the SampleProfileReaderText class. | |
| std::error_code SampleProfileWriterText::writeSample(const FunctionSamples &S) { | |
| auto &OS = *OutputStream; | |
| if (FunctionSamples::ProfileIsCS) | |
| OS << "[" << S.getContext().toString() << "]:" << S.getTotalSamples(); | |
| else | |
| OS << S.getFunction() << ":" << S.getTotalSamples(); | |
| if (Indent == 0) | |
| OS << ":" << S.getHeadSamples(); | |
| OS << "\n"; | |
| LineCount++; | |
| for (const auto &[Loc, Sample] : S.getBodySamples()) { | |
| OS.indent(Indent + 1); | |
| Loc.print(OS); | |
| OS << ": " << Sample.getSamples(); | |
| for (const auto &J : Sample.getSortedCallTargets()) | |
| OS << " " << J.first << ":" << J.second; | |
| OS << "\n"; | |
| LineCount++; | |
| if (const TypeCountMap *Map = S.findCallsiteTypeSamplesAt(Loc); | |
| Map && !Map->empty()) { | |
| OS.indent(Indent + 1); | |
| Loc.print(OS); | |
| OS << ": "; | |
| OS << kVTableProfPrefix; | |
| for (const auto &[TypeName, Count] : *Map) { | |
| OS << TypeName << ":" << Count << " "; | |
| } | |
| OS << "\n"; | |
| LineCount++; | |
| } | |
| } | |
| Indent += 1; | |
| for (const auto &[Loc, FunctionSamplesMap] : S.getCallsiteSamples()) { | |
| for (const FunctionSamples &CalleeSamples : | |
| make_second_range(FunctionSamplesMap)) { | |
| OS.indent(Indent); | |
| Loc.print(OS); | |
| OS << ": "; | |
| if (std::error_code EC = writeSample(CalleeSamples)) | |
| return EC; | |
| } | |
| if (const TypeCountMap *Map = S.findCallsiteTypeSamplesAt(Loc); | |
| Map && !Map->empty()) { | |
| OS.indent(Indent); | |
| Loc.print(OS); | |
| OS << ": "; | |
| OS << kVTableProfPrefix; | |
| for (const auto &[TypeId, Count] : *Map) { | |
| OS << TypeId << ":" << Count << " "; | |
| } | |
| OS << "\n"; | |
| LineCount++; | |
| } | |
| } | |
| Indent -= 1; | |
| if (FunctionSamples::ProfileIsProbeBased) { | |
| OS.indent(Indent + 1); | |
| OS << "!CFGChecksum: " << S.getFunctionHash() << "\n"; | |
| LineCount++; | |
| } | |
| if (S.getContext().getAllAttributes()) { | |
| OS.indent(Indent + 1); | |
| OS << "!Attributes: " << S.getContext().getAllAttributes() << "\n"; | |
| LineCount++; | |
| } | |
| if (Indent == 0 && MarkFlatProfiles && S.getCallsiteSamples().size() == 0) | |
| OS << " !Flat\n"; | |
| return sampleprof_error::success; | |
| } | |
| std::error_code | |
| SampleProfileWriterBinary::writeContextIdx(const SampleContext &Context) { | |
| assert(!Context.hasContext() && "cs profile is not supported"); | |
| return writeNameIdx(Context.getFunction()); | |
| } | |
| std::error_code SampleProfileWriterBinary::writeNameIdx(FunctionId FName) { | |
| auto &NTable = getNameTable(); | |
| const auto &Ret = NTable.find(FName); | |
| if (Ret == NTable.end()) | |
| return sampleprof_error::truncated_name_table; | |
| encodeULEB128(Ret->second, *OutputStream); | |
| return sampleprof_error::success; | |
| } | |
| void SampleProfileWriterBinary::addName(FunctionId FName) { | |
| auto &NTable = getNameTable(); | |
| NTable.insert(std::make_pair(FName, 0)); | |
| } | |
| void SampleProfileWriterBinary::addContext(const SampleContext &Context) { | |
| addName(Context.getFunction()); | |
| } | |
| void SampleProfileWriterBinary::addNames(const FunctionSamples &S) { | |
| // Add all the names in indirect call targets. | |
| for (const auto &I : S.getBodySamples()) { | |
| const SampleRecord &Sample = I.second; | |
| for (const auto &J : Sample.getCallTargets()) | |
| addName(J.first); | |
| } | |
| // Recursively add all the names for inlined callsites. | |
| for (const auto &J : S.getCallsiteSamples()) | |
| for (const auto &FS : J.second) { | |
| const FunctionSamples &CalleeSamples = FS.second; | |
| addName(CalleeSamples.getFunction()); | |
| addNames(CalleeSamples); | |
| } | |
| if (!WriteVTableProf) | |
| return; | |
| // Add all the vtable names to NameTable. | |
| for (const auto &VTableAccessCountMap : | |
| llvm::make_second_range(S.getCallsiteTypeCounts())) { | |
| // Add type name to NameTable. | |
| for (const auto Type : llvm::make_first_range(VTableAccessCountMap)) { | |
| addName(Type); | |
| } | |
| } | |
| } | |
| void SampleProfileWriterExtBinaryBase::addContext( | |
| const SampleContext &Context) { | |
| if (Context.hasContext()) { | |
| for (auto &Callsite : Context.getContextFrames()) | |
| SampleProfileWriterBinary::addName(Callsite.Func); | |
| CSNameTable.insert(std::make_pair(Context, 0)); | |
| } else { | |
| SampleProfileWriterBinary::addName(Context.getFunction()); | |
| } | |
| } | |
| std::error_code SampleProfileWriterBinary::writeNameTable() { | |
| auto &OS = *OutputStream; | |
| // Write out the name table. | |
| encodeULEB128(NameTable.size(), OS); | |
| for (const auto *Entry : stabilizeTable(NameTable)) { | |
| OS << Entry->first; | |
| encodeULEB128(0, OS); | |
| } | |
| return sampleprof_error::success; | |
| } | |
| std::error_code | |
| SampleProfileWriterBinary::writeMagicIdent(SampleProfileFormat Format) { | |
| auto &OS = *OutputStream; | |
| // Write file magic identifier. | |
| encodeULEB128(SPMagic(Format), OS); | |
| encodeULEB128(FormatVersion, OS); | |
| return sampleprof_error::success; | |
| } | |
| std::error_code | |
| SampleProfileWriterBinary::writeHeader(const SampleProfileMap &ProfileMap) { | |
| // When calling write on a different profile map, existing names should be | |
| // cleared. | |
| NameTable.clear(); | |
| writeMagicIdent(Format); | |
| computeSummary(ProfileMap); | |
| if (auto EC = writeSummary()) | |
| return EC; | |
| // Generate the name table for all the functions referenced in the profile. | |
| for (const auto &I : ProfileMap) { | |
| addContext(I.second.getContext()); | |
| addNames(I.second); | |
| } | |
| writeNameTable(); | |
| return sampleprof_error::success; | |
| } | |
| void SampleProfileWriterExtBinaryBase::setToCompressAllSections() { | |
| for (auto &Entry : SectionHdrLayout) | |
| addSecFlag(Entry, SecCommonFlags::SecFlagCompress); | |
| } | |
| void SampleProfileWriterExtBinaryBase::setToCompressSection(SecType Type) { | |
| addSectionFlag(Type, SecCommonFlags::SecFlagCompress); | |
| } | |
| void SampleProfileWriterExtBinaryBase::allocSecHdrTable() { | |
| support::endian::Writer Writer(*OutputStream, llvm::endianness::little); | |
| Writer.write(static_cast<uint64_t>(SectionHdrLayout.size())); | |
| SecHdrTableOffset = OutputStream->tell(); | |
| for (uint32_t i = 0; i < SectionHdrLayout.size(); i++) { | |
| Writer.write(static_cast<uint64_t>(-1)); | |
| Writer.write(static_cast<uint64_t>(-1)); | |
| Writer.write(static_cast<uint64_t>(-1)); | |
| Writer.write(static_cast<uint64_t>(-1)); | |
| } | |
| } | |
| std::error_code SampleProfileWriterExtBinaryBase::writeSecHdrTable() { | |
| assert(SecHdrTable.size() == SectionHdrLayout.size() && | |
| "SecHdrTable entries doesn't match SectionHdrLayout"); | |
| SmallVector<uint32_t, 16> IndexMap(SecHdrTable.size(), -1); | |
| for (uint32_t TableIdx = 0; TableIdx < SecHdrTable.size(); TableIdx++) { | |
| IndexMap[SecHdrTable[TableIdx].LayoutIndex] = TableIdx; | |
| } | |
| // Write the section header table in the order specified in | |
| // SectionHdrLayout. SectionHdrLayout specifies the sections | |
| // order in which profile reader expect to read, so the section | |
| // header table should be written in the order in SectionHdrLayout. | |
| // Note that the section order in SecHdrTable may be different | |
| // from the order in SectionHdrLayout, for example, SecFuncOffsetTable | |
| // needs to be computed after SecLBRProfile (the order in SecHdrTable), | |
| // but it needs to be read before SecLBRProfile (the order in | |
| // SectionHdrLayout). So we use IndexMap above to switch the order. | |
| support::endian::SeekableWriter Writer( | |
| static_cast<raw_pwrite_stream &>(*OutputStream), | |
| llvm::endianness::little); | |
| for (uint32_t LayoutIdx = 0; LayoutIdx < SectionHdrLayout.size(); | |
| LayoutIdx++) { | |
| assert(IndexMap[LayoutIdx] < SecHdrTable.size() && | |
| "Incorrect LayoutIdx in SecHdrTable"); | |
| auto Entry = SecHdrTable[IndexMap[LayoutIdx]]; | |
| Writer.pwrite(static_cast<uint64_t>(Entry.Type), | |
| SecHdrTableOffset + 4 * LayoutIdx * sizeof(uint64_t)); | |
| Writer.pwrite(static_cast<uint64_t>(Entry.Flags), | |
| SecHdrTableOffset + (4 * LayoutIdx + 1) * sizeof(uint64_t)); | |
| Writer.pwrite(static_cast<uint64_t>(Entry.Offset), | |
| SecHdrTableOffset + (4 * LayoutIdx + 2) * sizeof(uint64_t)); | |
| Writer.pwrite(static_cast<uint64_t>(Entry.Size), | |
| SecHdrTableOffset + (4 * LayoutIdx + 3) * sizeof(uint64_t)); | |
| } | |
| return sampleprof_error::success; | |
| } | |
| std::error_code SampleProfileWriterExtBinaryBase::writeHeader( | |
| const SampleProfileMap &ProfileMap) { | |
| // Reject a version that cannot describe the selected profile encoding before | |
| // emitting any part of the header. | |
| if (WriteCompositeProf && FormatVersion < CompositeProfileVersion) | |
| return sampleprof_error::unsupported_version; | |
| auto &OS = *OutputStream; | |
| FileStart = OS.tell(); | |
| writeMagicIdent(Format); | |
| allocSecHdrTable(); | |
| return sampleprof_error::success; | |
| } | |
| std::error_code SampleProfileWriterBinary::writeCallsiteVTableProf( | |
| const CallsiteTypeMap &CallsiteTypeMap, raw_ostream &OS) { | |
| assert(WriteVTableProf && | |
| "writeCallsiteVTableProf should not be called if WriteVTableProf is " | |
| "false"); | |
| encodeULEB128(CallsiteTypeMap.size(), OS); | |
| for (const auto &[Loc, TypeMap] : CallsiteTypeMap) { | |
| Loc.serialize(OS); | |
| if (std::error_code EC = serializeTypeMap(TypeMap, getNameTable(), OS)) | |
| return EC; | |
| } | |
| return sampleprof_error::success; | |
| } | |
| std::error_code SampleProfileWriterBinary::writeSummary() { | |
| auto &OS = *OutputStream; | |
| encodeULEB128(Summary->getTotalCount(), OS); | |
| encodeULEB128(Summary->getMaxCount(), OS); | |
| encodeULEB128(Summary->getMaxFunctionCount(), OS); | |
| encodeULEB128(Summary->getNumCounts(), OS); | |
| encodeULEB128(Summary->getNumFunctions(), OS); | |
| ArrayRef<ProfileSummaryEntry> Entries = Summary->getDetailedSummary(); | |
| encodeULEB128(Entries.size(), OS); | |
| for (auto Entry : Entries) { | |
| encodeULEB128(Entry.Cutoff, OS); | |
| encodeULEB128(Entry.MinCount, OS); | |
| encodeULEB128(Entry.NumCounts, OS); | |
| } | |
| return sampleprof_error::success; | |
| } | |
| std::error_code | |
| SampleProfileWriterBinary::writeLBRProfile(const FunctionSamples &S, | |
| bool IsNested) { | |
| auto &OS = *OutputStream; | |
| if (WriteCompositeProf && !IsNested) | |
| encodeULEB128(S.getHeadSamples(), OS); | |
| encodeULEB128(S.getTotalSamples(), OS); | |
| encodeULEB128(S.getBodySamples().size(), OS); | |
| for (const auto &I : S.getBodySamples()) { | |
| LineLocation Loc = I.first; | |
| const SampleRecord &Sample = I.second; | |
| Loc.serialize(OS); | |
| if (std::error_code EC = Sample.serialize(OS, getNameTable())) | |
| return EC; | |
| } | |
| return sampleprof_error::success; | |
| } | |
| namespace { | |
| /// A reusable stream that discards payload bytes while counting their size. | |
| class PayloadSizeCountingStream final : public raw_ostream { | |
| public: | |
| /// Avoid retaining payload data in raw_ostream's internal buffer. | |
| PayloadSizeCountingStream() { SetUnbuffered(); } | |
| /// Prepare the stream to count another payload. | |
| void resetPayload() { | |
| PayloadSize = 0; | |
| Overflowed = false; | |
| } | |
| /// Return whether the payload size exceeded the representable range. | |
| bool overflowed() const { return Overflowed; } | |
| /// Return the complete payload size when overflowed() is false. | |
| uint64_t payloadSize() const { return PayloadSize; } | |
| private: | |
| /// Count incoming bytes without retaining their contents. | |
| void write_impl(const char *, size_t Size) override { | |
| if (Overflowed) | |
| return; | |
| // Fail closed if the payload cannot be represented by its uint64_t size. | |
| if (Size > UINT64_MAX - PayloadSize) { | |
| Overflowed = true; | |
| return; | |
| } | |
| PayloadSize += Size; | |
| } | |
| /// Report the number of bytes accepted from the current payload. | |
| uint64_t current_pos() const override { return PayloadSize; } | |
| /// Number of bytes observed during the counting pass. | |
| uint64_t PayloadSize = 0; | |
| /// Whether the counted size no longer fits in uint64_t. | |
| bool Overflowed = false; | |
| }; | |
| } // namespace | |
| std::error_code SampleProfileWriterBinary::writeProfileType( | |
| ProfTypes Type, function_ref<std::error_code()> WritePayload) { | |
| // PayloadSizeStream temporarily owns the real output while the callback | |
| // writes through OutputStream. A nested call would therefore mistake the | |
| // real output for PayloadSizeCountingStream. | |
| if (WritingProfileType) | |
| return sampleprof_error::malformed; | |
| SaveAndRestore RestoreWritingProfileType(WritingProfileType, true); | |
| // A profile block stores its payload size before the payload, but that size | |
| // is not known until it has been serialized. Count one complete serialization | |
| // without retaining its bytes, then emit the header and serialize it again. | |
| // TODO: Avoid serializing each payload twice while retaining bounded memory | |
| // use and compatibility with compressed section output. | |
| if (!PayloadSizeStream) | |
| PayloadSizeStream = std::make_unique<PayloadSizeCountingStream>(); | |
| auto *SizeStream = | |
| static_cast<PayloadSizeCountingStream *>(PayloadSizeStream.get()); | |
| SizeStream->resetPayload(); | |
| OutputStream.swap(PayloadSizeStream); | |
| std::error_code EC = WritePayload(); | |
| OutputStream.swap(PayloadSizeStream); | |
| if (EC) | |
| return EC; | |
| if (SizeStream->overflowed()) | |
| return sampleprof_error::too_large; | |
| // Emit the compact header followed by the second, materialized pass. | |
| auto &OS = *OutputStream; | |
| encodeULEB128(Type, OS); | |
| encodeULEB128(SizeStream->payloadSize(), OS); | |
| uint64_t PayloadStart = OS.tell(); | |
| if (std::error_code SecondPassEC = WritePayload()) | |
| return SecondPassEC; | |
| // Reject a stateful callback that did not reproduce the counted payload. | |
| if (OS.tell() - PayloadStart != SizeStream->payloadSize()) | |
| return sampleprof_error::malformed; | |
| return sampleprof_error::success; | |
| } | |
| static bool hasNonEmptyLBRProfile(const FunctionSamples &S, bool IsNested) { | |
| return S.getTotalSamples() != 0 || (!IsNested && S.getHeadSamples() != 0) || | |
| !S.getBodySamples().empty(); | |
| } | |
| std::error_code | |
| SampleProfileWriterBinary::writeCompositeProfile(const FunctionSamples &S, | |
| bool IsNested) { | |
| auto &OS = *OutputStream; | |
| bool WriteLBRProf = hasNonEmptyLBRProfile(S, IsNested); | |
| // Other profile types should be added here. | |
| uint32_t TypesNum = WriteLBRProf; | |
| // Write the number of profile types for function. | |
| encodeULEB128(TypesNum, OS); | |
| if (WriteLBRProf) | |
| return writeProfileType(ProfTypeLBR, | |
| [&] { return writeLBRProfile(S, IsNested); }); | |
| return sampleprof_error::success; | |
| } | |
| std::error_code SampleProfileWriterBinary::writeBody(const FunctionSamples &S, | |
| bool IsNested) { | |
| auto &OS = *OutputStream; | |
| if (std::error_code EC = writeContextIdx(S.getContext())) | |
| return EC; | |
| // Emit all the body samples. | |
| if (WriteCompositeProf) { | |
| if (std::error_code EC = writeCompositeProfile(S, IsNested)) | |
| return EC; | |
| } else { | |
| if (std::error_code EC = writeLBRProfile(S, IsNested)) | |
| return EC; | |
| } | |
| // Recursively emit all the callsite samples. | |
| uint64_t NumCallsites = 0; | |
| for (const auto &J : S.getCallsiteSamples()) | |
| NumCallsites += J.second.size(); | |
| encodeULEB128(NumCallsites, OS); | |
| for (const auto &J : S.getCallsiteSamples()) | |
| for (const auto &FS : J.second) { | |
| J.first.serialize(OS); | |
| if (std::error_code EC = writeBody(FS.second, /*IsNested=*/true)) | |
| return EC; | |
| } | |
| if (WriteVTableProf) | |
| return writeCallsiteVTableProf(S.getCallsiteTypeCounts(), OS); | |
| return sampleprof_error::success; | |
| } | |
| /// Write samples of a top-level function to a binary file. | |
| /// | |
| /// \returns true if the samples were written successfully, false otherwise. | |
| std::error_code | |
| SampleProfileWriterBinary::writeSample(const FunctionSamples &S) { | |
| encodeULEB128(S.getHeadSamples(), *OutputStream); | |
| return writeBody(S, /*IsNested=*/false); | |
| } | |
| /// Create a sample profile file writer based on the specified format. | |
| /// | |
| /// \param Filename The file to create. | |
| /// | |
| /// \param Format Encoding format for the profile file. | |
| /// | |
| /// \returns an error code indicating the status of the created writer. | |
| ErrorOr<std::unique_ptr<SampleProfileWriter>> | |
| SampleProfileWriter::create(StringRef Filename, SampleProfileFormat Format) { | |
| std::error_code EC; | |
| std::unique_ptr<raw_ostream> OS; | |
| if (Format == SPF_Binary || Format == SPF_Ext_Binary) | |
| OS.reset(new raw_fd_ostream(Filename, EC, sys::fs::OF_None)); | |
| else | |
| OS.reset(new raw_fd_ostream(Filename, EC, sys::fs::OF_TextWithCRLF)); | |
| if (EC) | |
| return EC; | |
| return create(OS, Format); | |
| } | |
| /// Create a sample profile stream writer based on the specified format. | |
| /// | |
| /// \param OS The output stream to store the profile data to. | |
| /// | |
| /// \param Format Encoding format for the profile file. | |
| /// | |
| /// \returns an error code indicating the status of the created writer. | |
| ErrorOr<std::unique_ptr<SampleProfileWriter>> | |
| SampleProfileWriter::create(std::unique_ptr<raw_ostream> &OS, | |
| SampleProfileFormat Format) { | |
| std::error_code EC; | |
| std::unique_ptr<SampleProfileWriter> Writer; | |
| // Currently only Text and Extended Binary format are supported for CSSPGO. | |
| if ((FunctionSamples::ProfileIsCS || FunctionSamples::ProfileIsProbeBased) && | |
| Format == SPF_Binary) | |
| return sampleprof_error::unsupported_writing_format; | |
| if (Format == SPF_Binary) | |
| Writer.reset(new SampleProfileWriterRawBinary(OS)); | |
| else if (Format == SPF_Ext_Binary) | |
| Writer.reset(new SampleProfileWriterExtBinary(OS)); | |
| else if (Format == SPF_Text) | |
| Writer.reset(new SampleProfileWriterText(OS)); | |
| else if (Format == SPF_GCC) | |
| EC = sampleprof_error::unsupported_writing_format; | |
| else | |
| EC = sampleprof_error::unrecognized_format; | |
| if (EC) | |
| return EC; | |
| Writer->Format = Format; | |
| if (Format != SPF_Ext_Binary) { | |
| Writer->setFormatVersion(DefaultVersion); | |
| } else { | |
| if (!formatVersionIsSupported(RequestedVersion)) | |
| return sampleprof_error::unsupported_version; | |
| // Composite output defaults to its first compatible format version. | |
| // Preserve a compatible version explicitly selected by the user. | |
| if (ExtBinaryCompositeProf) { | |
| if (RequestedVersion.getNumOccurrences() == 0) { | |
| Writer->setFormatVersion(CompositeProfileVersion); | |
| } else { | |
| if (RequestedVersion < CompositeProfileVersion) | |
| return sampleprof_error::unsupported_version; | |
| Writer->setFormatVersion(RequestedVersion); | |
| } | |
| // Keep subsequent writes independent of the global command-line option. | |
| Writer->setUseCompositeProfile(true); | |
| } else { | |
| Writer->setFormatVersion(RequestedVersion); | |
| } | |
| } | |
| return std::move(Writer); | |
| } | |
| void SampleProfileWriter::computeSummary(const SampleProfileMap &ProfileMap) { | |
| SampleProfileSummaryBuilder Builder(ProfileSummaryBuilder::DefaultCutoffs); | |
| Summary = Builder.computeSummaryForProfiles(ProfileMap); | |
| } |