[llvm] Introduction of typified section in ExtBinary format (PR #166553)
Sergey Shcherbinin via llvm-commits
llvm-commits at lists.llvm.org
Sat Aug 1 12:49:51 PDT 2026
https://github.com/SergeyShch01 updated https://github.com/llvm/llvm-project/pull/166553
>From 53d1fedb59040aade8e558227b3eb04c6f582b69 Mon Sep 17 00:00:00 2001
From: Sergey Shcherbinin <sscherbinin at nvidia.com>
Date: Wed, 5 Nov 2025 17:12:18 +0400
Subject: [PATCH 01/10] Introduction of typified section in ExtBinary format
---
llvm/include/llvm/ProfileData/SampleProf.h | 28 ++++++-
.../llvm/ProfileData/SampleProfReader.h | 5 ++
.../llvm/ProfileData/SampleProfWriter.h | 15 ++++
llvm/lib/ProfileData/SampleProfReader.cpp | 51 +++++++++++-
llvm/lib/ProfileData/SampleProfWriter.cpp | 82 ++++++++++++++++---
.../llvm-profdata/sample-flatten-profile.test | 2 +
6 files changed, 168 insertions(+), 15 deletions(-)
diff --git a/llvm/include/llvm/ProfileData/SampleProf.h b/llvm/include/llvm/ProfileData/SampleProf.h
index c409b4293ee8d..1280fb5b34e3e 100644
--- a/llvm/include/llvm/ProfileData/SampleProf.h
+++ b/llvm/include/llvm/ProfileData/SampleProf.h
@@ -147,9 +147,13 @@ enum SecType {
SecFuncOffsetTable = 4,
SecFuncMetadata = 5,
SecCSNameTable = 6,
+ // Substitution to SecFuncOffsetTable when we have non-LBR profile types
+ SecTypifiedFuncOffsetTable = 7,
// marker for the first type of profile.
SecFuncProfileFirst = 32,
- SecLBRProfile = SecFuncProfileFirst
+ SecLBRProfile = SecFuncProfileFirst,
+ // Substitution to SecLBRProfile when we have non-LBR profile types
+ SecTypifiedProfile = 33
};
static inline std::string getSecName(SecType Type) {
@@ -168,13 +172,20 @@ static inline std::string getSecName(SecType Type) {
return "FunctionMetadata";
case SecCSNameTable:
return "CSNameTableSection";
+ case SecTypifiedFuncOffsetTable:
+ return "TypifiedFuncOffsetTableSection";
case SecLBRProfile:
return "LBRProfileSection";
+ case SecTypifiedProfile:
+ return "TypifiedProfileSection";
default:
return "UnknownSection";
}
}
+// Types of sample profile which can be in placed in SecTypifiedProfile
+enum ProfTypes { ProfTypeLBR = 0, ProfTypeNum };
+
// Entry type of section header table used by SampleProfileExtBinaryBaseReader
// and SampleProfileExtBinaryBaseWriter.
struct SecHdrTableEntry {
@@ -1398,6 +1409,12 @@ class FunctionSamples {
return !(*this == Other);
}
+ bool hasNonLBRSamples() const {
+ // currently just a stub - should be implemented when
+ // first non-LBR profile is encountered
+ return false;
+ }
+
private:
/// CFG hash value for the function.
uint64_t FunctionHash = 0;
@@ -1523,6 +1540,15 @@ class SampleProfileMap
size_t erase(const key_type &Key) { return base_type::erase(Key); }
iterator erase(iterator It) { return base_type::erase(It); }
+
+ bool hasNonLBRProfile() const {
+ for (const auto &[Context, FuncSamples] : *this) {
+ if (FuncSamples.hasNonLBRSamples()) {
+ return true;
+ }
+ }
+ return false;
+ }
};
using NameFunctionSamples = std::pair<hash_code, const FunctionSamples *>;
diff --git a/llvm/include/llvm/ProfileData/SampleProfReader.h b/llvm/include/llvm/ProfileData/SampleProfReader.h
index 0003d06e60373..eca32d1ab72e7 100644
--- a/llvm/include/llvm/ProfileData/SampleProfReader.h
+++ b/llvm/include/llvm/ProfileData/SampleProfReader.h
@@ -701,6 +701,7 @@ class SampleProfileReader {
FuncMetadataIndex;
std::pair<const uint8_t *, const uint8_t *> ProfileSecRange;
+ bool IsProfileTypified = false;
/// Whether the profile has attribute metadata.
bool ProfileHasAttribute = false;
@@ -825,6 +826,10 @@ class LLVM_ABI SampleProfileReaderBinary : public SampleProfileReader {
/// Read the contents of the given profile instance.
std::error_code readProfile(FunctionSamples &FProfile);
+ /// Read specific profile types.
+ std::error_code readLBRProfile(FunctionSamples &FProfile);
+ std::error_code readTypifiedProfile(FunctionSamples &FProfile);
+
/// Read the contents of Magic number and Version number.
std::error_code readMagicIdent();
diff --git a/llvm/include/llvm/ProfileData/SampleProfWriter.h b/llvm/include/llvm/ProfileData/SampleProfWriter.h
index ef368094d5889..e39402029d392 100644
--- a/llvm/include/llvm/ProfileData/SampleProfWriter.h
+++ b/llvm/include/llvm/ProfileData/SampleProfWriter.h
@@ -224,6 +224,12 @@ class LLVM_ABI SampleProfileWriterBinary : public SampleProfileWriter {
virtual std::error_code writeContextIdx(const SampleContext &Context);
std::error_code writeNameIdx(FunctionId FName);
std::error_code writeBody(const FunctionSamples &S);
+ void writeLBRProfile(const FunctionSamples &S);
+ /// Writes typified profile for function
+ void writeTypifiedProfile(const FunctionSamples &S);
+
+ inline void stablizeNameTable(MapVector<FunctionId, uint32_t> &NameTable,
+ std::set<FunctionId> &V);
MapVector<FunctionId, uint32_t> NameTable;
@@ -237,6 +243,7 @@ class LLVM_ABI SampleProfileWriterBinary : public SampleProfileWriter {
raw_ostream &OS);
bool WriteVTableProf = false;
+ bool WriteTypifiedProf = false;
private:
LLVM_ABI friend ErrorOr<std::unique_ptr<SampleProfileWriter>>
@@ -485,6 +492,9 @@ class LLVM_ABI SampleProfileWriterExtBinary
std::error_code writeSections(const SampleProfileMap &ProfileMap) override;
+ // Configure whether to use typified profile sections
+ void configureTypifiedProfile(const SampleProfileMap &ProfileMap);
+
std::error_code writeCustomSection(SecType Type) override {
return sampleprof_error::success;
};
@@ -493,6 +503,11 @@ class LLVM_ABI SampleProfileWriterExtBinary
assert((SL == DefaultLayout || SL == CtxSplitLayout) &&
"Unsupported layout");
}
+
+ /// Section types for profile storage and bookkeeping (used to switch between
+ /// typified and non-typified profiles).
+ SecType ProfSection = SecLBRProfile;
+ SecType FuncOffsetSection = SecFuncOffsetTable;
};
} // end namespace sampleprof
diff --git a/llvm/lib/ProfileData/SampleProfReader.cpp b/llvm/lib/ProfileData/SampleProfReader.cpp
index f7e291729a5d9..1ded32cd18b5f 100644
--- a/llvm/lib/ProfileData/SampleProfReader.cpp
+++ b/llvm/lib/ProfileData/SampleProfReader.cpp
@@ -717,7 +717,7 @@ SampleProfileReaderBinary::readCallsiteVTableProf(FunctionSamples &FProfile) {
}
std::error_code
-SampleProfileReaderBinary::readProfile(FunctionSamples &FProfile) {
+SampleProfileReaderBinary::readLBRProfile(FunctionSamples &FProfile) {
auto NumSamples = readNumber<uint64_t>();
if (std::error_code EC = NumSamples.getError())
return EC;
@@ -768,6 +768,52 @@ SampleProfileReaderBinary::readProfile(FunctionSamples &FProfile) {
FProfile.addBodySamples(*LineOffset, DiscriminatorVal, *NumSamples);
}
+ return sampleprof_error::success;
+}
+
+std::error_code
+SampleProfileReaderBinary::readTypifiedProfile(FunctionSamples &FProfile) {
+ // read the number of profile types
+ auto ProfNum = readNumber<uint64_t>();
+ if (std::error_code EC = ProfNum.getError())
+ return EC;
+
+ // read specified number of typified profiles
+ for (uint64_t i = 0; i < *ProfNum; i++) {
+ auto Type = readNumber<uint64_t>();
+ if (std::error_code EC = Type.getError())
+ return EC;
+ auto Size = readUnencodedNumber<uint64_t>();
+ if (std::error_code EC = Size.getError())
+ return EC;
+
+ switch (*Type) {
+ case ProfTypeLBR:
+ if (std::error_code EC = readLBRProfile(FProfile))
+ return EC;
+ break;
+ default:
+ // skip unknown profile type for forward compatibility
+ Data += *Size;
+ if (Data > End)
+ return sampleprof_error::truncated;
+ break;
+ }
+ }
+
+ return sampleprof_error::success;
+}
+
+std::error_code
+SampleProfileReaderBinary::readProfile(FunctionSamples &FProfile) {
+ if (IsProfileTypified) {
+ if (std::error_code EC = readTypifiedProfile(FProfile))
+ return EC;
+ } else {
+ if (std::error_code EC = readLBRProfile(FProfile))
+ return EC;
+ }
+
// Read all the samples for inlined function calls.
auto NumCallsites = readNumber<uint32_t>();
if (std::error_code EC = NumCallsites.getError())
@@ -885,11 +931,14 @@ std::error_code SampleProfileReaderExtBinaryBase::readOneSection(
break;
}
case SecLBRProfile:
+ case SecTypifiedProfile:
ProfileSecRange = std::make_pair(Data, End);
+ IsProfileTypified = Entry.Type == SecTypifiedProfile;
if (std::error_code EC = readFuncProfiles())
return EC;
break;
case SecFuncOffsetTable:
+ case SecTypifiedFuncOffsetTable:
// If module is absent, we are using LLVM tools, and need to read all
// profiles, so skip reading the function offset table.
if (!M) {
diff --git a/llvm/lib/ProfileData/SampleProfWriter.cpp b/llvm/lib/ProfileData/SampleProfWriter.cpp
index e17b080659927..98699039a2397 100644
--- a/llvm/lib/ProfileData/SampleProfWriter.cpp
+++ b/llvm/lib/ProfileData/SampleProfWriter.cpp
@@ -60,6 +60,10 @@ static cl::opt<bool> WriteEytzingerNameTables(
"sample-profile-write-eytzinger-name-tables", cl::init(false), cl::Hidden,
cl::desc("Write Eytzinger 3-span layout for NameTable"));
+static cl::opt<bool> ExtBinaryForceTypifiedProf(
+ "extbinary-force-typified-prof", cl::init(false), cl::Hidden,
+ cl::desc("Force utilization of typified profile format"));
+
namespace llvm {
namespace support {
namespace endian {
@@ -602,11 +606,13 @@ std::error_code SampleProfileWriterExtBinaryBase::writeOneSection(
return EC;
break;
case SecLBRProfile:
+ case SecTypifiedProfile:
SecLBRProfileStart = OutputStream->tell();
if (std::error_code EC = writeFuncProfiles(ProfileMap))
return EC;
break;
case SecFuncOffsetTable:
+ case SecTypifiedFuncOffsetTable:
if (auto EC = writeFuncOffsetTable())
return EC;
break;
@@ -646,11 +652,11 @@ std::error_code SampleProfileWriterExtBinary::writeDefaultLayout(
return EC;
if (auto EC = writeOneSection(SecCSNameTable, 2, ProfileMap))
return EC;
- if (auto EC = writeOneSection(SecLBRProfile, 4, ProfileMap))
+ if (auto EC = writeOneSection(ProfSection, 4, ProfileMap))
return EC;
if (auto EC = writeOneSection(SecProfileSymbolList, 5, ProfileMap))
return EC;
- if (auto EC = writeOneSection(SecFuncOffsetTable, 3, ProfileMap))
+ if (auto EC = writeOneSection(FuncOffsetSection, 3, ProfileMap))
return EC;
if (auto EC = writeOneSection(SecFuncMetadata, 6, ProfileMap))
return EC;
@@ -672,24 +678,23 @@ std::error_code SampleProfileWriterExtBinary::writeCtxSplitLayout(
const SampleProfileMap &ProfileMap) {
SampleProfileMap ContextProfileMap, NoContextProfileMap;
splitProfileMapToTwo(ProfileMap, ContextProfileMap, NoContextProfileMap);
-
if (auto EC = writeOneSection(SecProfSummary, 0, ProfileMap))
return EC;
if (auto EC = writeOneSection(SecNameTable, 1, ProfileMap))
return EC;
- if (auto EC = writeOneSection(SecLBRProfile, 3, ContextProfileMap))
+ if (auto EC = writeOneSection(ProfSection, 3, ContextProfileMap))
return EC;
- if (auto EC = writeOneSection(SecFuncOffsetTable, 2, ContextProfileMap))
+ if (auto EC = writeOneSection(FuncOffsetSection, 2, ContextProfileMap))
return EC;
// Mark the section to have no context. Note section flag needs to be set
// before writing the section.
addSectionFlag(5, SecCommonFlags::SecFlagFlat);
- if (auto EC = writeOneSection(SecLBRProfile, 5, NoContextProfileMap))
+ if (auto EC = writeOneSection(ProfSection, 5, NoContextProfileMap))
return EC;
// Mark the section to have no context. Note section flag needs to be set
// before writing the section.
addSectionFlag(4, SecCommonFlags::SecFlagFlat);
- if (auto EC = writeOneSection(SecFuncOffsetTable, 4, NoContextProfileMap))
+ if (auto EC = writeOneSection(FuncOffsetSection, 4, NoContextProfileMap))
return EC;
if (auto EC = writeOneSection(SecProfileSymbolList, 6, ProfileMap))
return EC;
@@ -699,8 +704,30 @@ std::error_code SampleProfileWriterExtBinary::writeCtxSplitLayout(
return sampleprof_error::success;
}
+void SampleProfileWriterExtBinary::configureTypifiedProfile(
+ const SampleProfileMap &ProfileMap) {
+ if (!ExtBinaryForceTypifiedProf && !ProfileMap.hasNonLBRProfile()) {
+ WriteTypifiedProf = false;
+ ProfSection = SecLBRProfile;
+ FuncOffsetSection = SecFuncOffsetTable;
+ return;
+ }
+ // Use typified profile sections: directly change the section types
+ // to avoid duplicating the whole layout and its handling.
+ WriteTypifiedProf = true;
+ FuncOffsetSection = SecTypifiedFuncOffsetTable;
+ ProfSection = SecTypifiedProfile;
+ for (auto &Entry : SectionHdrLayout) {
+ if (Entry.Type == SecFuncOffsetTable)
+ Entry.Type = SecTypifiedFuncOffsetTable;
+ else if (Entry.Type == SecLBRProfile)
+ Entry.Type = SecTypifiedProfile;
+ }
+}
+
std::error_code SampleProfileWriterExtBinary::writeSections(
const SampleProfileMap &ProfileMap) {
+ configureTypifiedProfile(ProfileMap);
std::error_code EC;
if (SecLayout == DefaultLayout)
EC = writeDefaultLayout(ProfileMap);
@@ -1015,14 +1042,10 @@ std::error_code SampleProfileWriterBinary::writeSummary() {
}
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;
+void SampleProfileWriterBinary::writeLBRProfile(const FunctionSamples &S) {
+ auto &OS = *OutputStream;
encodeULEB128(S.getTotalSamples(), OS);
-
- // Emit all the body samples.
encodeULEB128(S.getBodySamples().size(), OS);
for (const auto &I : S.getBodySamples()) {
LineLocation Loc = I.first;
@@ -1030,6 +1053,39 @@ std::error_code SampleProfileWriterBinary::writeBody(const FunctionSamples &S) {
Loc.serialize(OS);
Sample.serialize(OS, getNameTable());
}
+}
+
+void SampleProfileWriterBinary::writeTypifiedProfile(const FunctionSamples &S) {
+ // Currently only LBR profile is supported as typified profile.
+ auto &OS = *OutputStream;
+ // write the number of profile types for function
+ encodeULEB128(1, OS);
+ // Start first profile writing: write profile type
+ encodeULEB128(ProfTypeLBR, OS);
+ // create placeholder for profile size
+ uint64_t SizeOffset = OS.tell();
+ support::endian::Writer PlaceWriter(OS, llvm::endianness::little);
+ PlaceWriter.write(static_cast<uint64_t>(-1));
+ // write the profile itself
+ uint64_t BodyStart = OS.tell();
+ writeLBRProfile(S);
+ uint64_t BodySize = OS.tell() - BodyStart;
+ // write profile size
+ support::endian::SeekableWriter PWriter(static_cast<raw_pwrite_stream &>(OS),
+ llvm::endianness::little);
+ PWriter.pwrite(BodySize, SizeOffset);
+}
+
+std::error_code SampleProfileWriterBinary::writeBody(const FunctionSamples &S) {
+ auto &OS = *OutputStream;
+ if (std::error_code EC = writeContextIdx(S.getContext()))
+ return EC;
+
+ // Emit all the body samples.
+ if (WriteTypifiedProf)
+ writeTypifiedProfile(S);
+ else
+ writeLBRProfile(S);
// Recursively emit all the callsite samples.
uint64_t NumCallsites = 0;
diff --git a/llvm/test/tools/llvm-profdata/sample-flatten-profile.test b/llvm/test/tools/llvm-profdata/sample-flatten-profile.test
index f99021bc6b723..34dab716a067f 100644
--- a/llvm/test/tools/llvm-profdata/sample-flatten-profile.test
+++ b/llvm/test/tools/llvm-profdata/sample-flatten-profile.test
@@ -1,8 +1,10 @@
; RUN: llvm-profdata merge --sample --convert-sample-profile-layout=flat --text %S/Inputs/sample-flatten-profile.proftext -o - | FileCheck %s --match-full-lines --strict-whitespace
; RUN: llvm-profdata merge --sample --extbinary %S/Inputs/sample-flatten-profile.proftext -o %t2 && llvm-profdata merge --sample --convert-sample-profile-layout=flat --text %t2 -o - | FileCheck %s --match-full-lines --strict-whitespace
+; RUN: llvm-profdata merge --sample --extbinary-force-typified-prof --extbinary %S/Inputs/sample-flatten-profile.proftext -o %t2 && llvm-profdata merge --sample --convert-sample-profile-layout=flat --text %t2 -o - | FileCheck %s --match-full-lines --strict-whitespace
; RUN: llvm-profdata merge --sample --convert-sample-profile-layout=flat --text %S/Inputs/sample-flatten-profile-cs.proftext -o - | FileCheck %s --match-full-lines --strict-whitespace --check-prefix=CHECK-CS
; RUN: llvm-profdata merge --sample --extbinary %S/Inputs/sample-flatten-profile-cs.proftext -o %t2 && llvm-profdata merge --sample --convert-sample-profile-layout=flat --text %t2 -o - | FileCheck %s --match-full-lines --strict-whitespace --check-prefix=CHECK-CS
+; RUN: llvm-profdata merge --sample --extbinary-force-typified-prof --extbinary %S/Inputs/sample-flatten-profile-cs.proftext -o %t2 && llvm-profdata merge --sample --convert-sample-profile-layout=flat --text %t2 -o - | FileCheck %s --match-full-lines --strict-whitespace --check-prefix=CHECK-CS
; CHECK:baz:169:10
; CHECK-NEXT: 1: 10
>From 3bec3647b8d652a44c5b5a524d3aa1b94e4569a1 Mon Sep 17 00:00:00 2001
From: Sergey Shcherbinin <sscherbinin at nvidia.com>
Date: Tue, 11 Nov 2025 13:25:02 +0400
Subject: [PATCH 02/10] Typo fixed
Co-authored-by: Mingming Liu <minglotus6 at gmail.com>
---
llvm/include/llvm/ProfileData/SampleProf.h | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/llvm/include/llvm/ProfileData/SampleProf.h b/llvm/include/llvm/ProfileData/SampleProf.h
index 1280fb5b34e3e..bbe7e37e47717 100644
--- a/llvm/include/llvm/ProfileData/SampleProf.h
+++ b/llvm/include/llvm/ProfileData/SampleProf.h
@@ -183,7 +183,7 @@ static inline std::string getSecName(SecType Type) {
}
}
-// Types of sample profile which can be in placed in SecTypifiedProfile
+// Types of sample profile which can be placed in SecTypifiedProfile
enum ProfTypes { ProfTypeLBR = 0, ProfTypeNum };
// Entry type of section header table used by SampleProfileExtBinaryBaseReader
>From 2fc5d1ae6ac10542485b494e21c0f8e2c91c9996 Mon Sep 17 00:00:00 2001
From: Sergey Shcherbinin <sscherbinin at nvidia.com>
Date: Tue, 11 Nov 2025 14:39:46 +0400
Subject: [PATCH 03/10] Small additional changes
+ Comments LLVM-fication
+ Typified profile writing is split into several interfaces to support future introduction of new profile types
---
llvm/include/llvm/ProfileData/SampleProf.h | 4 +-
.../llvm/ProfileData/SampleProfWriter.h | 8 +++-
llvm/lib/ProfileData/SampleProfReader.cpp | 2 +-
llvm/lib/ProfileData/SampleProfWriter.cpp | 46 ++++++++++++++-----
4 files changed, 43 insertions(+), 17 deletions(-)
diff --git a/llvm/include/llvm/ProfileData/SampleProf.h b/llvm/include/llvm/ProfileData/SampleProf.h
index bbe7e37e47717..f44cc8eb90a82 100644
--- a/llvm/include/llvm/ProfileData/SampleProf.h
+++ b/llvm/include/llvm/ProfileData/SampleProf.h
@@ -1410,8 +1410,8 @@ class FunctionSamples {
}
bool hasNonLBRSamples() const {
- // currently just a stub - should be implemented when
- // first non-LBR profile is encountered
+ // Currently just a stub - should be implemented when
+ // first non-LBR profile is encountered.
return false;
}
diff --git a/llvm/include/llvm/ProfileData/SampleProfWriter.h b/llvm/include/llvm/ProfileData/SampleProfWriter.h
index e39402029d392..7779c8d8c4b31 100644
--- a/llvm/include/llvm/ProfileData/SampleProfWriter.h
+++ b/llvm/include/llvm/ProfileData/SampleProfWriter.h
@@ -225,8 +225,12 @@ class LLVM_ABI SampleProfileWriterBinary : public SampleProfileWriter {
std::error_code writeNameIdx(FunctionId FName);
std::error_code writeBody(const FunctionSamples &S);
void writeLBRProfile(const FunctionSamples &S);
- /// Writes typified profile for function
+
+ /// Interfaces for typified profile writing.
void writeTypifiedProfile(const FunctionSamples &S);
+ std::pair<uint64_t, uint64_t> startProfileType(ProfTypes Type);
+ void finishProfileType(uint64_t SizeOffset, uint64_t BodyOffset);
+ void writeTypifiedLBRProfile(const FunctionSamples &S);
inline void stablizeNameTable(MapVector<FunctionId, uint32_t> &NameTable,
std::set<FunctionId> &V);
@@ -492,7 +496,7 @@ class LLVM_ABI SampleProfileWriterExtBinary
std::error_code writeSections(const SampleProfileMap &ProfileMap) override;
- // Configure whether to use typified profile sections
+ // Configure whether to use typified profile sections.
void configureTypifiedProfile(const SampleProfileMap &ProfileMap);
std::error_code writeCustomSection(SecType Type) override {
diff --git a/llvm/lib/ProfileData/SampleProfReader.cpp b/llvm/lib/ProfileData/SampleProfReader.cpp
index 1ded32cd18b5f..898bd8888d5d4 100644
--- a/llvm/lib/ProfileData/SampleProfReader.cpp
+++ b/llvm/lib/ProfileData/SampleProfReader.cpp
@@ -773,7 +773,7 @@ SampleProfileReaderBinary::readLBRProfile(FunctionSamples &FProfile) {
std::error_code
SampleProfileReaderBinary::readTypifiedProfile(FunctionSamples &FProfile) {
- // read the number of profile types
+ // Read the number of profile types.
auto ProfNum = readNumber<uint64_t>();
if (std::error_code EC = ProfNum.getError())
return EC;
diff --git a/llvm/lib/ProfileData/SampleProfWriter.cpp b/llvm/lib/ProfileData/SampleProfWriter.cpp
index 98699039a2397..3d668aef616d2 100644
--- a/llvm/lib/ProfileData/SampleProfWriter.cpp
+++ b/llvm/lib/ProfileData/SampleProfWriter.cpp
@@ -1055,27 +1055,49 @@ void SampleProfileWriterBinary::writeLBRProfile(const FunctionSamples &S) {
}
}
-void SampleProfileWriterBinary::writeTypifiedProfile(const FunctionSamples &S) {
- // Currently only LBR profile is supported as typified profile.
+std::pair<uint64_t, uint64_t>
+SampleProfileWriterBinary::startProfileType(ProfTypes Type) {
auto &OS = *OutputStream;
- // write the number of profile types for function
- encodeULEB128(1, OS);
- // Start first profile writing: write profile type
- encodeULEB128(ProfTypeLBR, OS);
- // create placeholder for profile size
+ encodeULEB128(Type, OS);
+ // Create placeholder for profile size.
uint64_t SizeOffset = OS.tell();
support::endian::Writer PlaceWriter(OS, llvm::endianness::little);
PlaceWriter.write(static_cast<uint64_t>(-1));
- // write the profile itself
- uint64_t BodyStart = OS.tell();
- writeLBRProfile(S);
- uint64_t BodySize = OS.tell() - BodyStart;
- // write profile size
+ uint64_t BodyOffset = OS.tell();
+ return {SizeOffset, BodyOffset};
+}
+
+void SampleProfileWriterBinary::finishProfileType(uint64_t SizeOffset,
+ uint64_t BodyOffset) {
+ auto &OS = *OutputStream;
+ uint64_t BodySize = OS.tell() - BodyOffset;
+ // Write profile size.
support::endian::SeekableWriter PWriter(static_cast<raw_pwrite_stream &>(OS),
llvm::endianness::little);
PWriter.pwrite(BodySize, SizeOffset);
}
+void SampleProfileWriterBinary::writeTypifiedLBRProfile(
+ const FunctionSamples &S) {
+ auto [SizeOffset, BodyOffset] = startProfileType(ProfTypeLBR);
+ writeLBRProfile(S);
+ finishProfileType(SizeOffset, BodyOffset);
+}
+
+void SampleProfileWriterBinary::writeTypifiedProfile(const FunctionSamples &S) {
+ auto &OS = *OutputStream;
+ bool WriteLBRProf = !S.getBodySamples().empty();
+ // Other profile types should be added here.
+ uint32_t TypesNum = WriteLBRProf;
+ assert(TypesNum && "Empty function samples");
+
+ // Write the number of profile types for function.
+ encodeULEB128(TypesNum, OS);
+
+ if (WriteLBRProf)
+ writeTypifiedLBRProfile(S);
+}
+
std::error_code SampleProfileWriterBinary::writeBody(const FunctionSamples &S) {
auto &OS = *OutputStream;
if (std::error_code EC = writeContextIdx(S.getContext()))
>From 3c60a04bb165bad0a2c90e37fa2ca2ec55fd9ed7 Mon Sep 17 00:00:00 2001
From: Sergey Shcherbinin <sscherbinin at nvidia.com>
Date: Tue, 11 Nov 2025 18:34:29 +0400
Subject: [PATCH 04/10] Wrong assertion removed
---
llvm/lib/ProfileData/SampleProfWriter.cpp | 1 -
1 file changed, 1 deletion(-)
diff --git a/llvm/lib/ProfileData/SampleProfWriter.cpp b/llvm/lib/ProfileData/SampleProfWriter.cpp
index 3d668aef616d2..eab08504d3750 100644
--- a/llvm/lib/ProfileData/SampleProfWriter.cpp
+++ b/llvm/lib/ProfileData/SampleProfWriter.cpp
@@ -1089,7 +1089,6 @@ void SampleProfileWriterBinary::writeTypifiedProfile(const FunctionSamples &S) {
bool WriteLBRProf = !S.getBodySamples().empty();
// Other profile types should be added here.
uint32_t TypesNum = WriteLBRProf;
- assert(TypesNum && "Empty function samples");
// Write the number of profile types for function.
encodeULEB128(TypesNum, OS);
>From e25ac19a8cd40064ed48170c809d4ce92bc96449 Mon Sep 17 00:00:00 2001
From: Sergey Shcherbinin <sscherbinin at nvidia.com>
Date: Wed, 12 Nov 2025 18:58:09 +0400
Subject: [PATCH 05/10] HeadSamples value is moved to typified LBR profile
sub-section
---
.../llvm/ProfileData/SampleProfReader.h | 6 +--
.../llvm/ProfileData/SampleProfWriter.h | 8 ++--
llvm/lib/ProfileData/SampleProfReader.cpp | 38 ++++++++++++-------
llvm/lib/ProfileData/SampleProfWriter.cpp | 30 +++++++++------
4 files changed, 50 insertions(+), 32 deletions(-)
diff --git a/llvm/include/llvm/ProfileData/SampleProfReader.h b/llvm/include/llvm/ProfileData/SampleProfReader.h
index eca32d1ab72e7..55c6c47e3e3ad 100644
--- a/llvm/include/llvm/ProfileData/SampleProfReader.h
+++ b/llvm/include/llvm/ProfileData/SampleProfReader.h
@@ -824,11 +824,11 @@ class LLVM_ABI SampleProfileReaderBinary : public SampleProfileReader {
SampleProfileMap &Profiles);
/// Read the contents of the given profile instance.
- std::error_code readProfile(FunctionSamples &FProfile);
+ std::error_code readProfile(FunctionSamples &FProfile, bool IsNested);
/// Read specific profile types.
- std::error_code readLBRProfile(FunctionSamples &FProfile);
- std::error_code readTypifiedProfile(FunctionSamples &FProfile);
+ std::error_code readLBRProfile(FunctionSamples &FProfile, bool IsNested);
+ std::error_code readTypifiedProfile(FunctionSamples &FProfile, bool IsNested);
/// Read the contents of Magic number and Version number.
std::error_code readMagicIdent();
diff --git a/llvm/include/llvm/ProfileData/SampleProfWriter.h b/llvm/include/llvm/ProfileData/SampleProfWriter.h
index 7779c8d8c4b31..375d876cba41b 100644
--- a/llvm/include/llvm/ProfileData/SampleProfWriter.h
+++ b/llvm/include/llvm/ProfileData/SampleProfWriter.h
@@ -223,14 +223,14 @@ class LLVM_ABI SampleProfileWriterBinary : public SampleProfileWriter {
std::error_code writeSummary();
virtual std::error_code writeContextIdx(const SampleContext &Context);
std::error_code writeNameIdx(FunctionId FName);
- std::error_code writeBody(const FunctionSamples &S);
- void writeLBRProfile(const FunctionSamples &S);
+ std::error_code writeBody(const FunctionSamples &S, bool IsNested);
+ void writeLBRProfile(const FunctionSamples &S, bool IsNested);
/// Interfaces for typified profile writing.
- void writeTypifiedProfile(const FunctionSamples &S);
+ void writeTypifiedProfile(const FunctionSamples &S, bool IsNested);
std::pair<uint64_t, uint64_t> startProfileType(ProfTypes Type);
void finishProfileType(uint64_t SizeOffset, uint64_t BodyOffset);
- void writeTypifiedLBRProfile(const FunctionSamples &S);
+ void writeTypifiedLBRProfile(const FunctionSamples &S, bool IsNested);
inline void stablizeNameTable(MapVector<FunctionId, uint32_t> &NameTable,
std::set<FunctionId> &V);
diff --git a/llvm/lib/ProfileData/SampleProfReader.cpp b/llvm/lib/ProfileData/SampleProfReader.cpp
index 898bd8888d5d4..01c111a2f53ea 100644
--- a/llvm/lib/ProfileData/SampleProfReader.cpp
+++ b/llvm/lib/ProfileData/SampleProfReader.cpp
@@ -717,7 +717,14 @@ SampleProfileReaderBinary::readCallsiteVTableProf(FunctionSamples &FProfile) {
}
std::error_code
-SampleProfileReaderBinary::readLBRProfile(FunctionSamples &FProfile) {
+SampleProfileReaderBinary::readLBRProfile(FunctionSamples &FProfile,
+ bool IsNested) {
+ if (IsProfileTypified && !IsNested) {
+ auto NumHeadSamples = readNumber<uint64_t>();
+ if (std::error_code EC = NumHeadSamples.getError())
+ return EC;
+ FProfile.addHeadSamples(*NumHeadSamples);
+ }
auto NumSamples = readNumber<uint64_t>();
if (std::error_code EC = NumSamples.getError())
return EC;
@@ -772,7 +779,8 @@ SampleProfileReaderBinary::readLBRProfile(FunctionSamples &FProfile) {
}
std::error_code
-SampleProfileReaderBinary::readTypifiedProfile(FunctionSamples &FProfile) {
+SampleProfileReaderBinary::readTypifiedProfile(FunctionSamples &FProfile,
+ bool IsNested) {
// Read the number of profile types.
auto ProfNum = readNumber<uint64_t>();
if (std::error_code EC = ProfNum.getError())
@@ -789,7 +797,7 @@ SampleProfileReaderBinary::readTypifiedProfile(FunctionSamples &FProfile) {
switch (*Type) {
case ProfTypeLBR:
- if (std::error_code EC = readLBRProfile(FProfile))
+ if (std::error_code EC = readLBRProfile(FProfile, IsNested))
return EC;
break;
default:
@@ -805,12 +813,13 @@ SampleProfileReaderBinary::readTypifiedProfile(FunctionSamples &FProfile) {
}
std::error_code
-SampleProfileReaderBinary::readProfile(FunctionSamples &FProfile) {
+SampleProfileReaderBinary::readProfile(FunctionSamples &FProfile,
+ bool IsNested) {
if (IsProfileTypified) {
- if (std::error_code EC = readTypifiedProfile(FProfile))
+ if (std::error_code EC = readTypifiedProfile(FProfile, IsNested))
return EC;
} else {
- if (std::error_code EC = readLBRProfile(FProfile))
+ if (std::error_code EC = readLBRProfile(FProfile, IsNested))
return EC;
}
@@ -838,7 +847,7 @@ SampleProfileReaderBinary::readProfile(FunctionSamples &FProfile) {
FunctionSamples &CalleeProfile = FProfile.functionSamplesAt(
LineLocation(*LineOffset, DiscriminatorVal))[*FName];
CalleeProfile.setFunction(*FName);
- if (std::error_code EC = readProfile(CalleeProfile))
+ if (std::error_code EC = readProfile(CalleeProfile, true))
return EC;
}
@@ -852,10 +861,12 @@ std::error_code
SampleProfileReaderBinary::readFuncProfile(const uint8_t *Start,
SampleProfileMap &Profiles) {
Data = Start;
- auto NumHeadSamples = readNumber<uint64_t>();
- if (std::error_code EC = NumHeadSamples.getError())
- return EC;
-
+ ErrorOr<uint64_t> NumHeadSamples = 0;
+ if (!IsProfileTypified) {
+ NumHeadSamples = readNumber<uint64_t>();
+ if (std::error_code EC = NumHeadSamples.getError())
+ return EC;
+ }
auto FContextHash(readSampleContextFromTable());
if (std::error_code EC = FContextHash.getError())
return EC;
@@ -865,12 +876,13 @@ SampleProfileReaderBinary::readFuncProfile(const uint8_t *Start,
auto Res = Profiles.try_emplace(Hash, FContext, FunctionSamples());
FunctionSamples &FProfile = Res.first->second;
FProfile.setContext(FContext);
- FProfile.addHeadSamples(*NumHeadSamples);
+ if (!IsProfileTypified)
+ FProfile.addHeadSamples(*NumHeadSamples);
if (FContext.hasContext())
CSProfileCount++;
- if (std::error_code EC = readProfile(FProfile))
+ if (std::error_code EC = readProfile(FProfile, false))
return EC;
return sampleprof_error::success;
}
diff --git a/llvm/lib/ProfileData/SampleProfWriter.cpp b/llvm/lib/ProfileData/SampleProfWriter.cpp
index eab08504d3750..4eef406e3087c 100644
--- a/llvm/lib/ProfileData/SampleProfWriter.cpp
+++ b/llvm/lib/ProfileData/SampleProfWriter.cpp
@@ -277,8 +277,9 @@ 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);
+ if (!WriteTypifiedProf)
+ encodeULEB128(S.getHeadSamples(), *OutputStream);
+ return writeBody(S, false);
}
std::error_code SampleProfileWriterExtBinaryBase::writeFuncOffsetTable() {
@@ -1043,8 +1044,11 @@ std::error_code SampleProfileWriterBinary::writeSummary() {
return sampleprof_error::success;
}
-void SampleProfileWriterBinary::writeLBRProfile(const FunctionSamples &S) {
+void SampleProfileWriterBinary::writeLBRProfile(const FunctionSamples &S,
+ bool IsNested) {
auto &OS = *OutputStream;
+ if (WriteTypifiedProf && !IsNested)
+ encodeULEB128(S.getHeadSamples(), OS);
encodeULEB128(S.getTotalSamples(), OS);
encodeULEB128(S.getBodySamples().size(), OS);
for (const auto &I : S.getBodySamples()) {
@@ -1078,13 +1082,14 @@ void SampleProfileWriterBinary::finishProfileType(uint64_t SizeOffset,
}
void SampleProfileWriterBinary::writeTypifiedLBRProfile(
- const FunctionSamples &S) {
+ const FunctionSamples &S, bool IsNested) {
auto [SizeOffset, BodyOffset] = startProfileType(ProfTypeLBR);
- writeLBRProfile(S);
+ writeLBRProfile(S, IsNested);
finishProfileType(SizeOffset, BodyOffset);
}
-void SampleProfileWriterBinary::writeTypifiedProfile(const FunctionSamples &S) {
+void SampleProfileWriterBinary::writeTypifiedProfile(const FunctionSamples &S,
+ bool IsNested) {
auto &OS = *OutputStream;
bool WriteLBRProf = !S.getBodySamples().empty();
// Other profile types should be added here.
@@ -1094,19 +1099,20 @@ void SampleProfileWriterBinary::writeTypifiedProfile(const FunctionSamples &S) {
encodeULEB128(TypesNum, OS);
if (WriteLBRProf)
- writeTypifiedLBRProfile(S);
+ writeTypifiedLBRProfile(S, IsNested);
}
-std::error_code SampleProfileWriterBinary::writeBody(const FunctionSamples &S) {
+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 (WriteTypifiedProf)
- writeTypifiedProfile(S);
+ writeTypifiedProfile(S, IsNested);
else
- writeLBRProfile(S);
+ writeLBRProfile(S, IsNested);
// Recursively emit all the callsite samples.
uint64_t NumCallsites = 0;
@@ -1116,7 +1122,7 @@ std::error_code SampleProfileWriterBinary::writeBody(const FunctionSamples &S) {
for (const auto &J : S.getCallsiteSamples())
for (const auto &FS : J.second) {
J.first.serialize(OS);
- if (std::error_code EC = writeBody(FS.second))
+ if (std::error_code EC = writeBody(FS.second, true))
return EC;
}
@@ -1132,7 +1138,7 @@ std::error_code SampleProfileWriterBinary::writeBody(const FunctionSamples &S) {
std::error_code
SampleProfileWriterBinary::writeSample(const FunctionSamples &S) {
encodeULEB128(S.getHeadSamples(), *OutputStream);
- return writeBody(S);
+ return writeBody(S, false);
}
/// Create a sample profile file writer based on the specified format.
>From ac718444e67175a9d65553b8f304304fc5c3ba55 Mon Sep 17 00:00:00 2001
From: Sergey Shcherbinin <sscherbinin at nvidia.com>
Date: Tue, 30 Jun 2026 00:01:23 +0400
Subject: [PATCH 06/10] Apply suggestion from @kazutakahirata
Co-authored-by: Kazu Hirata <kazu at google.com>
---
llvm/include/llvm/ProfileData/SampleProf.h | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/llvm/include/llvm/ProfileData/SampleProf.h b/llvm/include/llvm/ProfileData/SampleProf.h
index f44cc8eb90a82..2b516e3f6de09 100644
--- a/llvm/include/llvm/ProfileData/SampleProf.h
+++ b/llvm/include/llvm/ProfileData/SampleProf.h
@@ -1545,7 +1545,8 @@ class SampleProfileMap
for (const auto &[Context, FuncSamples] : *this) {
if (FuncSamples.hasNonLBRSamples()) {
return true;
- }
+ if (FuncSamples.hasNonLBRSamples())
+ return true;
}
return false;
}
>From 1b9406a78c3f0698f79ff61ee56523776ef515ff Mon Sep 17 00:00:00 2001
From: Sergey Shcherbinin <sscherbinin at nvidia.com>
Date: Tue, 30 Jun 2026 09:14:56 +0400
Subject: [PATCH 07/10] Apply suggestion from @kazutakahirata (round 2)
---
llvm/include/llvm/ProfileData/SampleProf.h | 2 --
1 file changed, 2 deletions(-)
diff --git a/llvm/include/llvm/ProfileData/SampleProf.h b/llvm/include/llvm/ProfileData/SampleProf.h
index 2b516e3f6de09..6da1fbb1fc926 100644
--- a/llvm/include/llvm/ProfileData/SampleProf.h
+++ b/llvm/include/llvm/ProfileData/SampleProf.h
@@ -1543,8 +1543,6 @@ class SampleProfileMap
bool hasNonLBRProfile() const {
for (const auto &[Context, FuncSamples] : *this) {
- if (FuncSamples.hasNonLBRSamples()) {
- return true;
if (FuncSamples.hasNonLBRSamples())
return true;
}
>From 8972219886acf6f884cdcbf4c1cf2257fdfdbb99 Mon Sep 17 00:00:00 2001
From: Sergey Shcherbinin <sscherbinin at nvidia.com>
Date: Sat, 1 Aug 2026 10:13:17 +0400
Subject: [PATCH 08/10] [SampleProfile] Harden typified ExtBinary profile
handling
Make typified payload framing bounded and inspectable while preserving legacy behavior, and cover boundary, compression, CS, and writer-reuse paths.
---
llvm/docs/CommandGuide/llvm-profdata.rst | 5 +
llvm/include/llvm/ProfileData/SampleProf.h | 12 +-
.../llvm/ProfileData/SampleProfReader.h | 14 +
.../llvm/ProfileData/SampleProfWriter.h | 21 +-
llvm/lib/ProfileData/SampleProfReader.cpp | 39 ++-
llvm/lib/ProfileData/SampleProfWriter.cpp | 270 ++++++++++++++----
.../Inputs/typified-cs-profile.proftext | 2 +
.../SampleProfile/typified-cs-profile.ll | 37 +++
.../Inputs/corrupt-typified-payload.py | 152 ++++++++++
.../Inputs/generate-large-typified-profile.py | 18 ++
.../Inputs/sample-typified-empty-lbr.proftext | 3 +
.../Inputs/typified-head-only.proftext | 1 +
.../Inputs/typified-multiple-blocks.proftext | 2 +
.../Inputs/typified-zero-block.proftext | 3 +
.../llvm-profdata/sample-split-layout.test | 14 +-
.../show-typified-profile-info.test | 31 ++
.../llvm-profdata/typified-compress.test | 6 +
.../typified-compressed-info.test | 12 +
.../llvm-profdata/typified-empty-lbr.test | 8 +
.../llvm-profdata/typified-head-only.test | 6 +
.../typified-multiple-blocks.test | 14 +
.../typified-payload-boundaries.test | 17 ++
.../typified-payload-buffer-limit.test | 24 ++
.../llvm-profdata/typified-zero-block.test | 18 ++
llvm/tools/llvm-profdata/llvm-profdata.cpp | 19 ++
llvm/unittests/ProfileData/SampleProfTest.cpp | 84 ++++++
26 files changed, 761 insertions(+), 71 deletions(-)
create mode 100644 llvm/test/Transforms/SampleProfile/Inputs/typified-cs-profile.proftext
create mode 100644 llvm/test/Transforms/SampleProfile/typified-cs-profile.ll
create mode 100644 llvm/test/tools/llvm-profdata/Inputs/corrupt-typified-payload.py
create mode 100644 llvm/test/tools/llvm-profdata/Inputs/generate-large-typified-profile.py
create mode 100644 llvm/test/tools/llvm-profdata/Inputs/sample-typified-empty-lbr.proftext
create mode 100644 llvm/test/tools/llvm-profdata/Inputs/typified-head-only.proftext
create mode 100644 llvm/test/tools/llvm-profdata/Inputs/typified-multiple-blocks.proftext
create mode 100644 llvm/test/tools/llvm-profdata/Inputs/typified-zero-block.proftext
create mode 100644 llvm/test/tools/llvm-profdata/show-typified-profile-info.test
create mode 100644 llvm/test/tools/llvm-profdata/typified-compress.test
create mode 100644 llvm/test/tools/llvm-profdata/typified-compressed-info.test
create mode 100644 llvm/test/tools/llvm-profdata/typified-empty-lbr.test
create mode 100644 llvm/test/tools/llvm-profdata/typified-head-only.test
create mode 100644 llvm/test/tools/llvm-profdata/typified-multiple-blocks.test
create mode 100644 llvm/test/tools/llvm-profdata/typified-payload-boundaries.test
create mode 100644 llvm/test/tools/llvm-profdata/typified-payload-buffer-limit.test
create mode 100644 llvm/test/tools/llvm-profdata/typified-zero-block.test
diff --git a/llvm/docs/CommandGuide/llvm-profdata.rst b/llvm/docs/CommandGuide/llvm-profdata.rst
index 0b1cd02e2230f..fa03c875a816d 100644
--- a/llvm/docs/CommandGuide/llvm-profdata.rst
+++ b/llvm/docs/CommandGuide/llvm-profdata.rst
@@ -384,6 +384,11 @@ OPTIONS
Show basic information about each section in the profile. This option is
only meaningful for sample-based profile in extbinary format.
+.. option:: --show-typified-info-only=[true|false]
+
+ Show the per-function typified block structure. This option is only meaningful
+ for sample-based profiles in typified extbinary format.
+
.. option:: --debug-info=<path>
Specify the executable or ``.dSYM`` that contains debug info for the raw profile.
diff --git a/llvm/include/llvm/ProfileData/SampleProf.h b/llvm/include/llvm/ProfileData/SampleProf.h
index 6da1fbb1fc926..4a5585c721247 100644
--- a/llvm/include/llvm/ProfileData/SampleProf.h
+++ b/llvm/include/llvm/ProfileData/SampleProf.h
@@ -183,9 +183,18 @@ static inline std::string getSecName(SecType Type) {
}
}
-// Types of sample profile which can be placed in SecTypifiedProfile
+// Types of sample profiles that can be placed in SecTypifiedProfile.
enum ProfTypes { ProfTypeLBR = 0, ProfTypeNum };
+static inline StringRef getProfTypeName(uint64_t Type) {
+ switch (Type) {
+ case ProfTypeLBR:
+ return "LBR";
+ default:
+ return "unknown";
+ }
+}
+
// Entry type of section header table used by SampleProfileExtBinaryBaseReader
// and SampleProfileExtBinaryBaseWriter.
struct SecHdrTableEntry {
@@ -287,6 +296,7 @@ static inline void verifySecFlag(SecType Type, SecFlagType Flag) {
break;
default:
case SecFuncOffsetTable:
+ case SecTypifiedFuncOffsetTable:
IsFlagLegal = std::is_same<SecFuncOffsetFlags, SecFlagType>();
break;
}
diff --git a/llvm/include/llvm/ProfileData/SampleProfReader.h b/llvm/include/llvm/ProfileData/SampleProfReader.h
index 55c6c47e3e3ad..fe4d2b13952b1 100644
--- a/llvm/include/llvm/ProfileData/SampleProfReader.h
+++ b/llvm/include/llvm/ProfileData/SampleProfReader.h
@@ -631,6 +631,17 @@ class SampleProfileReader {
}
virtual bool dumpSectionInfo(raw_ostream &OS = dbgs()) { return false; };
+ /// Read the profile and print the structure of typified profile blocks.
+ std::error_code dumpProfileTypeInfo(raw_ostream &OS) {
+ ProfileTypeInfoOS = &OS;
+ std::error_code EC = read();
+ ProfileTypeInfoOS = nullptr;
+ return EC;
+ }
+
+ /// Return whether the profile uses typified profile blocks.
+ bool profileIsTypified() const { return IsProfileTypified; }
+
/// Return whether names in the profile are all MD5 numbers.
bool useMD5() const { return ProfileIsMD5; }
@@ -701,7 +712,10 @@ class SampleProfileReader {
FuncMetadataIndex;
std::pair<const uint8_t *, const uint8_t *> ProfileSecRange;
+ /// Whether the input uses SecTypifiedProfile for function profiles.
bool IsProfileTypified = false;
+ /// Optional stream for typified block structure; null disables the output.
+ raw_ostream *ProfileTypeInfoOS = nullptr;
/// Whether the profile has attribute metadata.
bool ProfileHasAttribute = false;
diff --git a/llvm/include/llvm/ProfileData/SampleProfWriter.h b/llvm/include/llvm/ProfileData/SampleProfWriter.h
index 375d876cba41b..91a3ccc2faf62 100644
--- a/llvm/include/llvm/ProfileData/SampleProfWriter.h
+++ b/llvm/include/llvm/ProfileData/SampleProfWriter.h
@@ -14,6 +14,7 @@
#include "llvm/ADT/Eytzinger.h"
#include "llvm/ADT/MapVector.h"
+#include "llvm/ADT/STLFunctionalExtras.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/IR/ProfileSummary.h"
#include "llvm/ProfileData/SampleProf.h"
@@ -224,13 +225,21 @@ class LLVM_ABI SampleProfileWriterBinary : public SampleProfileWriter {
virtual std::error_code writeContextIdx(const SampleContext &Context);
std::error_code writeNameIdx(FunctionId FName);
std::error_code writeBody(const FunctionSamples &S, bool IsNested);
- void writeLBRProfile(const FunctionSamples &S, bool IsNested);
+ std::error_code writeLBRProfile(const FunctionSamples &S, bool IsNested);
/// Interfaces for typified profile writing.
- void writeTypifiedProfile(const FunctionSamples &S, bool IsNested);
- std::pair<uint64_t, uint64_t> startProfileType(ProfTypes Type);
- void finishProfileType(uint64_t SizeOffset, uint64_t BodyOffset);
- void writeTypifiedLBRProfile(const FunctionSamples &S, bool IsNested);
+ std::error_code writeTypifiedProfile(const FunctionSamples &S, bool IsNested);
+ /// Write one \p Type and the size-prefixed payload emitted by \p
+ /// WritePayload. The callback may be invoked twice when its payload exceeds
+ /// the dynamic buffer limit, so it must emit identical bytes without external
+ /// side effects.
+ std::error_code
+ writeProfileType(ProfTypes Type,
+ function_ref<std::error_code()> WritePayload);
+ /// Reusable size-counting stream with bounded dynamic payload storage.
+ std::unique_ptr<raw_ostream> PayloadBufferStream;
+ /// Whether a profile payload callback is currently being executed.
+ bool WritingProfileType = false;
inline void stablizeNameTable(MapVector<FunctionId, uint32_t> &NameTable,
std::set<FunctionId> &V);
@@ -428,7 +437,7 @@ class LLVM_ABI SampleProfileWriterExtBinaryBase
std::error_code writeNameTableSection(const SampleProfileMap &ProfileMap);
std::error_code
writeEytzingerNameTableSection(const SampleProfileMap &ProfileMap);
- std::error_code writeFuncOffsetTable();
+ std::error_code writeFuncOffsetTable(SecType Type);
std::error_code writeProfileSymbolListSection();
std::error_code writeStringBasedProfileSymbolListSection();
std::error_code writeMD5ProfileSymbolListSection();
diff --git a/llvm/lib/ProfileData/SampleProfReader.cpp b/llvm/lib/ProfileData/SampleProfReader.cpp
index 01c111a2f53ea..0b07ff3ab46a7 100644
--- a/llvm/lib/ProfileData/SampleProfReader.cpp
+++ b/llvm/lib/ProfileData/SampleProfReader.cpp
@@ -35,6 +35,7 @@
#include "llvm/Support/LineIterator.h"
#include "llvm/Support/MD5.h"
#include "llvm/Support/MemoryBuffer.h"
+#include "llvm/Support/SaveAndRestore.h"
#include "llvm/Support/VirtualFileSystem.h"
#include "llvm/Support/raw_ostream.h"
#include <algorithm>
@@ -785,28 +786,45 @@ SampleProfileReaderBinary::readTypifiedProfile(FunctionSamples &FProfile,
auto ProfNum = readNumber<uint64_t>();
if (std::error_code EC = ProfNum.getError())
return EC;
+ if (ProfileTypeInfoOS)
+ *ProfileTypeInfoOS << (IsNested ? "Nested function: " : "Function: ")
+ << FProfile.getContext().toString()
+ << "\n Profile blocks: " << *ProfNum << "\n";
- // read specified number of typified profiles
+ // Read the specified number of typified profiles.
for (uint64_t i = 0; i < *ProfNum; i++) {
auto Type = readNumber<uint64_t>();
if (std::error_code EC = Type.getError())
return EC;
- auto Size = readUnencodedNumber<uint64_t>();
+ auto Size = readNumber<uint64_t>();
if (std::error_code EC = Size.getError())
return EC;
+ if (ProfileTypeInfoOS)
+ *ProfileTypeInfoOS << " Type: " << *Type << " ("
+ << getProfTypeName(*Type)
+ << "), Payload size: " << *Size << "\n";
+ if (*Size > static_cast<uint64_t>(End - Data))
+ return sampleprof_error::truncated;
+ const uint8_t *PayloadEnd = Data + *Size;
+ std::error_code EC = sampleprof_error::success;
+ // Restrict field readers to the current payload so they reject fields that
+ // extend into the following payload.
+ SaveAndRestore<const uint8_t *> RestoreEnd(End, PayloadEnd);
switch (*Type) {
case ProfTypeLBR:
- if (std::error_code EC = readLBRProfile(FProfile, IsNested))
- return EC;
+ EC = readLBRProfile(FProfile, IsNested);
break;
default:
- // skip unknown profile type for forward compatibility
- Data += *Size;
- if (Data > End)
- return sampleprof_error::truncated;
+ // Skip unknown profile types for forward compatibility.
+ Data = PayloadEnd;
break;
}
+
+ if (EC)
+ return EC;
+ if (Data != PayloadEnd)
+ return sampleprof_error::malformed;
}
return sampleprof_error::success;
@@ -945,7 +963,6 @@ std::error_code SampleProfileReaderExtBinaryBase::readOneSection(
case SecLBRProfile:
case SecTypifiedProfile:
ProfileSecRange = std::make_pair(Data, End);
- IsProfileTypified = Entry.Type == SecTypifiedProfile;
if (std::error_code EC = readFuncProfiles())
return EC;
break;
@@ -1266,6 +1283,9 @@ std::error_code SampleProfileReaderExtBinaryBase::readImpl() {
reinterpret_cast<const uint8_t *>(Buffer->getBufferStart());
for (auto &Entry : SecHdrTable) {
+ if (Entry.Type == SecTypifiedProfile)
+ IsProfileTypified = true;
+
// Skip empty section.
if (!Entry.Size)
continue;
@@ -1728,6 +1748,7 @@ static std::string getSecFlagsStr(const SecHdrTableEntry &Entry) {
Flags.append("fs-discriminator,");
break;
case SecFuncOffsetTable:
+ case SecTypifiedFuncOffsetTable:
if (hasSecFlag(Entry, SecFuncOffsetFlags::SecFlagOrdered))
Flags.append("ordered,");
break;
diff --git a/llvm/lib/ProfileData/SampleProfWriter.cpp b/llvm/lib/ProfileData/SampleProfWriter.cpp
index 4eef406e3087c..d9818d607e75a 100644
--- a/llvm/lib/ProfileData/SampleProfWriter.cpp
+++ b/llvm/lib/ProfileData/SampleProfWriter.cpp
@@ -28,10 +28,14 @@
#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 <algorithm>
#include <array>
#include <cmath>
#include <cstdint>
+#include <cstring>
+#include <limits>
#include <memory>
#include <system_error>
#include <utility>
@@ -47,6 +51,12 @@ 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> ExtBinaryProfileTypeBufferLimit(
+ "extbinary-profile-type-buffer-limit", cl::init(64ULL * 1024 * 1024),
+ cl::Hidden,
+ cl::desc("Maximum number of typified payload bytes to retain in the "
+ "dynamic buffer"));
+
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"));
@@ -190,7 +200,7 @@ SampleProfileWriterExtBinaryBase::markSectionStart(SecType Type,
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 writting data.
+ // Use LocalBuf as a temporary output for writing data.
if (hasSecFlag(Entry, SecCommonFlags::SecFlagCompress))
LocalBufStream.swap(OutputStream);
return SectionStart;
@@ -201,7 +211,7 @@ std::error_code SampleProfileWriterExtBinaryBase::compressAndOutput() {
return sampleprof_error::zlib_unavailable;
std::string &UncompressedStrings =
static_cast<raw_string_ostream *>(LocalBufStream.get())->str();
- if (UncompressedStrings.size() == 0)
+ if (UncompressedStrings.empty())
return sampleprof_error::success;
auto &OS = *OutputStream;
SmallVector<uint8_t, 128> CompressedStrings;
@@ -282,7 +292,8 @@ SampleProfileWriterExtBinaryBase::writeSample(const FunctionSamples &S) {
return writeBody(S, false);
}
-std::error_code SampleProfileWriterExtBinaryBase::writeFuncOffsetTable() {
+std::error_code
+SampleProfileWriterExtBinaryBase::writeFuncOffsetTable(SecType Type) {
auto &OS = *OutputStream;
// Write out the table size.
@@ -306,7 +317,7 @@ std::error_code SampleProfileWriterExtBinaryBase::writeFuncOffsetTable() {
if (std::error_code EC = WriteItem(Entry.first, Entry.second))
return EC;
}
- addSectionFlag(SecFuncOffsetTable, SecFuncOffsetFlags::SecFlagOrdered);
+ addSectionFlag(Type, SecFuncOffsetFlags::SecFlagOrdered);
} else {
for (const auto &Entry : FuncOffsetTable) {
if (std::error_code EC = WriteItem(Entry.first, Entry.second))
@@ -614,7 +625,7 @@ std::error_code SampleProfileWriterExtBinaryBase::writeOneSection(
break;
case SecFuncOffsetTable:
case SecTypifiedFuncOffsetTable:
- if (auto EC = writeFuncOffsetTable())
+ if (auto EC = writeFuncOffsetTable(Type))
return EC;
break;
case SecFuncMetadata:
@@ -707,22 +718,21 @@ std::error_code SampleProfileWriterExtBinary::writeCtxSplitLayout(
void SampleProfileWriterExtBinary::configureTypifiedProfile(
const SampleProfileMap &ProfileMap) {
- if (!ExtBinaryForceTypifiedProf && !ProfileMap.hasNonLBRProfile()) {
- WriteTypifiedProf = false;
- ProfSection = SecLBRProfile;
- FuncOffsetSection = SecFuncOffsetTable;
- return;
- }
- // Use typified profile sections: directly change the section types
- // to avoid duplicating the whole layout and its handling.
- WriteTypifiedProf = true;
- FuncOffsetSection = SecTypifiedFuncOffsetTable;
- ProfSection = SecTypifiedProfile;
+ WriteTypifiedProf =
+ ExtBinaryForceTypifiedProf || ProfileMap.hasNonLBRProfile();
+ ProfSection = WriteTypifiedProf ? SecTypifiedProfile : SecLBRProfile;
+ FuncOffsetSection =
+ WriteTypifiedProf ? SecTypifiedFuncOffsetTable : SecFuncOffsetTable;
+
+ // Change the section types in place to avoid duplicating the whole layout and
+ // its handling. Rewrite both legacy and typified entries so repeated writes
+ // can switch formats without losing configured flags.
for (auto &Entry : SectionHdrLayout) {
- if (Entry.Type == SecFuncOffsetTable)
- Entry.Type = SecTypifiedFuncOffsetTable;
- else if (Entry.Type == SecLBRProfile)
- Entry.Type = SecTypifiedProfile;
+ if (Entry.Type == SecFuncOffsetTable ||
+ Entry.Type == SecTypifiedFuncOffsetTable)
+ Entry.Type = FuncOffsetSection;
+ else if (Entry.Type == SecLBRProfile || Entry.Type == SecTypifiedProfile)
+ Entry.Type = ProfSection;
}
}
@@ -1044,8 +1054,9 @@ std::error_code SampleProfileWriterBinary::writeSummary() {
return sampleprof_error::success;
}
-void SampleProfileWriterBinary::writeLBRProfile(const FunctionSamples &S,
- bool IsNested) {
+std::error_code
+SampleProfileWriterBinary::writeLBRProfile(const FunctionSamples &S,
+ bool IsNested) {
auto &OS = *OutputStream;
if (WriteTypifiedProf && !IsNested)
encodeULEB128(S.getHeadSamples(), OS);
@@ -1055,43 +1066,189 @@ void SampleProfileWriterBinary::writeLBRProfile(const FunctionSamples &S,
LineLocation Loc = I.first;
const SampleRecord &Sample = I.second;
Loc.serialize(OS);
- Sample.serialize(OS, getNameTable());
+ if (std::error_code EC = Sample.serialize(OS, getNameTable()))
+ return EC;
}
+ return sampleprof_error::success;
}
-std::pair<uint64_t, uint64_t>
-SampleProfileWriterBinary::startProfileType(ProfTypes Type) {
- auto &OS = *OutputStream;
- encodeULEB128(Type, OS);
- // Create placeholder for profile size.
- uint64_t SizeOffset = OS.tell();
- support::endian::Writer PlaceWriter(OS, llvm::endianness::little);
- PlaceWriter.write(static_cast<uint64_t>(-1));
- uint64_t BodyOffset = OS.tell();
- return {SizeOffset, BodyOffset};
-}
+namespace {
+
+/// A reusable stream that always counts payload bytes and retains them in one
+/// dynamic buffer while their total size does not exceed a configured limit.
+/// The limit bounds the retained dynamic capacity, not the process RSS or the
+/// transient allocation peak: growth temporarily keeps the old and replacement
+/// buffers alive together, and the fixed front buffer is separate. On overflow,
+/// the stream discards retained bytes and stops storing subsequent writes, but
+/// continues counting the complete payload size.
+class BoundedBufferingStream final : public raw_ostream {
+ static constexpr size_t InitialBufferSize = 4096;
+
+public:
+ /// Create a stream whose retained dynamic capacity cannot exceed
+ /// \p BufferLimit.
+ explicit BoundedBufferingStream(uint64_t BufferLimit)
+ : BufferLimit(static_cast<size_t>(std::min<uint64_t>(
+ BufferLimit, std::numeric_limits<size_t>::max()))) {
+ SetBuffer(FrontBuffer, sizeof(FrontBuffer));
+ }
+
+ /// Prepare the stream to count and, if possible, retain another payload.
+ void resetPayload() {
+ assert(GetNumBytesInBuffer() == 0 && "front buffer is not empty");
+ BufferSize = 0;
+ PayloadSize = 0;
+ Overflowed = false;
+ }
+
+ /// Flush the front buffer so the final size and buffered payload are visible.
+ void finishPayload() {
+ // payload() cannot see bytes still held in raw_ostream's front buffer.
+ flush();
+ }
+
+ /// Return whether the payload exceeded the dynamic buffer limit.
+ bool overflowed() const { return Overflowed; }
+
+ /// Return the complete payload size, including bytes discarded after
+ /// overflow.
+ uint64_t payloadSize() const { return PayloadSize; }
+
+ /// Return the retained payload; valid only when overflowed() is false.
+ StringRef payload() const { return StringRef(Buffer.get(), BufferSize); }
+
+private:
+ /// Count incoming bytes and retain them while they fit within BufferLimit.
+ void write_impl(const char *Ptr, size_t Size) override {
+ // Always account for incoming bytes, including those received after the
+ // payload has exceeded the dynamic buffer limit.
+ PayloadSize += Size;
+
+ // After overflow, only size tracking remains active.
+ if (Overflowed)
+ return;
+
+ // If this write crosses the limit, release the retained prefix immediately
+ // and switch permanently to size-only mode for the current payload.
+ if (Size > BufferLimit - BufferSize) {
+ Overflowed = true;
+ Buffer.reset();
+ BufferSize = 0;
+ BufferCapacity = 0;
+ return;
+ }
+
+ size_t RequiredCapacity = BufferSize + Size;
+ if (RequiredCapacity > BufferCapacity) {
+ // Start with the front-buffer size, then grow geometrically without
+ // retaining a capacity beyond BufferLimit.
+ size_t NewCapacity = BufferCapacity
+ ? BufferCapacity
+ : std::min(BufferLimit, InitialBufferSize);
+ while (NewCapacity < RequiredCapacity) {
+ if (NewCapacity > BufferLimit / 2) {
+ NewCapacity = BufferLimit;
+ break;
+ }
+ NewCapacity *= 2;
+ }
+ auto NewBuffer = std::make_unique<char[]>(NewCapacity);
+ if (BufferSize)
+ std::memcpy(NewBuffer.get(), Buffer.get(), BufferSize);
+ Buffer = std::move(NewBuffer);
+ BufferCapacity = NewCapacity;
+ }
+
+ // Retain this write because the complete payload still fits.
+ std::memcpy(Buffer.get() + BufferSize, Ptr, Size);
+ BufferSize += Size;
+ }
+
+ /// Report the number of bytes accepted from the current payload.
+ uint64_t current_pos() const override { return PayloadSize; }
+
+ size_t BufferLimit;
+ std::unique_ptr<char[]> Buffer;
+ size_t BufferSize = 0;
+ size_t BufferCapacity = 0;
+ uint64_t PayloadSize = 0;
+ bool Overflowed = false;
+ /// Coalesce small raw_ostream writes before forwarding them to write_impl(),
+ /// avoiding a virtual call and buffer-growth check for every encoded byte.
+ /// This fixed staging storage is not counted against BufferLimit.
+ char FrontBuffer[InitialBufferSize];
+};
+
+} // namespace
+
+std::error_code SampleProfileWriterBinary::writeProfileType(
+ ProfTypes Type, function_ref<std::error_code()> WritePayload) {
+ // PayloadBufferStream temporarily owns the real output while the callback
+ // writes through OutputStream. A nested call would therefore mistake the
+ // real output for BoundedBufferingStream.
+ 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 the payload has been serialized. First serialize the
+ // payload into a reusable bounded buffer while counting its complete size.
+ // If it fits, emit the buffered bytes; after overflow, retain only the size
+ // and serialize the payload again directly.
+ //
+ // For common small payloads, this is faster than both a separate
+ // size-precomputation traversal and fixed-width backpatching. It avoids the
+ // extra traversal of the former and the pwrite() flush/seek/write/seek
+ // operations of the latter, while reusing allocated buffer storage. For
+ // oversized payloads, the counting fallback bounds retained dynamic payload
+ // capacity; it does not bound process RSS or transient replacement-buffer
+ // allocations during growth.
+ //
+ // Unlike fixed-width backpatching, knowing the complete size before emitting
+ // the payload also allows it to be encoded compactly as ULEB128.
+
+ if (!PayloadBufferStream)
+ PayloadBufferStream = std::make_unique<BoundedBufferingStream>(
+ ExtBinaryProfileTypeBufferLimit);
+ auto *BufferStream =
+ static_cast<BoundedBufferingStream *>(PayloadBufferStream.get());
+ BufferStream->resetPayload();
+ OutputStream.swap(PayloadBufferStream);
+ std::error_code EC = WritePayload();
+ BufferStream->finishPayload();
+ OutputStream.swap(PayloadBufferStream);
+ if (EC)
+ return EC;
-void SampleProfileWriterBinary::finishProfileType(uint64_t SizeOffset,
- uint64_t BodyOffset) {
+ // Emit the profile type and the complete payload size, then emit the payload.
auto &OS = *OutputStream;
- uint64_t BodySize = OS.tell() - BodyOffset;
- // Write profile size.
- support::endian::SeekableWriter PWriter(static_cast<raw_pwrite_stream &>(OS),
- llvm::endianness::little);
- PWriter.pwrite(BodySize, SizeOffset);
+ encodeULEB128(Type, OS);
+ encodeULEB128(BufferStream->payloadSize(), OS);
+ if (BufferStream->overflowed()) {
+ // An oversized payload was discarded during the counting pass, so serialize
+ // it directly now that its size has been emitted.
+ uint64_t PayloadStart = OS.tell();
+ if (std::error_code EC = WritePayload())
+ return EC;
+ // Reject output if the callback did not reproduce the counted size.
+ if (OS.tell() - PayloadStart != BufferStream->payloadSize())
+ return sampleprof_error::malformed;
+ } else {
+ OS << BufferStream->payload();
+ }
+ return sampleprof_error::success;
}
-void SampleProfileWriterBinary::writeTypifiedLBRProfile(
- const FunctionSamples &S, bool IsNested) {
- auto [SizeOffset, BodyOffset] = startProfileType(ProfTypeLBR);
- writeLBRProfile(S, IsNested);
- finishProfileType(SizeOffset, BodyOffset);
+static bool hasNonEmptyLBRProfile(const FunctionSamples &S, bool IsNested) {
+ return S.getTotalSamples() != 0 || (!IsNested && S.getHeadSamples() != 0) ||
+ !S.getBodySamples().empty();
}
-void SampleProfileWriterBinary::writeTypifiedProfile(const FunctionSamples &S,
- bool IsNested) {
+std::error_code
+SampleProfileWriterBinary::writeTypifiedProfile(const FunctionSamples &S,
+ bool IsNested) {
auto &OS = *OutputStream;
- bool WriteLBRProf = !S.getBodySamples().empty();
+ bool WriteLBRProf = hasNonEmptyLBRProfile(S, IsNested);
// Other profile types should be added here.
uint32_t TypesNum = WriteLBRProf;
@@ -1099,7 +1256,9 @@ void SampleProfileWriterBinary::writeTypifiedProfile(const FunctionSamples &S,
encodeULEB128(TypesNum, OS);
if (WriteLBRProf)
- writeTypifiedLBRProfile(S, IsNested);
+ return writeProfileType(ProfTypeLBR,
+ [&] { return writeLBRProfile(S, IsNested); });
+ return sampleprof_error::success;
}
std::error_code SampleProfileWriterBinary::writeBody(const FunctionSamples &S,
@@ -1109,10 +1268,13 @@ std::error_code SampleProfileWriterBinary::writeBody(const FunctionSamples &S,
return EC;
// Emit all the body samples.
- if (WriteTypifiedProf)
- writeTypifiedProfile(S, IsNested);
- else
- writeLBRProfile(S, IsNested);
+ if (WriteTypifiedProf) {
+ if (std::error_code EC = writeTypifiedProfile(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;
diff --git a/llvm/test/Transforms/SampleProfile/Inputs/typified-cs-profile.proftext b/llvm/test/Transforms/SampleProfile/Inputs/typified-cs-profile.proftext
new file mode 100644
index 0000000000000..4384533b57908
--- /dev/null
+++ b/llvm/test/Transforms/SampleProfile/Inputs/typified-cs-profile.proftext
@@ -0,0 +1,2 @@
+[test]:200:99
+ 0: 10
diff --git a/llvm/test/Transforms/SampleProfile/typified-cs-profile.ll b/llvm/test/Transforms/SampleProfile/typified-cs-profile.ll
new file mode 100644
index 0000000000000..5e4d13939cd96
--- /dev/null
+++ b/llvm/test/Transforms/SampleProfile/typified-cs-profile.ll
@@ -0,0 +1,37 @@
+; Verify that typified CS profiles use an ordered offset table, can be loaded by
+; the sample-profile pass, and provide payload data used for inlining.
+; RUN: llvm-profdata merge --sample --extbinary-force-typified-prof --extbinary %S/Inputs/typified-cs-profile.proftext -o %t.prof
+; RUN: llvm-profdata show --sample --show-sec-info-only %t.prof | FileCheck %s --check-prefix=SECTION
+; RUN: opt -S %s -passes=sample-profile -sample-profile-file=%t.prof | FileCheck %s --check-prefix=IR
+; RUN: llvm-profdata merge --sample --extbinary-force-typified-prof --extbinary \
+; RUN: %S/Inputs/indirect-call-csspgo.prof -o %t.payload.prof
+; RUN: opt -S %S/csspgo-inline-icall.ll -passes=sample-profile \
+; RUN: -sample-profile-file=%t.payload.prof \
+; RUN: -sample-profile-icp-relative-hotness=1 -pass-remarks=sample-profile \
+; RUN: -sample-profile-inline-size=0 -o /dev/null 2>&1 | \
+; RUN: FileCheck %s --check-prefix=PAYLOAD
+
+; SECTION: TypifiedFuncOffsetTableSection {{.*}} Flags: {ordered}
+; IR-LABEL: define void @test()
+; IR-SAME: !prof ![[ENTRY_COUNT:[0-9]+]]
+; IR: ![[ENTRY_COUNT]] = !{!"function_entry_count", i64 10}
+; PAYLOAD: remark: test.cc:4:0: '_Z3foov' inlined into 'test'
+
+define void @test() #0 !dbg !4 {
+entry:
+ ret void, !dbg !7
+}
+
+attributes #0 = { "use-sample-profile" }
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!2, !3}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_C99, file: !1, producer: "llvm", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug)
+!1 = !DIFile(filename: "typified-cs-profile.c", directory: "/")
+!2 = !{i32 2, !"Dwarf Version", i32 4}
+!3 = !{i32 2, !"Debug Info Version", i32 3}
+!4 = distinct !DISubprogram(name: "test", scope: !1, file: !1, line: 1, type: !5, scopeLine: 1, spFlags: DISPFlagDefinition, unit: !0)
+!5 = !DISubroutineType(types: !6)
+!6 = !{}
+!7 = !DILocation(line: 1, column: 1, scope: !4)
diff --git a/llvm/test/tools/llvm-profdata/Inputs/corrupt-typified-payload.py b/llvm/test/tools/llvm-profdata/Inputs/corrupt-typified-payload.py
new file mode 100644
index 0000000000000..2257893c72f2b
--- /dev/null
+++ b/llvm/test/tools/llvm-profdata/Inputs/corrupt-typified-payload.py
@@ -0,0 +1,152 @@
+import argparse
+import struct
+
+TYPIFIED_PROFILE_SECTION = 33
+UNKNOWN_PROFILE_TYPE = 127
+
+
+def read_uleb(data, offset):
+ """Decode one ULEB128 value and return it with the following offset."""
+ value = 0
+ shift = 0
+ while True:
+ byte = data[offset]
+ offset += 1
+ value |= (byte & 0x7F) << shift
+ if byte < 0x80:
+ return value, offset
+ shift += 7
+
+
+def encode_uleb(value):
+ """Encode an integer using canonical ULEB128."""
+ result = bytearray()
+ while True:
+ byte = value & 0x7F
+ value >>= 7
+ if value:
+ byte |= 0x80
+ result.append(byte)
+ if not value:
+ return result
+
+
+parser = argparse.ArgumentParser()
+parser.add_argument("input")
+parser.add_argument("output")
+parser.add_argument(
+ "modification",
+ choices=(
+ "undersized",
+ "oversized",
+ "unknown",
+ "unknown-out-of-bounds",
+ "prepended-unknown",
+ "empty-section",
+ ),
+)
+args = parser.parse_args()
+
+with open(args.input, "rb") as input_file:
+ data = bytearray(input_file.read())
+_, offset = read_uleb(data, 0)
+_, offset = read_uleb(data, offset)
+section_count = struct.unpack_from("<Q", data, offset)[0]
+offset += 8
+
+sections = []
+profile_offset = None
+profile_size = None
+profile_header_offset = None
+for _ in range(section_count):
+ section_header_offset = offset
+ section_type, _, section_offset, section_size = struct.unpack_from(
+ "<QQQQ", data, offset
+ )
+ offset += 32
+ sections.append((section_header_offset, section_type, section_offset, section_size))
+ if section_type == TYPIFIED_PROFILE_SECTION:
+ assert profile_offset is None, "expected exactly one typified profile section"
+ profile_offset = section_offset
+ profile_size = section_size
+ profile_header_offset = section_header_offset
+
+assert profile_offset is not None
+assert profile_size is not None
+assert profile_header_offset is not None
+profile_start = profile_offset
+profile_end = profile_offset + profile_size
+_, profile_offset = read_uleb(data, profile_offset)
+profile_count_offset = profile_offset
+profile_count, type_offset = read_uleb(data, profile_offset)
+assert profile_count == 1
+assert type_offset == profile_count_offset + 1
+profile_type, size_offset = read_uleb(data, type_offset)
+assert profile_type == 0
+payload_size, payload_offset = read_uleb(data, size_offset)
+assert payload_offset + payload_size < profile_end
+
+
+def adjust_sections_after(insertion_offset, size_delta):
+ """Adjust section offsets and typified section size after a byte edit."""
+ if not size_delta:
+ return
+ for section_header_offset, _, section_offset, _ in sections:
+ if section_offset > insertion_offset:
+ struct.pack_into(
+ "<Q",
+ data,
+ section_header_offset + 16,
+ section_offset + size_delta,
+ )
+ struct.pack_into(
+ "<Q",
+ data,
+ profile_header_offset + 24,
+ profile_size + size_delta,
+ )
+
+
+def replace_payload_size(new_size):
+ """Replace the ULEB128 payload size and repair affected section metadata."""
+ encoded_size = encode_uleb(new_size)
+ old_encoded_size = payload_offset - size_offset
+ data[size_offset:payload_offset] = encoded_size
+ adjust_sections_after(size_offset, len(encoded_size) - old_encoded_size)
+
+
+if args.modification == "undersized":
+ assert payload_size > 0
+ replace_payload_size(payload_size - 1)
+elif args.modification == "oversized":
+ replace_payload_size(payload_size + 1)
+elif args.modification == "unknown":
+ data[type_offset] = UNKNOWN_PROFILE_TYPE
+elif args.modification == "unknown-out-of-bounds":
+ data[type_offset] = UNKNOWN_PROFILE_TYPE
+ replace_payload_size((1 << 64) - 1)
+elif args.modification == "prepended-unknown":
+ unknown_payload = b"\xa5"
+ unknown_block = (
+ bytes([UNKNOWN_PROFILE_TYPE])
+ + encode_uleb(len(unknown_payload))
+ + unknown_payload
+ )
+ insertion_offset = type_offset
+ data[profile_count_offset] = 2
+ data[insertion_offset:insertion_offset] = unknown_block
+ adjust_sections_after(insertion_offset, len(unknown_block))
+else:
+ del data[profile_start:profile_end]
+ for section_header_offset, _, section_offset, _ in sections:
+ if section_offset > profile_start:
+ struct.pack_into(
+ "<Q",
+ data,
+ section_header_offset + 16,
+ section_offset - profile_size,
+ )
+ struct.pack_into("<Q", data, profile_header_offset + 24, 0)
+
+with open(args.output, "wb") as output_file:
+ output_file.write(data)
diff --git a/llvm/test/tools/llvm-profdata/Inputs/generate-large-typified-profile.py b/llvm/test/tools/llvm-profdata/Inputs/generate-large-typified-profile.py
new file mode 100644
index 0000000000000..3160153e79511
--- /dev/null
+++ b/llvm/test/tools/llvm-profdata/Inputs/generate-large-typified-profile.py
@@ -0,0 +1,18 @@
+import argparse
+
+
+# Generate enough distinct body records to exercise multi-byte payload sizes and
+# partial buffering before the configured payload limit is exceeded.
+parser = argparse.ArgumentParser(
+ description="Generate a sample profile with a large typified LBR payload"
+)
+parser.add_argument("output")
+parser.add_argument("records", type=int)
+args = parser.parse_args()
+
+assert 0 < args.records <= 65536
+
+with open(args.output, "w", encoding="utf-8", newline="\n") as output:
+ output.write(f"large:{args.records}:1\n")
+ for line_offset in range(args.records):
+ output.write(f" {line_offset}: 1\n")
diff --git a/llvm/test/tools/llvm-profdata/Inputs/sample-typified-empty-lbr.proftext b/llvm/test/tools/llvm-profdata/Inputs/sample-typified-empty-lbr.proftext
new file mode 100644
index 0000000000000..52edc3657ba71
--- /dev/null
+++ b/llvm/test/tools/llvm-profdata/Inputs/sample-typified-empty-lbr.proftext
@@ -0,0 +1,3 @@
+top:42:7
+caller:21:3
+ 1: callee:11
diff --git a/llvm/test/tools/llvm-profdata/Inputs/typified-head-only.proftext b/llvm/test/tools/llvm-profdata/Inputs/typified-head-only.proftext
new file mode 100644
index 0000000000000..d20cbecd18144
--- /dev/null
+++ b/llvm/test/tools/llvm-profdata/Inputs/typified-head-only.proftext
@@ -0,0 +1 @@
+head-only:0:7
diff --git a/llvm/test/tools/llvm-profdata/Inputs/typified-multiple-blocks.proftext b/llvm/test/tools/llvm-profdata/Inputs/typified-multiple-blocks.proftext
new file mode 100644
index 0000000000000..f60dd2e2b32fc
--- /dev/null
+++ b/llvm/test/tools/llvm-profdata/Inputs/typified-multiple-blocks.proftext
@@ -0,0 +1,2 @@
+foo:10:1
+ 0: 10
diff --git a/llvm/test/tools/llvm-profdata/Inputs/typified-zero-block.proftext b/llvm/test/tools/llvm-profdata/Inputs/typified-zero-block.proftext
new file mode 100644
index 0000000000000..4e96cc3d09650
--- /dev/null
+++ b/llvm/test/tools/llvm-profdata/Inputs/typified-zero-block.proftext
@@ -0,0 +1,3 @@
+caller:0:0
+ 1: callee:1
+ 0: 1
diff --git a/llvm/test/tools/llvm-profdata/sample-split-layout.test b/llvm/test/tools/llvm-profdata/sample-split-layout.test
index 51091e9f25c02..a762caf4dd385 100644
--- a/llvm/test/tools/llvm-profdata/sample-split-layout.test
+++ b/llvm/test/tools/llvm-profdata/sample-split-layout.test
@@ -1,6 +1,18 @@
+# Verify the legacy split layout round trip.
RUN: llvm-profdata merge --sample --extbinary --split-layout %p/Inputs/sample-profile.proftext -o %t-output
-
RUN: llvm-profdata merge --sample --text --split-layout %t-output | FileCheck %s
+
+# Verify that typified split layout writes and reads both contextual and flat
+# profile/offset section pairs.
+RUN: llvm-profdata merge --sample --extbinary --split-layout --extbinary-force-typified-prof %p/Inputs/sample-profile.proftext -o %t-typified
+RUN: llvm-profdata show --sample --show-sec-info-only %t-typified | FileCheck %s --check-prefix=TYPIFIED
+RUN: llvm-profdata merge --sample --text --split-layout %t-typified | FileCheck %s
+
+TYPIFIED: TypifiedFuncOffsetTableSection {{.*}} Flags: {}
+TYPIFIED-NEXT: TypifiedProfileSection {{.*}} Flags: {}
+TYPIFIED-NEXT: TypifiedFuncOffsetTableSection {{.*}} Flags: {flat}
+TYPIFIED-NEXT: TypifiedProfileSection {{.*}} Flags: {flat}
+
CHECK: main:184019:0
CHECK-NEXT: 4: 534
CHECK-NEXT: 4.2: 534
diff --git a/llvm/test/tools/llvm-profdata/show-typified-profile-info.test b/llvm/test/tools/llvm-profdata/show-typified-profile-info.test
new file mode 100644
index 0000000000000..983e584503324
--- /dev/null
+++ b/llvm/test/tools/llvm-profdata/show-typified-profile-info.test
@@ -0,0 +1,31 @@
+; Verify typified inspection for known, nested, unknown, and empty blocks, and
+; verify that requesting it for a legacy profile produces a warning.
+; RUN: llvm-profdata merge --sample --extbinary-force-typified-prof --extbinary %S/Inputs/sample-flatten-profile.proftext -o %t.typified
+; RUN: llvm-profdata show --sample --show-typified-info-only %t.typified | FileCheck %s --check-prefix=KNOWN
+
+; KNOWN: Function: baz
+; KNOWN-NEXT: Profile blocks: 1
+; KNOWN-NEXT: Type: 0 (LBR), Payload size: {{[1-9][0-9]*}}
+; KNOWN: Nested function: foo
+; KNOWN-NEXT: Profile blocks: 1
+; KNOWN-NEXT: Type: 0 (LBR), Payload size: {{[1-9][0-9]*}}
+
+; Verify that the two structure-only display modes cannot be requested together.
+; RUN: not llvm-profdata show --sample --show-sec-info-only --show-typified-info-only %t.typified 2>&1 | FileCheck %s --check-prefix=BOTH
+
+; BOTH: error: -show-sec-info-only and -show-typified-info-only cannot be used together
+
+; RUN: %python %S/Inputs/corrupt-typified-payload.py %t.typified %t.empty empty-section
+; RUN: llvm-profdata show --sample --show-typified-info-only %t.empty 2>&1 | count 0
+
+; RUN: %python %S/Inputs/corrupt-typified-payload.py %t.typified %t.unknown unknown
+; RUN: llvm-profdata show --sample --show-typified-info-only %t.unknown | FileCheck %s --check-prefix=UNKNOWN
+
+; UNKNOWN: Function: baz
+; UNKNOWN-NEXT: Profile blocks: 1
+; UNKNOWN-NEXT: Type: 127 (unknown), Payload size: {{[1-9][0-9]*}}
+
+; RUN: llvm-profdata merge --sample --extbinary %S/Inputs/sample-flatten-profile.proftext -o %t.legacy
+; RUN: llvm-profdata show --sample --show-typified-info-only %t.legacy 2>&1 | FileCheck %s --check-prefix=LEGACY
+
+; LEGACY: warning: -show-typified-info-only is only supported for typified sample profiles and is ignored for other formats.
diff --git a/llvm/test/tools/llvm-profdata/typified-compress.test b/llvm/test/tools/llvm-profdata/typified-compress.test
new file mode 100644
index 0000000000000..8327e1b472d53
--- /dev/null
+++ b/llvm/test/tools/llvm-profdata/typified-compress.test
@@ -0,0 +1,6 @@
+REQUIRES: zlib
+
+# Round trip from text to compressed typified extbinary and back to text.
+RUN: llvm-profdata merge --sample --extbinary --extbinary-force-typified-prof --compress-all-sections --output=%t.profdata %S/Inputs/sample-profile.proftext
+RUN: llvm-profdata merge --sample --text --output=%t.proftext %t.profdata
+RUN: diff -b %t.proftext %S/Inputs/sample-profile.proftext
diff --git a/llvm/test/tools/llvm-profdata/typified-compressed-info.test b/llvm/test/tools/llvm-profdata/typified-compressed-info.test
new file mode 100644
index 0000000000000..2314527f5269b
--- /dev/null
+++ b/llvm/test/tools/llvm-profdata/typified-compressed-info.test
@@ -0,0 +1,12 @@
+REQUIRES: zlib
+
+; Verify that compressed typified blocks remain inspectable when their payloads
+; exceed the bounded buffer and that overflow does not change the output.
+; RUN: llvm-profdata merge --sample --extbinary-force-typified-prof --extbinary --compress-all-sections %S/Inputs/sample-profile.proftext -o %t.buffered
+; RUN: llvm-profdata merge --sample --extbinary-force-typified-prof --extbinary --compress-all-sections --extbinary-profile-type-buffer-limit=1 %S/Inputs/sample-profile.proftext -o %t.overflow
+; RUN: cmp %t.buffered %t.overflow
+; RUN: llvm-profdata show --sample --show-typified-info-only %t.overflow | FileCheck %s
+
+; CHECK: Function:
+; CHECK-NEXT: Profile blocks: 1
+; CHECK-NEXT: Type: 0 (LBR), Payload size: {{[1-9][0-9]*}}
diff --git a/llvm/test/tools/llvm-profdata/typified-empty-lbr.test b/llvm/test/tools/llvm-profdata/typified-empty-lbr.test
new file mode 100644
index 0000000000000..e3c4f1be3725b
--- /dev/null
+++ b/llvm/test/tools/llvm-profdata/typified-empty-lbr.test
@@ -0,0 +1,8 @@
+; Verify that typified LBR blocks preserve nonzero head and total samples when
+; their body sample maps are empty.
+; RUN: llvm-profdata merge --sample --extbinary-force-typified-prof --extbinary %S/Inputs/sample-typified-empty-lbr.proftext -o %t.prof
+; RUN: llvm-profdata merge --sample --text %t.prof -o - | FileCheck %s --match-full-lines --strict-whitespace
+
+; CHECK:top:42:7
+; CHECK-NEXT:caller:21:3
+; CHECK-NEXT: 1: callee:11
diff --git a/llvm/test/tools/llvm-profdata/typified-head-only.test b/llvm/test/tools/llvm-profdata/typified-head-only.test
new file mode 100644
index 0000000000000..acf2337130510
--- /dev/null
+++ b/llvm/test/tools/llvm-profdata/typified-head-only.test
@@ -0,0 +1,6 @@
+; Verify that a typified LBR block preserves a nonzero head count when its total
+; count and body sample map are empty.
+; RUN: llvm-profdata merge --sample --extbinary-force-typified-prof --extbinary %S/Inputs/typified-head-only.proftext -o %t.prof
+; RUN: llvm-profdata merge --sample --text %t.prof -o - | FileCheck %s --match-full-lines --strict-whitespace
+
+; CHECK:head-only:0:7
diff --git a/llvm/test/tools/llvm-profdata/typified-multiple-blocks.test b/llvm/test/tools/llvm-profdata/typified-multiple-blocks.test
new file mode 100644
index 0000000000000..7bfaafefbe494
--- /dev/null
+++ b/llvm/test/tools/llvm-profdata/typified-multiple-blocks.test
@@ -0,0 +1,14 @@
+; Verify that inspection reports multiple blocks and that the reader skips an
+; unknown block before an LBR block without losing the known payload.
+; RUN: llvm-profdata merge --sample --extbinary-force-typified-prof --extbinary %S/Inputs/typified-multiple-blocks.proftext -o %t.single
+; RUN: %python %S/Inputs/corrupt-typified-payload.py %t.single %t.multiple prepended-unknown
+; RUN: llvm-profdata show --sample --show-typified-info-only %t.multiple | FileCheck %s
+; RUN: llvm-profdata merge --sample --text %t.multiple -o - | FileCheck %s --check-prefix=PROFILE --match-full-lines --strict-whitespace
+
+; CHECK: Function: foo
+; CHECK-NEXT: Profile blocks: 2
+; CHECK-NEXT: Type: 127 (unknown), Payload size: 1
+; CHECK-NEXT: Type: 0 (LBR), Payload size: {{[1-9][0-9]*}}
+
+; PROFILE:foo:10:1
+; PROFILE-NEXT: 0: 10
diff --git a/llvm/test/tools/llvm-profdata/typified-payload-boundaries.test b/llvm/test/tools/llvm-profdata/typified-payload-boundaries.test
new file mode 100644
index 0000000000000..f8e6d9da65ae8
--- /dev/null
+++ b/llvm/test/tools/llvm-profdata/typified-payload-boundaries.test
@@ -0,0 +1,17 @@
+; Verify that typified payload readers cannot cross declared block boundaries
+; and that inspection still reports an invalid unknown block size.
+; RUN: llvm-profdata merge --sample --extbinary-force-typified-prof --extbinary %S/Inputs/sample-typified-empty-lbr.proftext -o %t.valid
+
+; RUN: %python %S/Inputs/corrupt-typified-payload.py %t.valid %t.undersized undersized
+; RUN: not llvm-profdata merge --sample %t.undersized -o /dev/null 2>&1 | FileCheck %s --check-prefix=TRUNCATED
+
+; RUN: %python %S/Inputs/corrupt-typified-payload.py %t.valid %t.oversized oversized
+; RUN: not llvm-profdata merge --sample %t.oversized -o /dev/null 2>&1 | FileCheck %s --check-prefix=MALFORMED
+
+; RUN: %python %S/Inputs/corrupt-typified-payload.py %t.valid %t.unknown-out-of-bounds unknown-out-of-bounds
+; RUN: not llvm-profdata merge --sample %t.unknown-out-of-bounds -o /dev/null 2>&1 | FileCheck %s --check-prefix=TRUNCATED
+; RUN: not llvm-profdata show --sample --show-typified-info-only %t.unknown-out-of-bounds 2>&1 | FileCheck %s --check-prefix=INSPECT
+
+; TRUNCATED: error: {{.*}}Truncated profile data
+; MALFORMED: error: {{.*}}Malformed sample profile data
+; INSPECT: Type: 127 (unknown), Payload size: 18446744073709551615
diff --git a/llvm/test/tools/llvm-profdata/typified-payload-buffer-limit.test b/llvm/test/tools/llvm-profdata/typified-payload-buffer-limit.test
new file mode 100644
index 0000000000000..50b0801414e06
--- /dev/null
+++ b/llvm/test/tools/llvm-profdata/typified-payload-buffer-limit.test
@@ -0,0 +1,24 @@
+; Verify that exceeding the payload buffer limit falls back to a counting pass
+; followed by direct serialization without changing the file format.
+; RUN: llvm-profdata merge --sample --extbinary-force-typified-prof --extbinary %S/Inputs/sample-profile.proftext -o %t.buffered
+; RUN: llvm-profdata merge --sample --extbinary-force-typified-prof --extbinary --extbinary-profile-type-buffer-limit=1 %S/Inputs/sample-profile.proftext -o %t.limited
+; RUN: cmp %t.buffered %t.limited
+; RUN: llvm-profdata show --sample --show-typified-info-only %t.limited | FileCheck %s --check-prefix=SMALL
+
+; SMALL: Function: main
+; SMALL-NEXT: Profile blocks: 1
+; SMALL-NEXT: Type: 0 (LBR), Payload size: {{[1-9][0-9]*}}
+
+; Verify a valid multi-byte ULEB128 payload size and an overflow after the
+; dynamic buffer has already retained the first 4096-byte front-buffer flush.
+; RUN: %python %S/Inputs/generate-large-typified-profile.py %t.large.proftext 2048
+; RUN: llvm-profdata merge --sample --extbinary-force-typified-prof --extbinary %t.large.proftext -o %t.large.buffered
+; RUN: llvm-profdata merge --sample --extbinary-force-typified-prof --extbinary --extbinary-profile-type-buffer-limit=4200 %t.large.proftext -o %t.large.limited
+; RUN: cmp %t.large.buffered %t.large.limited
+; RUN: llvm-profdata show --sample --show-typified-info-only %t.large.limited | FileCheck %s --check-prefix=LARGE
+; RUN: llvm-profdata merge --sample --text %t.large.limited -o %t.large.roundtrip
+; RUN: diff -b %t.large.proftext %t.large.roundtrip
+
+; LARGE: Function: large
+; LARGE-NEXT: Profile blocks: 1
+; LARGE-NEXT: Type: 0 (LBR), Payload size: {{[1-9][0-9][0-9][0-9]+}}
diff --git a/llvm/test/tools/llvm-profdata/typified-zero-block.test b/llvm/test/tools/llvm-profdata/typified-zero-block.test
new file mode 100644
index 0000000000000..ce8d92c5209a8
--- /dev/null
+++ b/llvm/test/tools/llvm-profdata/typified-zero-block.test
@@ -0,0 +1,18 @@
+; Verify that a function may have no typified blocks while its nested function
+; still carries and exposes an LBR block.
+; RUN: llvm-profdata merge --sample --extbinary-force-typified-prof --extbinary %S/Inputs/typified-zero-block.proftext -o %t.prof
+; RUN: llvm-profdata show --sample --show-typified-info-only %t.prof | FileCheck %s
+
+; CHECK: Function: caller
+; CHECK-NEXT: Profile blocks: 0
+; CHECK-NEXT: Nested function: callee
+; CHECK-NEXT: Profile blocks: 1
+; CHECK-NEXT: Type: 0 (LBR), Payload size: {{[1-9][0-9]*}}
+
+; Verify that the zero-block parent and its nested LBR payload both survive a
+; round trip back to text.
+; RUN: llvm-profdata merge --sample --text %t.prof -o - | FileCheck %s --check-prefix=PROFILE --match-full-lines --strict-whitespace
+
+; PROFILE:caller:0:0
+; PROFILE-NEXT: 1: callee:1
+; PROFILE-NEXT: 0: 1
diff --git a/llvm/tools/llvm-profdata/llvm-profdata.cpp b/llvm/tools/llvm-profdata/llvm-profdata.cpp
index a50f427d3269c..43361c2f10495 100644
--- a/llvm/tools/llvm-profdata/llvm-profdata.cpp
+++ b/llvm/tools/llvm-profdata/llvm-profdata.cpp
@@ -469,6 +469,10 @@ static cl::opt<bool> ShowSectionInfoOnly(
"The flag is only usable when the sample profile is in "
"extbinary format"),
cl::sub(ShowSubcommand));
+static cl::opt<bool> ShowTypifiedInfoOnly(
+ "show-typified-info-only", cl::init(false),
+ cl::desc("Show type IDs and payload sizes in a typified sample profile"),
+ cl::sub(ShowSubcommand));
static cl::opt<bool> ShowBinaryIds("binary-ids", cl::init(false),
cl::desc("Show binary ids in the profile. "),
cl::sub(ShowSubcommand));
@@ -3252,11 +3256,26 @@ static int showSampleProfile(ShowFormat SFormat, raw_fd_ostream &OS) {
exitWithErrorCode(EC, Filename);
auto Reader = std::move(ReaderOrErr.get());
+ if (ShowSectionInfoOnly && ShowTypifiedInfoOnly)
+ exitWithError("-show-sec-info-only and "
+ "-show-typified-info-only cannot be used together");
if (ShowSectionInfoOnly) {
showSectionInfo(Reader.get(), OS);
return 0;
}
+ if (ShowTypifiedInfoOnly) {
+ if (std::error_code EC = Reader->dumpProfileTypeInfo(OS)) {
+ OS.flush();
+ exitWithErrorCode(EC, Filename);
+ }
+ if (!Reader->profileIsTypified())
+ WithColor::warning()
+ << "-show-typified-info-only is only supported for "
+ "typified sample profiles and is ignored for other formats.\n";
+ return 0;
+ }
+
if (std::error_code EC = Reader->read())
exitWithErrorCode(EC, Filename);
diff --git a/llvm/unittests/ProfileData/SampleProfTest.cpp b/llvm/unittests/ProfileData/SampleProfTest.cpp
index 50fe3a1951577..e779f467ff576 100644
--- a/llvm/unittests/ProfileData/SampleProfTest.cpp
+++ b/llvm/unittests/ProfileData/SampleProfTest.cpp
@@ -16,6 +16,7 @@
#include "llvm/ProfileData/SampleProfReader.h"
#include "llvm/ProfileData/SampleProfWriter.h"
#include "llvm/Support/Casting.h"
+#include "llvm/Support/CommandLine.h"
#include "llvm/Support/ErrorOr.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/LEB128.h"
@@ -25,6 +26,7 @@
#include "llvm/Support/raw_ostream.h"
#include "llvm/Testing/Support/SupportHelpers.h"
#include "gtest/gtest.h"
+#include <cassert>
#include <string>
#include <vector>
@@ -42,6 +44,34 @@ static ::testing::AssertionResult NoError(std::error_code EC) {
namespace {
+/// Temporarily control typified ExtBinary writing and restore the command-line
+/// option when the test scope ends.
+class ScopedForceTypifiedProfile {
+public:
+ /// Save the current option value and replace it with \p Enabled.
+ explicit ScopedForceTypifiedProfile(bool Enabled) {
+ auto &Options = cl::getRegisteredOptions();
+ auto OptionIt = Options.find("extbinary-force-typified-prof");
+ assert(OptionIt != Options.end() &&
+ "typified profile option is not registered");
+ Option = static_cast<cl::opt<bool> *>(OptionIt->second);
+ SavedValue = Option->getValue();
+ set(Enabled);
+ }
+
+ /// Restore the option value that was active before this scope.
+ ~ScopedForceTypifiedProfile() { set(SavedValue); }
+
+ /// Select whether ExtBinary writes use typified profile sections.
+ void set(bool Enabled) { *Option = Enabled; }
+
+private:
+ /// Registered option controlling forced typified ExtBinary output.
+ cl::opt<bool> *Option = nullptr;
+ /// Option value restored when this scope ends.
+ bool SavedValue = false;
+};
+
struct SampleProfTest : ::testing::Test {
LLVMContext Context;
std::unique_ptr<SampleProfileWriter> Writer;
@@ -472,6 +502,54 @@ TEST_F(SampleProfTest, roundtrip_ext_binary_profile) {
testRoundTrip(SampleProfileFormat::SPF_Ext_Binary, false, false);
}
+// Verify the full ExtBinary round trip through typified profile sections.
+TEST_F(SampleProfTest, roundtrip_typified_ext_binary_profile) {
+ [[maybe_unused]] ScopedForceTypifiedProfile ForceTypified(true);
+ testRoundTrip(SampleProfileFormat::SPF_Ext_Binary, false, false);
+}
+
+// Verify that reusing one ExtBinary writer for a typified profile and then a
+// legacy profile restores the legacy section types.
+TEST_F(SampleProfTest, ext_binary_writer_typified_to_legacy) {
+ ScopedForceTypifiedProfile ForceTypified(true);
+
+ SmallVector<char, 128> Buffer;
+ std::unique_ptr<raw_ostream> OS =
+ std::make_unique<raw_svector_ostream>(Buffer);
+ auto WriterOrErr =
+ SampleProfileWriter::create(OS, SampleProfileFormat::SPF_Ext_Binary);
+ ASSERT_TRUE(NoError(WriterOrErr.getError()));
+ auto ProfileWriter = std::move(WriterOrErr.get());
+
+ StringRef FooName("_Z3fooi");
+ FunctionSamples FooSamples;
+ FooSamples.setFunction(FunctionId(FooName));
+ FooSamples.addTotalSamples(1);
+ SampleProfileMap Profiles;
+ Profiles[FooName] = std::move(FooSamples);
+
+ auto VerifyFormat = [&](bool IsTypified) {
+ std::unique_ptr<MemoryBuffer> MemBuffer = MemoryBuffer::getMemBufferCopy(
+ StringRef(Buffer.data(), Buffer.size()), "profile");
+ auto FS = vfs::getRealFileSystem();
+ auto ReaderOrErr = SampleProfileReader::create(MemBuffer, Context, *FS);
+ ASSERT_TRUE(NoError(ReaderOrErr.getError()));
+ auto ProfileReader = std::move(ReaderOrErr.get());
+ ASSERT_TRUE(NoError(ProfileReader->read()));
+ EXPECT_EQ(IsTypified, ProfileReader->profileIsTypified());
+ };
+
+ ASSERT_TRUE(NoError(ProfileWriter->write(Profiles)));
+ ProfileWriter->getOutputStream().flush();
+ VerifyFormat(true);
+
+ Buffer.clear();
+ ForceTypified.set(false);
+ ASSERT_TRUE(NoError(ProfileWriter->write(Profiles)));
+ ProfileWriter->getOutputStream().flush();
+ VerifyFormat(false);
+}
+
TEST_F(SampleProfTest, roundtrip_md5_ext_binary_profile) {
testRoundTrip(SampleProfileFormat::SPF_Ext_Binary, false, true);
}
@@ -502,6 +580,12 @@ TEST_F(SampleProfTest, roundtrip_eytzinger_name_table_ext_binary_profile) {
cl::ParseCommandLineOptions(2, ArgsFalse, StringRef(), &llvm::nulls());
}
+// Verify typified ExtBinary round trips when the name table uses MD5 hashes.
+TEST_F(SampleProfTest, roundtrip_typified_md5_ext_binary_profile) {
+ [[maybe_unused]] ScopedForceTypifiedProfile ForceTypified(true);
+ testRoundTrip(SampleProfileFormat::SPF_Ext_Binary, false, true);
+}
+
TEST_F(SampleProfTest, remap_text_profile) {
testRoundTrip(SampleProfileFormat::SPF_Text, true, false);
}
>From f3220fdbbbc341e137ee359061ad0730fcf411c5 Mon Sep 17 00:00:00 2001
From: Sergey Shcherbinin <sscherbinin at nvidia.com>
Date: Sat, 1 Aug 2026 10:55:35 +0400
Subject: [PATCH 09/10] [SampleProfile] Remove stale writer declaration
Drop an obsolete declaration retained during conflict resolution so the rebased writer header remains self-contained and builds cleanly.
---
llvm/include/llvm/ProfileData/SampleProfWriter.h | 3 ---
1 file changed, 3 deletions(-)
diff --git a/llvm/include/llvm/ProfileData/SampleProfWriter.h b/llvm/include/llvm/ProfileData/SampleProfWriter.h
index 91a3ccc2faf62..4165fecfe7c95 100644
--- a/llvm/include/llvm/ProfileData/SampleProfWriter.h
+++ b/llvm/include/llvm/ProfileData/SampleProfWriter.h
@@ -241,9 +241,6 @@ class LLVM_ABI SampleProfileWriterBinary : public SampleProfileWriter {
/// Whether a profile payload callback is currently being executed.
bool WritingProfileType = false;
- inline void stablizeNameTable(MapVector<FunctionId, uint32_t> &NameTable,
- std::set<FunctionId> &V);
-
MapVector<FunctionId, uint32_t> NameTable;
void addName(FunctionId FName);
>From 9985713ecf56a24af711700bae5593e1d9242453 Mon Sep 17 00:00:00 2001
From: Sergey Shcherbinin <sscherbinin at nvidia.com>
Date: Sat, 1 Aug 2026 23:48:38 +0400
Subject: [PATCH 10/10] [SampleProfile] Strengthen typified ExtBinary handling
Validate typified payload boundaries and duplicate type IDs, preserve the
correct decoding mode for mixed and lazily loaded sections, and warn when
unknown typified blocks are discarded.
Clarify the on-disk format and expand coverage for malformed payloads, mixed
layouts, CS profiles, vtable profiles, and unknown profile types.
---
llvm/include/llvm/ProfileData/SampleProf.h | 6 +-
.../llvm/ProfileData/SampleProfReader.h | 61 ++++++++++-
llvm/lib/ProfileData/SampleProfReader.cpp | 54 +++++++---
llvm/lib/ProfileData/SampleProfWriter.cpp | 13 ++-
.../Inputs/typified-cs-inline-icall.ll | 50 +++++++++
.../SampleProfile/typified-cs-profile.ll | 2 +-
.../Inputs/corrupt-typified-payload.py | 102 +++++++++++++++---
.../show-typified-profile-info.test | 2 +-
.../typified-mixed-sections.test | 29 +++++
.../typified-multiple-blocks.test | 7 ++
.../typified-payload-boundaries.test | 13 ++-
.../tools/llvm-profdata/typified-vtable.test | 11 ++
llvm/tools/llvm-profdata/llvm-profdata.cpp | 22 ++--
llvm/unittests/ProfileData/SampleProfTest.cpp | 44 ++++----
14 files changed, 341 insertions(+), 75 deletions(-)
create mode 100644 llvm/test/Transforms/SampleProfile/Inputs/typified-cs-inline-icall.ll
create mode 100644 llvm/test/tools/llvm-profdata/typified-mixed-sections.test
create mode 100644 llvm/test/tools/llvm-profdata/typified-vtable.test
diff --git a/llvm/include/llvm/ProfileData/SampleProf.h b/llvm/include/llvm/ProfileData/SampleProf.h
index 4a5585c721247..7d0db0f25731b 100644
--- a/llvm/include/llvm/ProfileData/SampleProf.h
+++ b/llvm/include/llvm/ProfileData/SampleProf.h
@@ -183,8 +183,10 @@ static inline std::string getSecName(SecType Type) {
}
}
-// Types of sample profiles that can be placed in SecTypifiedProfile.
-enum ProfTypes { ProfTypeLBR = 0, ProfTypeNum };
+// Types of sample profiles that can be placed in SecTypifiedProfile. These
+// values are persisted on disk; never change existing values, only append new
+// profile type IDs.
+enum ProfTypes { ProfTypeLBR = 0 };
static inline StringRef getProfTypeName(uint64_t Type) {
switch (Type) {
diff --git a/llvm/include/llvm/ProfileData/SampleProfReader.h b/llvm/include/llvm/ProfileData/SampleProfReader.h
index fe4d2b13952b1..c7d373eb25fdd 100644
--- a/llvm/include/llvm/ProfileData/SampleProfReader.h
+++ b/llvm/include/llvm/ProfileData/SampleProfReader.h
@@ -220,6 +220,37 @@
// in the text format documentation above).
// FUNCTION BODY
// A FUNCTION BODY entry describing the inlined function.
+//
+// An ExtBinary file may contain both legacy and typified profile sections.
+// Each section's type independently selects its function-body encoding.
+//
+// TYPIFIED FUNCTION BODY (used by SecTypifiedProfile)
+// NAME_IDX (uint64_t)
+// Index into the name table indicating the function name.
+// NUM_PROFILE_TYPES (uint64_t)
+// Number of typed profile blocks attached to this function. Zero is
+// valid when the function has no payload for any profile type.
+// PROFILE TYPE BLOCKS
+// A list of NUM_PROFILE_TYPES entries. Each entry contains:
+// TYPE (uint64_t)
+// Profile type ID. A type may occur at most once per function;
+// duplicate IDs make the profile malformed.
+// PAYLOAD_SIZE (uint64_t)
+// Size of PAYLOAD in bytes.
+// PAYLOAD
+// Type-specific data occupying exactly PAYLOAD_SIZE bytes.
+// Readers skip unknown types using PAYLOAD_SIZE.
+// A known type must consume exactly PAYLOAD_SIZE bytes; extend an
+// existing payload by assigning a new profile type ID.
+//
+// The ProfTypeLBR payload contains HEAD_SAMPLES for top-level functions,
+// followed by SAMPLES, NRECS, and BODY RECORDS as described above.
+// Nested functions omit HEAD_SAMPLES.
+// NUM_INLINED_FUNCTIONS (uint32_t)
+// Number of callees inlined into this function.
+// INLINED FUNCTION RECORDS
+// Encoded as described above, except each nested FUNCTION BODY uses the
+// typified representation.
//===----------------------------------------------------------------------===//
#ifndef LLVM_PROFILEDATA_SAMPLEPROFREADER_H
@@ -639,8 +670,11 @@ class SampleProfileReader {
return EC;
}
- /// Return whether the profile uses typified profile blocks.
- bool profileIsTypified() const { return IsProfileTypified; }
+ /// Return whether the input contains a typified profile section.
+ virtual bool hasTypifiedProfileSection() const { return false; }
+
+ /// Return whether any unknown typified profile blocks were skipped.
+ bool hasUnknownProfileTypes() const { return HasUnknownProfileTypes; }
/// Return whether names in the profile are all MD5 numbers.
bool useMD5() const { return ProfileIsMD5; }
@@ -711,11 +745,21 @@ class SampleProfileReader {
DenseMap<uint64_t, std::pair<const uint8_t *, const uint8_t *>>
FuncMetadataIndex;
- std::pair<const uint8_t *, const uint8_t *> ProfileSecRange;
- /// Whether the input uses SecTypifiedProfile for function profiles.
- bool IsProfileTypified = false;
+ /// A profile section retained for loading additional functions on demand.
+ struct ProfileSectionRange {
+ /// First byte of the retained section.
+ const uint8_t *Start = nullptr;
+ /// One-past-the-end byte of the retained section.
+ const uint8_t *End = nullptr;
+ /// Whether the retained section uses typified payload encoding.
+ bool IsTypified = false;
+ };
+ /// Profile section most recently selected for on-demand loading.
+ ProfileSectionRange ProfileSecRange;
/// Optional stream for typified block structure; null disables the output.
raw_ostream *ProfileTypeInfoOS = nullptr;
+ /// Whether reading skipped at least one unknown typified profile block.
+ bool HasUnknownProfileTypes = false;
/// Whether the profile has attribute metadata.
bool ProfileHasAttribute = false;
@@ -1120,6 +1164,13 @@ class LLVM_ABI SampleProfileReaderExtBinaryBase
/// Get the total size of header and all sections.
uint64_t getFileSize();
bool dumpSectionInfo(raw_ostream &OS = dbgs()) override;
+ /// Return whether the section table contains a typified profile section.
+ bool hasTypifiedProfileSection() const override {
+ for (const auto &Entry : SecHdrTable)
+ if (Entry.Type == SecTypifiedProfile)
+ return true;
+ return false;
+ }
/// Collect functions with definitions in Module M. Return true if
/// the reader has been given a module.
diff --git a/llvm/lib/ProfileData/SampleProfReader.cpp b/llvm/lib/ProfileData/SampleProfReader.cpp
index 0b07ff3ab46a7..16c336c3718b8 100644
--- a/llvm/lib/ProfileData/SampleProfReader.cpp
+++ b/llvm/lib/ProfileData/SampleProfReader.cpp
@@ -22,6 +22,7 @@
#include "llvm/ProfileData/SampleProfReader.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallSet.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/IR/Module.h"
#include "llvm/IR/ProfileSummary.h"
@@ -720,7 +721,7 @@ SampleProfileReaderBinary::readCallsiteVTableProf(FunctionSamples &FProfile) {
std::error_code
SampleProfileReaderBinary::readLBRProfile(FunctionSamples &FProfile,
bool IsNested) {
- if (IsProfileTypified && !IsNested) {
+ if (ProfileSecRange.IsTypified && !IsNested) {
auto NumHeadSamples = readNumber<uint64_t>();
if (std::error_code EC = NumHeadSamples.getError())
return EC;
@@ -791,11 +792,21 @@ SampleProfileReaderBinary::readTypifiedProfile(FunctionSamples &FProfile,
<< FProfile.getContext().toString()
<< "\n Profile blocks: " << *ProfNum << "\n";
+ // Each type identifies one logical payload for the function. Decoding the
+ // same type twice would merge absolute counters from malformed input.
+ SmallSet<uint64_t, 4> SeenTypes;
+
// Read the specified number of typified profiles.
- for (uint64_t i = 0; i < *ProfNum; i++) {
+ for (uint64_t I = 0; I < *ProfNum; ++I) {
auto Type = readNumber<uint64_t>();
if (std::error_code EC = Type.getError())
return EC;
+ // Report the conflicting ID so malformed profiles can be diagnosed
+ // without inspecting their binary encoding.
+ if (!SeenTypes.insert(*Type).second) {
+ reportError(0, "Duplicate profile type ID: " + Twine(*Type));
+ return sampleprof_error::malformed;
+ }
auto Size = readNumber<uint64_t>();
if (std::error_code EC = Size.getError())
return EC;
@@ -803,8 +814,15 @@ SampleProfileReaderBinary::readTypifiedProfile(FunctionSamples &FProfile,
*ProfileTypeInfoOS << " Type: " << *Type << " ("
<< getProfTypeName(*Type)
<< "), Payload size: " << *Size << "\n";
- if (*Size > static_cast<uint64_t>(End - Data))
+ const uint64_t RemainingSize = End - Data;
+ // Diagnose a size that would let the payload cross its containing section.
+ if (*Size > RemainingSize) {
+ reportError(0, "Profile type ID " + Twine(*Type) +
+ " declares payload size " + Twine(*Size) +
+ ", but only " + Twine(RemainingSize) +
+ " bytes remain");
return sampleprof_error::truncated;
+ }
const uint8_t *PayloadEnd = Data + *Size;
std::error_code EC = sampleprof_error::success;
@@ -817,14 +835,22 @@ SampleProfileReaderBinary::readTypifiedProfile(FunctionSamples &FProfile,
break;
default:
// Skip unknown profile types for forward compatibility.
+ HasUnknownProfileTypes = true;
Data = PayloadEnd;
break;
}
if (EC)
return EC;
- if (Data != PayloadEnd)
+ // Reject trailing bytes because every known decoder must consume exactly
+ // the payload declared for its type.
+ if (Data != PayloadEnd) {
+ reportError(0,
+ "Profile type ID " + Twine(*Type) +
+ " did not consume its complete payload; unread bytes: " +
+ Twine(PayloadEnd - Data));
return sampleprof_error::malformed;
+ }
}
return sampleprof_error::success;
@@ -833,7 +859,7 @@ SampleProfileReaderBinary::readTypifiedProfile(FunctionSamples &FProfile,
std::error_code
SampleProfileReaderBinary::readProfile(FunctionSamples &FProfile,
bool IsNested) {
- if (IsProfileTypified) {
+ if (ProfileSecRange.IsTypified) {
if (std::error_code EC = readTypifiedProfile(FProfile, IsNested))
return EC;
} else {
@@ -865,7 +891,7 @@ SampleProfileReaderBinary::readProfile(FunctionSamples &FProfile,
FunctionSamples &CalleeProfile = FProfile.functionSamplesAt(
LineLocation(*LineOffset, DiscriminatorVal))[*FName];
CalleeProfile.setFunction(*FName);
- if (std::error_code EC = readProfile(CalleeProfile, true))
+ if (std::error_code EC = readProfile(CalleeProfile, /*IsNested=*/true))
return EC;
}
@@ -880,7 +906,7 @@ SampleProfileReaderBinary::readFuncProfile(const uint8_t *Start,
SampleProfileMap &Profiles) {
Data = Start;
ErrorOr<uint64_t> NumHeadSamples = 0;
- if (!IsProfileTypified) {
+ if (!ProfileSecRange.IsTypified) {
NumHeadSamples = readNumber<uint64_t>();
if (std::error_code EC = NumHeadSamples.getError())
return EC;
@@ -894,13 +920,13 @@ SampleProfileReaderBinary::readFuncProfile(const uint8_t *Start,
auto Res = Profiles.try_emplace(Hash, FContext, FunctionSamples());
FunctionSamples &FProfile = Res.first->second;
FProfile.setContext(FContext);
- if (!IsProfileTypified)
+ if (!ProfileSecRange.IsTypified)
FProfile.addHeadSamples(*NumHeadSamples);
if (FContext.hasContext())
CSProfileCount++;
- if (std::error_code EC = readProfile(FProfile, false))
+ if (std::error_code EC = readProfile(FProfile, /*IsNested=*/false))
return EC;
return sampleprof_error::success;
}
@@ -962,7 +988,8 @@ std::error_code SampleProfileReaderExtBinaryBase::readOneSection(
}
case SecLBRProfile:
case SecTypifiedProfile:
- ProfileSecRange = std::make_pair(Data, End);
+ // Retain the section and its encoding for subsequent on-demand reads.
+ ProfileSecRange = {Data, End, Entry.Type == SecTypifiedProfile};
if (std::error_code EC = readFuncProfiles())
return EC;
break;
@@ -1037,8 +1064,8 @@ SampleProfileReaderExtBinaryBase::read(const DenseSet<StringRef> &FuncsToUse,
if (FuncsToUse.empty())
return sampleprof_error::success;
- Data = ProfileSecRange.first;
- End = ProfileSecRange.second;
+ Data = ProfileSecRange.Start;
+ End = ProfileSecRange.End;
if (std::error_code EC = readFuncProfiles(FuncsToUse, Profiles))
return EC;
End = Data;
@@ -1283,9 +1310,6 @@ std::error_code SampleProfileReaderExtBinaryBase::readImpl() {
reinterpret_cast<const uint8_t *>(Buffer->getBufferStart());
for (auto &Entry : SecHdrTable) {
- if (Entry.Type == SecTypifiedProfile)
- IsProfileTypified = true;
-
// Skip empty section.
if (!Entry.Size)
continue;
diff --git a/llvm/lib/ProfileData/SampleProfWriter.cpp b/llvm/lib/ProfileData/SampleProfWriter.cpp
index d9818d607e75a..c9bb857050c23 100644
--- a/llvm/lib/ProfileData/SampleProfWriter.cpp
+++ b/llvm/lib/ProfileData/SampleProfWriter.cpp
@@ -289,7 +289,7 @@ SampleProfileWriterExtBinaryBase::writeSample(const FunctionSamples &S) {
FuncOffsetTable[Context] = Offset - SecLBRProfileStart;
if (!WriteTypifiedProf)
encodeULEB128(S.getHeadSamples(), *OutputStream);
- return writeBody(S, false);
+ return writeBody(S, /*IsNested=*/false);
}
std::error_code
@@ -1093,6 +1093,9 @@ class BoundedBufferingStream final : public raw_ostream {
SetBuffer(FrontBuffer, sizeof(FrontBuffer));
}
+ /// Flush staged bytes before raw_ostream verifies that its buffer is empty.
+ ~BoundedBufferingStream() override { flush(); }
+
/// Prepare the stream to count and, if possible, retain another payload.
void resetPayload() {
assert(GetNumBytesInBuffer() == 0 && "front buffer is not empty");
@@ -1228,8 +1231,8 @@ std::error_code SampleProfileWriterBinary::writeProfileType(
// An oversized payload was discarded during the counting pass, so serialize
// it directly now that its size has been emitted.
uint64_t PayloadStart = OS.tell();
- if (std::error_code EC = WritePayload())
- return EC;
+ if (std::error_code SecondPassEC = WritePayload())
+ return SecondPassEC;
// Reject output if the callback did not reproduce the counted size.
if (OS.tell() - PayloadStart != BufferStream->payloadSize())
return sampleprof_error::malformed;
@@ -1284,7 +1287,7 @@ std::error_code SampleProfileWriterBinary::writeBody(const FunctionSamples &S,
for (const auto &J : S.getCallsiteSamples())
for (const auto &FS : J.second) {
J.first.serialize(OS);
- if (std::error_code EC = writeBody(FS.second, true))
+ if (std::error_code EC = writeBody(FS.second, /*IsNested=*/true))
return EC;
}
@@ -1300,7 +1303,7 @@ std::error_code SampleProfileWriterBinary::writeBody(const FunctionSamples &S,
std::error_code
SampleProfileWriterBinary::writeSample(const FunctionSamples &S) {
encodeULEB128(S.getHeadSamples(), *OutputStream);
- return writeBody(S, false);
+ return writeBody(S, /*IsNested=*/false);
}
/// Create a sample profile file writer based on the specified format.
diff --git a/llvm/test/Transforms/SampleProfile/Inputs/typified-cs-inline-icall.ll b/llvm/test/Transforms/SampleProfile/Inputs/typified-cs-inline-icall.ll
new file mode 100644
index 0000000000000..c635a018b473d
--- /dev/null
+++ b/llvm/test/Transforms/SampleProfile/Inputs/typified-cs-inline-icall.ll
@@ -0,0 +1,50 @@
+define void @test(ptr) #0 !dbg !3 {
+ call void @_Z3foov(), !dbg !7
+ call void @_Z3barv(), !dbg !7
+ call void @_Z3bazv(), !dbg !7
+ %2 = alloca ptr
+ store ptr %0, ptr %2
+ %3 = load ptr, ptr %2
+ call void %3(), !dbg !4
+ %4 = alloca ptr
+ store ptr %0, ptr %4
+ %5 = load ptr, ptr %4
+ call void %5(), !dbg !5
+ ret void
+}
+
+define void @_Z3foov() #0 !dbg !8 {
+ ret void
+}
+
+define void @_Z3barv() #0 !dbg !9 {
+ ret void
+}
+
+define void @_Z3bazv() #0 !dbg !10 {
+ ret void
+}
+
+define void @_Z3zoov() #0 !dbg !11 {
+ ret void
+}
+
+attributes #0 = { "use-sample-profile" }
+
+!llvm.dbg.cu = !{!0}
+!12 = !{null}
+!13 = !DISubroutineType(types: !12)
+!llvm.module.flags = !{!2}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus, file: !1)
+!1 = !DIFile(filename: "test.cc", directory: "/")
+!2 = !{i32 2, !"Debug Info Version", i32 3}
+!3 = distinct !DISubprogram(name: "test", scope: !1, file: !1, line: 3, type: !13, unit: !0)
+!4 = !DILocation(line: 4, scope: !3)
+!5 = !DILocation(line: 5, scope: !3)
+!6 = !DILocation(line: 6, scope: !3)
+!7 = !DILocation(line: 7, scope: !3)
+!8 = distinct !DISubprogram(name: "foo", linkageName: "_Z3foov", scope: !1, file: !1, line: 29, type: !13, unit: !0)
+!9 = distinct !DISubprogram(name: "bar", linkageName: "_Z3barv", scope: !1, file: !1, line: 32, type: !13, unit: !0)
+!10 = distinct !DISubprogram(name: "baz", linkageName: "_Z3bazv", scope: !1, file: !1, line: 24, type: !13, unit: !0)
+!11 = distinct !DISubprogram(name: "zoo", linkageName: "_Z3zoov", scope: !1, file: !1, line: 24, type: !13, unit: !0)
diff --git a/llvm/test/Transforms/SampleProfile/typified-cs-profile.ll b/llvm/test/Transforms/SampleProfile/typified-cs-profile.ll
index 5e4d13939cd96..26706d01b2c85 100644
--- a/llvm/test/Transforms/SampleProfile/typified-cs-profile.ll
+++ b/llvm/test/Transforms/SampleProfile/typified-cs-profile.ll
@@ -5,7 +5,7 @@
; RUN: opt -S %s -passes=sample-profile -sample-profile-file=%t.prof | FileCheck %s --check-prefix=IR
; RUN: llvm-profdata merge --sample --extbinary-force-typified-prof --extbinary \
; RUN: %S/Inputs/indirect-call-csspgo.prof -o %t.payload.prof
-; RUN: opt -S %S/csspgo-inline-icall.ll -passes=sample-profile \
+; RUN: opt -S %S/Inputs/typified-cs-inline-icall.ll -passes=sample-profile \
; RUN: -sample-profile-file=%t.payload.prof \
; RUN: -sample-profile-icp-relative-hotness=1 -pass-remarks=sample-profile \
; RUN: -sample-profile-inline-size=0 -o /dev/null 2>&1 | \
diff --git a/llvm/test/tools/llvm-profdata/Inputs/corrupt-typified-payload.py b/llvm/test/tools/llvm-profdata/Inputs/corrupt-typified-payload.py
index 2257893c72f2b..7f06eb798688b 100644
--- a/llvm/test/tools/llvm-profdata/Inputs/corrupt-typified-payload.py
+++ b/llvm/test/tools/llvm-profdata/Inputs/corrupt-typified-payload.py
@@ -2,6 +2,7 @@
import struct
TYPIFIED_PROFILE_SECTION = 33
+LEGACY_PROFILE_SECTION = 32
UNKNOWN_PROFILE_TYPE = 127
@@ -31,9 +32,37 @@ def encode_uleb(value):
return result
+def write_output(data, path):
+ """Write the transformed profile to its requested output path."""
+ with open(path, "wb") as output_file:
+ output_file.write(data)
+
+
+def read_sections(data):
+ """Return the section-count offset and decoded section headers."""
+ _, offset = read_uleb(data, 0)
+ _, offset = read_uleb(data, offset)
+ section_count_offset = offset
+ section_count = struct.unpack_from("<Q", data, offset)[0]
+ offset += 8
+
+ sections = []
+ for _ in range(section_count):
+ section_header_offset = offset
+ section_type, _, section_offset, section_size = struct.unpack_from(
+ "<QQQQ", data, offset
+ )
+ offset += 32
+ sections.append(
+ (section_header_offset, section_type, section_offset, section_size)
+ )
+ return section_count_offset, sections
+
+
parser = argparse.ArgumentParser()
parser.add_argument("input")
parser.add_argument("output")
+parser.add_argument("--donor")
parser.add_argument(
"modification",
choices=(
@@ -42,35 +71,75 @@ def encode_uleb(value):
"unknown",
"unknown-out-of-bounds",
"prepended-unknown",
+ "duplicate-type",
"empty-section",
+ "prepend-empty-typified",
+ "prepend-typified",
),
)
args = parser.parse_args()
with open(args.input, "rb") as input_file:
data = bytearray(input_file.read())
-_, offset = read_uleb(data, 0)
-_, offset = read_uleb(data, offset)
-section_count = struct.unpack_from("<Q", data, offset)[0]
-offset += 8
-sections = []
+section_count_offset, sections = read_sections(data)
+section_count = len(sections)
profile_offset = None
profile_size = None
profile_header_offset = None
-for _ in range(section_count):
- section_header_offset = offset
- section_type, _, section_offset, section_size = struct.unpack_from(
- "<QQQQ", data, offset
- )
- offset += 32
- sections.append((section_header_offset, section_type, section_offset, section_size))
+for section_header_offset, section_type, section_offset, section_size in sections:
if section_type == TYPIFIED_PROFILE_SECTION:
assert profile_offset is None, "expected exactly one typified profile section"
profile_offset = section_offset
profile_size = section_size
profile_header_offset = section_header_offset
+if args.modification in ("prepend-empty-typified", "prepend-typified"):
+ legacy_sections = [
+ section for section in sections if section[1] == LEGACY_PROFILE_SECTION
+ ]
+ assert legacy_sections, "expected a legacy profile section"
+ legacy_header_offset, _, legacy_offset, _ = legacy_sections[0]
+ header_size = struct.calcsize("<QQQQ")
+
+ # Growing the section table shifts every section payload by one header.
+ for section_header_offset, _, section_offset, _ in sections:
+ struct.pack_into(
+ "<Q", data, section_header_offset + 16, section_offset + header_size
+ )
+
+ typified_payload = b""
+ typified_offset = legacy_offset + header_size
+ if args.modification == "prepend-typified":
+ assert args.donor, "expected --donor for a nonempty typified section"
+ with open(args.donor, "rb") as donor_file:
+ donor_data = donor_file.read()
+ _, donor_sections = read_sections(donor_data)
+ donor_profiles = [
+ section
+ for section in donor_sections
+ if section[1] == TYPIFIED_PROFILE_SECTION and section[3] != 0
+ ]
+ assert len(donor_profiles) == 1, "expected one nonempty donor section"
+ _, _, donor_offset, donor_size = donor_profiles[0]
+ typified_payload = donor_data[donor_offset : donor_offset + donor_size]
+ typified_offset = len(data) + header_size
+
+ # Place the typified section immediately before a valid legacy one to
+ # verify that its decoding mode does not leak into the following section.
+ typified_header = struct.pack(
+ "<QQQQ",
+ TYPIFIED_PROFILE_SECTION,
+ 0,
+ typified_offset,
+ len(typified_payload),
+ )
+ data[legacy_header_offset:legacy_header_offset] = typified_header
+ data.extend(typified_payload)
+ struct.pack_into("<Q", data, section_count_offset, section_count + 1)
+ write_output(data, args.output)
+ raise SystemExit
+
assert profile_offset is not None
assert profile_size is not None
assert profile_header_offset is not None
@@ -136,6 +205,12 @@ def replace_payload_size(new_size):
data[profile_count_offset] = 2
data[insertion_offset:insertion_offset] = unknown_block
adjust_sections_after(insertion_offset, len(unknown_block))
+elif args.modification == "duplicate-type":
+ block_end = payload_offset + payload_size
+ duplicate_block = bytes(data[type_offset:block_end])
+ data[profile_count_offset] = 2
+ data[block_end:block_end] = duplicate_block
+ adjust_sections_after(block_end, len(duplicate_block))
else:
del data[profile_start:profile_end]
for section_header_offset, _, section_offset, _ in sections:
@@ -148,5 +223,4 @@ def replace_payload_size(new_size):
)
struct.pack_into("<Q", data, profile_header_offset + 24, 0)
-with open(args.output, "wb") as output_file:
- output_file.write(data)
+write_output(data, args.output)
diff --git a/llvm/test/tools/llvm-profdata/show-typified-profile-info.test b/llvm/test/tools/llvm-profdata/show-typified-profile-info.test
index 983e584503324..f6664b0d1321d 100644
--- a/llvm/test/tools/llvm-profdata/show-typified-profile-info.test
+++ b/llvm/test/tools/llvm-profdata/show-typified-profile-info.test
@@ -28,4 +28,4 @@
; RUN: llvm-profdata merge --sample --extbinary %S/Inputs/sample-flatten-profile.proftext -o %t.legacy
; RUN: llvm-profdata show --sample --show-typified-info-only %t.legacy 2>&1 | FileCheck %s --check-prefix=LEGACY
-; LEGACY: warning: -show-typified-info-only is only supported for typified sample profiles and is ignored for other formats.
+; LEGACY: warning: no typified profile section; nothing to show
diff --git a/llvm/test/tools/llvm-profdata/typified-mixed-sections.test b/llvm/test/tools/llvm-profdata/typified-mixed-sections.test
new file mode 100644
index 0000000000000..49c9e394dfacf
--- /dev/null
+++ b/llvm/test/tools/llvm-profdata/typified-mixed-sections.test
@@ -0,0 +1,29 @@
+; Verify that an empty typified section is skipped without preventing a
+; following legacy profile section from being read.
+; RUN: llvm-profdata merge --sample --extbinary %S/Inputs/sample-typified-empty-lbr.proftext -o %t.legacy
+; RUN: %python %S/Inputs/corrupt-typified-payload.py %t.legacy %t.empty-mixed prepend-empty-typified
+; RUN: llvm-profdata merge --sample --text %t.empty-mixed -o - | FileCheck %s --check-prefix=PROFILE
+
+; Verify that the temporary decoding mode is also restored after reading a
+; nonempty typified section. Both sections contain the same profiles, so every
+; counter is expected to be accumulated exactly twice.
+; RUN: llvm-profdata merge --sample --extbinary-force-typified-prof --extbinary %S/Inputs/sample-typified-empty-lbr.proftext -o %t.typified
+; RUN: %python %S/Inputs/corrupt-typified-payload.py %t.legacy %t.nonempty-mixed prepend-typified --donor %t.typified
+; RUN: llvm-profdata merge --sample --text %t.nonempty-mixed -o - | FileCheck %s --check-prefix=ACCUMULATED
+
+; Verify that the section table reports both physical profile section formats.
+; RUN: llvm-profdata show --sample --show-sec-info-only %t.empty-mixed | FileCheck %s --check-prefix=SECTIONS
+
+; Verify that an empty typified section still makes typified inspection
+; applicable, without producing the legacy-profile warning.
+; RUN: llvm-profdata show --sample --show-typified-info-only %t.empty-mixed 2>&1 | FileCheck %s --allow-empty --check-prefix=NO-WARNING
+
+; PROFILE: top:42:7
+; PROFILE-NEXT: caller:21:3
+; PROFILE-NEXT: 1: callee:11
+; ACCUMULATED: top:84:14
+; ACCUMULATED-NEXT: caller:42:6
+; ACCUMULATED-NEXT: 1: callee:22
+; SECTIONS: TypifiedProfileSection
+; SECTIONS: LBRProfileSection
+; NO-WARNING-NOT: only supported for typified sample profiles
diff --git a/llvm/test/tools/llvm-profdata/typified-multiple-blocks.test b/llvm/test/tools/llvm-profdata/typified-multiple-blocks.test
index 7bfaafefbe494..b9a7a64a9acaa 100644
--- a/llvm/test/tools/llvm-profdata/typified-multiple-blocks.test
+++ b/llvm/test/tools/llvm-profdata/typified-multiple-blocks.test
@@ -5,10 +5,17 @@
; RUN: llvm-profdata show --sample --show-typified-info-only %t.multiple | FileCheck %s
; RUN: llvm-profdata merge --sample --text %t.multiple -o - | FileCheck %s --check-prefix=PROFILE --match-full-lines --strict-whitespace
+; Verify that re-emitting a profile warns about the unknown block that cannot
+; be preserved, while retaining the known LBR payload.
+; RUN: llvm-profdata merge --sample --extbinary %t.multiple -o %t.reemitted 2>&1 | FileCheck %s --check-prefix=WARNING
+; RUN: llvm-profdata merge --sample --text %t.reemitted -o - | FileCheck %s --check-prefix=PROFILE --match-full-lines --strict-whitespace
+
; CHECK: Function: foo
; CHECK-NEXT: Profile blocks: 2
; CHECK-NEXT: Type: 127 (unknown), Payload size: 1
; CHECK-NEXT: Type: 0 (LBR), Payload size: {{[1-9][0-9]*}}
+; WARNING: warning: {{.*}}: unknown typified profile blocks were ignored and will not be preserved
+
; PROFILE:foo:10:1
; PROFILE-NEXT: 0: 10
diff --git a/llvm/test/tools/llvm-profdata/typified-payload-boundaries.test b/llvm/test/tools/llvm-profdata/typified-payload-boundaries.test
index f8e6d9da65ae8..596e09a606628 100644
--- a/llvm/test/tools/llvm-profdata/typified-payload-boundaries.test
+++ b/llvm/test/tools/llvm-profdata/typified-payload-boundaries.test
@@ -6,12 +6,19 @@
; RUN: not llvm-profdata merge --sample %t.undersized -o /dev/null 2>&1 | FileCheck %s --check-prefix=TRUNCATED
; RUN: %python %S/Inputs/corrupt-typified-payload.py %t.valid %t.oversized oversized
-; RUN: not llvm-profdata merge --sample %t.oversized -o /dev/null 2>&1 | FileCheck %s --check-prefix=MALFORMED
+; RUN: not llvm-profdata merge --sample %t.oversized -o /dev/null 2>&1 | FileCheck %s --check-prefix=UNCONSUMED
+
+; Verify that duplicate type IDs are rejected instead of decoding and merging
+; the same logical payload twice.
+; RUN: %python %S/Inputs/corrupt-typified-payload.py %t.valid %t.duplicate duplicate-type
+; RUN: not llvm-profdata merge --sample %t.duplicate -o /dev/null 2>&1 | FileCheck %s --check-prefix=DUPLICATE
; RUN: %python %S/Inputs/corrupt-typified-payload.py %t.valid %t.unknown-out-of-bounds unknown-out-of-bounds
-; RUN: not llvm-profdata merge --sample %t.unknown-out-of-bounds -o /dev/null 2>&1 | FileCheck %s --check-prefix=TRUNCATED
+; RUN: not llvm-profdata merge --sample %t.unknown-out-of-bounds -o /dev/null 2>&1 | FileCheck %s --check-prefix=DECLARED-SIZE
; RUN: not llvm-profdata show --sample --show-typified-info-only %t.unknown-out-of-bounds 2>&1 | FileCheck %s --check-prefix=INSPECT
; TRUNCATED: error: {{.*}}Truncated profile data
-; MALFORMED: error: {{.*}}Malformed sample profile data
+; UNCONSUMED: Profile type ID 0 did not consume its complete payload; unread bytes: 1
+; DUPLICATE: Duplicate profile type ID: 0
+; DECLARED-SIZE: Profile type ID 127 declares payload size 18446744073709551615
; INSPECT: Type: 127 (unknown), Payload size: 18446744073709551615
diff --git a/llvm/test/tools/llvm-profdata/typified-vtable.test b/llvm/test/tools/llvm-profdata/typified-vtable.test
new file mode 100644
index 0000000000000..19bdd298a153c
--- /dev/null
+++ b/llvm/test/tools/llvm-profdata/typified-vtable.test
@@ -0,0 +1,11 @@
+; Verify that vtable type profiles remain readable when their function records
+; use typified LBR payloads.
+; RUN: llvm-profdata merge --sample --extbinary \
+; RUN: --extbinary-force-typified-prof \
+; RUN: --extbinary-write-vtable-type-prof \
+; RUN: %S/Inputs/sample-profile-ext.proftext -o %t.prof
+; RUN: llvm-profdata show --sample --show-sec-info-only %t.prof | FileCheck %s
+; RUN: llvm-profdata merge --sample --text %t.prof -o %t.proftext
+; RUN: diff -b %S/Inputs/sample-profile-ext.proftext %t.proftext
+
+; CHECK: TypifiedProfileSection
diff --git a/llvm/tools/llvm-profdata/llvm-profdata.cpp b/llvm/tools/llvm-profdata/llvm-profdata.cpp
index 43361c2f10495..3d67c25d1d36a 100644
--- a/llvm/tools/llvm-profdata/llvm-profdata.cpp
+++ b/llvm/tools/llvm-profdata/llvm-profdata.cpp
@@ -1630,6 +1630,13 @@ static void mergeSampleProfile(const WeightedFileVector &Inputs,
continue;
}
+ // Merging cannot preserve payloads that this reader does not understand,
+ // so make the otherwise intentional forward-compatible skip visible.
+ if (Reader->hasUnknownProfileTypes())
+ warn("unknown typified profile blocks were ignored and will not be "
+ "preserved",
+ Input.Filename);
+
SampleProfileMap &Profiles = Reader->getProfiles();
if (ProfileIsProbeBased &&
ProfileIsProbeBased != FunctionSamples::ProfileIsProbeBased)
@@ -3247,6 +3254,10 @@ static int showHotFunctionList(const sampleprof::SampleProfileMap &Profiles,
static int showSampleProfile(ShowFormat SFormat, raw_fd_ostream &OS) {
if (SFormat == ShowFormat::Yaml)
exitWithError("YAML output is not supported for sample profiles");
+ if (ShowSectionInfoOnly && ShowTypifiedInfoOnly)
+ exitWithError("-show-sec-info-only and "
+ "-show-typified-info-only cannot be used together");
+
using namespace sampleprof;
LLVMContext Context;
auto FS = vfs::getRealFileSystem();
@@ -3256,23 +3267,20 @@ static int showSampleProfile(ShowFormat SFormat, raw_fd_ostream &OS) {
exitWithErrorCode(EC, Filename);
auto Reader = std::move(ReaderOrErr.get());
- if (ShowSectionInfoOnly && ShowTypifiedInfoOnly)
- exitWithError("-show-sec-info-only and "
- "-show-typified-info-only cannot be used together");
if (ShowSectionInfoOnly) {
showSectionInfo(Reader.get(), OS);
return 0;
}
if (ShowTypifiedInfoOnly) {
+ if (!Reader->hasTypifiedProfileSection()) {
+ WithColor::warning() << "no typified profile section; nothing to show\n";
+ return 0;
+ }
if (std::error_code EC = Reader->dumpProfileTypeInfo(OS)) {
OS.flush();
exitWithErrorCode(EC, Filename);
}
- if (!Reader->profileIsTypified())
- WithColor::warning()
- << "-show-typified-info-only is only supported for "
- "typified sample profiles and is ignored for other formats.\n";
return 0;
}
diff --git a/llvm/unittests/ProfileData/SampleProfTest.cpp b/llvm/unittests/ProfileData/SampleProfTest.cpp
index e779f467ff576..9716bb1d0373d 100644
--- a/llvm/unittests/ProfileData/SampleProfTest.cpp
+++ b/llvm/unittests/ProfileData/SampleProfTest.cpp
@@ -17,6 +17,7 @@
#include "llvm/ProfileData/SampleProfWriter.h"
#include "llvm/Support/Casting.h"
#include "llvm/Support/CommandLine.h"
+#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/ErrorOr.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/LEB128.h"
@@ -26,7 +27,6 @@
#include "llvm/Support/raw_ostream.h"
#include "llvm/Testing/Support/SupportHelpers.h"
#include "gtest/gtest.h"
-#include <cassert>
#include <string>
#include <vector>
@@ -48,28 +48,28 @@ namespace {
/// option when the test scope ends.
class ScopedForceTypifiedProfile {
public:
- /// Save the current option value and replace it with \p Enabled.
- explicit ScopedForceTypifiedProfile(bool Enabled) {
- auto &Options = cl::getRegisteredOptions();
- auto OptionIt = Options.find("extbinary-force-typified-prof");
- assert(OptionIt != Options.end() &&
- "typified profile option is not registered");
- Option = static_cast<cl::opt<bool> *>(OptionIt->second);
- SavedValue = Option->getValue();
- set(Enabled);
- }
-
- /// Restore the option value that was active before this scope.
- ~ScopedForceTypifiedProfile() { set(SavedValue); }
+ /// Select the requested option value for the lifetime of this scope.
+ explicit ScopedForceTypifiedProfile(bool Enabled) { set(Enabled); }
- /// Select whether ExtBinary writes use typified profile sections.
- void set(bool Enabled) { *Option = Enabled; }
+ /// Restore the default disabled state when leaving the test scope.
+ ~ScopedForceTypifiedProfile() { set(false); }
-private:
- /// Registered option controlling forced typified ExtBinary output.
- cl::opt<bool> *Option = nullptr;
- /// Option value restored when this scope ends.
- bool SavedValue = false;
+ /// Select whether ExtBinary writes use typified profile sections through the
+ /// same type-checked command-line parser used by the production tool.
+ void set(bool Enabled) {
+ constexpr StringLiteral OptionName = "extbinary-force-typified-prof";
+ auto &Options = cl::getRegisteredOptions();
+ auto OptionIt = Options.find(OptionName);
+ if (OptionIt == Options.end())
+ report_fatal_error("typified profile option is not registered");
+
+ // Reset only the option controlled by this scope, leaving unrelated test
+ // process configuration unchanged.
+ cl::Option *Option = OptionIt->second;
+ Option->reset();
+ if (Option->addOccurrence(0, OptionName, Enabled ? "true" : "false"))
+ report_fatal_error("failed to set typified profile option");
+ }
};
struct SampleProfTest : ::testing::Test {
@@ -536,7 +536,7 @@ TEST_F(SampleProfTest, ext_binary_writer_typified_to_legacy) {
ASSERT_TRUE(NoError(ReaderOrErr.getError()));
auto ProfileReader = std::move(ReaderOrErr.get());
ASSERT_TRUE(NoError(ProfileReader->read()));
- EXPECT_EQ(IsTypified, ProfileReader->profileIsTypified());
+ EXPECT_EQ(IsTypified, ProfileReader->hasTypifiedProfileSection());
};
ASSERT_TRUE(NoError(ProfileWriter->write(Profiles)));
More information about the llvm-commits
mailing list