[llvm] Introduction of Composite Profile section in ExtBinary format (PR #166553)

Sergey Shcherbinin via llvm-commits llvm-commits at lists.llvm.org
Wed Sep 2 00:03:46 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/17] 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/17] 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/17] 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/17] 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/17] 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/17] 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/17] 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/17] [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/17] [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/17] [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)));

>From b7fb77fba2e93e6872284a9f94519aa0f92e0fdb Mon Sep 17 00:00:00 2001
From: Sergey Shcherbinin <sscherbinin at nvidia.com>
Date: Fri, 14 Aug 2026 18:15:42 +0400
Subject: [PATCH 11/17] [SampleProfile] Format typified writer and cover
 Eytzinger offsets

clang-format the merged layout/offset writers and add a lit test that
force-typified ExtBinary carries eytzinger FuncOffset flags under
split-layout + MD5.
---
 llvm/lib/ProfileData/SampleProfWriter.cpp     | 21 +++++------
 .../eytzinger-split-nametable-partition.test  |  2 +-
 ...d-eytzinger-split-nametable-partition.test | 35 +++++++++++++++++++
 3 files changed, 45 insertions(+), 13 deletions(-)
 create mode 100644 llvm/test/tools/llvm-profdata/typified-eytzinger-split-nametable-partition.test

diff --git a/llvm/lib/ProfileData/SampleProfWriter.cpp b/llvm/lib/ProfileData/SampleProfWriter.cpp
index 8f024146d5343..9353a7e4d728e 100644
--- a/llvm/lib/ProfileData/SampleProfWriter.cpp
+++ b/llvm/lib/ProfileData/SampleProfWriter.cpp
@@ -293,8 +293,9 @@ SampleProfileWriterExtBinaryBase::writeSample(const FunctionSamples &S) {
   return writeBody(S, /*IsNested=*/false);
 }
 
-std::error_code SampleProfileWriterExtBinaryBase::writeFuncOffsetTable(
-    SecType Type, bool IsNested) {
+std::error_code
+SampleProfileWriterExtBinaryBase::writeFuncOffsetTable(SecType Type,
+                                                       bool IsNested) {
   if (WriteEytzingerNameTables) {
     // Eytzinger layout requires MD5 representation and does not support
     // multi-context Context-Sensitive profiles.
@@ -745,8 +746,8 @@ std::error_code SampleProfileWriterExtBinary::writeDefaultLayout(
   // ProfSection / FuncOffsetSection are SecLBR* or SecTypified* after
   // configureTypifiedProfile.
   const SecType Sections[] = {
-      SecProfSummary,       SecNameTable,        SecCSNameTable,  ProfSection,
-      SecProfileSymbolList, FuncOffsetSection,   SecFuncMetadata,
+      SecProfSummary,       SecNameTable,      SecCSNameTable,  ProfSection,
+      SecProfileSymbolList, FuncOffsetSection, SecFuncMetadata,
   };
   for (SecType Type : Sections)
     if (std::error_code EC = writeOneSection(Type, ProfileMap))
@@ -773,14 +774,10 @@ std::error_code SampleProfileWriterExtBinary::writeCtxSplitLayout(
   // Flat SecFlag is pre-set in ExtBinaryHdrLayoutTable; findUnwrittenEntry
   // picks the matching unwritten ProfSection / FuncOffsetSection slot.
   const std::pair<SecType, const SampleProfileMap &> Sections[] = {
-      {SecProfSummary, ProfileMap},
-      {SecNameTable, ProfileMap},
-      {ProfSection, NestedProfileMap},
-      {FuncOffsetSection, NestedProfileMap},
-      {ProfSection, FlatProfileMap},
-      {FuncOffsetSection, FlatProfileMap},
-      {SecProfileSymbolList, ProfileMap},
-      {SecFuncMetadata, ProfileMap},
+      {SecProfSummary, ProfileMap},       {SecNameTable, ProfileMap},
+      {ProfSection, NestedProfileMap},    {FuncOffsetSection, NestedProfileMap},
+      {ProfSection, FlatProfileMap},      {FuncOffsetSection, FlatProfileMap},
+      {SecProfileSymbolList, ProfileMap}, {SecFuncMetadata, ProfileMap},
   };
   for (const auto &[Type, Map] : Sections)
     if (std::error_code EC = writeOneSection(Type, Map))
diff --git a/llvm/test/tools/llvm-profdata/eytzinger-split-nametable-partition.test b/llvm/test/tools/llvm-profdata/eytzinger-split-nametable-partition.test
index e27fd8ed6081c..d9de4a453b022 100644
--- a/llvm/test/tools/llvm-profdata/eytzinger-split-nametable-partition.test
+++ b/llvm/test/tools/llvm-profdata/eytzinger-split-nametable-partition.test
@@ -4,7 +4,7 @@
 # RUN: FileCheck %s -input-file %t.secinfo
 # RUN: llvm-profdata merge --sample --text %t.xfdo -o %t.text
 # RUN: FileCheck %s --check-prefix=TEXT -input-file %t.text
-# Extract and verify the Eytzinger span counts (NumCS, NumFlat,
+# Extract and verify the Eytzinger span counts (NumNested, NumFlat,
 # NumInlinees) from NameTableSection to ensure correct symbol
 # partitioning.
 # RUN: %python -c "import re; off = int(re.search(r'NameTableSection - Offset: (\d+)', open(r'%t.secinfo').read()).group(1)); d = open(r'%t.xfdo', 'rb').read()[off:off+3]; print(list(d))" | FileCheck %s --check-prefix=COUNTS
diff --git a/llvm/test/tools/llvm-profdata/typified-eytzinger-split-nametable-partition.test b/llvm/test/tools/llvm-profdata/typified-eytzinger-split-nametable-partition.test
new file mode 100644
index 0000000000000..1ecb822308e83
--- /dev/null
+++ b/llvm/test/tools/llvm-profdata/typified-eytzinger-split-nametable-partition.test
@@ -0,0 +1,35 @@
+## Verify typified ExtBinary sections carry Eytzinger FuncOffset flags under
+## --split-layout + --sample-profile-write-eytzinger-name-tables + --use-md5.
+# RUN: llvm-profdata merge --sample --extbinary --use-md5 --split-layout \
+# RUN:   --extbinary-force-typified-prof \
+# RUN:   --sample-profile-write-eytzinger-name-tables %s -o %t.xfdo
+# RUN: llvm-profdata show --sample --show-sec-info-only %t.xfdo > %t.secinfo
+# RUN: FileCheck %s -input-file %t.secinfo
+# RUN: llvm-profdata merge --sample --text %t.xfdo -o %t.text
+# RUN: FileCheck %s --check-prefix=TEXT -input-file %t.text
+# Extract Nested/Flat/Inlinee span counts from NameTableSection.
+# RUN: %python -c "import re; off = int(re.search(r'NameTableSection - Offset: (\d+)', open(r'%t.secinfo').read()).group(1)); d = open(r'%t.xfdo', 'rb').read()[off:off+3]; print(list(d))" | FileCheck %s --check-prefix=COUNTS
+
+# CHECK: ProfileSummarySection - Offset: {{.*}}, Size: {{.*}}, Flags: {}
+# CHECK: NameTableSection - Offset: {{.*}}, Size: {{.*}}, Flags: {eytzinger,fixlenmd5}
+# CHECK: TypifiedFuncOffsetTableSection - Offset: {{.*}}, Size: {{.*}}, Flags: {eytzinger}
+# CHECK: TypifiedProfileSection - Offset: {{.*}}, Size: {{.*}}, Flags: {}
+# CHECK: TypifiedFuncOffsetTableSection - Offset: {{.*}}, Size: {{.*}}, Flags: {flat,eytzinger}
+# CHECK: TypifiedProfileSection - Offset: {{.*}}, Size: {{.*}}, Flags: {flat}
+
+# COUNTS: [1, 1, 1]
+
+# TEXT: 15822663052811949562:1000:10
+# TEXT:  1: 1000
+# TEXT:  2: 6699318081062747564:500
+# TEXT:   1: 500
+# TEXT: 110832587303349897:100:5
+# TEXT:  1: 100
+
+main:1000:10
+ 1: 1000
+ 2: foo:500
+  1: 500
+
+leaf_func:100:5
+ 1: 100

>From a66bb8844b9fe866003a9f2d190536edd750e2ec Mon Sep 17 00:00:00 2001
From: Sergey Shcherbinin <sscherbinin at nvidia.com>
Date: Tue, 18 Aug 2026 14:50:48 +0400
Subject: [PATCH 12/17] [SampleProfile] Rename typified profiles as composite

Assign the composite ExtBinary representation its own format version so readers and writers reject mismatched section encodings.
---
 llvm/docs/CommandGuide/llvm-profdata.rst      |   6 +-
 llvm/include/llvm/ProfileData/SampleProf.h    |  24 ++--
 .../llvm/ProfileData/SampleProfReader.h       |  30 ++---
 .../llvm/ProfileData/SampleProfWriter.h       |  14 +-
 llvm/lib/ProfileData/SampleProfReader.cpp     |  28 ++--
 llvm/lib/ProfileData/SampleProfWriter.cpp     |  74 +++++++----
 ...-icall.ll => composite-cs-inline-icall.ll} |   0
 ...proftext => composite-cs-profile.proftext} |   0
 ...-cs-profile.ll => composite-cs-profile.ll} |  12 +-
 ....proftext => composite-head-only.proftext} |   0
 ...ext => composite-multiple-blocks.proftext} |   0
 ...proftext => composite-zero-block.proftext} |   0
 ...ayload.py => corrupt-composite-payload.py} |  52 +++++---
 ...py => generate-large-composite-profile.py} |   2 +-
 ...xt => sample-composite-empty-lbr.proftext} |   0
 .../llvm-profdata/composite-compress.test     |   6 +
 .../composite-compressed-info.test            |  12 ++
 ...mpty-lbr.test => composite-empty-lbr.test} |   4 +-
 ...-eytzinger-split-nametable-partition.test} |  12 +-
 .../llvm-profdata/composite-head-only.test    |   6 +
 ...ons.test => composite-mixed-sections.test} |  20 +--
 ...ks.test => composite-multiple-blocks.test} |   8 +-
 ...test => composite-payload-boundaries.test} |  14 +-
 .../composite-payload-buffer-limit.test       |  24 ++++
 ...fied-vtable.test => composite-vtable.test} |   6 +-
 ...o-block.test => composite-zero-block.test} |   6 +-
 .../llvm-profdata/sample-flatten-profile.test |   7 +-
 .../llvm-profdata/sample-split-layout.test    |  16 +--
 .../show-composite-profile-info.test          |  31 +++++
 .../show-typified-profile-info.test           |  31 -----
 .../llvm-profdata/typified-compress.test      |   6 -
 .../typified-compressed-info.test             |  12 --
 .../llvm-profdata/typified-head-only.test     |   6 -
 .../typified-payload-buffer-limit.test        |  24 ----
 llvm/tools/llvm-profdata/llvm-profdata.cpp    |  18 +--
 llvm/unittests/ProfileData/SampleProfTest.cpp | 124 +++++++++++++-----
 36 files changed, 372 insertions(+), 263 deletions(-)
 rename llvm/test/Transforms/SampleProfile/Inputs/{typified-cs-inline-icall.ll => composite-cs-inline-icall.ll} (100%)
 rename llvm/test/Transforms/SampleProfile/Inputs/{typified-cs-profile.proftext => composite-cs-profile.proftext} (100%)
 rename llvm/test/Transforms/SampleProfile/{typified-cs-profile.ll => composite-cs-profile.ll} (73%)
 rename llvm/test/tools/llvm-profdata/Inputs/{typified-head-only.proftext => composite-head-only.proftext} (100%)
 rename llvm/test/tools/llvm-profdata/Inputs/{typified-multiple-blocks.proftext => composite-multiple-blocks.proftext} (100%)
 rename llvm/test/tools/llvm-profdata/Inputs/{typified-zero-block.proftext => composite-zero-block.proftext} (100%)
 rename llvm/test/tools/llvm-profdata/Inputs/{corrupt-typified-payload.py => corrupt-composite-payload.py} (80%)
 rename llvm/test/tools/llvm-profdata/Inputs/{generate-large-typified-profile.py => generate-large-composite-profile.py} (87%)
 rename llvm/test/tools/llvm-profdata/Inputs/{sample-typified-empty-lbr.proftext => sample-composite-empty-lbr.proftext} (100%)
 create mode 100644 llvm/test/tools/llvm-profdata/composite-compress.test
 create mode 100644 llvm/test/tools/llvm-profdata/composite-compressed-info.test
 rename llvm/test/tools/llvm-profdata/{typified-empty-lbr.test => composite-empty-lbr.test} (50%)
 rename llvm/test/tools/llvm-profdata/{typified-eytzinger-split-nametable-partition.test => composite-eytzinger-split-nametable-partition.test} (71%)
 create mode 100644 llvm/test/tools/llvm-profdata/composite-head-only.test
 rename llvm/test/tools/llvm-profdata/{typified-mixed-sections.test => composite-mixed-sections.test} (51%)
 rename llvm/test/tools/llvm-profdata/{typified-multiple-blocks.test => composite-multiple-blocks.test} (68%)
 rename llvm/test/tools/llvm-profdata/{typified-payload-boundaries.test => composite-payload-boundaries.test} (57%)
 create mode 100644 llvm/test/tools/llvm-profdata/composite-payload-buffer-limit.test
 rename llvm/test/tools/llvm-profdata/{typified-vtable.test => composite-vtable.test} (81%)
 rename llvm/test/tools/llvm-profdata/{typified-zero-block.test => composite-zero-block.test} (65%)
 create mode 100644 llvm/test/tools/llvm-profdata/show-composite-profile-info.test
 delete mode 100644 llvm/test/tools/llvm-profdata/show-typified-profile-info.test
 delete mode 100644 llvm/test/tools/llvm-profdata/typified-compress.test
 delete mode 100644 llvm/test/tools/llvm-profdata/typified-compressed-info.test
 delete mode 100644 llvm/test/tools/llvm-profdata/typified-head-only.test
 delete mode 100644 llvm/test/tools/llvm-profdata/typified-payload-buffer-limit.test

diff --git a/llvm/docs/CommandGuide/llvm-profdata.rst b/llvm/docs/CommandGuide/llvm-profdata.rst
index fa03c875a816d..9e4a4b7c95bed 100644
--- a/llvm/docs/CommandGuide/llvm-profdata.rst
+++ b/llvm/docs/CommandGuide/llvm-profdata.rst
@@ -384,10 +384,10 @@ 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]
+.. option:: --show-composite-info-only=[true|false]
 
- Show the per-function typified block structure. This option is only meaningful
- for sample-based profiles in typified extbinary format.
+ Show the per-function composite block structure. This option is only meaningful
+ for sample-based profiles in composite extbinary format.
 
 .. option:: --debug-info=<path>
 
diff --git a/llvm/include/llvm/ProfileData/SampleProf.h b/llvm/include/llvm/ProfileData/SampleProf.h
index 4f959147fd847..5c2415b03b58a 100644
--- a/llvm/include/llvm/ProfileData/SampleProf.h
+++ b/llvm/include/llvm/ProfileData/SampleProf.h
@@ -122,11 +122,15 @@ static inline uint64_t SPMagic(SampleProfileFormat Format = SPF_Binary) {
 static constexpr uint64_t MinSupportedVersion = 103;
 
 // The default version of the extensible binary profile format written by the
-// compiler.  We default to v103 as v104 is work in progress.
+// compiler. We default to v103 as v104 is reserved for the in-progress on-disk
+// function offset table.
 static constexpr uint64_t DefaultVersion = 103;
 
+// The first version that permits composite profile sections.
+static constexpr uint64_t CompositeProfileVersion = 105;
+
 // The latest supported version of the extensible binary profile format.
-static constexpr uint64_t LatestVersion = 104;
+static constexpr uint64_t LatestVersion = CompositeProfileVersion;
 
 // Query if a given format version is supported by this compiler.
 static inline bool formatVersionIsSupported(uint64_t Version) {
@@ -149,12 +153,12 @@ enum SecType {
   SecFuncMetadata = 5,
   SecCSNameTable = 6,
   // Substitution to SecFuncOffsetTable when we have non-LBR profile types
-  SecTypifiedFuncOffsetTable = 7,
+  SecCompositeFuncOffsetTable = 7,
   // marker for the first type of profile.
   SecFuncProfileFirst = 32,
   SecLBRProfile = SecFuncProfileFirst,
   // Substitution to SecLBRProfile when we have non-LBR profile types
-  SecTypifiedProfile = 33
+  SecCompositeProfile = 33
 };
 
 static inline std::string getSecName(SecType Type) {
@@ -173,18 +177,18 @@ static inline std::string getSecName(SecType Type) {
     return "FunctionMetadata";
   case SecCSNameTable:
     return "CSNameTableSection";
-  case SecTypifiedFuncOffsetTable:
-    return "TypifiedFuncOffsetTableSection";
+  case SecCompositeFuncOffsetTable:
+    return "CompositeFuncOffsetTableSection";
   case SecLBRProfile:
     return "LBRProfileSection";
-  case SecTypifiedProfile:
-    return "TypifiedProfileSection";
+  case SecCompositeProfile:
+    return "CompositeProfileSection";
   default:
     return "UnknownSection";
   }
 }
 
-// Types of sample profiles that can be placed in SecTypifiedProfile. These
+// Types of sample profiles that can be placed in SecCompositeProfile. These
 // values are persisted on disk; never change existing values, only append new
 // profile type IDs.
 enum ProfTypes { ProfTypeLBR = 0 };
@@ -301,7 +305,7 @@ static inline void verifySecFlag(SecType Type, SecFlagType Flag) {
     IsFlagLegal = std::is_same<SecFuncMetadataFlags, SecFlagType>();
     break;
   case SecFuncOffsetTable:
-  case SecTypifiedFuncOffsetTable:
+  case SecCompositeFuncOffsetTable:
     IsFlagLegal = std::is_same<SecFuncOffsetFlags, SecFlagType>();
     break;
   default:
diff --git a/llvm/include/llvm/ProfileData/SampleProfReader.h b/llvm/include/llvm/ProfileData/SampleProfReader.h
index ba67e1c5f2a50..f69442350d5b7 100644
--- a/llvm/include/llvm/ProfileData/SampleProfReader.h
+++ b/llvm/include/llvm/ProfileData/SampleProfReader.h
@@ -221,10 +221,10 @@
 //        FUNCTION BODY
 //          A FUNCTION BODY entry describing the inlined function.
 //
-// An ExtBinary file may contain both legacy and typified profile sections.
+// An ExtBinary file may contain both legacy and composite profile sections.
 // Each section's type independently selects its function-body encoding.
 //
-// TYPIFIED FUNCTION BODY (used by SecTypifiedProfile)
+// COMPOSITE FUNCTION BODY (used by SecCompositeProfile)
 //    NAME_IDX (uint64_t)
 //        Index into the name table indicating the function name.
 //    NUM_PROFILE_TYPES (uint64_t)
@@ -250,7 +250,7 @@
 //        Number of callees inlined into this function.
 //    INLINED FUNCTION RECORDS
 //        Encoded as described above, except each nested FUNCTION BODY uses the
-//        typified representation.
+//        composite representation.
 //===----------------------------------------------------------------------===//
 
 #ifndef LLVM_PROFILEDATA_SAMPLEPROFREADER_H
@@ -727,7 +727,7 @@ class SampleProfileReader {
   virtual bool contains(StringRef Key) const { return false; }
   virtual bool contains(uint64_t GUID) const { return false; }
 
-  /// Read the profile and print the structure of typified profile blocks.
+  /// Read the profile and print the structure of composite profile blocks.
   std::error_code dumpProfileTypeInfo(raw_ostream &OS) {
     ProfileTypeInfoOS = &OS;
     std::error_code EC = read();
@@ -735,10 +735,10 @@ class SampleProfileReader {
     return EC;
   }
 
-  /// Return whether the input contains a typified profile section.
-  virtual bool hasTypifiedProfileSection() const { return false; }
+  /// Return whether the input contains a composite profile section.
+  virtual bool hasCompositeProfileSection() const { return false; }
 
-  /// Return whether any unknown typified profile blocks were skipped.
+  /// Return whether any unknown composite profile blocks were skipped.
   bool hasUnknownProfileTypes() const { return HasUnknownProfileTypes; }
 
   /// Return whether names in the profile are all MD5 numbers.
@@ -816,14 +816,14 @@ class SampleProfileReader {
     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;
+    /// Whether the retained section uses composite payload encoding.
+    bool IsComposite = false;
   };
   /// Profile section most recently selected for on-demand loading.
   ProfileSectionRange ProfileSecRange;
-  /// Optional stream for typified block structure; null disables the output.
+  /// Optional stream for composite block structure; null disables the output.
   raw_ostream *ProfileTypeInfoOS = nullptr;
-  /// Whether reading skipped at least one unknown typified profile block.
+  /// Whether reading skipped at least one unknown composite profile block.
   bool HasUnknownProfileTypes = false;
 
   /// Whether the profile has attribute metadata.
@@ -961,7 +961,7 @@ class LLVM_ABI SampleProfileReaderBinary : public SampleProfileReader {
 
   /// Read specific profile types.
   std::error_code readLBRProfile(FunctionSamples &FProfile, bool IsNested);
-  std::error_code readTypifiedProfile(FunctionSamples &FProfile, bool IsNested);
+  std::error_code readCompositeProfile(FunctionSamples &FProfile, bool IsNested);
 
   /// Read the contents of Magic number and Version number.
   std::error_code readMagicIdent();
@@ -1217,10 +1217,10 @@ 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 {
+  /// Return whether the section table contains a composite profile section.
+  bool hasCompositeProfileSection() const override {
     for (const auto &Entry : SecHdrTable)
-      if (Entry.Type == SecTypifiedProfile)
+      if (Entry.Type == SecCompositeProfile)
         return true;
     return false;
   }
diff --git a/llvm/include/llvm/ProfileData/SampleProfWriter.h b/llvm/include/llvm/ProfileData/SampleProfWriter.h
index 2abcd8a431560..9c645c67715e6 100644
--- a/llvm/include/llvm/ProfileData/SampleProfWriter.h
+++ b/llvm/include/llvm/ProfileData/SampleProfWriter.h
@@ -226,8 +226,8 @@ class LLVM_ABI SampleProfileWriterBinary : public SampleProfileWriter {
   std::error_code writeBody(const FunctionSamples &S, bool IsNested);
   std::error_code writeLBRProfile(const FunctionSamples &S, bool IsNested);
 
-  /// Interfaces for typified profile writing.
-  std::error_code writeTypifiedProfile(const FunctionSamples &S, bool IsNested);
+  /// Interfaces for composite profile writing.
+  std::error_code writeCompositeProfile(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
@@ -252,7 +252,7 @@ class LLVM_ABI SampleProfileWriterBinary : public SampleProfileWriter {
                           raw_ostream &OS);
 
   bool WriteVTableProf = false;
-  bool WriteTypifiedProf = false;
+  bool WriteCompositeProf = false;
 
 private:
   LLVM_ABI friend ErrorOr<std::unique_ptr<SampleProfileWriter>>
@@ -388,7 +388,7 @@ class LLVM_ABI SampleProfileWriterExtBinaryBase
   std::error_code writeNameTableSection(const SampleProfileMap &ProfileMap);
   std::error_code
   writeEytzingerNameTableSection(const SampleProfileMap &ProfileMap);
-  // Type selects SecFuncOffsetTable vs SecTypifiedFuncOffsetTable for flags.
+  // Type selects SecFuncOffsetTable vs SecCompositeFuncOffsetTable for flags.
   std::error_code writeFuncOffsetTable(SecType Type, bool IsNested);
   std::error_code writeEytzingerFuncOffsetTable(SecType Type, bool IsNested);
   std::error_code writeLegacyFuncOffsetTable(SecType Type);
@@ -461,8 +461,8 @@ class LLVM_ABI SampleProfileWriterExtBinary
 
   std::error_code writeSections(const SampleProfileMap &ProfileMap) override;
 
-  // Configure whether to use typified profile sections.
-  void configureTypifiedProfile(const SampleProfileMap &ProfileMap);
+  /// Select the profile representation and update its section layout as needed.
+  void configureCompositeProfile(const SampleProfileMap &ProfileMap);
 
   std::error_code writeCustomSection(SecType Type) override {
     return sampleprof_error::success;
@@ -474,7 +474,7 @@ class LLVM_ABI SampleProfileWriterExtBinary
   }
 
   /// Section types for profile storage and bookkeeping (used to switch between
-  /// typified and non-typified profiles).
+  /// composite and non-composite profiles).
   SecType ProfSection = SecLBRProfile;
   SecType FuncOffsetSection = SecFuncOffsetTable;
 };
diff --git a/llvm/lib/ProfileData/SampleProfReader.cpp b/llvm/lib/ProfileData/SampleProfReader.cpp
index b3f28e3fc0521..a74644ff9947c 100644
--- a/llvm/lib/ProfileData/SampleProfReader.cpp
+++ b/llvm/lib/ProfileData/SampleProfReader.cpp
@@ -721,7 +721,7 @@ SampleProfileReaderBinary::readCallsiteVTableProf(FunctionSamples &FProfile) {
 std::error_code
 SampleProfileReaderBinary::readLBRProfile(FunctionSamples &FProfile,
                                           bool IsNested) {
-  if (ProfileSecRange.IsTypified && !IsNested) {
+  if (ProfileSecRange.IsComposite && !IsNested) {
     auto NumHeadSamples = readNumber<uint64_t>();
     if (std::error_code EC = NumHeadSamples.getError())
       return EC;
@@ -781,7 +781,7 @@ SampleProfileReaderBinary::readLBRProfile(FunctionSamples &FProfile,
 }
 
 std::error_code
-SampleProfileReaderBinary::readTypifiedProfile(FunctionSamples &FProfile,
+SampleProfileReaderBinary::readCompositeProfile(FunctionSamples &FProfile,
                                                bool IsNested) {
   // Read the number of profile types.
   auto ProfNum = readNumber<uint64_t>();
@@ -796,7 +796,7 @@ SampleProfileReaderBinary::readTypifiedProfile(FunctionSamples &FProfile,
   // same type twice would merge absolute counters from malformed input.
   SmallSet<uint64_t, 4> SeenTypes;
 
-  // Read the specified number of typified profiles.
+  // Read the specified number of composite profiles.
   for (uint64_t I = 0; I < *ProfNum; ++I) {
     auto Type = readNumber<uint64_t>();
     if (std::error_code EC = Type.getError())
@@ -859,8 +859,8 @@ SampleProfileReaderBinary::readTypifiedProfile(FunctionSamples &FProfile,
 std::error_code
 SampleProfileReaderBinary::readProfile(FunctionSamples &FProfile,
                                        bool IsNested) {
-  if (ProfileSecRange.IsTypified) {
-    if (std::error_code EC = readTypifiedProfile(FProfile, IsNested))
+  if (ProfileSecRange.IsComposite) {
+    if (std::error_code EC = readCompositeProfile(FProfile, IsNested))
       return EC;
   } else {
     if (std::error_code EC = readLBRProfile(FProfile, IsNested))
@@ -906,7 +906,7 @@ SampleProfileReaderBinary::readFuncProfile(const uint8_t *Start,
                                            SampleProfileMap &Profiles) {
   Data = Start;
   ErrorOr<uint64_t> NumHeadSamples = 0;
-  if (!ProfileSecRange.IsTypified) {
+  if (!ProfileSecRange.IsComposite) {
     NumHeadSamples = readNumber<uint64_t>();
     if (std::error_code EC = NumHeadSamples.getError())
       return EC;
@@ -920,7 +920,7 @@ SampleProfileReaderBinary::readFuncProfile(const uint8_t *Start,
   auto Res = Profiles.try_emplace(Hash, FContext, FunctionSamples());
   FunctionSamples &FProfile = Res.first->second;
   FProfile.setContext(FContext);
-  if (!ProfileSecRange.IsTypified)
+  if (!ProfileSecRange.IsComposite)
     FProfile.addHeadSamples(*NumHeadSamples);
 
   if (FContext.hasContext())
@@ -987,14 +987,14 @@ std::error_code SampleProfileReaderExtBinaryBase::readOneSection(
     break;
   }
   case SecLBRProfile:
-  case SecTypifiedProfile:
+  case SecCompositeProfile:
     // Retain the section and its encoding for subsequent on-demand reads.
-    ProfileSecRange = {Data, End, Entry.Type == SecTypifiedProfile};
+    ProfileSecRange = {Data, End, Entry.Type == SecCompositeProfile};
     if (std::error_code EC = readFuncProfiles())
       return EC;
     break;
   case SecFuncOffsetTable:
-  case SecTypifiedFuncOffsetTable:
+  case SecCompositeFuncOffsetTable:
     // If module is absent, we are using LLVM tools, and need to read all
     // profiles, so skip reading the function offset table.
     if (!M) {
@@ -1723,6 +1723,12 @@ SampleProfileReaderExtBinaryBase::readSecHdrTableEntry(uint64_t Idx) {
     return EC;
   Entry.Type = static_cast<SecType>(*Type);
 
+  // Reject a section whose encoding is newer than the declared file version.
+  if ((Entry.Type == SecCompositeProfile ||
+       Entry.Type == SecCompositeFuncOffsetTable) &&
+      FormatVersion < CompositeProfileVersion)
+    return sampleprof_error::unsupported_version;
+
   auto Flags = readUnencodedNumber<uint64_t>();
   if (std::error_code EC = Flags.getError())
     return EC;
@@ -1824,7 +1830,7 @@ static std::string getSecFlagsStr(const SecHdrTableEntry &Entry) {
       Flags.append("fs-discriminator,");
     break;
   case SecFuncOffsetTable:
-  case SecTypifiedFuncOffsetTable:
+  case SecCompositeFuncOffsetTable:
     if (hasSecFlag(Entry, SecFuncOffsetFlags::SecFlagOrdered))
       Flags.append("ordered,");
     if (hasSecFlag(Entry, SecFuncOffsetFlags::SecFlagEytzinger))
diff --git a/llvm/lib/ProfileData/SampleProfWriter.cpp b/llvm/lib/ProfileData/SampleProfWriter.cpp
index 9353a7e4d728e..82df5063de2c5 100644
--- a/llvm/lib/ProfileData/SampleProfWriter.cpp
+++ b/llvm/lib/ProfileData/SampleProfWriter.cpp
@@ -54,7 +54,7 @@ static cl::opt<bool> ExtBinaryWriteVTableTypeProf(
 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 "
+    cl::desc("Maximum number of composite payload bytes to retain in the "
              "dynamic buffer"));
 
 static cl::opt<uint64_t> RequestedVersion(
@@ -71,9 +71,14 @@ static cl::opt<bool> WriteEytzingerNameTables(
     cl::desc("Write Eytzinger 3-span layout for NameTable and parallel "
              "FuncOffsetTable"));
 
-static cl::opt<bool> ExtBinaryForceTypifiedProf(
-    "extbinary-force-typified-prof", cl::init(false), cl::Hidden,
-    cl::desc("Force utilization of typified profile format"));
+static cl::opt<bool> ExtBinaryCompositeProf(
+    "extbinary-composite-prof", cl::init(false), cl::Hidden,
+    cl::desc("Use the composite profile format"));
+
+/// Return whether the profile requires the composite ExtBinary representation.
+static bool usesCompositeProfile(const SampleProfileMap &ProfileMap) {
+  return ExtBinaryCompositeProf || ProfileMap.hasNonLBRProfile();
+}
 
 namespace llvm {
 namespace support {
@@ -288,7 +293,7 @@ SampleProfileWriterExtBinaryBase::writeSample(const FunctionSamples &S) {
   uint64_t Offset = OutputStream->tell();
   auto &Context = S.getContext();
   FuncOffsetTable[Context] = Offset - SecLBRProfileStart;
-  if (!WriteTypifiedProf)
+  if (!WriteCompositeProf)
     encodeULEB128(S.getHeadSamples(), *OutputStream);
   return writeBody(S, /*IsNested=*/false);
 }
@@ -345,7 +350,7 @@ SampleProfileWriterExtBinaryBase::writeEytzingerFuncOffsetTable(SecType Type,
 
   OutputStream->write(reinterpret_cast<const char *>(FuncOffsets.data()),
                       SpanSize * sizeof(support::ulittle32_t));
-  // Type is SecFuncOffsetTable or SecTypifiedFuncOffsetTable.
+  // Type is SecFuncOffsetTable or SecCompositeFuncOffsetTable.
   addSectionFlag(Type, SecFuncOffsetFlags::SecFlagEytzinger);
   FuncOffsetTable.clear();
   return sampleprof_error::success;
@@ -661,6 +666,12 @@ unsigned SampleProfileWriterExtBinaryBase::findUnwrittenEntry(SecType Type) {
 
 std::error_code SampleProfileWriterExtBinaryBase::writeOneSection(
     SecType Type, const SampleProfileMap &ProfileMap) {
+  // Composite sections require the file version that defines their encoding.
+  if ((Type == SecCompositeProfile ||
+       Type == SecCompositeFuncOffsetTable) &&
+      FormatVersion < CompositeProfileVersion)
+    return sampleprof_error::unsupported_version;
+
   unsigned LayoutIdx = findUnwrittenEntry(Type);
   SecHdrTableEntry &Entry = SectionHdrLayout[LayoutIdx];
 
@@ -702,13 +713,13 @@ std::error_code SampleProfileWriterExtBinaryBase::writeOneSection(
       return EC;
     break;
   case SecLBRProfile:
-  case SecTypifiedProfile:
+  case SecCompositeProfile:
     SecLBRProfileStart = OutputStream->tell();
     if (std::error_code EC = writeFuncProfiles(ProfileMap))
       return EC;
     break;
   case SecFuncOffsetTable:
-  case SecTypifiedFuncOffsetTable: {
+  case SecCompositeFuncOffsetTable: {
     bool IsFlat = hasSecFlag(Entry, SecCommonFlags::SecFlagFlat);
     // An unflagged function offset table inherently indexes the primary
     // Nested symbol span.
@@ -743,8 +754,8 @@ SampleProfileWriterExtBinary::SampleProfileWriterExtBinary(
 
 std::error_code SampleProfileWriterExtBinary::writeDefaultLayout(
     const SampleProfileMap &ProfileMap) {
-  // ProfSection / FuncOffsetSection are SecLBR* or SecTypified* after
-  // configureTypifiedProfile.
+  // ProfSection / FuncOffsetSection are SecLBR* or SecComposite* after
+  // configureCompositeProfile.
   const SecType Sections[] = {
       SecProfSummary,       SecNameTable,      SecCSNameTable,  ProfSection,
       SecProfileSymbolList, FuncOffsetSection, SecFuncMetadata,
@@ -786,29 +797,31 @@ std::error_code SampleProfileWriterExtBinary::writeCtxSplitLayout(
   return sampleprof_error::success;
 }
 
-void SampleProfileWriterExtBinary::configureTypifiedProfile(
+void SampleProfileWriterExtBinary::configureCompositeProfile(
     const SampleProfileMap &ProfileMap) {
-  WriteTypifiedProf =
-      ExtBinaryForceTypifiedProf || ProfileMap.hasNonLBRProfile();
-  ProfSection = WriteTypifiedProf ? SecTypifiedProfile : SecLBRProfile;
+  WriteCompositeProf = usesCompositeProfile(ProfileMap);
+  ProfSection = WriteCompositeProf ? SecCompositeProfile : SecLBRProfile;
   FuncOffsetSection =
-      WriteTypifiedProf ? SecTypifiedFuncOffsetTable : SecFuncOffsetTable;
+      WriteCompositeProf ? SecCompositeFuncOffsetTable : 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
+  // its handling. Rewrite both legacy and composite entries so repeated writes
   // can switch formats without losing configured flags.
   for (auto &Entry : SectionHdrLayout) {
     if (Entry.Type == SecFuncOffsetTable ||
-        Entry.Type == SecTypifiedFuncOffsetTable)
+        Entry.Type == SecCompositeFuncOffsetTable)
       Entry.Type = FuncOffsetSection;
-    else if (Entry.Type == SecLBRProfile || Entry.Type == SecTypifiedProfile)
+    else if (Entry.Type == SecLBRProfile || Entry.Type == SecCompositeProfile)
       Entry.Type = ProfSection;
   }
 }
 
 std::error_code SampleProfileWriterExtBinary::writeSections(
     const SampleProfileMap &ProfileMap) {
-  configureTypifiedProfile(ProfileMap);
+  // Preserve section configuration at the point where the selected layout is
+  // consumed, after the base writer has emitted the validated file version.
+  configureCompositeProfile(ProfileMap);
+
   std::error_code EC;
   if (SecLayout == DefaultLayout)
     EC = writeDefaultLayout(ProfileMap);
@@ -1121,7 +1134,7 @@ std::error_code
 SampleProfileWriterBinary::writeLBRProfile(const FunctionSamples &S,
                                            bool IsNested) {
   auto &OS = *OutputStream;
-  if (WriteTypifiedProf && !IsNested)
+  if (WriteCompositeProf && !IsNested)
     encodeULEB128(S.getHeadSamples(), OS);
   encodeULEB128(S.getTotalSamples(), OS);
   encodeULEB128(S.getBodySamples().size(), OS);
@@ -1311,7 +1324,7 @@ static bool hasNonEmptyLBRProfile(const FunctionSamples &S, bool IsNested) {
 }
 
 std::error_code
-SampleProfileWriterBinary::writeTypifiedProfile(const FunctionSamples &S,
+SampleProfileWriterBinary::writeCompositeProfile(const FunctionSamples &S,
                                                 bool IsNested) {
   auto &OS = *OutputStream;
   bool WriteLBRProf = hasNonEmptyLBRProfile(S, IsNested);
@@ -1334,8 +1347,8 @@ std::error_code SampleProfileWriterBinary::writeBody(const FunctionSamples &S,
     return EC;
 
   // Emit all the body samples.
-  if (WriteTypifiedProf) {
-    if (std::error_code EC = writeTypifiedProfile(S, IsNested))
+  if (WriteCompositeProf) {
+    if (std::error_code EC = writeCompositeProfile(S, IsNested))
       return EC;
   } else {
     if (std::error_code EC = writeLBRProfile(S, IsNested))
@@ -1425,9 +1438,18 @@ SampleProfileWriter::create(std::unique_ptr<raw_ostream> &OS,
   Writer->Format = Format;
   if (Format != SPF_Ext_Binary)
     Writer->setFormatVersion(DefaultVersion);
-  else if (formatVersionIsSupported(RequestedVersion))
-    Writer->setFormatVersion(RequestedVersion);
-  else
+  else if (formatVersionIsSupported(RequestedVersion)) {
+    // A composite request selects its required format version unless the user
+    // explicitly requested an incompatible legacy version.
+    if (ExtBinaryCompositeProf) {
+      if (RequestedVersion.getNumOccurrences() != 0 &&
+          RequestedVersion < CompositeProfileVersion)
+        return sampleprof_error::unsupported_version;
+      Writer->setFormatVersion(CompositeProfileVersion);
+    } else {
+      Writer->setFormatVersion(RequestedVersion);
+    }
+  } else
     return sampleprof_error::unsupported_version;
   return std::move(Writer);
 }
diff --git a/llvm/test/Transforms/SampleProfile/Inputs/typified-cs-inline-icall.ll b/llvm/test/Transforms/SampleProfile/Inputs/composite-cs-inline-icall.ll
similarity index 100%
rename from llvm/test/Transforms/SampleProfile/Inputs/typified-cs-inline-icall.ll
rename to llvm/test/Transforms/SampleProfile/Inputs/composite-cs-inline-icall.ll
diff --git a/llvm/test/Transforms/SampleProfile/Inputs/typified-cs-profile.proftext b/llvm/test/Transforms/SampleProfile/Inputs/composite-cs-profile.proftext
similarity index 100%
rename from llvm/test/Transforms/SampleProfile/Inputs/typified-cs-profile.proftext
rename to llvm/test/Transforms/SampleProfile/Inputs/composite-cs-profile.proftext
diff --git a/llvm/test/Transforms/SampleProfile/typified-cs-profile.ll b/llvm/test/Transforms/SampleProfile/composite-cs-profile.ll
similarity index 73%
rename from llvm/test/Transforms/SampleProfile/typified-cs-profile.ll
rename to llvm/test/Transforms/SampleProfile/composite-cs-profile.ll
index 26706d01b2c85..1a5ad72e3d261 100644
--- a/llvm/test/Transforms/SampleProfile/typified-cs-profile.ll
+++ b/llvm/test/Transforms/SampleProfile/composite-cs-profile.ll
@@ -1,17 +1,17 @@
-; Verify that typified CS profiles use an ordered offset table, can be loaded by
+; Verify that composite 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 merge --sample --extbinary-composite-prof --extbinary %S/Inputs/composite-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: llvm-profdata merge --sample --extbinary-composite-prof --extbinary \
 ; RUN:   %S/Inputs/indirect-call-csspgo.prof -o %t.payload.prof
-; RUN: opt -S %S/Inputs/typified-cs-inline-icall.ll -passes=sample-profile \
+; RUN: opt -S %S/Inputs/composite-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 | \
 ; RUN:   FileCheck %s --check-prefix=PAYLOAD
 
-; SECTION: TypifiedFuncOffsetTableSection {{.*}} Flags: {ordered}
+; SECTION: CompositeFuncOffsetTableSection {{.*}} Flags: {ordered}
 ; IR-LABEL: define void @test()
 ; IR-SAME: !prof ![[ENTRY_COUNT:[0-9]+]]
 ; IR: ![[ENTRY_COUNT]] = !{!"function_entry_count", i64 10}
@@ -28,7 +28,7 @@ attributes #0 = { "use-sample-profile" }
 !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: "/")
+!1 = !DIFile(filename: "composite-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)
diff --git a/llvm/test/tools/llvm-profdata/Inputs/typified-head-only.proftext b/llvm/test/tools/llvm-profdata/Inputs/composite-head-only.proftext
similarity index 100%
rename from llvm/test/tools/llvm-profdata/Inputs/typified-head-only.proftext
rename to llvm/test/tools/llvm-profdata/Inputs/composite-head-only.proftext
diff --git a/llvm/test/tools/llvm-profdata/Inputs/typified-multiple-blocks.proftext b/llvm/test/tools/llvm-profdata/Inputs/composite-multiple-blocks.proftext
similarity index 100%
rename from llvm/test/tools/llvm-profdata/Inputs/typified-multiple-blocks.proftext
rename to llvm/test/tools/llvm-profdata/Inputs/composite-multiple-blocks.proftext
diff --git a/llvm/test/tools/llvm-profdata/Inputs/typified-zero-block.proftext b/llvm/test/tools/llvm-profdata/Inputs/composite-zero-block.proftext
similarity index 100%
rename from llvm/test/tools/llvm-profdata/Inputs/typified-zero-block.proftext
rename to llvm/test/tools/llvm-profdata/Inputs/composite-zero-block.proftext
diff --git a/llvm/test/tools/llvm-profdata/Inputs/corrupt-typified-payload.py b/llvm/test/tools/llvm-profdata/Inputs/corrupt-composite-payload.py
similarity index 80%
rename from llvm/test/tools/llvm-profdata/Inputs/corrupt-typified-payload.py
rename to llvm/test/tools/llvm-profdata/Inputs/corrupt-composite-payload.py
index 7f06eb798688b..10dd1482c4743 100644
--- a/llvm/test/tools/llvm-profdata/Inputs/corrupt-typified-payload.py
+++ b/llvm/test/tools/llvm-profdata/Inputs/corrupt-composite-payload.py
@@ -1,7 +1,8 @@
 import argparse
 import struct
 
-TYPIFIED_PROFILE_SECTION = 33
+COMPOSITE_PROFILE_SECTION = 33
+COMPOSITE_PROFILE_VERSION = 105
 LEGACY_PROFILE_SECTION = 32
 UNKNOWN_PROFILE_TYPE = 127
 
@@ -73,8 +74,8 @@ def read_sections(data):
         "prepended-unknown",
         "duplicate-type",
         "empty-section",
-        "prepend-empty-typified",
-        "prepend-typified",
+        "prepend-empty-composite",
+        "prepend-composite",
     ),
 )
 args = parser.parse_args()
@@ -88,13 +89,13 @@ def read_sections(data):
 profile_size = None
 profile_header_offset = None
 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"
+    if section_type == COMPOSITE_PROFILE_SECTION:
+        assert profile_offset is None, "expected exactly one composite 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"):
+if args.modification in ("prepend-empty-composite", "prepend-composite"):
     legacy_sections = [
         section for section in sections if section[1] == LEGACY_PROFILE_SECTION
     ]
@@ -108,35 +109,44 @@ def read_sections(data):
             "<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"
+    composite_payload = b""
+    composite_offset = legacy_offset + header_size
+    if args.modification == "prepend-composite":
+        assert args.donor, "expected --donor for a nonempty composite 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
+            if section[1] == COMPOSITE_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
+        composite_payload = donor_data[donor_offset : donor_offset + donor_size]
+        composite_offset = len(data) + header_size
 
-    # Place the typified section immediately before a valid legacy one to
+    # Place the composite section immediately before a valid legacy one to
     # verify that its decoding mode does not leak into the following section.
-    typified_header = struct.pack(
+    composite_header = struct.pack(
         "<QQQQ",
-        TYPIFIED_PROFILE_SECTION,
+        COMPOSITE_PROFILE_SECTION,
         0,
-        typified_offset,
-        len(typified_payload),
+        composite_offset,
+        len(composite_payload),
     )
-    data[legacy_header_offset:legacy_header_offset] = typified_header
-    data.extend(typified_payload)
+    data[legacy_header_offset:legacy_header_offset] = composite_header
+    data.extend(composite_payload)
     struct.pack_into("<Q", data, section_count_offset, section_count + 1)
+
+    # Associate the newly inserted composite section with its required file
+    # version while preserving the surrounding legacy sections.
+    _, version_offset = read_uleb(data, 0)
+    _, version_end = read_uleb(data, version_offset)
+    encoded_version = encode_uleb(COMPOSITE_PROFILE_VERSION)
+    assert len(encoded_version) == version_end - version_offset
+    data[version_offset:version_end] = encoded_version
+
     write_output(data, args.output)
     raise SystemExit
 
@@ -157,7 +167,7 @@ def read_sections(data):
 
 
 def adjust_sections_after(insertion_offset, size_delta):
-    """Adjust section offsets and typified section size after a byte edit."""
+    """Adjust section offsets and composite section size after a byte edit."""
     if not size_delta:
         return
     for section_header_offset, _, section_offset, _ in sections:
diff --git a/llvm/test/tools/llvm-profdata/Inputs/generate-large-typified-profile.py b/llvm/test/tools/llvm-profdata/Inputs/generate-large-composite-profile.py
similarity index 87%
rename from llvm/test/tools/llvm-profdata/Inputs/generate-large-typified-profile.py
rename to llvm/test/tools/llvm-profdata/Inputs/generate-large-composite-profile.py
index 3160153e79511..9d696b3f47f61 100644
--- a/llvm/test/tools/llvm-profdata/Inputs/generate-large-typified-profile.py
+++ b/llvm/test/tools/llvm-profdata/Inputs/generate-large-composite-profile.py
@@ -4,7 +4,7 @@
 # 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"
+    description="Generate a sample profile with a large composite LBR payload"
 )
 parser.add_argument("output")
 parser.add_argument("records", type=int)
diff --git a/llvm/test/tools/llvm-profdata/Inputs/sample-typified-empty-lbr.proftext b/llvm/test/tools/llvm-profdata/Inputs/sample-composite-empty-lbr.proftext
similarity index 100%
rename from llvm/test/tools/llvm-profdata/Inputs/sample-typified-empty-lbr.proftext
rename to llvm/test/tools/llvm-profdata/Inputs/sample-composite-empty-lbr.proftext
diff --git a/llvm/test/tools/llvm-profdata/composite-compress.test b/llvm/test/tools/llvm-profdata/composite-compress.test
new file mode 100644
index 0000000000000..918878988896d
--- /dev/null
+++ b/llvm/test/tools/llvm-profdata/composite-compress.test
@@ -0,0 +1,6 @@
+REQUIRES: zlib
+
+# Round trip from text to compressed composite extbinary and back to text.
+RUN: llvm-profdata merge --sample --extbinary --extbinary-composite-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/composite-compressed-info.test b/llvm/test/tools/llvm-profdata/composite-compressed-info.test
new file mode 100644
index 0000000000000..f4dc0fba4aa4d
--- /dev/null
+++ b/llvm/test/tools/llvm-profdata/composite-compressed-info.test
@@ -0,0 +1,12 @@
+REQUIRES: zlib
+
+; Verify that compressed composite blocks remain inspectable when their payloads
+; exceed the bounded buffer and that overflow does not change the output.
+; RUN: llvm-profdata merge --sample --extbinary-composite-prof --extbinary --compress-all-sections %S/Inputs/sample-profile.proftext -o %t.buffered
+; RUN: llvm-profdata merge --sample --extbinary-composite-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-composite-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/composite-empty-lbr.test
similarity index 50%
rename from llvm/test/tools/llvm-profdata/typified-empty-lbr.test
rename to llvm/test/tools/llvm-profdata/composite-empty-lbr.test
index e3c4f1be3725b..20f7cf2f2c0d3 100644
--- a/llvm/test/tools/llvm-profdata/typified-empty-lbr.test
+++ b/llvm/test/tools/llvm-profdata/composite-empty-lbr.test
@@ -1,6 +1,6 @@
-; Verify that typified LBR blocks preserve nonzero head and total samples when
+; Verify that composite 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 --extbinary-composite-prof --extbinary %S/Inputs/sample-composite-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
diff --git a/llvm/test/tools/llvm-profdata/typified-eytzinger-split-nametable-partition.test b/llvm/test/tools/llvm-profdata/composite-eytzinger-split-nametable-partition.test
similarity index 71%
rename from llvm/test/tools/llvm-profdata/typified-eytzinger-split-nametable-partition.test
rename to llvm/test/tools/llvm-profdata/composite-eytzinger-split-nametable-partition.test
index 1ecb822308e83..2fe3cc4066f3b 100644
--- a/llvm/test/tools/llvm-profdata/typified-eytzinger-split-nametable-partition.test
+++ b/llvm/test/tools/llvm-profdata/composite-eytzinger-split-nametable-partition.test
@@ -1,7 +1,7 @@
-## Verify typified ExtBinary sections carry Eytzinger FuncOffset flags under
+## Verify composite ExtBinary sections carry Eytzinger FuncOffset flags under
 ## --split-layout + --sample-profile-write-eytzinger-name-tables + --use-md5.
 # RUN: llvm-profdata merge --sample --extbinary --use-md5 --split-layout \
-# RUN:   --extbinary-force-typified-prof \
+# RUN:   --extbinary-composite-prof \
 # RUN:   --sample-profile-write-eytzinger-name-tables %s -o %t.xfdo
 # RUN: llvm-profdata show --sample --show-sec-info-only %t.xfdo > %t.secinfo
 # RUN: FileCheck %s -input-file %t.secinfo
@@ -12,10 +12,10 @@
 
 # CHECK: ProfileSummarySection - Offset: {{.*}}, Size: {{.*}}, Flags: {}
 # CHECK: NameTableSection - Offset: {{.*}}, Size: {{.*}}, Flags: {eytzinger,fixlenmd5}
-# CHECK: TypifiedFuncOffsetTableSection - Offset: {{.*}}, Size: {{.*}}, Flags: {eytzinger}
-# CHECK: TypifiedProfileSection - Offset: {{.*}}, Size: {{.*}}, Flags: {}
-# CHECK: TypifiedFuncOffsetTableSection - Offset: {{.*}}, Size: {{.*}}, Flags: {flat,eytzinger}
-# CHECK: TypifiedProfileSection - Offset: {{.*}}, Size: {{.*}}, Flags: {flat}
+# CHECK: CompositeFuncOffsetTableSection - Offset: {{.*}}, Size: {{.*}}, Flags: {eytzinger}
+# CHECK: CompositeProfileSection - Offset: {{.*}}, Size: {{.*}}, Flags: {}
+# CHECK: CompositeFuncOffsetTableSection - Offset: {{.*}}, Size: {{.*}}, Flags: {flat,eytzinger}
+# CHECK: CompositeProfileSection - Offset: {{.*}}, Size: {{.*}}, Flags: {flat}
 
 # COUNTS: [1, 1, 1]
 
diff --git a/llvm/test/tools/llvm-profdata/composite-head-only.test b/llvm/test/tools/llvm-profdata/composite-head-only.test
new file mode 100644
index 0000000000000..85ccfb6c6de4f
--- /dev/null
+++ b/llvm/test/tools/llvm-profdata/composite-head-only.test
@@ -0,0 +1,6 @@
+; Verify that a composite LBR block preserves a nonzero head count when its total
+; count and body sample map are empty.
+; RUN: llvm-profdata merge --sample --extbinary-composite-prof --extbinary %S/Inputs/composite-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-mixed-sections.test b/llvm/test/tools/llvm-profdata/composite-mixed-sections.test
similarity index 51%
rename from llvm/test/tools/llvm-profdata/typified-mixed-sections.test
rename to llvm/test/tools/llvm-profdata/composite-mixed-sections.test
index 49c9e394dfacf..41d001b8a860f 100644
--- a/llvm/test/tools/llvm-profdata/typified-mixed-sections.test
+++ b/llvm/test/tools/llvm-profdata/composite-mixed-sections.test
@@ -1,22 +1,22 @@
-; Verify that an empty typified section is skipped without preventing a
+; Verify that an empty composite 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 --extbinary %S/Inputs/sample-composite-empty-lbr.proftext -o %t.legacy
+; RUN: %python %S/Inputs/corrupt-composite-payload.py %t.legacy %t.empty-mixed prepend-empty-composite
 ; 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
+; nonempty composite 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 --extbinary-composite-prof --extbinary %S/Inputs/sample-composite-empty-lbr.proftext -o %t.composite
+; RUN: %python %S/Inputs/corrupt-composite-payload.py %t.legacy %t.nonempty-mixed prepend-composite --donor %t.composite
 ; 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
+; Verify that an empty composite section still makes composite 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
+; RUN: llvm-profdata show --sample --show-composite-info-only %t.empty-mixed 2>&1 | FileCheck %s --allow-empty --check-prefix=NO-WARNING
 
 ; PROFILE: top:42:7
 ; PROFILE-NEXT: caller:21:3
@@ -24,6 +24,6 @@
 ; ACCUMULATED: top:84:14
 ; ACCUMULATED-NEXT: caller:42:6
 ; ACCUMULATED-NEXT:  1: callee:22
-; SECTIONS: TypifiedProfileSection
+; SECTIONS: CompositeProfileSection
 ; SECTIONS: LBRProfileSection
-; NO-WARNING-NOT: only supported for typified sample profiles
+; NO-WARNING-NOT: only supported for composite sample profiles
diff --git a/llvm/test/tools/llvm-profdata/typified-multiple-blocks.test b/llvm/test/tools/llvm-profdata/composite-multiple-blocks.test
similarity index 68%
rename from llvm/test/tools/llvm-profdata/typified-multiple-blocks.test
rename to llvm/test/tools/llvm-profdata/composite-multiple-blocks.test
index b9a7a64a9acaa..a073561c61db1 100644
--- a/llvm/test/tools/llvm-profdata/typified-multiple-blocks.test
+++ b/llvm/test/tools/llvm-profdata/composite-multiple-blocks.test
@@ -1,8 +1,8 @@
 ; 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 --extbinary-composite-prof --extbinary %S/Inputs/composite-multiple-blocks.proftext -o %t.single
+; RUN: %python %S/Inputs/corrupt-composite-payload.py %t.single %t.multiple prepended-unknown
+; RUN: llvm-profdata show --sample --show-composite-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
@@ -15,7 +15,7 @@
 ; 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
+; WARNING: warning: {{.*}}: unknown composite 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/composite-payload-boundaries.test
similarity index 57%
rename from llvm/test/tools/llvm-profdata/typified-payload-boundaries.test
rename to llvm/test/tools/llvm-profdata/composite-payload-boundaries.test
index 596e09a606628..c28148122beaa 100644
--- a/llvm/test/tools/llvm-profdata/typified-payload-boundaries.test
+++ b/llvm/test/tools/llvm-profdata/composite-payload-boundaries.test
@@ -1,21 +1,21 @@
-; Verify that typified payload readers cannot cross declared block boundaries
+; Verify that composite 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: llvm-profdata merge --sample --extbinary-composite-prof --extbinary %S/Inputs/sample-composite-empty-lbr.proftext -o %t.valid
 
-; RUN: %python %S/Inputs/corrupt-typified-payload.py %t.valid %t.undersized undersized
+; RUN: %python %S/Inputs/corrupt-composite-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: %python %S/Inputs/corrupt-composite-payload.py %t.valid %t.oversized oversized
 ; 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: %python %S/Inputs/corrupt-composite-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: %python %S/Inputs/corrupt-composite-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=DECLARED-SIZE
-; RUN: not llvm-profdata show --sample --show-typified-info-only %t.unknown-out-of-bounds 2>&1 | FileCheck %s --check-prefix=INSPECT
+; RUN: not llvm-profdata show --sample --show-composite-info-only %t.unknown-out-of-bounds 2>&1 | FileCheck %s --check-prefix=INSPECT
 
 ; TRUNCATED: error: {{.*}}Truncated profile data
 ; UNCONSUMED: Profile type ID 0 did not consume its complete payload; unread bytes: 1
diff --git a/llvm/test/tools/llvm-profdata/composite-payload-buffer-limit.test b/llvm/test/tools/llvm-profdata/composite-payload-buffer-limit.test
new file mode 100644
index 0000000000000..3761a68624643
--- /dev/null
+++ b/llvm/test/tools/llvm-profdata/composite-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-composite-prof --extbinary %S/Inputs/sample-profile.proftext -o %t.buffered
+; RUN: llvm-profdata merge --sample --extbinary-composite-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-composite-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-composite-profile.py %t.large.proftext 2048
+; RUN: llvm-profdata merge --sample --extbinary-composite-prof --extbinary %t.large.proftext -o %t.large.buffered
+; RUN: llvm-profdata merge --sample --extbinary-composite-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-composite-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-vtable.test b/llvm/test/tools/llvm-profdata/composite-vtable.test
similarity index 81%
rename from llvm/test/tools/llvm-profdata/typified-vtable.test
rename to llvm/test/tools/llvm-profdata/composite-vtable.test
index 19bdd298a153c..b57a5a00f365a 100644
--- a/llvm/test/tools/llvm-profdata/typified-vtable.test
+++ b/llvm/test/tools/llvm-profdata/composite-vtable.test
@@ -1,11 +1,11 @@
 ; Verify that vtable type profiles remain readable when their function records
-; use typified LBR payloads.
+; use composite LBR payloads.
 ; RUN: llvm-profdata merge --sample --extbinary \
-; RUN:   --extbinary-force-typified-prof \
+; RUN:   --extbinary-composite-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
+; CHECK: CompositeProfileSection
diff --git a/llvm/test/tools/llvm-profdata/typified-zero-block.test b/llvm/test/tools/llvm-profdata/composite-zero-block.test
similarity index 65%
rename from llvm/test/tools/llvm-profdata/typified-zero-block.test
rename to llvm/test/tools/llvm-profdata/composite-zero-block.test
index ce8d92c5209a8..f94f60672b499 100644
--- a/llvm/test/tools/llvm-profdata/typified-zero-block.test
+++ b/llvm/test/tools/llvm-profdata/composite-zero-block.test
@@ -1,7 +1,7 @@
-; Verify that a function may have no typified blocks while its nested function
+; Verify that a function may have no composite 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
+; RUN: llvm-profdata merge --sample --extbinary-composite-prof --extbinary %S/Inputs/composite-zero-block.proftext -o %t.prof
+; RUN: llvm-profdata show --sample --show-composite-info-only %t.prof | FileCheck %s
 
 ; CHECK: Function: caller
 ; CHECK-NEXT:   Profile blocks: 0
diff --git a/llvm/test/tools/llvm-profdata/sample-flatten-profile.test b/llvm/test/tools/llvm-profdata/sample-flatten-profile.test
index 34dab716a067f..a373ee98cca90 100644
--- a/llvm/test/tools/llvm-profdata/sample-flatten-profile.test
+++ b/llvm/test/tools/llvm-profdata/sample-flatten-profile.test
@@ -1,10 +1,13 @@
 ; 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 --extbinary-composite-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: not llvm-profdata merge --sample --extbinary-composite-prof --sample-profile-format-version=104 --extbinary %S/Inputs/sample-flatten-profile.proftext -o %t.invalid 2>&1 | FileCheck %s --check-prefix=VERSION
 
 ; 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
+; RUN: llvm-profdata merge --sample --extbinary-composite-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
+
+; VERSION: Unsupported sample profile format version
 
 ; CHECK:baz:169:10
 ; CHECK-NEXT: 1: 10
diff --git a/llvm/test/tools/llvm-profdata/sample-split-layout.test b/llvm/test/tools/llvm-profdata/sample-split-layout.test
index a762caf4dd385..0338023a11a46 100644
--- a/llvm/test/tools/llvm-profdata/sample-split-layout.test
+++ b/llvm/test/tools/llvm-profdata/sample-split-layout.test
@@ -2,16 +2,16 @@
 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
+# Verify that composite 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
+RUN: llvm-profdata merge --sample --extbinary --split-layout --extbinary-composite-prof %p/Inputs/sample-profile.proftext -o %t-composite
+RUN: llvm-profdata show --sample --show-sec-info-only %t-composite | FileCheck %s --check-prefix=COMPOSITE
+RUN: llvm-profdata merge --sample --text --split-layout %t-composite | FileCheck %s
 
-TYPIFIED: TypifiedFuncOffsetTableSection {{.*}} Flags: {}
-TYPIFIED-NEXT: TypifiedProfileSection {{.*}} Flags: {}
-TYPIFIED-NEXT: TypifiedFuncOffsetTableSection {{.*}} Flags: {flat}
-TYPIFIED-NEXT: TypifiedProfileSection {{.*}} Flags: {flat}
+COMPOSITE: CompositeFuncOffsetTableSection {{.*}} Flags: {}
+COMPOSITE-NEXT: CompositeProfileSection {{.*}} Flags: {}
+COMPOSITE-NEXT: CompositeFuncOffsetTableSection {{.*}} Flags: {flat}
+COMPOSITE-NEXT: CompositeProfileSection {{.*}} Flags: {flat}
 
 CHECK: main:184019:0
 CHECK-NEXT:  4: 534
diff --git a/llvm/test/tools/llvm-profdata/show-composite-profile-info.test b/llvm/test/tools/llvm-profdata/show-composite-profile-info.test
new file mode 100644
index 0000000000000..ae15a8290bad8
--- /dev/null
+++ b/llvm/test/tools/llvm-profdata/show-composite-profile-info.test
@@ -0,0 +1,31 @@
+; Verify composite 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-composite-prof --extbinary %S/Inputs/sample-flatten-profile.proftext -o %t.composite
+; RUN: llvm-profdata show --sample --show-composite-info-only %t.composite | 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-composite-info-only %t.composite 2>&1 | FileCheck %s --check-prefix=BOTH
+
+; BOTH: error: -show-sec-info-only and -show-composite-info-only cannot be used together
+
+; RUN: %python %S/Inputs/corrupt-composite-payload.py %t.composite %t.empty empty-section
+; RUN: llvm-profdata show --sample --show-composite-info-only %t.empty 2>&1 | count 0
+
+; RUN: %python %S/Inputs/corrupt-composite-payload.py %t.composite %t.unknown unknown
+; RUN: llvm-profdata show --sample --show-composite-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-composite-info-only %t.legacy 2>&1 | FileCheck %s --check-prefix=LEGACY
+
+; LEGACY: warning: no composite profile section; nothing to show
diff --git a/llvm/test/tools/llvm-profdata/show-typified-profile-info.test b/llvm/test/tools/llvm-profdata/show-typified-profile-info.test
deleted file mode 100644
index f6664b0d1321d..0000000000000
--- a/llvm/test/tools/llvm-profdata/show-typified-profile-info.test
+++ /dev/null
@@ -1,31 +0,0 @@
-; 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: no typified profile section; nothing to show
diff --git a/llvm/test/tools/llvm-profdata/typified-compress.test b/llvm/test/tools/llvm-profdata/typified-compress.test
deleted file mode 100644
index 8327e1b472d53..0000000000000
--- a/llvm/test/tools/llvm-profdata/typified-compress.test
+++ /dev/null
@@ -1,6 +0,0 @@
-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
deleted file mode 100644
index 2314527f5269b..0000000000000
--- a/llvm/test/tools/llvm-profdata/typified-compressed-info.test
+++ /dev/null
@@ -1,12 +0,0 @@
-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-head-only.test b/llvm/test/tools/llvm-profdata/typified-head-only.test
deleted file mode 100644
index acf2337130510..0000000000000
--- a/llvm/test/tools/llvm-profdata/typified-head-only.test
+++ /dev/null
@@ -1,6 +0,0 @@
-; 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-payload-buffer-limit.test b/llvm/test/tools/llvm-profdata/typified-payload-buffer-limit.test
deleted file mode 100644
index 50b0801414e06..0000000000000
--- a/llvm/test/tools/llvm-profdata/typified-payload-buffer-limit.test
+++ /dev/null
@@ -1,24 +0,0 @@
-; 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/tools/llvm-profdata/llvm-profdata.cpp b/llvm/tools/llvm-profdata/llvm-profdata.cpp
index 3592343a281b9..3e26ed97fd2c4 100644
--- a/llvm/tools/llvm-profdata/llvm-profdata.cpp
+++ b/llvm/tools/llvm-profdata/llvm-profdata.cpp
@@ -469,9 +469,9 @@ 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"),
+static cl::opt<bool> ShowCompositeInfoOnly(
+    "show-composite-info-only", cl::init(false),
+    cl::desc("Show type IDs and payload sizes in a composite sample profile"),
     cl::sub(ShowSubcommand));
 static cl::opt<bool> ShowBinaryIds("binary-ids", cl::init(false),
                                    cl::desc("Show binary ids in the profile. "),
@@ -1628,7 +1628,7 @@ static void mergeSampleProfile(const WeightedFileVector &Inputs,
     // 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 "
+      warn("unknown composite profile blocks were ignored and will not be "
            "preserved",
            Input.Filename);
 
@@ -3249,9 +3249,9 @@ 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)
+  if (ShowSectionInfoOnly && ShowCompositeInfoOnly)
     exitWithError("-show-sec-info-only and "
-                  "-show-typified-info-only cannot be used together");
+                  "-show-composite-info-only cannot be used together");
 
   using namespace sampleprof;
   LLVMContext Context;
@@ -3267,9 +3267,9 @@ static int showSampleProfile(ShowFormat SFormat, raw_fd_ostream &OS) {
     return 0;
   }
 
-  if (ShowTypifiedInfoOnly) {
-    if (!Reader->hasTypifiedProfileSection()) {
-      WithColor::warning() << "no typified profile section; nothing to show\n";
+  if (ShowCompositeInfoOnly) {
+    if (!Reader->hasCompositeProfileSection()) {
+      WithColor::warning() << "no composite profile section; nothing to show\n";
       return 0;
     }
     if (std::error_code EC = Reader->dumpProfileTypeInfo(OS)) {
diff --git a/llvm/unittests/ProfileData/SampleProfTest.cpp b/llvm/unittests/ProfileData/SampleProfTest.cpp
index bcca6d8cc3b3f..ba3810d025f65 100644
--- a/llvm/unittests/ProfileData/SampleProfTest.cpp
+++ b/llvm/unittests/ProfileData/SampleProfTest.cpp
@@ -26,6 +26,7 @@
 #include "llvm/Support/raw_ostream.h"
 #include "llvm/Testing/Support/SupportHelpers.h"
 #include "gtest/gtest.h"
+#include <optional>
 #include <string>
 #include <vector>
 
@@ -43,31 +44,31 @@ static ::testing::AssertionResult NoError(std::error_code EC) {
 
 namespace {
 
-/// Temporarily control typified ExtBinary writing and restore the command-line
+/// Temporarily control composite ExtBinary writing and restore the command-line
 /// option when the test scope ends.
-class ScopedForceTypifiedProfile {
+class ScopedCompositeProfile {
 public:
   /// Select the requested option value for the lifetime of this scope.
-  explicit ScopedForceTypifiedProfile(bool Enabled) { set(Enabled); }
+  explicit ScopedCompositeProfile(bool Enabled) { set(Enabled); }
 
   /// Restore the default disabled state when leaving the test scope.
-  ~ScopedForceTypifiedProfile() { set(false); }
+  ~ScopedCompositeProfile() { set(false); }
 
-  /// Select whether ExtBinary writes use typified profile sections through the
+  /// Select whether ExtBinary writes use composite 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";
+    constexpr StringLiteral OptionName = "extbinary-composite-prof";
     auto &Options = cl::getRegisteredOptions();
     auto OptionIt = Options.find(OptionName);
     if (OptionIt == Options.end())
-      report_fatal_error("typified profile option is not registered");
+      report_fatal_error("composite 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");
+      report_fatal_error("failed to set composite profile option");
   }
 };
 
@@ -168,9 +169,10 @@ struct SampleProfTest : ::testing::Test {
     delete PS;
   }
 
-  // Write a minimal profile with a specific format version to an in-memory
-  // buffer.
-  ErrorOr<SmallVector<char, 128>> writeProfileToBuffer(uint64_t Version) {
+  // Write a minimal profile, optionally requesting a specific format version,
+  // to an in-memory buffer.
+  ErrorOr<SmallVector<char, 128>>
+  writeProfileToBuffer(std::optional<uint64_t> Version = std::nullopt) {
     SmallVector<char, 128> Buffer;
     std::unique_ptr<raw_ostream> OS =
         std::make_unique<raw_svector_ostream>(Buffer);
@@ -179,7 +181,8 @@ struct SampleProfTest : ::testing::Test {
     if (std::error_code EC = WriterOrErr.getError())
       return EC;
     auto Writer = std::move(WriterOrErr.get());
-    Writer->setFormatVersion(Version);
+    if (Version)
+      Writer->setFormatVersion(*Version);
 
     StringRef FooName("_Z3fooi");
     FunctionSamples FooSamples;
@@ -196,6 +199,14 @@ struct SampleProfTest : ::testing::Test {
     return Buffer;
   }
 
+  // Replace the single-byte ExtBinary version in a complete profile.
+  void overwriteProfileVersion(MutableArrayRef<char> Buffer, uint8_t Version) {
+    unsigned MagicSize = 0;
+    decodeULEB128(reinterpret_cast<const uint8_t *>(Buffer.data()), &MagicSize);
+    ASSERT_LT(MagicSize, Buffer.size());
+    Buffer[MagicSize] = static_cast<char>(Version);
+  }
+
   // Write a raw profile header (Magic + Version) directly to a buffer.
   // This bypasses the writer validation and is used to test reader error
   // handling.
@@ -512,16 +523,17 @@ 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);
+// Verify the full ExtBinary round trip through composite profile sections.
+TEST_F(SampleProfTest, roundtrip_composite_ext_binary_profile) {
+  [[maybe_unused]] ScopedCompositeProfile Composite(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);
+// Verify that reusing one ExtBinary writer for a composite profile and then a
+// legacy profile restores the legacy section types. The already-selected v105
+// remains a valid version for the subsequent legacy representation.
+TEST_F(SampleProfTest, ext_binary_writer_composite_to_legacy) {
+  ScopedCompositeProfile Composite(true);
 
   SmallVector<char, 128> Buffer;
   std::unique_ptr<raw_ostream> OS =
@@ -538,7 +550,7 @@ TEST_F(SampleProfTest, ext_binary_writer_typified_to_legacy) {
   SampleProfileMap Profiles;
   Profiles[FooName] = std::move(FooSamples);
 
-  auto VerifyFormat = [&](bool IsTypified) {
+  auto VerifyFormat = [&](bool IsComposite, uint64_t ExpectedVersion) {
     std::unique_ptr<MemoryBuffer> MemBuffer = MemoryBuffer::getMemBufferCopy(
         StringRef(Buffer.data(), Buffer.size()), "profile");
     auto FS = vfs::getRealFileSystem();
@@ -546,18 +558,19 @@ 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->hasTypifiedProfileSection());
+    EXPECT_EQ(IsComposite, ProfileReader->hasCompositeProfileSection());
+    EXPECT_EQ(ExpectedVersion, ProfileReader->getFormatVersion());
   };
 
   ASSERT_TRUE(NoError(ProfileWriter->write(Profiles)));
   ProfileWriter->getOutputStream().flush();
-  VerifyFormat(true);
+  VerifyFormat(true, CompositeProfileVersion);
 
   Buffer.clear();
-  ForceTypified.set(false);
+  Composite.set(false);
   ASSERT_TRUE(NoError(ProfileWriter->write(Profiles)));
   ProfileWriter->getOutputStream().flush();
-  VerifyFormat(false);
+  VerifyFormat(false, CompositeProfileVersion);
 }
 
 TEST_F(SampleProfTest, roundtrip_md5_ext_binary_profile) {
@@ -590,9 +603,9 @@ 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);
+// Verify composite ExtBinary round trips when the name table uses MD5 hashes.
+TEST_F(SampleProfTest, roundtrip_composite_md5_ext_binary_profile) {
+  [[maybe_unused]] ScopedCompositeProfile Composite(true);
   testRoundTrip(SampleProfileFormat::SPF_Ext_Binary, false, true);
 }
 
@@ -727,14 +740,65 @@ TEST_F(SampleProfTest, SampleProfileFormatVersion102) {
   EXPECT_EQ(ReadVersionOrErr.getError(), sampleprof_error::unsupported_version);
 }
 
-// Verify that requesting format version 105 (above latest supported) is
-// rejected by the reader.
+// Verify that requesting format version 105 results in a version 105 legacy
+// profile.
 TEST_F(SampleProfTest, SampleProfileFormatVersion105) {
-  auto Buffer = writeRawHeaderToBuffer(105);
+  auto BufferOrErr = writeProfileToBuffer(105);
+  ASSERT_TRUE(NoError(BufferOrErr.getError()));
+  auto Buffer = std::move(*BufferOrErr);
+
+  auto ReadVersionOrErr = readVersionFromBuffer(Buffer);
+  ASSERT_TRUE(NoError(ReadVersionOrErr.getError()));
+  EXPECT_EQ(*ReadVersionOrErr, CompositeProfileVersion);
+}
+
+// Verify that requesting format version 106 (above latest supported) is
+// rejected by the reader.
+TEST_F(SampleProfTest, SampleProfileFormatVersion106) {
+  auto Buffer = writeRawHeaderToBuffer(106);
   auto ReadVersionOrErr = readVersionFromBuffer(Buffer);
   EXPECT_EQ(ReadVersionOrErr.getError(), sampleprof_error::unsupported_version);
 }
 
+// Verify that the default composite writer selects the first compatible
+// ExtBinary version.
+TEST_F(SampleProfTest, CompositeProfileUsesVersion105) {
+  [[maybe_unused]] ScopedCompositeProfile Composite(true);
+  auto BufferOrErr = writeProfileToBuffer();
+  ASSERT_TRUE(NoError(BufferOrErr.getError()));
+
+  auto ReadVersionOrErr = readVersionFromBuffer(*BufferOrErr);
+  ASSERT_TRUE(NoError(ReadVersionOrErr.getError()));
+  EXPECT_EQ(*ReadVersionOrErr, CompositeProfileVersion);
+}
+
+// Verify that the writer rejects a programmatic legacy version instead of
+// silently overriding it when composite output is required.
+TEST_F(SampleProfTest, CompositeProfileRejectsProgrammaticLegacyVersions) {
+  [[maybe_unused]] ScopedCompositeProfile Composite(true);
+
+  for (uint64_t Version : {103u, 104u}) {
+    auto BufferOrErr = writeProfileToBuffer(Version);
+    EXPECT_EQ(BufferOrErr.getError(), sampleprof_error::unsupported_version);
+  }
+}
+
+// Verify that readers reject composite sections whose header claims a legacy
+// ExtBinary version.
+TEST_F(SampleProfTest, CompositeProfileRejectsLegacyFileVersions) {
+  [[maybe_unused]] ScopedCompositeProfile Composite(true);
+  auto BufferOrErr = writeProfileToBuffer();
+  ASSERT_TRUE(NoError(BufferOrErr.getError()));
+  auto Buffer = std::move(*BufferOrErr);
+
+  for (uint8_t Version : {uint8_t(103), uint8_t(104)}) {
+    overwriteProfileVersion(Buffer, Version);
+    auto ReadVersionOrErr = readVersionFromBuffer(Buffer);
+    EXPECT_EQ(ReadVersionOrErr.getError(),
+              sampleprof_error::unsupported_version);
+  }
+}
+
 TEST_F(SampleProfTest, ProfileSymbolListMD5) {
   std::vector<uint64_t> Keys = {FunctionId("foo").getHashCode(),
                                 FunctionId("bar").getHashCode()};

>From 8e204bd2e53ea5fd155a30b594dd959ee495a479 Mon Sep 17 00:00:00 2001
From: Sergey Shcherbinin <sscherbinin at nvidia.com>
Date: Tue, 18 Aug 2026 15:10:06 +0400
Subject: [PATCH 13/17] [SampleProfile] Apply clang-format to composite changes

Match the repository formatter output so the composite profile update passes code-format validation.
---
 llvm/include/llvm/ProfileData/SampleProfReader.h |  3 ++-
 llvm/include/llvm/ProfileData/SampleProfWriter.h |  3 ++-
 llvm/lib/ProfileData/SampleProfReader.cpp        |  2 +-
 llvm/lib/ProfileData/SampleProfWriter.cpp        | 12 ++++++------
 4 files changed, 11 insertions(+), 9 deletions(-)

diff --git a/llvm/include/llvm/ProfileData/SampleProfReader.h b/llvm/include/llvm/ProfileData/SampleProfReader.h
index f69442350d5b7..82f26f3133ad3 100644
--- a/llvm/include/llvm/ProfileData/SampleProfReader.h
+++ b/llvm/include/llvm/ProfileData/SampleProfReader.h
@@ -961,7 +961,8 @@ class LLVM_ABI SampleProfileReaderBinary : public SampleProfileReader {
 
   /// Read specific profile types.
   std::error_code readLBRProfile(FunctionSamples &FProfile, bool IsNested);
-  std::error_code readCompositeProfile(FunctionSamples &FProfile, bool IsNested);
+  std::error_code readCompositeProfile(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 9c645c67715e6..aaab8c21d9b00 100644
--- a/llvm/include/llvm/ProfileData/SampleProfWriter.h
+++ b/llvm/include/llvm/ProfileData/SampleProfWriter.h
@@ -227,7 +227,8 @@ class LLVM_ABI SampleProfileWriterBinary : public SampleProfileWriter {
   std::error_code writeLBRProfile(const FunctionSamples &S, bool IsNested);
 
   /// Interfaces for composite profile writing.
-  std::error_code writeCompositeProfile(const FunctionSamples &S, bool IsNested);
+  std::error_code writeCompositeProfile(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
diff --git a/llvm/lib/ProfileData/SampleProfReader.cpp b/llvm/lib/ProfileData/SampleProfReader.cpp
index a74644ff9947c..71df8083251d4 100644
--- a/llvm/lib/ProfileData/SampleProfReader.cpp
+++ b/llvm/lib/ProfileData/SampleProfReader.cpp
@@ -782,7 +782,7 @@ SampleProfileReaderBinary::readLBRProfile(FunctionSamples &FProfile,
 
 std::error_code
 SampleProfileReaderBinary::readCompositeProfile(FunctionSamples &FProfile,
-                                               bool IsNested) {
+                                                bool IsNested) {
   // Read the number of profile types.
   auto ProfNum = readNumber<uint64_t>();
   if (std::error_code EC = ProfNum.getError())
diff --git a/llvm/lib/ProfileData/SampleProfWriter.cpp b/llvm/lib/ProfileData/SampleProfWriter.cpp
index 82df5063de2c5..38d75d35421bd 100644
--- a/llvm/lib/ProfileData/SampleProfWriter.cpp
+++ b/llvm/lib/ProfileData/SampleProfWriter.cpp
@@ -71,9 +71,10 @@ static cl::opt<bool> WriteEytzingerNameTables(
     cl::desc("Write Eytzinger 3-span layout for NameTable and parallel "
              "FuncOffsetTable"));
 
-static cl::opt<bool> ExtBinaryCompositeProf(
-    "extbinary-composite-prof", cl::init(false), cl::Hidden,
-    cl::desc("Use the composite profile format"));
+static cl::opt<bool>
+    ExtBinaryCompositeProf("extbinary-composite-prof", cl::init(false),
+                           cl::Hidden,
+                           cl::desc("Use the composite profile format"));
 
 /// Return whether the profile requires the composite ExtBinary representation.
 static bool usesCompositeProfile(const SampleProfileMap &ProfileMap) {
@@ -667,8 +668,7 @@ unsigned SampleProfileWriterExtBinaryBase::findUnwrittenEntry(SecType Type) {
 std::error_code SampleProfileWriterExtBinaryBase::writeOneSection(
     SecType Type, const SampleProfileMap &ProfileMap) {
   // Composite sections require the file version that defines their encoding.
-  if ((Type == SecCompositeProfile ||
-       Type == SecCompositeFuncOffsetTable) &&
+  if ((Type == SecCompositeProfile || Type == SecCompositeFuncOffsetTable) &&
       FormatVersion < CompositeProfileVersion)
     return sampleprof_error::unsupported_version;
 
@@ -1325,7 +1325,7 @@ static bool hasNonEmptyLBRProfile(const FunctionSamples &S, bool IsNested) {
 
 std::error_code
 SampleProfileWriterBinary::writeCompositeProfile(const FunctionSamples &S,
-                                                bool IsNested) {
+                                                 bool IsNested) {
   auto &OS = *OutputStream;
   bool WriteLBRProf = hasNonEmptyLBRProfile(S, IsNested);
   // Other profile types should be added here.

>From 3fc66fedeace50ad919e4a07c2ef66572b7932f7 Mon Sep 17 00:00:00 2001
From: Sergey Shcherbinin <sscherbinin at nvidia.com>
Date: Fri, 21 Aug 2026 18:24:17 +0400
Subject: [PATCH 14/17] [SampleProfile] Simplify composite payload writing

Use a size-counting pass to avoid payload backpatching and bounded-buffer complexity while preserving compression compatibility. Keep large multi-byte payload coverage in the unit tests.
---
 .../llvm/ProfileData/SampleProfWriter.h       |   8 +-
 llvm/lib/ProfileData/SampleProfWriter.cpp     | 183 ++++--------------
 .../generate-large-composite-profile.py       |  18 --
 .../composite-compressed-info.test            |  10 +-
 .../composite-payload-buffer-limit.test       |  24 ---
 .../eytzinger-split-nametable-partition.test  |   2 +-
 llvm/unittests/ProfileData/SampleProfTest.cpp |  80 +++++++-
 7 files changed, 123 insertions(+), 202 deletions(-)
 delete mode 100644 llvm/test/tools/llvm-profdata/Inputs/generate-large-composite-profile.py
 delete mode 100644 llvm/test/tools/llvm-profdata/composite-payload-buffer-limit.test

diff --git a/llvm/include/llvm/ProfileData/SampleProfWriter.h b/llvm/include/llvm/ProfileData/SampleProfWriter.h
index 6469859523426..82ef36ab2f0ff 100644
--- a/llvm/include/llvm/ProfileData/SampleProfWriter.h
+++ b/llvm/include/llvm/ProfileData/SampleProfWriter.h
@@ -232,14 +232,14 @@ class LLVM_ABI SampleProfileWriterBinary : public SampleProfileWriter {
   std::error_code writeCompositeProfile(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
+  /// WritePayload. The callback is invoked once to count its bytes and again to
+  /// emit them, so both calls must produce identical output 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;
+  /// Reusable stream that counts payload bytes without retaining them.
+  std::unique_ptr<raw_ostream> PayloadSizeStream;
   /// Whether a profile payload callback is currently being executed.
   bool WritingProfileType = false;
 
diff --git a/llvm/lib/ProfileData/SampleProfWriter.cpp b/llvm/lib/ProfileData/SampleProfWriter.cpp
index 5f36371fd93bb..cc727670ea9a4 100644
--- a/llvm/lib/ProfileData/SampleProfWriter.cpp
+++ b/llvm/lib/ProfileData/SampleProfWriter.cpp
@@ -30,12 +30,9 @@
 #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>
@@ -51,12 +48,6 @@ 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 composite 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"));
@@ -66,11 +57,6 @@ static cl::opt<bool>
                            cl::Hidden,
                            cl::desc("Use the composite profile format"));
 
-/// Return whether the profile requires the composite ExtBinary representation.
-static bool usesCompositeProfile(const SampleProfileMap &ProfileMap) {
-  return ExtBinaryCompositeProf || ProfileMap.hasNonLBRProfile();
-}
-
 namespace llvm {
 namespace support {
 namespace endian {
@@ -197,7 +183,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 writing data.
+  // Use LocalBuf as a temporary output for writting data.
   if (hasSecFlag(Entry, SecCommonFlags::SecFlagCompress))
     LocalBufStream.swap(OutputStream);
   return SectionStart;
@@ -208,7 +194,7 @@ std::error_code SampleProfileWriterExtBinaryBase::compressAndOutput() {
     return sampleprof_error::zlib_unavailable;
   std::string &UncompressedStrings =
       static_cast<raw_string_ostream *>(LocalBufStream.get())->str();
-  if (UncompressedStrings.empty())
+  if (UncompressedStrings.size() == 0)
     return sampleprof_error::success;
   auto &OS = *OutputStream;
   SmallVector<uint8_t, 128> CompressedStrings;
@@ -659,8 +645,9 @@ std::error_code SampleProfileWriterExtBinaryBase::writeOneSection(
     SecType Type, const SampleProfileMap &ProfileMap) {
   // Composite sections require the file version that defines their encoding.
   if ((Type == SecCompositeProfile || Type == SecCompositeFuncOffsetTable) &&
-      FormatVersion < CompositeProfileVersion)
+      FormatVersion < CompositeProfileVersion) {
     return sampleprof_error::unsupported_version;
+  }
 
   unsigned LayoutIdx = findUnwrittenEntry(Type);
   SecHdrTableEntry &Entry = SectionHdrLayout[LayoutIdx];
@@ -787,7 +774,7 @@ std::error_code SampleProfileWriterExtBinary::writeCtxSplitLayout(
 
 void SampleProfileWriterExtBinary::configureCompositeProfile(
     const SampleProfileMap &ProfileMap) {
-  WriteCompositeProf = usesCompositeProfile(ProfileMap);
+  WriteCompositeProf = ExtBinaryCompositeProf || ProfileMap.hasNonLBRProfile();
   ProfSection = WriteCompositeProf ? SecCompositeProfile : SecLBRProfile;
   FuncOffsetSection =
       WriteCompositeProf ? SecCompositeFuncOffsetTable : SecFuncOffsetTable;
@@ -1138,171 +1125,87 @@ SampleProfileWriterBinary::writeLBRProfile(const FunctionSamples &S,
 
 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;
-
+/// A reusable stream that discards payload bytes while counting their size.
+class PayloadSizeCountingStream final : public raw_ostream {
 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));
-  }
+  /// Avoid retaining payload data in raw_ostream's internal buffer.
+  PayloadSizeCountingStream() { SetUnbuffered(); }
 
-  /// 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.
+  /// Prepare the stream to count 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.
+  /// Return whether the payload size exceeded the representable range.
   bool overflowed() const { return Overflowed; }
 
-  /// Return the complete payload size, including bytes discarded after
-  /// overflow.
+  /// Return the complete payload size when overflowed() is false.
   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.
+  /// Count incoming bytes without retaining their contents.
+  void write_impl(const char *, size_t Size) override {
     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) {
+    // Fail closed if the payload cannot be represented by its uint64_t size.
+    if (Size > UINT64_MAX - PayloadSize) {
       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;
+    PayloadSize += 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;
+  /// Number of bytes observed during the counting pass.
   uint64_t PayloadSize = 0;
+  /// Whether the counted size no longer fits in uint64_t.
   bool Overflowed = false;
-  /// 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
+  // PayloadSizeStream temporarily owns the real output while the callback
   // writes through OutputStream. A nested call would therefore mistake the
-  // real output for BoundedBufferingStream.
+  // real output for PayloadSizeCountingStream.
   if (WritingProfileType)
     return sampleprof_error::malformed;
   SaveAndRestore RestoreWritingProfileType(WritingProfileType, true);
 
   // A profile block stores its payload size before the payload, but that size
-  // is not known until 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);
+  // is not known until it has been serialized. Count one complete serialization
+  // without retaining its bytes, then emit the header and serialize it again.
+  // TODO: Avoid serializing each payload twice while retaining bounded memory
+  // use and compatibility with compressed section output.
+  if (!PayloadSizeStream)
+    PayloadSizeStream = std::make_unique<PayloadSizeCountingStream>();
+  auto *SizeStream =
+      static_cast<PayloadSizeCountingStream *>(PayloadSizeStream.get());
+  SizeStream->resetPayload();
+  OutputStream.swap(PayloadSizeStream);
   std::error_code EC = WritePayload();
-  BufferStream->finishPayload();
-  OutputStream.swap(PayloadBufferStream);
+  OutputStream.swap(PayloadSizeStream);
   if (EC)
     return EC;
+  if (SizeStream->overflowed())
+    return sampleprof_error::too_large;
 
-  // Emit the profile type and the complete payload size, then emit the payload.
+  // Emit the compact header followed by the second, materialized pass.
   auto &OS = *OutputStream;
   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 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;
-  } else {
-    OS << BufferStream->payload();
-  }
+  encodeULEB128(SizeStream->payloadSize(), OS);
+  uint64_t PayloadStart = OS.tell();
+  if (std::error_code SecondPassEC = WritePayload())
+    return SecondPassEC;
+
+  // Reject a stateful callback that did not reproduce the counted payload.
+  if (OS.tell() - PayloadStart != SizeStream->payloadSize())
+    return sampleprof_error::malformed;
   return sampleprof_error::success;
 }
 
diff --git a/llvm/test/tools/llvm-profdata/Inputs/generate-large-composite-profile.py b/llvm/test/tools/llvm-profdata/Inputs/generate-large-composite-profile.py
deleted file mode 100644
index 9d696b3f47f61..0000000000000
--- a/llvm/test/tools/llvm-profdata/Inputs/generate-large-composite-profile.py
+++ /dev/null
@@ -1,18 +0,0 @@
-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 composite 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/composite-compressed-info.test b/llvm/test/tools/llvm-profdata/composite-compressed-info.test
index f4dc0fba4aa4d..ed18dee7395dc 100644
--- a/llvm/test/tools/llvm-profdata/composite-compressed-info.test
+++ b/llvm/test/tools/llvm-profdata/composite-compressed-info.test
@@ -1,11 +1,9 @@
 REQUIRES: zlib
 
-; Verify that compressed composite blocks remain inspectable when their payloads
-; exceed the bounded buffer and that overflow does not change the output.
-; RUN: llvm-profdata merge --sample --extbinary-composite-prof --extbinary --compress-all-sections %S/Inputs/sample-profile.proftext -o %t.buffered
-; RUN: llvm-profdata merge --sample --extbinary-composite-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-composite-info-only %t.overflow | FileCheck %s
+; Verify that composite payload size counting works while the enclosing section
+; is redirected through the existing compression buffer.
+; RUN: llvm-profdata merge --sample --extbinary-composite-prof --extbinary --compress-all-sections %S/Inputs/sample-profile.proftext -o %t.prof
+; RUN: llvm-profdata show --sample --show-composite-info-only %t.prof | FileCheck %s
 
 ; CHECK: Function:
 ; CHECK-NEXT:   Profile blocks: 1
diff --git a/llvm/test/tools/llvm-profdata/composite-payload-buffer-limit.test b/llvm/test/tools/llvm-profdata/composite-payload-buffer-limit.test
deleted file mode 100644
index 3761a68624643..0000000000000
--- a/llvm/test/tools/llvm-profdata/composite-payload-buffer-limit.test
+++ /dev/null
@@ -1,24 +0,0 @@
-; 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-composite-prof --extbinary %S/Inputs/sample-profile.proftext -o %t.buffered
-; RUN: llvm-profdata merge --sample --extbinary-composite-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-composite-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-composite-profile.py %t.large.proftext 2048
-; RUN: llvm-profdata merge --sample --extbinary-composite-prof --extbinary %t.large.proftext -o %t.large.buffered
-; RUN: llvm-profdata merge --sample --extbinary-composite-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-composite-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/eytzinger-split-nametable-partition.test b/llvm/test/tools/llvm-profdata/eytzinger-split-nametable-partition.test
index 5dea26189b807..62646d5bf43bf 100644
--- a/llvm/test/tools/llvm-profdata/eytzinger-split-nametable-partition.test
+++ b/llvm/test/tools/llvm-profdata/eytzinger-split-nametable-partition.test
@@ -4,7 +4,7 @@
 # RUN: FileCheck %s -input-file %t.secinfo
 # RUN: llvm-profdata merge --sample --text %t.xfdo -o %t.text
 # RUN: FileCheck %s --check-prefix=TEXT -input-file %t.text
-# Extract and verify the Eytzinger span counts (NumNested, NumFlat,
+# Extract and verify the Eytzinger span counts (NumCS, NumFlat,
 # NumInlinees) from NameTableSection to ensure correct symbol
 # partitioning.
 # RUN: %python -c "import re; off = int(re.search(r'NameTableSection - Offset: (\d+)', open(r'%t.secinfo').read()).group(1)); d = open(r'%t.xfdo', 'rb').read()[off:off+3]; print(list(d))" | FileCheck %s --check-prefix=COUNTS
diff --git a/llvm/unittests/ProfileData/SampleProfTest.cpp b/llvm/unittests/ProfileData/SampleProfTest.cpp
index 63ede944b4444..9767bf5eccc7d 100644
--- a/llvm/unittests/ProfileData/SampleProfTest.cpp
+++ b/llvm/unittests/ProfileData/SampleProfTest.cpp
@@ -169,10 +169,11 @@ struct SampleProfTest : ::testing::Test {
     delete PS;
   }
 
-  // Write a minimal profile, optionally requesting a specific format version,
-  // to an in-memory buffer.
+  // Write the supplied profile map, optionally requesting a specific format
+  // version, to an in-memory buffer.
   ErrorOr<SmallVector<char, 128>>
-  writeProfileToBuffer(std::optional<uint64_t> Version = std::nullopt) {
+  writeProfileToBuffer(const SampleProfileMap &Profiles,
+                       std::optional<uint64_t> Version = std::nullopt) {
     SmallVector<char, 128> Buffer;
     std::unique_ptr<raw_ostream> OS =
         std::make_unique<raw_svector_ostream>(Buffer);
@@ -184,6 +185,17 @@ struct SampleProfTest : ::testing::Test {
     if (Version)
       Writer->setFormatVersion(*Version);
 
+    if (std::error_code EC = Writer->write(Profiles))
+      return EC;
+    Writer->getOutputStream().flush();
+    Writer.reset();
+    return Buffer;
+  }
+
+  // Write a minimal profile, optionally requesting a specific format version,
+  // to an in-memory buffer.
+  ErrorOr<SmallVector<char, 128>>
+  writeProfileToBuffer(std::optional<uint64_t> Version = std::nullopt) {
     StringRef FooName("_Z3fooi");
     FunctionSamples FooSamples;
     FooSamples.setFunction(FunctionId(FooName));
@@ -191,12 +203,7 @@ struct SampleProfTest : ::testing::Test {
 
     SampleProfileMap Profiles;
     Profiles[FooName] = std::move(FooSamples);
-
-    if (std::error_code EC = Writer->write(Profiles))
-      return EC;
-    Writer->getOutputStream().flush();
-    Writer.reset();
-    return Buffer;
+    return writeProfileToBuffer(Profiles, Version);
   }
 
   // Replace the single-byte ExtBinary version in a complete profile.
@@ -537,6 +544,61 @@ TEST_F(SampleProfTest, roundtrip_composite_ext_binary_profile) {
   testRoundTrip(SampleProfileFormat::SPF_Ext_Binary, false, false);
 }
 
+// Verify that the writer and reader handle a multi-byte ULEB128 payload size.
+TEST_F(SampleProfTest, roundtrip_large_composite_ext_binary_profile) {
+  [[maybe_unused]] ScopedCompositeProfile Composite(true);
+  constexpr uint32_t BodyRecordCount = 128;
+  StringRef FunctionName("_Z5largev");
+
+  // Build one function whose encoded LBR payload is necessarily larger than the
+  // single-byte ULEB128 range.
+  FunctionSamples LargeSamples;
+  LargeSamples.setFunction(FunctionId(FunctionName));
+  ASSERT_EQ(sampleprof_error::success,
+            LargeSamples.addTotalSamples(BodyRecordCount));
+  ASSERT_EQ(sampleprof_error::success, LargeSamples.addHeadSamples(1));
+  for (uint32_t LineOffset = 1; LineOffset <= BodyRecordCount; ++LineOffset)
+    ASSERT_EQ(sampleprof_error::success,
+              LargeSamples.addBodySamples(LineOffset, 0, 1));
+
+  SampleProfileMap Profiles;
+  Profiles[FunctionName] = std::move(LargeSamples);
+  auto BufferOrErr = writeProfileToBuffer(Profiles);
+  ASSERT_TRUE(NoError(BufferOrErr.getError()));
+
+  // Inspect the decoded block structure to verify that the writer emitted a
+  // payload size outside the single-byte ULEB128 range.
+  std::unique_ptr<MemoryBuffer> MemBuffer = MemoryBuffer::getMemBufferCopy(
+      StringRef(BufferOrErr->data(), BufferOrErr->size()), "profile");
+  auto FS = vfs::getRealFileSystem();
+  auto ReaderOrErr = SampleProfileReader::create(MemBuffer, Context, *FS);
+  ASSERT_TRUE(NoError(ReaderOrErr.getError()));
+  auto ProfileReader = std::move(ReaderOrErr.get());
+  std::string ProfileTypeInfo;
+  raw_string_ostream InfoOS(ProfileTypeInfo);
+  ASSERT_TRUE(NoError(ProfileReader->dumpProfileTypeInfo(InfoOS)));
+
+  StringRef PayloadSizeText =
+      StringRef(ProfileTypeInfo).split("Payload size: ").second;
+  ASSERT_FALSE(PayloadSizeText.empty());
+  PayloadSizeText = PayloadSizeText.split('\n').first;
+  uint64_t PayloadSize = 0;
+  ASSERT_FALSE(PayloadSizeText.getAsInteger(10, PayloadSize));
+  EXPECT_GE(PayloadSize, 128u);
+
+  // Confirm that the same read preserved every body record, including both
+  // ends of the generated range.
+  FunctionSamples *ReadSamples = ProfileReader->getSamplesFor(FunctionName);
+  ASSERT_NE(ReadSamples, nullptr);
+  EXPECT_EQ(ReadSamples->getBodySamples().size(), BodyRecordCount);
+  auto FirstSample = ReadSamples->findSamplesAt(1, 0);
+  ASSERT_TRUE(NoError(FirstSample.getError()));
+  EXPECT_EQ(FirstSample.get(), 1u);
+  auto LastSample = ReadSamples->findSamplesAt(BodyRecordCount, 0);
+  ASSERT_TRUE(NoError(LastSample.getError()));
+  EXPECT_EQ(LastSample.get(), 1u);
+}
+
 // Verify that reusing one ExtBinary writer for a composite profile and then a
 // legacy profile restores the legacy section types. The already-selected v105
 // remains a valid version for the subsequent legacy representation.

>From b13d9073442e40a8dd40553e3ae1225917d0e025 Mon Sep 17 00:00:00 2001
From: Sergey Shcherbinin <sscherbinin at nvidia.com>
Date: Fri, 21 Aug 2026 18:59:32 +0400
Subject: [PATCH 15/17] [SampleProfile] Rename composite profile test
 transformer

Use a neutral name because the script constructs both valid structural variants and intentionally malformed profiles.
---
 ...t-composite-payload.py => modify-composite-profile.py} | 3 +++
 .../tools/llvm-profdata/composite-mixed-sections.test     | 4 ++--
 .../tools/llvm-profdata/composite-multiple-blocks.test    | 2 +-
 .../tools/llvm-profdata/composite-payload-boundaries.test | 8 ++++----
 .../tools/llvm-profdata/show-composite-profile-info.test  | 4 ++--
 5 files changed, 12 insertions(+), 9 deletions(-)
 rename llvm/test/tools/llvm-profdata/Inputs/{corrupt-composite-payload.py => modify-composite-profile.py} (98%)

diff --git a/llvm/test/tools/llvm-profdata/Inputs/corrupt-composite-payload.py b/llvm/test/tools/llvm-profdata/Inputs/modify-composite-profile.py
similarity index 98%
rename from llvm/test/tools/llvm-profdata/Inputs/corrupt-composite-payload.py
rename to llvm/test/tools/llvm-profdata/Inputs/modify-composite-profile.py
index 10dd1482c4743..54305d3822520 100644
--- a/llvm/test/tools/llvm-profdata/Inputs/corrupt-composite-payload.py
+++ b/llvm/test/tools/llvm-profdata/Inputs/modify-composite-profile.py
@@ -1,3 +1,6 @@
+# Transform ExtBinary composite profiles into valid structural variants and
+# intentionally malformed inputs that cannot be produced through writer APIs.
+
 import argparse
 import struct
 
diff --git a/llvm/test/tools/llvm-profdata/composite-mixed-sections.test b/llvm/test/tools/llvm-profdata/composite-mixed-sections.test
index 41d001b8a860f..d7bca2d82d1df 100644
--- a/llvm/test/tools/llvm-profdata/composite-mixed-sections.test
+++ b/llvm/test/tools/llvm-profdata/composite-mixed-sections.test
@@ -1,14 +1,14 @@
 ; Verify that an empty composite section is skipped without preventing a
 ; following legacy profile section from being read.
 ; RUN: llvm-profdata merge --sample --extbinary %S/Inputs/sample-composite-empty-lbr.proftext -o %t.legacy
-; RUN: %python %S/Inputs/corrupt-composite-payload.py %t.legacy %t.empty-mixed prepend-empty-composite
+; RUN: %python %S/Inputs/modify-composite-profile.py %t.legacy %t.empty-mixed prepend-empty-composite
 ; 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 composite section. Both sections contain the same profiles, so every
 ; counter is expected to be accumulated exactly twice.
 ; RUN: llvm-profdata merge --sample --extbinary-composite-prof --extbinary %S/Inputs/sample-composite-empty-lbr.proftext -o %t.composite
-; RUN: %python %S/Inputs/corrupt-composite-payload.py %t.legacy %t.nonempty-mixed prepend-composite --donor %t.composite
+; RUN: %python %S/Inputs/modify-composite-profile.py %t.legacy %t.nonempty-mixed prepend-composite --donor %t.composite
 ; 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.
diff --git a/llvm/test/tools/llvm-profdata/composite-multiple-blocks.test b/llvm/test/tools/llvm-profdata/composite-multiple-blocks.test
index a073561c61db1..fd5134df83c0f 100644
--- a/llvm/test/tools/llvm-profdata/composite-multiple-blocks.test
+++ b/llvm/test/tools/llvm-profdata/composite-multiple-blocks.test
@@ -1,7 +1,7 @@
 ; 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-composite-prof --extbinary %S/Inputs/composite-multiple-blocks.proftext -o %t.single
-; RUN: %python %S/Inputs/corrupt-composite-payload.py %t.single %t.multiple prepended-unknown
+; RUN: %python %S/Inputs/modify-composite-profile.py %t.single %t.multiple prepended-unknown
 ; RUN: llvm-profdata show --sample --show-composite-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
 
diff --git a/llvm/test/tools/llvm-profdata/composite-payload-boundaries.test b/llvm/test/tools/llvm-profdata/composite-payload-boundaries.test
index c28148122beaa..3f1541431cbfb 100644
--- a/llvm/test/tools/llvm-profdata/composite-payload-boundaries.test
+++ b/llvm/test/tools/llvm-profdata/composite-payload-boundaries.test
@@ -2,18 +2,18 @@
 ; and that inspection still reports an invalid unknown block size.
 ; RUN: llvm-profdata merge --sample --extbinary-composite-prof --extbinary %S/Inputs/sample-composite-empty-lbr.proftext -o %t.valid
 
-; RUN: %python %S/Inputs/corrupt-composite-payload.py %t.valid %t.undersized undersized
+; RUN: %python %S/Inputs/modify-composite-profile.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-composite-payload.py %t.valid %t.oversized oversized
+; RUN: %python %S/Inputs/modify-composite-profile.py %t.valid %t.oversized oversized
 ; 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-composite-payload.py %t.valid %t.duplicate duplicate-type
+; RUN: %python %S/Inputs/modify-composite-profile.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-composite-payload.py %t.valid %t.unknown-out-of-bounds unknown-out-of-bounds
+; RUN: %python %S/Inputs/modify-composite-profile.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=DECLARED-SIZE
 ; RUN: not llvm-profdata show --sample --show-composite-info-only %t.unknown-out-of-bounds 2>&1 | FileCheck %s --check-prefix=INSPECT
 
diff --git a/llvm/test/tools/llvm-profdata/show-composite-profile-info.test b/llvm/test/tools/llvm-profdata/show-composite-profile-info.test
index ae15a8290bad8..d13e1aff0ad2f 100644
--- a/llvm/test/tools/llvm-profdata/show-composite-profile-info.test
+++ b/llvm/test/tools/llvm-profdata/show-composite-profile-info.test
@@ -15,10 +15,10 @@
 
 ; BOTH: error: -show-sec-info-only and -show-composite-info-only cannot be used together
 
-; RUN: %python %S/Inputs/corrupt-composite-payload.py %t.composite %t.empty empty-section
+; RUN: %python %S/Inputs/modify-composite-profile.py %t.composite %t.empty empty-section
 ; RUN: llvm-profdata show --sample --show-composite-info-only %t.empty 2>&1 | count 0
 
-; RUN: %python %S/Inputs/corrupt-composite-payload.py %t.composite %t.unknown unknown
+; RUN: %python %S/Inputs/modify-composite-profile.py %t.composite %t.unknown unknown
 ; RUN: llvm-profdata show --sample --show-composite-info-only %t.unknown | FileCheck %s --check-prefix=UNKNOWN
 
 ; UNKNOWN: Function: baz

>From 7202a7d4bd86598848286a6bfe8acb8de82b5f63 Mon Sep 17 00:00:00 2001
From: Sergey Shcherbinin <sscherbinin at nvidia.com>
Date: Wed, 26 Aug 2026 18:40:23 +0400
Subject: [PATCH 16/17] [SampleProfile] Make composite output explicitly
 selected

Select composite sections only when requested and preserve compatible explicit format versions.
---
 llvm/docs/CommandGuide/llvm-profdata.rst      |  3 ++-
 llvm/include/llvm/ProfileData/SampleProf.h    | 18 ++-----------
 .../llvm/ProfileData/SampleProfWriter.h       |  2 +-
 llvm/lib/ProfileData/SampleProfWriter.cpp     | 26 ++++++++++---------
 .../composite-mixed-sections.test             |  7 +++--
 .../composite-multiple-blocks.test            |  2 +-
 .../composite-payload-boundaries.test         |  2 +-
 .../llvm-profdata/sample-flatten-profile.test |  1 +
 .../show-composite-profile-info.test          |  2 +-
 9 files changed, 26 insertions(+), 37 deletions(-)

diff --git a/llvm/docs/CommandGuide/llvm-profdata.rst b/llvm/docs/CommandGuide/llvm-profdata.rst
index 9e4a4b7c95bed..428ff98a281ce 100644
--- a/llvm/docs/CommandGuide/llvm-profdata.rst
+++ b/llvm/docs/CommandGuide/llvm-profdata.rst
@@ -387,7 +387,8 @@ OPTIONS
 .. option:: --show-composite-info-only=[true|false]
 
  Show the per-function composite block structure. This option is only meaningful
- for sample-based profiles in composite extbinary format.
+ for sample-based profiles in the composite extbinary format, introduced in
+ version 105.
 
 .. option:: --debug-info=<path>
 
diff --git a/llvm/include/llvm/ProfileData/SampleProf.h b/llvm/include/llvm/ProfileData/SampleProf.h
index 9a682c24715b5..9f4ff3f1adab6 100644
--- a/llvm/include/llvm/ProfileData/SampleProf.h
+++ b/llvm/include/llvm/ProfileData/SampleProf.h
@@ -152,12 +152,12 @@ enum SecType {
   SecFuncOffsetTable = 4,
   SecFuncMetadata = 5,
   SecCSNameTable = 6,
-  // Substitution to SecFuncOffsetTable when we have non-LBR profile types
+  // Function offset table used by the composite profile representation.
   SecCompositeFuncOffsetTable = 7,
   // marker for the first type of profile.
   SecFuncProfileFirst = 32,
   SecLBRProfile = SecFuncProfileFirst,
-  // Substitution to SecLBRProfile when we have non-LBR profile types
+  // Function profile section used by the composite profile representation.
   SecCompositeProfile = 33
 };
 
@@ -1430,12 +1430,6 @@ 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;
@@ -1561,14 +1555,6 @@ 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/SampleProfWriter.h b/llvm/include/llvm/ProfileData/SampleProfWriter.h
index 82ef36ab2f0ff..47f019234636e 100644
--- a/llvm/include/llvm/ProfileData/SampleProfWriter.h
+++ b/llvm/include/llvm/ProfileData/SampleProfWriter.h
@@ -475,7 +475,7 @@ class LLVM_ABI SampleProfileWriterExtBinary
   std::error_code writeSections(const SampleProfileMap &ProfileMap) override;
 
   /// Select the profile representation and update its section layout as needed.
-  void configureCompositeProfile(const SampleProfileMap &ProfileMap);
+  void configureCompositeProfile();
 
   std::error_code writeCustomSection(SecType Type) override {
     return sampleprof_error::success;
diff --git a/llvm/lib/ProfileData/SampleProfWriter.cpp b/llvm/lib/ProfileData/SampleProfWriter.cpp
index cc727670ea9a4..20d7b82605bbf 100644
--- a/llvm/lib/ProfileData/SampleProfWriter.cpp
+++ b/llvm/lib/ProfileData/SampleProfWriter.cpp
@@ -772,9 +772,8 @@ std::error_code SampleProfileWriterExtBinary::writeCtxSplitLayout(
   return sampleprof_error::success;
 }
 
-void SampleProfileWriterExtBinary::configureCompositeProfile(
-    const SampleProfileMap &ProfileMap) {
-  WriteCompositeProf = ExtBinaryCompositeProf || ProfileMap.hasNonLBRProfile();
+void SampleProfileWriterExtBinary::configureCompositeProfile() {
+  WriteCompositeProf = ExtBinaryCompositeProf;
   ProfSection = WriteCompositeProf ? SecCompositeProfile : SecLBRProfile;
   FuncOffsetSection =
       WriteCompositeProf ? SecCompositeFuncOffsetTable : SecFuncOffsetTable;
@@ -793,9 +792,9 @@ void SampleProfileWriterExtBinary::configureCompositeProfile(
 
 std::error_code SampleProfileWriterExtBinary::writeSections(
     const SampleProfileMap &ProfileMap) {
-  // Preserve section configuration at the point where the selected layout is
-  // consumed, after the base writer has emitted the validated file version.
-  configureCompositeProfile(ProfileMap);
+  // Rewrite the final configured layout immediately before its section types
+  // are consumed. Earlier layout configuration may replace SectionHdrLayout.
+  configureCompositeProfile();
 
   std::error_code EC;
   if (SecLayout == DefaultLayout)
@@ -1330,13 +1329,16 @@ SampleProfileWriter::create(std::unique_ptr<raw_ostream> &OS,
   if (Format != SPF_Ext_Binary)
     Writer->setFormatVersion(DefaultVersion);
   else if (formatVersionIsSupported(RequestedVersion)) {
-    // A composite request selects its required format version unless the user
-    // explicitly requested an incompatible legacy version.
+    // Composite output defaults to its first compatible format version.
+    // Preserve a compatible version explicitly selected by the user.
     if (ExtBinaryCompositeProf) {
-      if (RequestedVersion.getNumOccurrences() != 0 &&
-          RequestedVersion < CompositeProfileVersion)
-        return sampleprof_error::unsupported_version;
-      Writer->setFormatVersion(CompositeProfileVersion);
+      if (RequestedVersion.getNumOccurrences() == 0) {
+        Writer->setFormatVersion(CompositeProfileVersion);
+      } else {
+        if (RequestedVersion < CompositeProfileVersion)
+          return sampleprof_error::unsupported_version;
+        Writer->setFormatVersion(RequestedVersion);
+      }
     } else {
       Writer->setFormatVersion(RequestedVersion);
     }
diff --git a/llvm/test/tools/llvm-profdata/composite-mixed-sections.test b/llvm/test/tools/llvm-profdata/composite-mixed-sections.test
index d7bca2d82d1df..35317e02fb985 100644
--- a/llvm/test/tools/llvm-profdata/composite-mixed-sections.test
+++ b/llvm/test/tools/llvm-profdata/composite-mixed-sections.test
@@ -1,13 +1,13 @@
 ; Verify that an empty composite section is skipped without preventing a
 ; following legacy profile section from being read.
-; RUN: llvm-profdata merge --sample --extbinary %S/Inputs/sample-composite-empty-lbr.proftext -o %t.legacy
+; RUN: llvm-profdata merge --sample --sample-profile-format-version=105 --extbinary %S/Inputs/sample-composite-empty-lbr.proftext -o %t.legacy
 ; RUN: %python %S/Inputs/modify-composite-profile.py %t.legacy %t.empty-mixed prepend-empty-composite
 ; 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 composite section. Both sections contain the same profiles, so every
 ; counter is expected to be accumulated exactly twice.
-; RUN: llvm-profdata merge --sample --extbinary-composite-prof --extbinary %S/Inputs/sample-composite-empty-lbr.proftext -o %t.composite
+; RUN: llvm-profdata merge --sample --extbinary-composite-prof --sample-profile-format-version=105 --extbinary %S/Inputs/sample-composite-empty-lbr.proftext -o %t.composite
 ; RUN: %python %S/Inputs/modify-composite-profile.py %t.legacy %t.nonempty-mixed prepend-composite --donor %t.composite
 ; RUN: llvm-profdata merge --sample --text %t.nonempty-mixed -o - | FileCheck %s --check-prefix=ACCUMULATED
 
@@ -16,7 +16,7 @@
 
 ; Verify that an empty composite section still makes composite inspection
 ; applicable, without producing the legacy-profile warning.
-; RUN: llvm-profdata show --sample --show-composite-info-only %t.empty-mixed 2>&1 | FileCheck %s --allow-empty --check-prefix=NO-WARNING
+; RUN: llvm-profdata show --sample --show-composite-info-only %t.empty-mixed 2>&1 | count 0
 
 ; PROFILE: top:42:7
 ; PROFILE-NEXT: caller:21:3
@@ -26,4 +26,3 @@
 ; ACCUMULATED-NEXT:  1: callee:22
 ; SECTIONS: CompositeProfileSection
 ; SECTIONS: LBRProfileSection
-; NO-WARNING-NOT: only supported for composite sample profiles
diff --git a/llvm/test/tools/llvm-profdata/composite-multiple-blocks.test b/llvm/test/tools/llvm-profdata/composite-multiple-blocks.test
index fd5134df83c0f..18e3b6726f3ff 100644
--- a/llvm/test/tools/llvm-profdata/composite-multiple-blocks.test
+++ b/llvm/test/tools/llvm-profdata/composite-multiple-blocks.test
@@ -1,6 +1,6 @@
 ; 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-composite-prof --extbinary %S/Inputs/composite-multiple-blocks.proftext -o %t.single
+; RUN: llvm-profdata merge --sample --extbinary-composite-prof --sample-profile-format-version=105 --extbinary %S/Inputs/composite-multiple-blocks.proftext -o %t.single
 ; RUN: %python %S/Inputs/modify-composite-profile.py %t.single %t.multiple prepended-unknown
 ; RUN: llvm-profdata show --sample --show-composite-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
diff --git a/llvm/test/tools/llvm-profdata/composite-payload-boundaries.test b/llvm/test/tools/llvm-profdata/composite-payload-boundaries.test
index 3f1541431cbfb..62f9235c79cf2 100644
--- a/llvm/test/tools/llvm-profdata/composite-payload-boundaries.test
+++ b/llvm/test/tools/llvm-profdata/composite-payload-boundaries.test
@@ -1,6 +1,6 @@
 ; Verify that composite payload readers cannot cross declared block boundaries
 ; and that inspection still reports an invalid unknown block size.
-; RUN: llvm-profdata merge --sample --extbinary-composite-prof --extbinary %S/Inputs/sample-composite-empty-lbr.proftext -o %t.valid
+; RUN: llvm-profdata merge --sample --extbinary-composite-prof --sample-profile-format-version=105 --extbinary %S/Inputs/sample-composite-empty-lbr.proftext -o %t.valid
 
 ; RUN: %python %S/Inputs/modify-composite-profile.py %t.valid %t.undersized undersized
 ; RUN: not llvm-profdata merge --sample %t.undersized -o /dev/null 2>&1 | FileCheck %s --check-prefix=TRUNCATED
diff --git a/llvm/test/tools/llvm-profdata/sample-flatten-profile.test b/llvm/test/tools/llvm-profdata/sample-flatten-profile.test
index a373ee98cca90..295dc76f049cb 100644
--- a/llvm/test/tools/llvm-profdata/sample-flatten-profile.test
+++ b/llvm/test/tools/llvm-profdata/sample-flatten-profile.test
@@ -1,6 +1,7 @@
 ; 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-composite-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: not llvm-profdata merge --sample --extbinary-composite-prof --sample-profile-format-version=103 --extbinary %S/Inputs/sample-flatten-profile.proftext -o %t.invalid 2>&1 | FileCheck %s --check-prefix=VERSION
 ; RUN: not llvm-profdata merge --sample --extbinary-composite-prof --sample-profile-format-version=104 --extbinary %S/Inputs/sample-flatten-profile.proftext -o %t.invalid 2>&1 | FileCheck %s --check-prefix=VERSION
 
 ; 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
diff --git a/llvm/test/tools/llvm-profdata/show-composite-profile-info.test b/llvm/test/tools/llvm-profdata/show-composite-profile-info.test
index d13e1aff0ad2f..ebf37e7eea4e1 100644
--- a/llvm/test/tools/llvm-profdata/show-composite-profile-info.test
+++ b/llvm/test/tools/llvm-profdata/show-composite-profile-info.test
@@ -1,6 +1,6 @@
 ; Verify composite 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-composite-prof --extbinary %S/Inputs/sample-flatten-profile.proftext -o %t.composite
+; RUN: llvm-profdata merge --sample --extbinary-composite-prof --sample-profile-format-version=105 --extbinary %S/Inputs/sample-flatten-profile.proftext -o %t.composite
 ; RUN: llvm-profdata show --sample --show-composite-info-only %t.composite | FileCheck %s --check-prefix=KNOWN
 
 ; KNOWN: Function: baz

>From 1deb51fa1d5cf41d57cc88eb60f0df0864e0e823 Mon Sep 17 00:00:00 2001
From: Sergey Shcherbinin <sscherbinin at nvidia.com>
Date: Wed, 2 Sep 2026 11:02:12 +0400
Subject: [PATCH 17/17] [SampleProfile] Replace composite test mutators with
 binary fixtures

Keep malformed ExtBinary coverage without duplicating format parsing in test code.
---
 .../Inputs/composite-duplicate-type.profdata  | Bin 0 -> 382 bytes
 .../Inputs/composite-empty-section.profdata   | Bin 0 -> 352 bytes
 .../Inputs/composite-mixed-empty.profdata     | Bin 0 -> 400 bytes
 .../Inputs/composite-mixed-nonempty.profdata  | Bin 0 -> 425 bytes
 .../Inputs/composite-multiple-blocks.proftext |   2 -
 .../composite-payload-oversized.profdata      | Bin 0 -> 377 bytes
 .../composite-payload-undersized.profdata     | Bin 0 -> 377 bytes
 .../composite-prepended-unknown.profdata      | Bin 0 -> 351 bytes
 .../Inputs/composite-unknown-nested.profdata  | Bin 0 -> 363 bytes
 .../Inputs/composite-unknown-oob.profdata     | Bin 0 -> 386 bytes
 .../Inputs/composite-unknown-top.profdata     | Bin 0 -> 377 bytes
 .../Inputs/composite-version-103.profdata     | Bin 0 -> 377 bytes
 .../Inputs/composite-version-104.profdata     | Bin 0 -> 377 bytes
 .../Inputs/modify-composite-profile.py        | 239 ------------------
 .../composite-mixed-sections.test             |  33 ++-
 .../composite-multiple-blocks.test            |  42 +--
 .../composite-payload-boundaries.test         |  38 ++-
 .../composite-unknown-resume-nested.test      |  21 ++
 .../composite-version-reader.test             |  12 +
 .../show-composite-profile-info.test          |  34 ++-
 llvm/unittests/ProfileData/SampleProfTest.cpp |  98 ++++---
 21 files changed, 188 insertions(+), 331 deletions(-)
 create mode 100644 llvm/test/tools/llvm-profdata/Inputs/composite-duplicate-type.profdata
 create mode 100644 llvm/test/tools/llvm-profdata/Inputs/composite-empty-section.profdata
 create mode 100644 llvm/test/tools/llvm-profdata/Inputs/composite-mixed-empty.profdata
 create mode 100644 llvm/test/tools/llvm-profdata/Inputs/composite-mixed-nonempty.profdata
 delete mode 100644 llvm/test/tools/llvm-profdata/Inputs/composite-multiple-blocks.proftext
 create mode 100644 llvm/test/tools/llvm-profdata/Inputs/composite-payload-oversized.profdata
 create mode 100644 llvm/test/tools/llvm-profdata/Inputs/composite-payload-undersized.profdata
 create mode 100644 llvm/test/tools/llvm-profdata/Inputs/composite-prepended-unknown.profdata
 create mode 100644 llvm/test/tools/llvm-profdata/Inputs/composite-unknown-nested.profdata
 create mode 100644 llvm/test/tools/llvm-profdata/Inputs/composite-unknown-oob.profdata
 create mode 100644 llvm/test/tools/llvm-profdata/Inputs/composite-unknown-top.profdata
 create mode 100644 llvm/test/tools/llvm-profdata/Inputs/composite-version-103.profdata
 create mode 100644 llvm/test/tools/llvm-profdata/Inputs/composite-version-104.profdata
 delete mode 100644 llvm/test/tools/llvm-profdata/Inputs/modify-composite-profile.py
 create mode 100644 llvm/test/tools/llvm-profdata/composite-unknown-resume-nested.test
 create mode 100644 llvm/test/tools/llvm-profdata/composite-version-reader.test

diff --git a/llvm/test/tools/llvm-profdata/Inputs/composite-duplicate-type.profdata b/llvm/test/tools/llvm-profdata/Inputs/composite-duplicate-type.profdata
new file mode 100644
index 0000000000000000000000000000000000000000..8cf7dd02877a33fa860496f022a8c280ac65c07c
GIT binary patch
literal 382
zcmZp9a$)0_lT%g%XR<Ru03(!!QJ<iUP$<m=lYr9hPz}ORJ{y{R6jUB&F3cX7vP!4~
zD^x%cCIO|Rq4IK2J~Ns;Og~H*W)6&B2eTL^z`)MHBrw5`fnh-}(5(k%@h~tvST4lC
z(6ChkD0T>F&w=wQK(RYoK(SW_K(X&;3=ALsS~4(f*k;4PaN`e<xYKOQz_6_aNbCm@
v%*ly4IjN}(V5*3rB)@=xfr*KMnH^{bm|$dNU}hF&08$K$3{2c$Sw;>3t)?pl

literal 0
HcmV?d00001

diff --git a/llvm/test/tools/llvm-profdata/Inputs/composite-empty-section.profdata b/llvm/test/tools/llvm-profdata/Inputs/composite-empty-section.profdata
new file mode 100644
index 0000000000000000000000000000000000000000..d9a40cdc96fb66f3dd5953ec663ade0ff0eb948f
GIT binary patch
literal 352
zcmZp9a$)0_lT%g%XR<Ru03(!!QJ<iUP$<m=lYr9hPz}ORJ{y{R6jUB&F3cX7vS_FT
zD^x%cCIO{k at -Sg$7z2kq%p91q1enDz0S0ykCV>fl3=9i;fo?r8i-&>X!Ezx6hK8*Y
zK(Rw|K(X^GK(RYoK(SW_K(X&;3=ALsS~4(f*k;4PaN`e<xYKOQz_6_aNbCm@%*ly4
WIjN}(V5*3rB)@=xfr*KMkplo%I4Sf1

literal 0
HcmV?d00001

diff --git a/llvm/test/tools/llvm-profdata/Inputs/composite-mixed-empty.profdata b/llvm/test/tools/llvm-profdata/Inputs/composite-mixed-empty.profdata
new file mode 100644
index 0000000000000000000000000000000000000000..080f50c3e7b123d3579d8e90f53a8380f0e0465a
GIT binary patch
literal 400
zcmZp9a$)0_lT%g%XL2w=03(!!Q9 at 9GP^bVCOae-$L*<2`d^R-sDyTfnToyF>Zm0w+
zR9q2Fz8a<xDz1P-UH~f2j3y5=A0`a52gaX(W<CQ0I|Gxz1V09b1-(GO9GJz!!0=$X
z5CcQQRtcckAvvJfc@?189W9{PD+8d|cQXct4}UEg7&dIPVPLrN2T0s$wq;=0)&eB<
pg9zs2#GIVeR0c3r#88r70JMe)XaF;#C<7zV1>6h_OiT=ntN>IlD+K at m

literal 0
HcmV?d00001

diff --git a/llvm/test/tools/llvm-profdata/Inputs/composite-mixed-nonempty.profdata b/llvm/test/tools/llvm-profdata/Inputs/composite-mixed-nonempty.profdata
new file mode 100644
index 0000000000000000000000000000000000000000..f2f6082c031ae739d45b5a866b92b23914fcc808
GIT binary patch
literal 425
zcmZp9a$)0_lT%g%XL2w=03(!!Q9 at 9GP^bVCOae-$L*<2`d^R-sDyTfnToyF>Zm0w+
zR9q2Fegafp5-P5MCSMJe7l4X0qsha}hY7>%f$?GH!}tsg><ml-6Z{w$7W4xBa$pt@
z1H*&mLJSNITP1*Ehva}_=T(4WceH?FuMB`<-^~~pKK!+0VA!zDhJoS6A0Tn3*_MG}
zTMLlb4<eY86LWG>QyIWi5kpCS0ni#IpaIN`q6~~cA8<1;FflPOvNAC;FtY>2fnv<e
L5Gh6mCZH?;ym%{@

literal 0
HcmV?d00001

diff --git a/llvm/test/tools/llvm-profdata/Inputs/composite-multiple-blocks.proftext b/llvm/test/tools/llvm-profdata/Inputs/composite-multiple-blocks.proftext
deleted file mode 100644
index f60dd2e2b32fc..0000000000000
--- a/llvm/test/tools/llvm-profdata/Inputs/composite-multiple-blocks.proftext
+++ /dev/null
@@ -1,2 +0,0 @@
-foo:10:1
- 0: 10
diff --git a/llvm/test/tools/llvm-profdata/Inputs/composite-payload-oversized.profdata b/llvm/test/tools/llvm-profdata/Inputs/composite-payload-oversized.profdata
new file mode 100644
index 0000000000000000000000000000000000000000..05c51e35d7065623ffbf51ccb7612fa815c633f2
GIT binary patch
literal 377
zcmZp9a$)0_lT%g%XR<Ru03(!!QJ<iUP$<m=lYr9hPz}ORJ{y{R6jUB&F3cX7vJ$8S
zD^x%cCIO|Rq4JVYJ~Ns;Og~H*W)6&B39}d`z`)MHBrw5`fnh-}(5(k%@h~tvST4lC
z(6ChkD0WB=D0W^2D0W8+DE7(#DE8fqf#JhnO9qAw+iVyZZu|ifcbaV(7`C+liTxmg
uIXN*WCpDD;OcgPd<QFh7FflT)umjCtWMp7w76nlZj0{ZNKnW%WMh*bP9V)#5

literal 0
HcmV?d00001

diff --git a/llvm/test/tools/llvm-profdata/Inputs/composite-payload-undersized.profdata b/llvm/test/tools/llvm-profdata/Inputs/composite-payload-undersized.profdata
new file mode 100644
index 0000000000000000000000000000000000000000..d038f82fcd79a6d3da2159381273382909857c5e
GIT binary patch
literal 377
zcmZp9a$)0_lT%g%XR<Ru03(!!QJ<iUP$<m=lYr9hPz}ORJ{y{R6jUB&F3cX7vJ$8S
zD^x%cCIO|Rq4JVYJ~Ns;Og~H*W)6&B39}d`z`)MHBrw5`fnh-}(5(k%@h~tvST4lC
z(6ChkD0WB=D0W^2D0W8+DE7(#DE8fqf#JhnO9qAw+iVyZZu|ifcbaV(7`C+liTxmg
tIXN*WCpDD;OcgPd<QFh7FflSPu>;LuWMp7w76nlZKoM at B1QP=z2LQw8D!Tvx

literal 0
HcmV?d00001

diff --git a/llvm/test/tools/llvm-profdata/Inputs/composite-prepended-unknown.profdata b/llvm/test/tools/llvm-profdata/Inputs/composite-prepended-unknown.profdata
new file mode 100644
index 0000000000000000000000000000000000000000..d29781c9b13995e564d8755c05216fe0b2fd3085
GIT binary patch
literal 351
zcmZp9a$)0_lT%g%XR<Ru03(!!QJ<iUP$<m=lYr9hPz|h5J{y|64^$pzF3cX7vKXiY
zGgLqkCIO{=q4NAtK1>}<8B9Nn4|4~M9}lw_D!|3X$jB%#!H<DqK`$E@<AGT`T#OHv
z3vn?vY?a_*TyRJZD0W^2D0W8+DE7(#DE8fqi}Ax>OD at I@+ibWPZ~OrgcbaXv7`L?m
diTxmgF)cqIXg*Ut<5C88MlPV0Tnr3A5&#Z7CfNW0

literal 0
HcmV?d00001

diff --git a/llvm/test/tools/llvm-profdata/Inputs/composite-unknown-nested.profdata b/llvm/test/tools/llvm-profdata/Inputs/composite-unknown-nested.profdata
new file mode 100644
index 0000000000000000000000000000000000000000..15d8b43b0e9c667ed9330e8ebd91510fc67b3d98
GIT binary patch
literal 363
zcmZp9a$)0_lT%g%XR<Ru03(!!QJ<iUP$<m=lYr9hP!0T0J{y{R7*rl+F3cX7vJ9vM
zGgLqkCIO|xq4I)IK1>}<8B9Nn&kAEe>1>$AaB*e^Mu7=_3=9i;fo?r8i-&>X!Ezx6
zhK8*YK(Rw|K(X^GK(RYoK(SW_K(X&;3=ALsS~4(f*k;4PaN`e<xYKOQz_6_aNbCm@
gOv#BkIjN}(V5*3LfswJEnOPJ_0htU;+#mr404wY%WB>pF

literal 0
HcmV?d00001

diff --git a/llvm/test/tools/llvm-profdata/Inputs/composite-unknown-oob.profdata b/llvm/test/tools/llvm-profdata/Inputs/composite-unknown-oob.profdata
new file mode 100644
index 0000000000000000000000000000000000000000..949fa2b72386d67b5d4f6a2641d22957a6399851
GIT binary patch
literal 386
zcmZp9a$)0_lT%g%XR<Ru03(!!QJ<iUP$<m=lYr9hPz}ORJ{y{R6jUB&F3cX7vRbGF
zD^x%cCIO|Rq4G*lJ~Ns;Og~H*W)6(s1hW_>z`)MHBrw5`fnh-}(5(k%@h~tvST4lC
z(6ChkD0WB=D0W^2D0W8+DE7(#DE8fqf#JhnO9qAw+iVyZZu|ifcbaV(7`C+liTxmg
xIXN*WCpDD;OcgPd<QFh7FfrEuhXF=*pka)R49v`;Ac}#Jfr%Taf{B5V0|1WdG*kcp

literal 0
HcmV?d00001

diff --git a/llvm/test/tools/llvm-profdata/Inputs/composite-unknown-top.profdata b/llvm/test/tools/llvm-profdata/Inputs/composite-unknown-top.profdata
new file mode 100644
index 0000000000000000000000000000000000000000..741299dbfa0eaff4b13709e6e5e8820aba34f97f
GIT binary patch
literal 377
zcmZp9a$)0_lT%g%XR<Ru03(!!QJ<iUP$<m=lYr9hPz}ORJ{y{R6jUB&F3cX7vJ$8S
zD^x%cCIO|Rq4JVYJ~Ns;Og~H*W)6&B39}d`z`)MHBrw5`fnh-}(5(k%@h~tvST4lC
z(6ChkD0WB=D0W^2D0W8+DE7(#DE8fqf#JhnO9qAw+iVyZZu|ifcbaV(7`C+liTxmg
uIXN*WCpDD;OcgPd<QFh7FfrCMvjfdwWMp7w76nlZj0{ZNKnW%WMh*bc<|{h@

literal 0
HcmV?d00001

diff --git a/llvm/test/tools/llvm-profdata/Inputs/composite-version-103.profdata b/llvm/test/tools/llvm-profdata/Inputs/composite-version-103.profdata
new file mode 100644
index 0000000000000000000000000000000000000000..6b58b7d136b56603eae9b8826760b88e436a632f
GIT binary patch
literal 377
zcmZp9a$)0_lT%g%r?WFa03(!!QJ<iUP$<m=lYr9hPz}ORJ{y{R6jUB&F3cX7vJ$8S
zD^x%cCIO|Rq4JVYJ~Ns;Og~H*W)6&B39}d`z`)MHBrw5`fnh-}(5(k%@h~tvST4lC
z(6ChkD0WB=D0W^2D0W8+DE7(#DE8fqf#JhnO9qAw+iVyZZu|ifcbaV(7`C+liTxmg
tIXN*WCpDD;OcgPd<QFh7FflSPvjfdw1k%i+Ac}#Jfr%R^!NkDG0RX`wD!Kpw

literal 0
HcmV?d00001

diff --git a/llvm/test/tools/llvm-profdata/Inputs/composite-version-104.profdata b/llvm/test/tools/llvm-profdata/Inputs/composite-version-104.profdata
new file mode 100644
index 0000000000000000000000000000000000000000..b1931b588404e2a65e539fb875cb8d03348a7c36
GIT binary patch
literal 377
zcmZp9a$)0_lT%g%XRtFs03(!!QJ<iUP$<m=lYr9hPz}ORJ{y{R6jUB&F3cX7vJ$8S
zD^x%cCIO|Rq4JVYJ~Ns;Og~H*W)6&B39}d`z`)MHBrw5`fnh-}(5(k%@h~tvST4lC
z(6ChkD0WB=D0W^2D0W8+DE7(#DE8fqf#JhnO9qAw+iVyZZu|ifcbaV(7`C+liTxmg
tIXN*WCpDD;OcgPd<QFh7FflSPvjfdw1k%i+Ac}#Jfr%R^!NkDG0RY01D!Tvx

literal 0
HcmV?d00001

diff --git a/llvm/test/tools/llvm-profdata/Inputs/modify-composite-profile.py b/llvm/test/tools/llvm-profdata/Inputs/modify-composite-profile.py
deleted file mode 100644
index 54305d3822520..0000000000000
--- a/llvm/test/tools/llvm-profdata/Inputs/modify-composite-profile.py
+++ /dev/null
@@ -1,239 +0,0 @@
-# Transform ExtBinary composite profiles into valid structural variants and
-# intentionally malformed inputs that cannot be produced through writer APIs.
-
-import argparse
-import struct
-
-COMPOSITE_PROFILE_SECTION = 33
-COMPOSITE_PROFILE_VERSION = 105
-LEGACY_PROFILE_SECTION = 32
-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
-
-
-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=(
-        "undersized",
-        "oversized",
-        "unknown",
-        "unknown-out-of-bounds",
-        "prepended-unknown",
-        "duplicate-type",
-        "empty-section",
-        "prepend-empty-composite",
-        "prepend-composite",
-    ),
-)
-args = parser.parse_args()
-
-with open(args.input, "rb") as input_file:
-    data = bytearray(input_file.read())
-
-section_count_offset, sections = read_sections(data)
-section_count = len(sections)
-profile_offset = None
-profile_size = None
-profile_header_offset = None
-for section_header_offset, section_type, section_offset, section_size in sections:
-    if section_type == COMPOSITE_PROFILE_SECTION:
-        assert profile_offset is None, "expected exactly one composite profile section"
-        profile_offset = section_offset
-        profile_size = section_size
-        profile_header_offset = section_header_offset
-
-if args.modification in ("prepend-empty-composite", "prepend-composite"):
-    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
-        )
-
-    composite_payload = b""
-    composite_offset = legacy_offset + header_size
-    if args.modification == "prepend-composite":
-        assert args.donor, "expected --donor for a nonempty composite 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] == COMPOSITE_PROFILE_SECTION and section[3] != 0
-        ]
-        assert len(donor_profiles) == 1, "expected one nonempty donor section"
-        _, _, donor_offset, donor_size = donor_profiles[0]
-        composite_payload = donor_data[donor_offset : donor_offset + donor_size]
-        composite_offset = len(data) + header_size
-
-    # Place the composite section immediately before a valid legacy one to
-    # verify that its decoding mode does not leak into the following section.
-    composite_header = struct.pack(
-        "<QQQQ",
-        COMPOSITE_PROFILE_SECTION,
-        0,
-        composite_offset,
-        len(composite_payload),
-    )
-    data[legacy_header_offset:legacy_header_offset] = composite_header
-    data.extend(composite_payload)
-    struct.pack_into("<Q", data, section_count_offset, section_count + 1)
-
-    # Associate the newly inserted composite section with its required file
-    # version while preserving the surrounding legacy sections.
-    _, version_offset = read_uleb(data, 0)
-    _, version_end = read_uleb(data, version_offset)
-    encoded_version = encode_uleb(COMPOSITE_PROFILE_VERSION)
-    assert len(encoded_version) == version_end - version_offset
-    data[version_offset:version_end] = encoded_version
-
-    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
-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 composite 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))
-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:
-        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)
-
-write_output(data, args.output)
diff --git a/llvm/test/tools/llvm-profdata/composite-mixed-sections.test b/llvm/test/tools/llvm-profdata/composite-mixed-sections.test
index 35317e02fb985..c191834d2da47 100644
--- a/llvm/test/tools/llvm-profdata/composite-mixed-sections.test
+++ b/llvm/test/tools/llvm-profdata/composite-mixed-sections.test
@@ -1,22 +1,29 @@
-; Verify that an empty composite section is skipped without preventing a
+; Both fixtures were written from sample-composite-empty-lbr.proftext. The
+; first has an empty composite section before its legacy LBR section; the
+; second has an equivalent nonempty composite section before the legacy data.
+
+; Verify that an empty composite section is skipped without preventing the
 ; following legacy profile section from being read.
-; RUN: llvm-profdata merge --sample --sample-profile-format-version=105 --extbinary %S/Inputs/sample-composite-empty-lbr.proftext -o %t.legacy
-; RUN: %python %S/Inputs/modify-composite-profile.py %t.legacy %t.empty-mixed prepend-empty-composite
-; RUN: llvm-profdata merge --sample --text %t.empty-mixed -o - | FileCheck %s --check-prefix=PROFILE
+; RUN: llvm-profdata merge --sample --text \
+; RUN:   %S/Inputs/composite-mixed-empty.profdata -o - \
+; RUN:   | FileCheck %s --check-prefix=PROFILE
 
-; Verify that the temporary decoding mode is also restored after reading a
-; nonempty composite section. Both sections contain the same profiles, so every
-; counter is expected to be accumulated exactly twice.
-; RUN: llvm-profdata merge --sample --extbinary-composite-prof --sample-profile-format-version=105 --extbinary %S/Inputs/sample-composite-empty-lbr.proftext -o %t.composite
-; RUN: %python %S/Inputs/modify-composite-profile.py %t.legacy %t.nonempty-mixed prepend-composite --donor %t.composite
-; RUN: llvm-profdata merge --sample --text %t.nonempty-mixed -o - | FileCheck %s --check-prefix=ACCUMULATED
+; Verify that decoding mode is restored after a nonempty composite section.
+; Both sections contain the same profiles, so every counter is accumulated
+; exactly twice.
+; RUN: llvm-profdata merge --sample --text \
+; RUN:   %S/Inputs/composite-mixed-nonempty.profdata -o - \
+; RUN:   | 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
+; RUN: llvm-profdata show --sample --show-sec-info-only \
+; RUN:   %S/Inputs/composite-mixed-empty.profdata \
+; RUN:   | FileCheck %s --check-prefix=SECTIONS
 
 ; Verify that an empty composite section still makes composite inspection
-; applicable, without producing the legacy-profile warning.
-; RUN: llvm-profdata show --sample --show-composite-info-only %t.empty-mixed 2>&1 | count 0
+; applicable without producing the legacy-profile warning.
+; RUN: llvm-profdata show --sample --show-composite-info-only \
+; RUN:   %S/Inputs/composite-mixed-empty.profdata 2>&1 | count 0
 
 ; PROFILE: top:42:7
 ; PROFILE-NEXT: caller:21:3
diff --git a/llvm/test/tools/llvm-profdata/composite-multiple-blocks.test b/llvm/test/tools/llvm-profdata/composite-multiple-blocks.test
index 18e3b6726f3ff..f259ff63e7f9f 100644
--- a/llvm/test/tools/llvm-profdata/composite-multiple-blocks.test
+++ b/llvm/test/tools/llvm-profdata/composite-multiple-blocks.test
@@ -1,21 +1,33 @@
-; 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-composite-prof --sample-profile-format-version=105 --extbinary %S/Inputs/composite-multiple-blocks.proftext -o %t.single
-; RUN: %python %S/Inputs/modify-composite-profile.py %t.single %t.multiple prepended-unknown
-; RUN: llvm-profdata show --sample --show-composite-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
+; The fixture was written as a v105 composite ExtBinary profile for foo, then
+; edited once to prepend a one-byte unknown block before its LBR block.
 
-; 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
+; Verify that inspection reports both blocks and that skipping the unknown
+; block preserves the following LBR payload.
+; RUN: llvm-profdata show --sample --show-composite-info-only \
+; RUN:   %S/Inputs/composite-prepended-unknown.profdata \
+; RUN:   | FileCheck %s --check-prefix=BLOCKS
+; RUN: llvm-profdata merge --sample --text \
+; RUN:   %S/Inputs/composite-prepended-unknown.profdata -o - \
+; RUN:   | FileCheck %s --check-prefix=PROFILE --match-full-lines \
+; RUN:   --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]*}}
+; Verify that re-emission reports the unknown block that cannot be preserved
+; while retaining the known LBR payload.
+; RUN: llvm-profdata merge --sample --extbinary \
+; RUN:   %S/Inputs/composite-prepended-unknown.profdata -o %t.reemitted 2>&1 \
+; RUN:   | FileCheck %s --check-prefix=WARNING
+; RUN: llvm-profdata merge --sample --text %t.reemitted -o - \
+; RUN:   | FileCheck %s --check-prefix=PROFILE --match-full-lines \
+; RUN:   --strict-whitespace
 
-; WARNING: warning: {{.*}}: unknown composite profile blocks were ignored and will not be preserved
+; BLOCKS: Function: foo
+; BLOCKS-NEXT:   Profile blocks: 2
+; BLOCKS-NEXT:   Type: 127 (unknown), Payload size: 1
+; BLOCKS-NEXT:   Type: 0 (LBR), Payload size: {{[1-9][0-9]*}}
+
+; WARNING: warning: {{.*}}:
+; WARNING-SAME: unknown composite profile blocks were ignored
+; WARNING-SAME: and will not be preserved
 
 ; PROFILE:foo:10:1
 ; PROFILE-NEXT: 0: 10
diff --git a/llvm/test/tools/llvm-profdata/composite-payload-boundaries.test b/llvm/test/tools/llvm-profdata/composite-payload-boundaries.test
index 62f9235c79cf2..fcf4cf2b64976 100644
--- a/llvm/test/tools/llvm-profdata/composite-payload-boundaries.test
+++ b/llvm/test/tools/llvm-profdata/composite-payload-boundaries.test
@@ -1,24 +1,34 @@
-; Verify that composite payload readers cannot cross declared block boundaries
-; and that inspection still reports an invalid unknown block size.
-; RUN: llvm-profdata merge --sample --extbinary-composite-prof --sample-profile-format-version=105 --extbinary %S/Inputs/sample-composite-empty-lbr.proftext -o %t.valid
+; These fixtures were written as v105 composite ExtBinary profiles from
+; sample-composite-empty-lbr.proftext, then edited once to encode the named
+; malformed block shape. They remain binary because no writer API can produce
+; these invalid payloads.
 
-; RUN: %python %S/Inputs/modify-composite-profile.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/modify-composite-profile.py %t.valid %t.oversized oversized
-; RUN: not llvm-profdata merge --sample %t.oversized -o /dev/null 2>&1 | FileCheck %s --check-prefix=UNCONSUMED
+; Verify that composite payload readers cannot cross declared block boundaries.
+; RUN: not llvm-profdata merge --sample \
+; RUN:   %S/Inputs/composite-payload-undersized.profdata -o %t.undersized \
+; RUN:   2>&1 | FileCheck %s --check-prefix=TRUNCATED
+; RUN: not llvm-profdata merge --sample \
+; RUN:   %S/Inputs/composite-payload-oversized.profdata -o %t.oversized \
+; RUN:   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/modify-composite-profile.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: not llvm-profdata merge --sample \
+; RUN:   %S/Inputs/composite-duplicate-type.profdata -o %t.duplicate \
+; RUN:   2>&1 | FileCheck %s --check-prefix=DUPLICATE
 
-; RUN: %python %S/Inputs/modify-composite-profile.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=DECLARED-SIZE
-; RUN: not llvm-profdata show --sample --show-composite-info-only %t.unknown-out-of-bounds 2>&1 | FileCheck %s --check-prefix=INSPECT
+; Verify that an unknown block cannot declare a payload beyond its section and
+; that inspection still reports the invalid declared size.
+; RUN: not llvm-profdata merge --sample \
+; RUN:   %S/Inputs/composite-unknown-oob.profdata -o %t.unknown-oob \
+; RUN:   2>&1 | FileCheck %s --check-prefix=DECLARED-SIZE
+; RUN: not llvm-profdata show --sample --show-composite-info-only \
+; RUN:   %S/Inputs/composite-unknown-oob.profdata 2>&1 \
+; RUN:   | FileCheck %s --check-prefix=INSPECT
 
 ; TRUNCATED: error: {{.*}}Truncated profile data
-; UNCONSUMED: Profile type ID 0 did not consume its complete payload; unread bytes: 1
+; UNCONSUMED: Profile type ID 0 did not consume its complete payload;
+; UNCONSUMED-SAME: 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/composite-unknown-resume-nested.test b/llvm/test/tools/llvm-profdata/composite-unknown-resume-nested.test
new file mode 100644
index 0000000000000..92871b0846d64
--- /dev/null
+++ b/llvm/test/tools/llvm-profdata/composite-unknown-resume-nested.test
@@ -0,0 +1,21 @@
+; The fixture was written as a v105 composite ExtBinary caller with a nested
+; callee, then the caller's LBR block type was changed to 127.
+
+; Verify that skipping the caller's unknown block resumes at its nested
+; callsite records instead of stopping the function decode.
+; RUN: llvm-profdata show --sample --show-composite-info-only \
+; RUN:   %S/Inputs/composite-unknown-nested.profdata \
+; RUN:   | FileCheck %s --check-prefix=BLOCKS
+; RUN: llvm-profdata merge --sample --text \
+; RUN:   %S/Inputs/composite-unknown-nested.profdata -o - \
+; RUN:   | FileCheck %s --check-prefix=PROFILE
+
+; BLOCKS: Function: caller
+; BLOCKS-NEXT:   Profile blocks: 1
+; BLOCKS-NEXT:   Type: 127 (unknown), Payload size: {{[1-9][0-9]*}}
+; BLOCKS: Nested function: callee
+; BLOCKS-NEXT:   Profile blocks: 1
+; BLOCKS-NEXT:   Type: 0 (LBR), Payload size: {{[1-9][0-9]*}}
+
+; PROFILE: caller:0:0
+; PROFILE-NEXT:  1: callee:11
diff --git a/llvm/test/tools/llvm-profdata/composite-version-reader.test b/llvm/test/tools/llvm-profdata/composite-version-reader.test
new file mode 100644
index 0000000000000..07c59b2fd3b98
--- /dev/null
+++ b/llvm/test/tools/llvm-profdata/composite-version-reader.test
@@ -0,0 +1,12 @@
+; These fixtures were written as v105 composite ExtBinary profiles, then only
+; their declared format versions were changed to 103 and 104. Composite
+; sections are not valid before version 105.
+
+; RUN: not llvm-profdata merge --sample \
+; RUN:   %S/Inputs/composite-version-103.profdata -o %t.v103 2>&1 \
+; RUN:   | FileCheck %s --check-prefix=VERSION
+; RUN: not llvm-profdata merge --sample \
+; RUN:   %S/Inputs/composite-version-104.profdata -o %t.v104 2>&1 \
+; RUN:   | FileCheck %s --check-prefix=VERSION
+
+; VERSION: Unsupported sample profile format version
diff --git a/llvm/test/tools/llvm-profdata/show-composite-profile-info.test b/llvm/test/tools/llvm-profdata/show-composite-profile-info.test
index ebf37e7eea4e1..ff23464a770b4 100644
--- a/llvm/test/tools/llvm-profdata/show-composite-profile-info.test
+++ b/llvm/test/tools/llvm-profdata/show-composite-profile-info.test
@@ -1,5 +1,5 @@
-; Verify composite inspection for known, nested, unknown, and empty blocks, and
-; verify that requesting it for a legacy profile produces a warning.
+; Verify composite inspection for known nested LBR, unknown, and empty blocks,
+; and verify that requesting it for a legacy profile produces a warning.
 ; RUN: llvm-profdata merge --sample --extbinary-composite-prof --sample-profile-format-version=105 --extbinary %S/Inputs/sample-flatten-profile.proftext -o %t.composite
 ; RUN: llvm-profdata show --sample --show-composite-info-only %t.composite | FileCheck %s --check-prefix=KNOWN
 
@@ -15,15 +15,33 @@
 
 ; BOTH: error: -show-sec-info-only and -show-composite-info-only cannot be used together
 
-; RUN: %python %S/Inputs/modify-composite-profile.py %t.composite %t.empty empty-section
-; RUN: llvm-profdata show --sample --show-composite-info-only %t.empty 2>&1 | count 0
+; The unknown fixture was written from sample-composite-empty-lbr.proftext,
+; then the first function's LBR block type was changed to 127.
+; RUN: llvm-profdata show --sample --show-composite-info-only \
+; RUN:   %S/Inputs/composite-unknown-top.profdata \
+; RUN:   | FileCheck %s --check-prefix=UNKNOWN
+; RUN: llvm-profdata merge --sample --text \
+; RUN:   %S/Inputs/composite-unknown-top.profdata -o - \
+; RUN:   | FileCheck %s --check-prefix=UNKNOWN-PROFILE
 
-; RUN: %python %S/Inputs/modify-composite-profile.py %t.composite %t.unknown unknown
-; RUN: llvm-profdata show --sample --show-composite-info-only %t.unknown | FileCheck %s --check-prefix=UNKNOWN
-
-; UNKNOWN: Function: baz
+; UNKNOWN: Function: top
 ; UNKNOWN-NEXT:   Profile blocks: 1
 ; UNKNOWN-NEXT:   Type: 127 (unknown), Payload size: {{[1-9][0-9]*}}
+; UNKNOWN-NEXT: Function: caller
+; UNKNOWN-NEXT:   Profile blocks: 1
+; UNKNOWN-NEXT:   Type: 0 (LBR), Payload size: {{[1-9][0-9]*}}
+; UNKNOWN-NEXT: Nested function: callee
+; UNKNOWN-NEXT:   Profile blocks: 1
+; UNKNOWN-NEXT:   Type: 0 (LBR), Payload size: {{[1-9][0-9]*}}
+
+; UNKNOWN-PROFILE: caller:21:3
+; UNKNOWN-PROFILE-NEXT:  1: callee:11
+; UNKNOWN-PROFILE-NEXT: top:0:0
+
+; The empty fixture was written from the same profile, then its composite
+; payload was removed and the section size was set to zero.
+; RUN: llvm-profdata show --sample --show-composite-info-only \
+; RUN:   %S/Inputs/composite-empty-section.profdata 2>&1 | count 0
 
 ; RUN: llvm-profdata merge --sample --extbinary %S/Inputs/sample-flatten-profile.proftext -o %t.legacy
 ; RUN: llvm-profdata show --sample --show-composite-info-only %t.legacy 2>&1 | FileCheck %s --check-prefix=LEGACY
diff --git a/llvm/unittests/ProfileData/SampleProfTest.cpp b/llvm/unittests/ProfileData/SampleProfTest.cpp
index 9767bf5eccc7d..2e8703e771cad 100644
--- a/llvm/unittests/ProfileData/SampleProfTest.cpp
+++ b/llvm/unittests/ProfileData/SampleProfTest.cpp
@@ -7,6 +7,7 @@
 //===----------------------------------------------------------------------===//
 
 #include "llvm/ProfileData/SampleProf.h"
+#include "llvm/ADT/ArrayRef.h"
 #include "llvm/ADT/StringMap.h"
 #include "llvm/ADT/StringRef.h"
 #include "llvm/IR/DebugInfoMetadata.h"
@@ -26,6 +27,7 @@
 #include "llvm/Support/raw_ostream.h"
 #include "llvm/Testing/Support/SupportHelpers.h"
 #include "gtest/gtest.h"
+#include <limits>
 #include <optional>
 #include <string>
 #include <vector>
@@ -44,32 +46,43 @@ static ::testing::AssertionResult NoError(std::error_code EC) {
 
 namespace {
 
-/// Temporarily control composite ExtBinary writing and restore the command-line
-/// option when the test scope ends.
+/// Temporarily control composite ExtBinary writing and restore the previous
+/// command-line option value when the test scope ends.
 class ScopedCompositeProfile {
-public:
-  /// Select the requested option value for the lifetime of this scope.
-  explicit ScopedCompositeProfile(bool Enabled) { set(Enabled); }
-
-  /// Restore the default disabled state when leaving the test scope.
-  ~ScopedCompositeProfile() { set(false); }
+  /// Option value that was in effect when this scope was entered.
+  bool Previous = false;
 
-  /// Select whether ExtBinary writes use composite profile sections through the
-  /// same type-checked command-line parser used by the production tool.
-  void set(bool Enabled) {
+  /// Return the registered composite-output option.
+  static cl::opt<bool> &option() {
     constexpr StringLiteral OptionName = "extbinary-composite-prof";
     auto &Options = cl::getRegisteredOptions();
     auto OptionIt = Options.find(OptionName);
     if (OptionIt == Options.end())
       report_fatal_error("composite profile option is not registered");
+    return *static_cast<cl::opt<bool> *>(OptionIt->second);
+  }
 
-    // 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"))
+  /// Select the option through the same type-checked parser used by the tool.
+  static void apply(bool Enabled) {
+    constexpr StringLiteral OptionName = "extbinary-composite-prof";
+    cl::opt<bool> &Option = option();
+    Option.reset();
+    if (Option.addOccurrence(0, OptionName, Enabled ? "true" : "false"))
       report_fatal_error("failed to set composite profile option");
   }
+
+public:
+  /// Select the requested option value for the lifetime of this scope.
+  explicit ScopedCompositeProfile(bool Enabled) : Previous(option()) {
+    apply(Enabled);
+  }
+
+  /// Restore the option value that was in effect before this scope.
+  ~ScopedCompositeProfile() { apply(Previous); }
+
+  /// Change the option for the remainder of this scope without updating the
+  /// value restored on destruction.
+  void set(bool Enabled) { apply(Enabled); }
 };
 
 struct SampleProfTest : ::testing::Test {
@@ -206,14 +219,6 @@ struct SampleProfTest : ::testing::Test {
     return writeProfileToBuffer(Profiles, Version);
   }
 
-  // Replace the single-byte ExtBinary version in a complete profile.
-  void overwriteProfileVersion(MutableArrayRef<char> Buffer, uint8_t Version) {
-    unsigned MagicSize = 0;
-    decodeULEB128(reinterpret_cast<const uint8_t *>(Buffer.data()), &MagicSize);
-    ASSERT_LT(MagicSize, Buffer.size());
-    Buffer[MagicSize] = static_cast<char>(Version);
-  }
-
   // Write a raw profile header (Magic + Version) directly to a buffer.
   // This bypasses the writer validation and is used to test reader error
   // handling.
@@ -246,6 +251,21 @@ struct SampleProfTest : ::testing::Test {
     return Reader->getFormatVersion();
   }
 
+  /// Create an ExtBinary reader over a complete in-memory profile.
+  ErrorOr<std::unique_ptr<SampleProfileReader>>
+  createReaderFromBuffer(ArrayRef<char> Buffer) {
+    std::unique_ptr<MemoryBuffer> MemBuffer = MemoryBuffer::getMemBufferCopy(
+        StringRef(Buffer.data(), Buffer.size()), "profile");
+    auto FS = vfs::getRealFileSystem();
+    return SampleProfileReader::create(MemBuffer, Context, *FS);
+  }
+
+  /// Write the default one-function profile using composite ExtBinary sections.
+  ErrorOr<SmallVector<char, 128>> writeCompositeProfileToBuffer() {
+    ScopedCompositeProfile Composite(true);
+    return writeProfileToBuffer();
+  }
+
   void testRoundTrip(SampleProfileFormat Format, bool Remap, bool UseMD5,
                      bool UseMD5ProfSymList = false,
                      bool UseMD5IndexedTables = false) {
@@ -814,6 +834,20 @@ TEST_F(SampleProfTest, SampleProfileFormatVersion106) {
   EXPECT_EQ(ReadVersionOrErr.getError(), sampleprof_error::unsupported_version);
 }
 
+// Verify that a nested composite-writing helper restores the outer option
+// instead of forcing composite output back off.
+TEST_F(SampleProfTest, CompositeWriterScopeRestoresPreviousValue) {
+  ScopedCompositeProfile Outer(true);
+  auto InnerOrErr = writeCompositeProfileToBuffer();
+  ASSERT_TRUE(NoError(InnerOrErr.getError()));
+
+  auto OuterOrErr = writeProfileToBuffer();
+  ASSERT_TRUE(NoError(OuterOrErr.getError()));
+  auto ReaderOrErr = createReaderFromBuffer(*OuterOrErr);
+  ASSERT_TRUE(NoError(ReaderOrErr.getError()));
+  EXPECT_TRUE(ReaderOrErr.get()->hasCompositeProfileSection());
+}
+
 // Verify that the default composite writer selects the first compatible
 // ExtBinary version.
 TEST_F(SampleProfTest, CompositeProfileUsesVersion105) {
@@ -837,22 +871,6 @@ TEST_F(SampleProfTest, CompositeProfileRejectsProgrammaticLegacyVersions) {
   }
 }
 
-// Verify that readers reject composite sections whose header claims a legacy
-// ExtBinary version.
-TEST_F(SampleProfTest, CompositeProfileRejectsLegacyFileVersions) {
-  [[maybe_unused]] ScopedCompositeProfile Composite(true);
-  auto BufferOrErr = writeProfileToBuffer();
-  ASSERT_TRUE(NoError(BufferOrErr.getError()));
-  auto Buffer = std::move(*BufferOrErr);
-
-  for (uint8_t Version : {uint8_t(103), uint8_t(104)}) {
-    overwriteProfileVersion(Buffer, Version);
-    auto ReadVersionOrErr = readVersionFromBuffer(Buffer);
-    EXPECT_EQ(ReadVersionOrErr.getError(),
-              sampleprof_error::unsupported_version);
-  }
-}
-
 TEST_F(SampleProfTest, ProfileSymbolListMD5) {
   std::vector<uint64_t> Keys = {FunctionId("foo").getHashCode(),
                                 FunctionId("bar").getHashCode()};



More information about the llvm-commits mailing list