[llvm] [ProfileData] Preserve cross-image targets in concatenated raw profiles (PR #212715)

via llvm-commits llvm-commits at lists.llvm.org
Sun Aug 2 05:53:19 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-pgo

Author: Karim Alweheshy (karim-alweheshy)

<details>
<summary>Changes</summary>

## Summary

- Build one raw-profile symbol table from every concatenated header before deserializing value records.
- Preserve indirect-call and vtable targets whose addresses belong to another image in the same raw profile buffer.
- Keep the header pre-scan lightweight by reading value-record sizes without allocating or copying their payloads.

## Problem

An instrumented process can write one `.profraw` buffer containing concatenated profiles from multiple images. `RawInstrProfReader` currently creates or replaces the symbol table as each header is entered. Records from image A are deserialized before header B is entered, so a value-profile target whose address belongs to B cannot be translated into B's name hash. When header B is entered, mappings from A are replaced as well. Cross-image targets become external/zero and are lost during merge.

This prevents downstream ICP and WPD from seeing valid cross-image targets even though the relevant function or vtable address/name mappings are present in the same raw buffer.

## Fix

Pre-scan the concatenated headers, accumulating function and vtable address mappings into one symbol table. The scan advances through value data using validated `TotalSize` fields, restores the first header's iteration state, and then preserves normal record iteration for valid profiles. Binary IDs are collected once.

Malformed data in a later concatenated profile may now be diagnosed during the initial pre-scan rather than after records from earlier profiles have been returned.

No raw profile format change is required.

## Scope

This change covers concatenated raw profiles that carry inline profile data/name mappings. The existing limitation around externally correlating one raw-profile buffer containing multiple build IDs is unchanged.

## Testing

- Extended `raw-two-profiles.test`:
  - header A owns `foo`, an indirect-call target at `0x2000`, and a vtable target at `0x3008`;
  - header B alone maps `0x2000` to `bar` and maps `[0x3000, 0x3010)` to `_ZTV1A`;
  - the merged targets must be `bar` with count 7 and `_ZTV1A` with count 9;
  - a malformed value-profile `TotalSize` is rejected during the pre-scan.
- `ninja -C /tmp/llvm-pr-212715-build check-llvm-tools-llvm-profdata`: 102 discovered, 88 passed, 14 unsupported.
- `ProfileDataTests`: 255/255 passed.


---
Full diff: https://github.com/llvm/llvm-project/pull/212715.diff


3 Files Affected:

- (modified) llvm/include/llvm/ProfileData/InstrProfReader.h (+5-1) 
- (modified) llvm/lib/ProfileData/InstrProfReader.cpp (+78-15) 
- (modified) llvm/test/tools/llvm-profdata/raw-two-profiles.test (+32-8) 


``````````diff
diff --git a/llvm/include/llvm/ProfileData/InstrProfReader.h b/llvm/include/llvm/ProfileData/InstrProfReader.h
index 51b97e9f88ee3..776c3ab859840 100644
--- a/llvm/include/llvm/ProfileData/InstrProfReader.h
+++ b/llvm/include/llvm/ProfileData/InstrProfReader.h
@@ -448,8 +448,12 @@ class RawInstrProfReader : public InstrProfReader {
 
 private:
   Error createSymtab(InstrProfSymtab &Symtab);
+  Expected<const RawInstrProf::Header *>
+  getNextHeader(const char *CurrentPos) const;
+  Expected<const char *> getNextHeaderPosForCurrentHeader() const;
   Error readNextHeader(const char *CurrentPos);
-  Error readHeader(const RawInstrProf::Header &Header);
+  Error readHeader(const RawInstrProf::Header &Header,
+                   InstrProfSymtab *SymtabToPopulate, bool RecordBinaryIds);
 
   template <class IntT> IntT swap(IntT Int) const {
     return ShouldSwapBytes ? llvm::byteswap(Int) : Int;
diff --git a/llvm/lib/ProfileData/InstrProfReader.cpp b/llvm/lib/ProfileData/InstrProfReader.cpp
index 05770f83ed160..dbe0aec6c4938 100644
--- a/llvm/lib/ProfileData/InstrProfReader.cpp
+++ b/llvm/lib/ProfileData/InstrProfReader.cpp
@@ -514,21 +514,46 @@ Error RawInstrProfReader<IntPtrT>::readHeader() {
     return error(instrprof_error::bad_magic);
   if (DataBuffer->getBufferSize() < sizeof(RawInstrProf::Header))
     return error(instrprof_error::bad_header);
-  auto *Header = reinterpret_cast<const RawInstrProf::Header *>(
+  auto *FirstHeader = reinterpret_cast<const RawInstrProf::Header *>(
       DataBuffer->getBufferStart());
-  ShouldSwapBytes = Header->Magic != RawInstrProf::getMagic<IntPtrT>();
-  return readHeader(*Header);
+  ShouldSwapBytes = FirstHeader->Magic != RawInstrProf::getMagic<IntPtrT>();
+
+  BinaryIds.clear();
+  Symtab = std::make_unique<InstrProfSymtab>();
+
+  // A value-profile target recorded by one image can name a function or
+  // vtable in a later concatenated raw profile. Build the address-to-name
+  // mappings for the entire buffer before deserializing the first record.
+  const RawInstrProf::Header *Header = FirstHeader;
+  while (Header) {
+    if (Error E = readHeader(*Header, Symtab.get(), true))
+      return E;
+
+    Expected<const char *> NextPos = getNextHeaderPosForCurrentHeader();
+    if (Error E = NextPos.takeError())
+      return error(std::move(E));
+
+    Expected<const RawInstrProf::Header *> NextHeader = getNextHeader(*NextPos);
+    if (Error E = NextHeader.takeError())
+      return error(std::move(E));
+    Header = *NextHeader;
+  }
+
+  // Restore the first header's iteration state without adding its mappings or
+  // binary IDs a second time.
+  return readHeader(*FirstHeader, nullptr, false);
 }
 
 template <class IntPtrT>
-Error RawInstrProfReader<IntPtrT>::readNextHeader(const char *CurrentPos) {
+Expected<const RawInstrProf::Header *>
+RawInstrProfReader<IntPtrT>::getNextHeader(const char *CurrentPos) const {
   const char *End = DataBuffer->getBufferEnd();
   // Skip zero padding between profiles.
   while (CurrentPos != End && *CurrentPos == 0)
     ++CurrentPos;
   // If there's nothing left, we're done.
   if (CurrentPos == End)
-    return make_error<InstrProfError>(instrprof_error::eof);
+    return nullptr;
   // If there isn't enough space for another header, this is probably just
   // garbage at the end of the file.
   if (CurrentPos + sizeof(RawInstrProf::Header) > End)
@@ -543,9 +568,48 @@ Error RawInstrProfReader<IntPtrT>::readNextHeader(const char *CurrentPos) {
   if (Magic != swap(RawInstrProf::getMagic<IntPtrT>()))
     return make_error<InstrProfError>(instrprof_error::bad_magic);
 
-  // There's another profile to read, so we need to process the header.
-  auto *Header = reinterpret_cast<const RawInstrProf::Header *>(CurrentPos);
-  return readHeader(*Header);
+  return reinterpret_cast<const RawInstrProf::Header *>(CurrentPos);
+}
+
+template <class IntPtrT>
+Expected<const char *>
+RawInstrProfReader<IntPtrT>::getNextHeaderPosForCurrentHeader() const {
+  const uint8_t *CurrentPos = ValueDataStart;
+  const uint8_t *BufferEnd =
+      reinterpret_cast<const uint8_t *>(DataBuffer->getBufferEnd());
+  for (const RawInstrProf::ProfileData<IntPtrT> *I = Data; I != DataEnd; ++I) {
+    uint32_t NumValueKinds = 0;
+    for (uint32_t K = 0; K < IPVK_Last + 1; ++K)
+      NumValueKinds += I->NumValueSites[K] != 0;
+    if (!NumValueKinds)
+      continue;
+
+    if (BufferEnd - CurrentPos < static_cast<ptrdiff_t>(sizeof(ValueProfData)))
+      return make_error<InstrProfError>(instrprof_error::truncated);
+
+    uint32_t TotalSize =
+        support::endian::read32(CurrentPos, getDataEndianness());
+    if (TotalSize < sizeof(ValueProfData) || TotalSize % sizeof(uint64_t) != 0)
+      return make_error<InstrProfError>(
+          instrprof_error::malformed,
+          "invalid total size for value profile data");
+    if (TotalSize > static_cast<uint64_t>(BufferEnd - CurrentPos))
+      return make_error<InstrProfError>(instrprof_error::too_large);
+    CurrentPos += TotalSize;
+  }
+  return reinterpret_cast<const char *>(CurrentPos);
+}
+
+template <class IntPtrT>
+Error RawInstrProfReader<IntPtrT>::readNextHeader(const char *CurrentPos) {
+  Expected<const RawInstrProf::Header *> Header = getNextHeader(CurrentPos);
+  if (Error E = Header.takeError())
+    return E;
+  if (!*Header)
+    return make_error<InstrProfError>(instrprof_error::eof);
+
+  // The full-buffer symtab and binary ID list were built by readHeader().
+  return readHeader(**Header, nullptr, false);
 }
 
 template <class IntPtrT>
@@ -579,7 +643,8 @@ Error RawInstrProfReader<IntPtrT>::createSymtab(InstrProfSymtab &Symtab) {
 
 template <class IntPtrT>
 Error RawInstrProfReader<IntPtrT>::readHeader(
-    const RawInstrProf::Header &Header) {
+    const RawInstrProf::Header &Header, InstrProfSymtab *SymtabToPopulate,
+    bool RecordBinaryIds) {
   Version = swap(Header.Version);
   if (GET_VERSION(Version) != RawInstrProf::Version)
     return error(instrprof_error::raw_profile_version_mismatch,
@@ -599,7 +664,7 @@ Error RawInstrProfReader<IntPtrT>::readHeader(
   if (BinaryIdSize % sizeof(uint64_t) || BinaryIdEnd > BufferEnd)
     return error(instrprof_error::bad_header);
   ArrayRef<uint8_t> BinaryIdsBuffer(BinaryIdStart, BinaryIdSize);
-  if (!BinaryIdsBuffer.empty()) {
+  if (RecordBinaryIds && !BinaryIdsBuffer.empty()) {
     if (Error Err = readBinaryIdsInternal(*DataBuffer, BinaryIdsBuffer,
                                           BinaryIds, getDataEndianness()))
       return Err;
@@ -705,11 +770,9 @@ Error RawInstrProfReader<IntPtrT>::readHeader(
   UniformCountersEnd = UniformCountersStart + UniformCountersSectionSize;
   ValueDataStart = reinterpret_cast<const uint8_t *>(Start + ValueDataOffset);
 
-  std::unique_ptr<InstrProfSymtab> NewSymtab = std::make_unique<InstrProfSymtab>();
-  if (Error E = createSymtab(*NewSymtab))
-    return E;
-
-  Symtab = std::move(NewSymtab);
+  if (SymtabToPopulate)
+    if (Error E = createSymtab(*SymtabToPopulate))
+      return E;
   return success();
 }
 
diff --git a/llvm/test/tools/llvm-profdata/raw-two-profiles.test b/llvm/test/tools/llvm-profdata/raw-two-profiles.test
index 80fbba33e8e01..04d101c60d3f5 100644
--- a/llvm/test/tools/llvm-profdata/raw-two-profiles.test
+++ b/llvm/test/tools/llvm-profdata/raw-two-profiles.test
@@ -22,20 +22,29 @@ RUN: printf '\0\0\0\0\0\0\0\0' >> %t-foo.profraw
 RUN: printf '\0\0\4\0\2\0\0\0' >> %t-foo.profraw
 RUN: printf '\0\0\0\0\0\0\0\0' >> %t-foo.profraw
 RUN: printf '\0\0\0\0\0\0\0\0' >> %t-foo.profraw
-RUN: printf '\0\0\0\0\0\0\0\0' >> %t-foo.profraw
+RUN: printf '\2\0\0\0\0\0\0\0' >> %t-foo.profraw
 
 RUN: printf '\254\275\030\333\114\302\370\134' >> %t-foo.profraw
 RUN: printf '\1\0\0\0\0\0\0\0' >> %t-foo.profraw
 RUN: printf '\0\0\4\0\1\0\0\0' >> %t-foo.profraw
 RUN: printf '\0\0\0\0\0\0\0\0' >> %t-foo.profraw
 RUN: printf '\0\0\0\0\0\0\0\0' >> %t-foo.profraw
+RUN: printf '\0\20\0\0\0\0\0\0' >> %t-foo.profraw
 RUN: printf '\0\0\0\0\0\0\0\0' >> %t-foo.profraw
-RUN: printf '\0\0\0\0\0\0\0\0' >> %t-foo.profraw
+RUN: printf '\1\0\0\0\1\0\0\0' >> %t-foo.profraw
 RUN: printf '\1\0\0\0\0\0\0\0' >> %t-foo.profraw
-RUN: printf '\0\0\0\0\0\0\0\0' >> %t-foo.profraw
 
 RUN: printf '\023\0\0\0\0\0\0\0' >> %t-foo.profraw
 RUN: printf '\3\0foo\0\0\0' >> %t-foo.profraw
+RUN: printf '\110\0\0\0\2\0\0\0' >> %t-foo.profraw
+RUN: printf '\0\0\0\0\1\0\0\0' >> %t-foo.profraw
+RUN: printf '\1\0\0\0\0\0\0\0' >> %t-foo.profraw
+RUN: printf '\0\40\0\0\0\0\0\0' >> %t-foo.profraw
+RUN: printf '\7\0\0\0\0\0\0\0' >> %t-foo.profraw
+RUN: printf '\2\0\0\0\1\0\0\0' >> %t-foo.profraw
+RUN: printf '\1\0\0\0\0\0\0\0' >> %t-foo.profraw
+RUN: printf '\10\60\0\0\0\0\0\0' >> %t-foo.profraw
+RUN: printf '\11\0\0\0\0\0\0\0' >> %t-foo.profraw
 
 RUN: printf '\201rforpl\377' > %t-bar.profraw
 RUN: printf '\13\0\0\0\0\0\0\0' >> %t-bar.profraw
@@ -53,16 +62,16 @@ RUN: printf '\10\0\0\0\0\0\0\0' >> %t-bar.profraw
 RUN: printf '\0\0\6\0\1\0\0\0' >> %t-bar.profraw
 RUN: printf '\0\0\0\0\0\0\0\0' >> %t-bar.profraw
 RUN: printf '\0\0\6\0\2\0\0\0' >> %t-bar.profraw
-RUN: printf '\0\0\0\0\0\0\0\0' >> %t-bar.profraw
-RUN: printf '\0\0\0\0\0\0\0\0' >> %t-bar.profraw
-RUN: printf '\0\0\0\0\0\0\0\0' >> %t-bar.profraw
+RUN: printf '\1\0\0\0\0\0\0\0' >> %t-bar.profraw
+RUN: printf '\10\0\0\0\0\0\0\0' >> %t-bar.profraw
+RUN: printf '\2\0\0\0\0\0\0\0' >> %t-bar.profraw
 
 RUN: printf '\067\265\035\031\112\165\023\344' >> %t-bar.profraw
 RUN: printf '\02\0\0\0\0\0\0\0' >> %t-bar.profraw
 RUN: printf '\0\0\6\0\1\0\0\0' >> %t-bar.profraw
 RUN: printf '\0\0\0\0\0\0\0\0' >> %t-bar.profraw
 RUN: printf '\0\0\0\0\0\0\0\0' >> %t-bar.profraw
-RUN: printf '\0\0\0\0\0\0\0\0' >> %t-bar.profraw
+RUN: printf '\0\40\0\0\0\0\0\0' >> %t-bar.profraw
 RUN: printf '\0\0\0\0\0\0\0\0' >> %t-bar.profraw
 RUN: printf '\02\0\0\0\0\0\0\0' >> %t-bar.profraw
 RUN: printf '\0\0\0\0\0\0\0\0' >> %t-bar.profraw
@@ -70,16 +79,29 @@ RUN: printf '\0\0\0\0\0\0\0\0' >> %t-bar.profraw
 RUN: printf '\067\0\0\0\0\0\0\0' >> %t-bar.profraw
 RUN: printf '\101\0\0\0\0\0\0\0' >> %t-bar.profraw
 RUN: printf '\3\0bar\0\0\0' >> %t-bar.profraw
+RUN: printf '\320\115\170\140\375\107\0\250' >> %t-bar.profraw
+RUN: printf '\0\60\0\0\0\0\0\0' >> %t-bar.profraw
+RUN: printf '\20\0\0\0\0\0\0\0' >> %t-bar.profraw
+RUN: printf '\6\0_ZTV1A' >> %t-bar.profraw
 
 RUN: cat %t-foo.profraw %t-bar.profraw > %t-pad.profraw
-RUN: llvm-profdata show %t-pad.profraw -all-functions -counts | FileCheck %s
+RUN: llvm-profdata show %t-pad.profraw -all-functions -counts -ic-targets -show-vtables | FileCheck %s
+RUN: cp %t-pad.profraw %t-bad-value-size.profraw
+RUN: printf '\1\0\0\0' | dd of=%t-bad-value-size.profraw bs=1 seek=240 conv=notrunc 2>/dev/null
+RUN: not llvm-profdata show %t-bad-value-size.profraw 2>&1 | FileCheck %s --check-prefix=BAD-VALUE-SIZE
 
 CHECK: Counters:
 CHECK:   foo:
 CHECK:     Hash: 0x0000000000000001
 CHECK:     Counters: 1
 CHECK:     Function count: 19
+CHECK:     Indirect Call Site Count: 1
+CHECK:     Number of instrumented vtables: 1
 CHECK:     Block counts: []
+CHECK:     Indirect Target Results:
+CHECK:         [  0, bar,          7 ]
+CHECK:     VTable Results:
+CHECK:         [  0, _ZTV1A,          9 ]
 CHECK:   bar:
 CHECK:     Hash: 0x0000000000000002
 CHECK:     Counters: 2
@@ -89,3 +111,5 @@ CHECK: Functions shown: 2
 CHECK: Total functions: 2
 CHECK: Maximum function count: 55
 CHECK: Maximum internal block count: 65
+
+BAD-VALUE-SIZE: malformed instrumentation profile data: invalid total size for value profile data

``````````

</details>


https://github.com/llvm/llvm-project/pull/212715


More information about the llvm-commits mailing list