Skip to content

Commit ea7e84e

Browse files
Add Header::MinimumProfileReaderVersion to detect incompatible changes
1 parent 7cd93b1 commit ea7e84e

File tree

7 files changed

+219
-68
lines changed

7 files changed

+219
-68
lines changed

llvm/include/llvm/ProfileData/InstrProf.h

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,7 @@ enum class instrprof_error {
368368
zlib_unavailable,
369369
raw_profile_version_mismatch,
370370
counter_value_too_large,
371+
unsupported_incompatible_future_version,
371372
};
372373

373374
/// An ordered list of functions identified by their NameRef found in
@@ -1146,7 +1147,13 @@ enum ProfVersion {
11461147
Version11 = 11,
11471148
// VTable profiling,
11481149
Version12 = 12,
1149-
// Record the on-disk byte size of header.
1150+
// Two additional header fields to add partial forward compatibility.
1151+
// Indexed profile reader compares the latest version it can parse with the
1152+
// minimum version required by (and recorded in) the profile; if the reader
1153+
// can reasonably interprets the payload, it proceeds to parse known sections
1154+
// and skip unknown sections; otherwise it stops reading and throws error
1155+
// (users should update compilers and/or command line tools to parse profiles
1156+
// with newer versions).
11501157
Version13 = 13,
11511158
// The current version is 13.
11521159
CurrentVersion = INSTR_PROF_INDEX_VERSION
@@ -1177,9 +1184,24 @@ struct Header {
11771184
uint64_t TemporalProfTracesOffset;
11781185
uint64_t VTableNamesOffset;
11791186

1180-
// The on-disk byte size of the header. As with other fields, do not read its
1181-
// value before calling readFromBuffer.
1182-
uint64_t Size = 0;
1187+
// Records the on-disk byte size of the header.
1188+
uint64_t OnDiskByteSize = 0;
1189+
1190+
// Indexed profile writer will records the minimum profile reader version
1191+
// required to parse this profile. If a profile reader's supported version is
1192+
// smaller than what's recorded in this field, the profile reader (in
1193+
// compilers or command line tools) should be updated.
1194+
//
1195+
// Example scenario:
1196+
// The semantics of an existing section change starting from version V + 1
1197+
// (with readers supporting both versions to preserve backward compatibility),
1198+
// indexed profile writer should record `MinimumProfileReaderVersion` as V
1199+
// + 1. Previously-built profile readers won't know how to interpret the
1200+
// existing section correctly; these readers will find the
1201+
// `MinimumProfileReaderVersion` recorded in the profile is higher than the
1202+
// readers' supported version (a constant baked in the library), and stop the
1203+
// read process.
1204+
uint64_t MinimumProfileReaderVersion = 0;
11831205

11841206
// New fields should only be added at the end to ensure that the size
11851207
// computation is correct. The methods below need to be updated to ensure that
@@ -1192,6 +1214,7 @@ struct Header {
11921214
// the version.
11931215
size_t size() const;
11941216

1217+
// Returns the end byte offset of all known fields by the reader.
11951218
Expected<size_t> knownFieldsEndByteOffset() const;
11961219

11971220
// Returns the format version in little endian. The header retains the

llvm/include/llvm/ProfileData/InstrProfWriter.h

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ class InstrProfWriter {
4040

4141
private:
4242
bool Sparse;
43+
4344
StringMap<ProfilingData> FunctionData;
4445
/// The maximum length of a single temporal profile trace.
4546
uint64_t MaxTemporalProfTraceLength;
@@ -81,6 +82,25 @@ class InstrProfWriter {
8182
// The MemProf version we should write.
8283
memprof::IndexedVersion MemProfVersionRequested;
8384

85+
// Returns the profile version in uint32_t.
86+
// Header.Version is uint64_t with the lowest 32 bits specifying profile
87+
// version and the most significant bits specyfing profile flavors.
88+
uint32_t profileVersion() const;
89+
90+
// Returns the minimum profile reader version required to parse this profile.
91+
uint64_t minProfileReaderVersion() const;
92+
93+
// The following fields are used in unit tests only.
94+
// If not std::nullopt, this field overwrites the lowest 32 bits of
95+
// Header::Version in the generated profile.
96+
std::optional<uint32_t> ProfileVersion = std::nullopt;
97+
// If true, profile writer will append one 64-bit dummy value as unknown
98+
// header fields.
99+
bool AppendAdditionalHeaderFields = false;
100+
// If not std::nullopt, this field overwrites
101+
// Header::MinimumProfileReaderVersion in the generated profile.
102+
std::optional<int> MinCompatibleReaderProfileVersion = std::nullopt;
103+
84104
public:
85105
InstrProfWriter(
86106
bool Sparse = false, uint64_t TemporalProfTraceReservoirSize = 0,
@@ -187,15 +207,20 @@ class InstrProfWriter {
187207
return static_cast<bool>(ProfileKind & InstrProfKind::SingleByteCoverage);
188208
}
189209

190-
// Internal interface for testing purpose only.
191-
void setValueProfDataEndianness(llvm::endianness Endianness);
192-
void setOutputSparse(bool Sparse);
193210
// Compute the overlap b/w this object and Other. Program level result is
194211
// stored in Overlap and function level result is stored in FuncLevelOverlap.
195212
void overlapRecord(NamedInstrProfRecord &&Other, OverlapStats &Overlap,
196213
OverlapStats &FuncLevelOverlap,
197214
const OverlapFuncFilters &FuncFilter);
198215

216+
// Internal interface for testing purpose only.
217+
void setValueProfDataEndianness(llvm::endianness Endianness);
218+
void setOutputSparse(bool Sparse);
219+
void setProfileVersion(uint32_t Version);
220+
void setMinCompatibleReaderProfileVersion(uint32_t Version);
221+
void setAppendAdditionalHeaderFields();
222+
void resetTestOnlyStatesForHeaderSection();
223+
199224
private:
200225
void addRecord(StringRef Name, uint64_t Hash, InstrProfRecord &&I,
201226
uint64_t Weight, function_ref<void(Error)> Warn);

llvm/lib/ProfileData/InstrProf.cpp

Lines changed: 56 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
#include "llvm/Support/Endian.h"
3939
#include "llvm/Support/Error.h"
4040
#include "llvm/Support/ErrorHandling.h"
41+
#include "llvm/Support/FormatVariadic.h"
4142
#include "llvm/Support/LEB128.h"
4243
#include "llvm/Support/MathExtras.h"
4344
#include "llvm/Support/Path.h"
@@ -165,6 +166,11 @@ static std::string getInstrProfErrString(instrprof_error Err,
165166
case instrprof_error::counter_value_too_large:
166167
OS << "excessively large counter value suggests corrupted profile data";
167168
break;
169+
case instrprof_error::unsupported_incompatible_future_version:
170+
OS << "unsupported incompatible future version. The profile is likely "
171+
"generated from newer released compilers/tools and not parsable by "
172+
"current reader.";
173+
break;
168174
}
169175

170176
// If optional error message is not empty, append it to the message.
@@ -1612,37 +1618,49 @@ Expected<Header> Header::readFromBuffer(const unsigned char *Buffer) {
16121618

16131619
// Read the version.
16141620
H.Version = read(Buffer, sizeof(uint64_t));
1615-
uint64_t ProfileVersion = GET_VERSION(H.formatVersion());
1616-
1617-
if (ProfileVersion > IndexedInstrProf::ProfVersion::CurrentVersion) {
1618-
if (CurrentVersion < ProfVersion::Version13)
1619-
return make_error<InstrProfError>(instrprof_error::unsupported_version);
1620-
1621-
// FIXME: When the semantic of an existing payload section changes, allowing
1622-
// profile reader to interpret new profiles using the old code in the
1623-
// current way is too error-prone.
1624-
// Should the payload section record its own version and fail the profile
1625-
// reader to require a re-build of the profile reader in this case?
1621+
uint64_t ProfileRecordedProfileVersion = GET_VERSION(H.formatVersion());
1622+
1623+
if (ProfileRecordedProfileVersion >= ProfVersion::Version13) {
1624+
// Starting from version 13, profiles should records header's on-disk byte
1625+
// size and the minimum profile reader version required.
1626+
H.OnDiskByteSize = read(Buffer, kOnDiskSizeOffset);
1627+
H.MinimumProfileReaderVersion =
1628+
read(Buffer, kOnDiskSizeOffset + sizeof(uint64_t));
1629+
} else {
1630+
// Prior to version 13, the largest version supported by the reader
1631+
// (ProfVersion::CurrentVersion) must be greater than or equal to the
1632+
// profile-recorded version.
1633+
H.MinimumProfileReaderVersion = ProfileRecordedProfileVersion;
16261634
}
16271635

1628-
if (ProfileVersion >= ProfVersion::Version13)
1629-
H.Size = read(Buffer, kOnDiskSizeOffset);
1636+
// Stop reading and return error if the largest version supported by the
1637+
// reader falls behind the minimum reader version required by the profiles.
1638+
if (IndexedInstrProf::ProfVersion::CurrentVersion <
1639+
H.MinimumProfileReaderVersion)
1640+
return make_error<InstrProfError>(
1641+
instrprof_error::unsupported_incompatible_future_version,
1642+
formatv("Profile reader should support {0} to parse profile of version "
1643+
"{1} ",
1644+
H.MinimumProfileReaderVersion, ProfileRecordedProfileVersion));
16301645

16311646
// `FieldByteOffset` represents the end byte of the last known header field.
16321647
auto FieldByteOffsetOrErr = H.knownFieldsEndByteOffset();
1633-
if (FieldByteOffsetOrErr)
1648+
if (Error E = FieldByteOffsetOrErr.takeError())
1649+
return E;
1650+
1651+
auto FieldByteOffset = FieldByteOffsetOrErr.get();
16341652

16351653
assert(FieldByteOffset != 0 &&
16361654
"FieldByteOffset specifies the byte offset of the last known field in "
16371655
"header and should not be zero");
16381656

16391657
// If the version from profile is higher than the currently supported version,
16401658
// read the known defined header fields and discard the rest.
1641-
if (ProfileVersion > ProfVersion::CurrentVersion)
1642-
ProfileVersion = ProfVersion::CurrentVersion;
1659+
if (ProfileRecordedProfileVersion > ProfVersion::CurrentVersion)
1660+
ProfileRecordedProfileVersion = ProfVersion::CurrentVersion;
16431661

16441662
// Initialize header fields based on the currently supported version.
1645-
switch (ProfileVersion) {
1663+
switch (ProfileRecordedProfileVersion) {
16461664
// When a new field is added in the header add a case statement here to
16471665
// populate it.
16481666
static_assert(
@@ -1651,7 +1669,8 @@ Expected<Header> Header::readFromBuffer(const unsigned char *Buffer) {
16511669
"if not add a case statement to fall through to the latest version.");
16521670
case 13ull:
16531671
// Size field is already read.
1654-
FieldByteOffset -= sizeof(Header::Size);
1672+
FieldByteOffset -= sizeof(Header::OnDiskByteSize);
1673+
FieldByteOffset -= sizeof(Header::MinimumProfileReaderVersion);
16551674
[[fallthrough]];
16561675
case 12ull:
16571676
FieldByteOffset -= sizeof(Header::VTableNamesOffset);
@@ -1682,21 +1701,23 @@ Expected<Header> Header::readFromBuffer(const unsigned char *Buffer) {
16821701
}
16831702

16841703
Expected<size_t> Header::knownFieldsEndByteOffset() const {
1685-
const uint64_t ProfileVersion = GET_VERSION(formatVersion());
1686-
if (ProfileVersion <= ProfVersion::Version12)
1704+
// All header fields are known before, so the end byte offset of known fields
1705+
// is the same as header byte size.
1706+
const uint64_t ProfileRecordedProfileVersion = GET_VERSION(formatVersion());
1707+
if (ProfileRecordedProfileVersion <= ProfVersion::Version12)
16871708
return this->size();
16881709

1689-
// Starting from version 13, the known field end byte offset is inferred based
1690-
// on the currently supported version.
1710+
// Starting from version 13, the known field end byte offset is calculated
1711+
// based on the currently supported version recorded in the reader.
16911712
switch (IndexedInstrProf::ProfVersion::CurrentVersion) {
16921713
// When a new field is added in the header add a case statement here to
16931714
// populate it.
1694-
static_assert(
1695-
IndexedInstrProf::ProfVersion::CurrentVersion == Version13,
1696-
"Please update the reading code below if a new field has been added, "
1697-
"if not add a case statement to fall through to the latest version.");
1715+
static_assert(IndexedInstrProf::ProfVersion::CurrentVersion == Version13,
1716+
"If current version bumps, please update the case below to "
1717+
"calculate the known field end byte offset.");
16981718
case 13ull:
1699-
return kOnDiskSizeOffset + sizeof(Header::Size);
1719+
return kOnDiskSizeOffset + sizeof(Header::OnDiskByteSize) +
1720+
sizeof(Header::MinimumProfileReaderVersion);
17001721
default:
17011722
break;
17021723
}
@@ -1705,16 +1726,17 @@ Expected<size_t> Header::knownFieldsEndByteOffset() const {
17051726
}
17061727

17071728
size_t Header::size() const {
1708-
const uint64_t ProfileVersion = GET_VERSION(formatVersion());
1729+
const uint64_t ProfileRecordedProfileVersion = GET_VERSION(formatVersion());
17091730
// Starting from version 13, the indexed profile records the byte size of
17101731
// header.
1711-
if (ProfileVersion >= ProfVersion::Version13) {
1712-
assert(Size != 0 && "User can call Header::size() only after reading it "
1713-
"from readMemoryBuffer");
1714-
return Size;
1732+
if (ProfileRecordedProfileVersion >= ProfVersion::Version13) {
1733+
assert(OnDiskByteSize != 0 &&
1734+
"User can call Header::size() only after reading it "
1735+
"from readMemoryBuffer");
1736+
return OnDiskByteSize;
17151737
}
1716-
switch (ProfileVersion) {
1717-
assert(ProfileVersion <= ProfVersion::Version12 &&
1738+
switch (ProfileRecordedProfileVersion) {
1739+
assert(ProfileRecordedProfileVersion <= ProfVersion::Version12 &&
17181740
"Do not infer header size field for newer version");
17191741
case 12ull:
17201742
return offsetOf(&Header::VTableNamesOffset) +

llvm/lib/ProfileData/InstrProfWriter.cpp

Lines changed: 51 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,6 @@
3636

3737
using namespace llvm;
3838

39-
static cl::opt<bool> WriteFutureVersion("write-future-version",
40-
cl::init(false));
41-
4239
// A struct to define how the data stream should be patched. For Indexed
4340
// profiling, only uint64_t data type is needed.
4441
struct PatchItem {
@@ -196,7 +193,7 @@ InstrProfWriter::InstrProfWriter(
196193

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

199-
// Internal interface for testing purpose only.
196+
// Begin: Internal interface for testing purpose only.
200197
void InstrProfWriter::setValueProfDataEndianness(llvm::endianness Endianness) {
201198
InfoObj->ValueProfDataEndianness = Endianness;
202199
}
@@ -205,6 +202,25 @@ void InstrProfWriter::setOutputSparse(bool Sparse) {
205202
this->Sparse = Sparse;
206203
}
207204

205+
void InstrProfWriter::setProfileVersion(uint32_t Version) {
206+
ProfileVersion = std::make_optional(Version);
207+
}
208+
209+
void InstrProfWriter::setMinCompatibleReaderProfileVersion(uint32_t Version) {
210+
MinCompatibleReaderProfileVersion = std::make_optional(Version);
211+
}
212+
213+
void InstrProfWriter::setAppendAdditionalHeaderFields() {
214+
AppendAdditionalHeaderFields = true;
215+
}
216+
217+
void InstrProfWriter::resetTestOnlyStatesForHeaderSection() {
218+
ProfileVersion = std::nullopt;
219+
MinCompatibleReaderProfileVersion = std::nullopt;
220+
AppendAdditionalHeaderFields = false;
221+
}
222+
// End: Internal interface for testing purpose only.
223+
208224
void InstrProfWriter::addRecord(NamedInstrProfRecord &&I, uint64_t Weight,
209225
function_ref<void(Error)> Warn) {
210226
auto Name = I.Name;
@@ -555,6 +571,24 @@ static Error writeMemProf(
555571
memprof::MaximumSupportedVersion));
556572
}
557573

574+
uint32_t InstrProfWriter::profileVersion() const {
575+
// ProfileVersion is set in tests only.
576+
if (this->ProfileVersion)
577+
return *(this->ProfileVersion);
578+
579+
uint64_t CurVersion = IndexedInstrProf::ProfVersion::CurrentVersion;
580+
581+
return WritePrevVersion ? CurVersion - 1 : CurVersion;
582+
}
583+
584+
uint64_t InstrProfWriter::minProfileReaderVersion() const {
585+
// MinCompatibleReaderProfileVersion is set in tests only.
586+
if (this->MinCompatibleReaderProfileVersion)
587+
return *(this->MinCompatibleReaderProfileVersion);
588+
589+
return IndexedInstrProf::ProfVersion::Version13;
590+
}
591+
558592
Error InstrProfWriter::writeImpl(ProfOStream &OS) {
559593
using namespace IndexedInstrProf;
560594
using namespace support;
@@ -578,19 +612,8 @@ Error InstrProfWriter::writeImpl(ProfOStream &OS) {
578612
// Write the header.
579613
IndexedInstrProf::Header Header;
580614
Header.Magic = IndexedInstrProf::Magic;
581-
Header.Version = WritePrevVersion
582-
? IndexedInstrProf::ProfVersion::Version12
583-
: IndexedInstrProf::ProfVersion::CurrentVersion;
584-
if (WriteFutureVersion)
585-
Header.Version = 14;
586-
// The WritePrevVersion handling will either need to be removed or updated
587-
// if the version is advanced beyond 13.
588-
// Starting from version 13, an indexed profile records the on-disk
589-
// byte size of header at a fixed byte offset. Compilers or tools built
590-
// starting from this version can read the on-disk byte size (as opposed to
591-
// relying on struct definition of Header) to locate the start of payload
592-
// sections.
593-
// FIXME: Clean up WritePrevVersion later.
615+
Header.Version = profileVersion();
616+
594617
assert(IndexedInstrProf::ProfVersion::CurrentVersion ==
595618
IndexedInstrProf::ProfVersion::Version13);
596619
if (static_cast<bool>(ProfileKind & InstrProfKind::IRInstrumentation))
@@ -647,24 +670,26 @@ Error InstrProfWriter::writeImpl(ProfOStream &OS) {
647670
uint64_t VTableNamesOffset = OS.tell();
648671
OS.write(0);
649672

650-
// Record the offset of 'Header::Size' field and reserve the space to allow
651-
// back patching later.
673+
// Record the offset of 'Header::Size' field.
652674
const uint64_t OnDiskHeaderSizeOffset = OS.tell();
653-
if (!WritePrevVersion)
675+
676+
if (!WritePrevVersion) {
677+
// Reserve the space for `OnDiskByteSize` to allow back patching later.
654678
OS.write(0);
655679

656-
// Write a dummy value
657-
if (WriteFutureVersion)
680+
// Write the minimum compatible version required.
681+
OS.write(minProfileReaderVersion());
682+
}
683+
684+
// This is a test-only path to append dummy header fields.
685+
// NOTE: please write all other header fields before this one.
686+
if (AppendAdditionalHeaderFields)
658687
OS.write(0);
659688

660689
// Record the OnDiskHeader Size after each header field either gets written or
661690
// gets its space reserved.
662691
uint64_t OnDiskHeaderSize = OS.tell() - StartOffset;
663692

664-
llvm::errs() << "OnDiskHeaderSize is " << OnDiskHeaderSize << "\n";
665-
fflush(stdout);
666-
fflush(stderr);
667-
668693
// Reserve space to write profile summary data.
669694
uint32_t NumEntries = ProfileSummaryBuilder::DefaultCutoffs.size();
670695
uint32_t SummarySize = Summary::getSize(Summary::NumKinds, NumEntries);

0 commit comments

Comments
 (0)