blob: 2d317a7f6f7443b638a5f6e610de758c3c063414 [file]
//===- 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/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"));
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;
encodeULEB128(S.getHeadSamples(), *OutputStream);
return writeBody(S);
}
std::error_code
SampleProfileWriterExtBinaryBase::writeFuncOffsetTable(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(IsNested);
}
return writeLegacyFuncOffsetTable();
}
std::error_code
SampleProfileWriterExtBinaryBase::writeEytzingerFuncOffsetTable(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));
addSectionFlag(SecFuncOffsetTable, SecFuncOffsetFlags::SecFlagEytzinger);
FuncOffsetTable.clear();
return sampleprof_error::success;
}
std::error_code SampleProfileWriterExtBinaryBase::writeLegacyFuncOffsetTable() {
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(SecFuncOffsetTable, 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;
assert(!ProfSymList->isMD5() &&
"Writing MD5 ProfileSymbolListSection from existing MD5 "
"table not yet implemented");
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:
SecLBRProfileStart = OutputStream->tell();
if (std::error_code EC = writeFuncProfiles(ProfileMap))
return EC;
break;
case SecFuncOffsetTable: {
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(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) {
static constexpr SecType Sections[] = {
SecProfSummary, SecNameTable, SecCSNameTable, SecLBRProfile,
SecProfileSymbolList, SecFuncOffsetTable, 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);
const std::pair<SecType, const SampleProfileMap &> Sections[] = {
{SecProfSummary, ProfileMap},
{SecNameTable, ProfileMap},
{SecLBRProfile, NestedProfileMap},
{SecFuncOffsetTable, NestedProfileMap},
{SecLBRProfile, FlatProfileMap},
{SecFuncOffsetTable, 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;
}
std::error_code SampleProfileWriterExtBinary::writeSections(
const SampleProfileMap &ProfileMap) {
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) {
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::writeBody(const FunctionSamples &S) {
auto &OS = *OutputStream;
if (std::error_code EC = writeContextIdx(S.getContext()))
return EC;
encodeULEB128(S.getTotalSamples(), OS);
// Emit all the body samples.
encodeULEB128(S.getBodySamples().size(), OS);
for (const auto &I : S.getBodySamples()) {
LineLocation Loc = I.first;
const SampleRecord &Sample = I.second;
Loc.serialize(OS);
Sample.serialize(OS, getNameTable());
}
// 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))
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);
}
/// 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))
Writer->setFormatVersion(RequestedVersion);
else
return sampleprof_error::unsupported_version;
return std::move(Writer);
}
void SampleProfileWriter::computeSummary(const SampleProfileMap &ProfileMap) {
SampleProfileSummaryBuilder Builder(ProfileSummaryBuilder::DefaultCutoffs);
Summary = Builder.computeSummaryForProfiles(ProfileMap);
}