Skip to content

[InstrFDO]Allow indexed profile reader to parse compatible future versions and returns errors for incompatible ones. #88212

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion compiler-rt/include/profile/InstrProfData.inc
Original file line number Diff line number Diff line change
Expand Up @@ -710,7 +710,7 @@ serializeValueProfDataFrom(ValueProfRecordClosure *Closure,
/* Raw profile format version (start from 1). */
#define INSTR_PROF_RAW_VERSION 10
/* Indexed profile format version (start from 1). */
#define INSTR_PROF_INDEX_VERSION 12
#define INSTR_PROF_INDEX_VERSION 13
/* Coverage mapping format version (start from 0). */
#define INSTR_PROF_COVMAP_VERSION 6

Expand Down
24 changes: 21 additions & 3 deletions llvm/include/llvm/ProfileData/InstrProf.h
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,7 @@ enum class instrprof_error {
zlib_unavailable,
raw_profile_version_mismatch,
counter_value_too_large,
unsupported_incompatible_future_version,
};

/// An ordered list of functions identified by their NameRef found in
Expand Down Expand Up @@ -1172,7 +1173,9 @@ enum ProfVersion {
Version11 = 11,
// VTable profiling,
Version12 = 12,
// The current version is 12.
// Added limited forward compatibility. See XYZ for details.
Version13 = 13,
// The current version is 13.
CurrentVersion = INSTR_PROF_INDEX_VERSION
};
const uint64_t Version = ProfVersion::CurrentVersion;
Expand Down Expand Up @@ -1200,6 +1203,22 @@ struct Header {
uint64_t BinaryIdOffset = 0;
uint64_t TemporalProfTracesOffset = 0;
uint64_t VTableNamesOffset = 0;
// The on-disk byte size of the header.
uint64_t HeaderByteSize = 0;
// The minimum profile version that the profile requires the reader to be able
// to parse. If a profile reader's newest known version
// is smaller than what's recorded in this field, the profile reader will stop
// parsing profiles and throw error.
//
// Here is an example scenario; the semantics of an existing section change
// starting from version V + 1, indexed profile writer should record
// `MinCompatibleReaderVersion` as V + 1. Previously-built profile readers
// won't know how to interpret the existing section correctly; these readers
// will find the `MinCompatibleReaderVersion` recorded in the profile is
// higher than the readers' supported version (a constant baked in the
// executable).
uint64_t MinCompatibleReaderVersion = 0;

// New fields should only be added at the end to ensure that the size
// computation is correct. The methods below need to be updated to ensure that
// the new field is read correctly.
Expand All @@ -1208,8 +1227,7 @@ struct Header {
// endianness.
static Expected<Header> readFromBuffer(const unsigned char *Buffer);

// Returns the size of the header in bytes for all valid fields based on the
// version. I.e a older version header will return a smaller size.
// Returns the on-disk byte size of the header.
size_t size() const;

// Return the indexed profile version, i.e., the least significant 32 bits
Expand Down
2 changes: 1 addition & 1 deletion llvm/include/llvm/ProfileData/InstrProfData.inc
Original file line number Diff line number Diff line change
Expand Up @@ -710,7 +710,7 @@ serializeValueProfDataFrom(ValueProfRecordClosure *Closure,
/* Raw profile format version (start from 1). */
#define INSTR_PROF_RAW_VERSION 10
/* Indexed profile format version (start from 1). */
#define INSTR_PROF_INDEX_VERSION 12
#define INSTR_PROF_INDEX_VERSION 13
/* Coverage mapping format version (start from 0). */
#define INSTR_PROF_COVMAP_VERSION 6

Expand Down
29 changes: 26 additions & 3 deletions llvm/include/llvm/ProfileData/InstrProfWriter.h
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,24 @@ class InstrProfWriter {
// Whether to serialize the full schema.
bool MemProfFullSchema;

// Returns the profile version in uint32_t, which should be used as the
// the lowest 32 bits in Header.Version.
uint32_t profileVersion() const;

// Returns the minimum profile reader version required to parse this profile.
uint64_t minCompatibleReaderVersion() const;

// The following fields are used by unit tests only.
// If not std::nullopt, this field overwrites the lowest 32 bits of
// Header::Version in the generated profile.
std::optional<uint32_t> ProfileVersion;
// If true, profile writer will append one 64-bit dummy value as an unknown
// new header field.
bool AppendAdditionalHeaderFields = false;
// If not std::nullopt, this field overwrites
// Header::MinCompatibleReaderVersion in the generated profile.
std::optional<int> MinCompatibleReaderProfileVersion;

public:
InstrProfWriter(
bool Sparse = false, uint64_t TemporalProfTraceReservoirSize = 0,
Expand Down Expand Up @@ -191,9 +209,6 @@ class InstrProfWriter {
return static_cast<bool>(ProfileKind & InstrProfKind::SingleByteCoverage);
}

// Internal interfaces for testing purpose only.
void setValueProfDataEndianness(llvm::endianness Endianness);
void setOutputSparse(bool Sparse);
void setMemProfVersionRequested(memprof::IndexedVersion Version) {
MemProfVersionRequested = Version;
}
Expand All @@ -204,6 +219,14 @@ class InstrProfWriter {
OverlapStats &FuncLevelOverlap,
const OverlapFuncFilters &FuncFilter);

// Internal interface for testing purpose only.
void setValueProfDataEndianness(llvm::endianness Endianness);
void setOutputSparse(bool Sparse);
void setProfileVersion(uint32_t Version);
void setMinCompatibleReaderProfileVersion(uint32_t Version);
void setAppendAdditionalHeaderFields();
void resetTestOnlyStatesForHeaderSection();

private:
void addRecord(StringRef Name, uint64_t Hash, InstrProfRecord &&I,
uint64_t Weight, function_ref<void(Error)> Warn);
Expand Down
40 changes: 33 additions & 7 deletions llvm/lib/ProfileData/InstrProf.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
#include "llvm/Support/Endian.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/FormatVariadic.h"
#include "llvm/Support/LEB128.h"
#include "llvm/Support/MathExtras.h"
#include "llvm/Support/Path.h"
Expand Down Expand Up @@ -165,6 +166,11 @@ static std::string getInstrProfErrString(instrprof_error Err,
case instrprof_error::counter_value_too_large:
OS << "excessively large counter value suggests corrupted profile data";
break;
case instrprof_error::unsupported_incompatible_future_version:
OS << "unsupported incompatible future version. The profile is likely "
"generated from newer released compilers/tools and not parsable by "
"current reader.";
break;
}

// If optional error message is not empty, append it to the message.
Expand Down Expand Up @@ -1640,13 +1646,28 @@ Expected<Header> Header::readFromBuffer(const unsigned char *Buffer) {

// Read the version.
H.Version = endian::readNext<uint64_t, llvm::endianness::little>(Buffer);
if (H.getIndexedProfileVersion() >
IndexedInstrProf::ProfVersion::CurrentVersion)
return make_error<InstrProfError>(instrprof_error::unsupported_version);

static_assert(IndexedInstrProf::ProfVersion::CurrentVersion == Version12,
"Please update the reader as needed when a new field is added "
"or when indexed profile version gets bumped.");
// Starting from version 13, profile records the minimum compatible reader
// version at a fixed byte offset in the header.
if (H.getIndexedProfileVersion() >= IndexedInstrProf::ProfVersion::Version13)
H.MinCompatibleReaderVersion =
endian::read<uint64_t, llvm::endianness::little>(Buffer + 64);
else {
// Use profile recorded version. This is consistent with reader/profile
// compatibility detection prior to version 13.
H.MinCompatibleReaderVersion = H.getIndexedProfileVersion();
}

// Stop reading and return error if the largest version supported by the
// reader falls behind the minimum reader version required by the profiles.
if (IndexedInstrProf::ProfVersion::CurrentVersion <
H.MinCompatibleReaderVersion)
return make_error<InstrProfError>(
instrprof_error::unsupported_incompatible_future_version,
formatv("Profile reader should support version {0} or later versions "
"to parse profile of version {1}. Please update the tools or "
"rebuild it.",
H.MinCompatibleReaderVersion, H.getIndexedProfileVersion()));

Buffer += sizeof(uint64_t); // Skip Header.Unused field.
H.HashType = endian::readNext<uint64_t, llvm::endianness::little>(Buffer);
Expand All @@ -1664,6 +1685,9 @@ Expected<Header> Header::readFromBuffer(const unsigned char *Buffer) {
if (H.getIndexedProfileVersion() >= 12)
H.VTableNamesOffset =
endian::readNext<uint64_t, llvm::endianness::little>(Buffer);
if (H.getIndexedProfileVersion() >= 13)
H.HeaderByteSize =
endian::readNext<uint64_t, llvm::endianness::little>(Buffer);
return H;
}

Expand All @@ -1672,12 +1696,14 @@ uint64_t Header::getIndexedProfileVersion() const {
}

size_t Header::size() const {
if (getIndexedProfileVersion() >= Version13)
return HeaderByteSize;
switch (getIndexedProfileVersion()) {
// To retain backward compatibility, new fields must be appended to the end
// of the header, and byte offset of existing fields shouldn't change when
// indexed profile version gets incremented.
static_assert(
IndexedInstrProf::ProfVersion::CurrentVersion == Version12,
IndexedInstrProf::ProfVersion::CurrentVersion == Version13,
"Please update the size computation below if a new field has "
"been added to the header; for a version bump without new "
"fields, add a case statement to fall through to the latest version.");
Expand Down
82 changes: 63 additions & 19 deletions llvm/lib/ProfileData/InstrProfWriter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -195,15 +195,32 @@ InstrProfWriter::InstrProfWriter(

InstrProfWriter::~InstrProfWriter() { delete InfoObj; }

// Internal interface for testing purpose only.
// Begin: Internal interface for testing purpose only.
void InstrProfWriter::setValueProfDataEndianness(llvm::endianness Endianness) {
InfoObj->ValueProfDataEndianness = Endianness;
}

void InstrProfWriter::setOutputSparse(bool Sparse) {
this->Sparse = Sparse;
void InstrProfWriter::setOutputSparse(bool Sparse) { this->Sparse = Sparse; }

void InstrProfWriter::setProfileVersion(uint32_t Version) {
ProfileVersion = Version;
}

void InstrProfWriter::setMinCompatibleReaderProfileVersion(uint32_t Version) {
MinCompatibleReaderProfileVersion = Version;
}

void InstrProfWriter::setAppendAdditionalHeaderFields() {
AppendAdditionalHeaderFields = true;
}

void InstrProfWriter::resetTestOnlyStatesForHeaderSection() {
ProfileVersion = std::nullopt;
MinCompatibleReaderProfileVersion = std::nullopt;
AppendAdditionalHeaderFields = false;
}
// End: Internal interface for testing purpose only.

void InstrProfWriter::addRecord(NamedInstrProfRecord &&I, uint64_t Weight,
function_ref<void(Error)> Warn) {
auto Name = I.Name;
Expand Down Expand Up @@ -755,6 +772,22 @@ static Error writeMemProf(ProfOStream &OS,
memprof::MaximumSupportedVersion));
}

uint32_t InstrProfWriter::profileVersion() const {
// ProfileVersion is set in tests only.
if (this->ProfileVersion)
return *(this->ProfileVersion);
return WritePrevVersion ? (IndexedInstrProf::ProfVersion::CurrentVersion - 1)
: IndexedInstrProf::ProfVersion::CurrentVersion;
}

uint64_t InstrProfWriter::minCompatibleReaderVersion() const {
// MinCompatibleReaderProfileVersion is set in tests only.
if (this->MinCompatibleReaderProfileVersion)
return *(this->MinCompatibleReaderProfileVersion);

return IndexedInstrProf::ProfVersion::Version13;
}

uint64_t InstrProfWriter::writeHeader(const IndexedInstrProf::Header &Header,
const bool WritePrevVersion,
ProfOStream &OS) {
Expand All @@ -770,8 +803,16 @@ uint64_t InstrProfWriter::writeHeader(const IndexedInstrProf::Header &Header,
OS.write(0); // MemProfOffset
OS.write(0); // BinaryIdOffset
OS.write(0); // TemporalProfTracesOffset
if (!WritePrevVersion)
OS.write(0); // VTableNamesOffset
OS.write(0); // VTableNamesOffset
if (!WritePrevVersion) {
OS.write(0); // HeaderByteSize
OS.write(0); // MinimumProfileReaderVersion
}

// This is a test-only path to append dummy header fields.
// NOTE: please write all other header fields before this one.
if (AppendAdditionalHeaderFields)
OS.write(0);

return BackPatchStartOffset;
}
Expand Down Expand Up @@ -829,13 +870,11 @@ Error InstrProfWriter::writeImpl(ProfOStream &OS) {

// Write the header.
IndexedInstrProf::Header Header;
Header.Version = WritePrevVersion
? IndexedInstrProf::ProfVersion::Version11
: IndexedInstrProf::ProfVersion::CurrentVersion;
// The WritePrevVersion handling will either need to be removed or updated
// if the version is advanced beyond 12.
Header.Magic = IndexedInstrProf::Magic;
Header.Version = profileVersion();

static_assert(IndexedInstrProf::ProfVersion::CurrentVersion ==
IndexedInstrProf::ProfVersion::Version12);
IndexedInstrProf::ProfVersion::Version13);
if (static_cast<bool>(ProfileKind & InstrProfKind::IRInstrumentation))
Header.Version |= VARIANT_MASK_IR_PROF;
if (static_cast<bool>(ProfileKind & InstrProfKind::ContextSensitive))
Expand All @@ -852,9 +891,13 @@ Error InstrProfWriter::writeImpl(ProfOStream &OS) {
if (static_cast<bool>(ProfileKind & InstrProfKind::TemporalProfile))
Header.Version |= VARIANT_MASK_TEMPORAL_PROF;

const uint64_t StartOffset = OS.tell();
const uint64_t BackPatchStartOffset =
writeHeader(Header, WritePrevVersion, OS);

// Record the header byte size.
const uint64_t OnDiskHeaderSize = OS.tell() - StartOffset;

// Reserve space to write profile summary data.
uint32_t NumEntries = ProfileSummaryBuilder::DefaultCutoffs.size();
uint32_t SummarySize = Summary::getSize(Summary::NumKinds, NumEntries);
Expand Down Expand Up @@ -922,9 +965,8 @@ Error InstrProfWriter::writeImpl(ProfOStream &OS) {

uint64_t VTableNamesSectionStart = OS.tell();

if (!WritePrevVersion)
if (Error E = writeVTableNames(OS))
return E;
if (Error E = writeVTableNames(OS))
return E;

uint64_t TemporalProfTracesSectionStart = 0;
if (static_cast<bool>(ProfileKind & InstrProfKind::TemporalProfile)) {
Expand Down Expand Up @@ -957,11 +999,13 @@ Error InstrProfWriter::writeImpl(ProfOStream &OS) {
}
InfoObj->CSSummaryBuilder = nullptr;

SmallVector<uint64_t, 8> HeaderOffsets = {HashTableStart, MemProfSectionStart,
BinaryIdSectionStart,
TemporalProfTracesSectionStart};
if (!WritePrevVersion)
HeaderOffsets.push_back(VTableNamesSectionStart);
SmallVector<uint64_t, 8> HeaderOffsets = {
HashTableStart, MemProfSectionStart, BinaryIdSectionStart,
TemporalProfTracesSectionStart, VTableNamesSectionStart};
if (!WritePrevVersion) {
HeaderOffsets.push_back(OnDiskHeaderSize);
HeaderOffsets.push_back(minCompatibleReaderVersion());
}

PatchItem PatchItems[] = {
// Patch the Header fields
Expand Down
4 changes: 2 additions & 2 deletions llvm/test/tools/llvm-profdata/profile-version.test
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ Test the profile version.

RUN: llvm-profdata merge -o %t.profdata %p/Inputs/basic.proftext
RUN: llvm-profdata show --profile-version %t.profdata | FileCheck %s
CHECK: Profile version: 12
CHECK: Profile version: 13

RUN: llvm-profdata merge -o %t.prev.profdata %p/Inputs/basic.proftext --write-prev-version
RUN: llvm-profdata show --profile-version %t.prev.profdata | FileCheck %s --check-prefix=PREV
PREV: Profile version: 11
PREV: Profile version: 12
Loading