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

Karim Alweheshy via llvm-commits llvm-commits at lists.llvm.org
Wed Jul 29 01:49:21 PDT 2026


https://github.com/karim-alweheshy created https://github.com/llvm/llvm-project/pull/212715

## 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 a later 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. It becomes external/zero and is lost during merge.

This prevents downstream ICP and WPD from seeing valid cross-image targets even though both function 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 the existing record iteration behavior. Binary IDs are collected once.

No raw profile format change is required.

## Testing

- Extended `raw-two-profiles.test`: header A owns `foo` and an indirect-call target at `0x2000`; header B alone maps `0x2000` to `bar`; the merged target must be `bar` with count 7.
- `ninja -C build llvm-profdata`
- 14 raw, binary-ID, and malformed-profile `llvm-profdata` lit tests
- `ProfileDataTests`: 255/255 passed


>From f067813e41b8a6c42f713c6d9706075039579eca Mon Sep 17 00:00:00 2001
From: Karim Alweheshy <karim.alweheshy at reddit.com>
Date: Wed, 29 Jul 2026 10:45:23 +0200
Subject: [PATCH] [ProfileData] Preserve cross-image targets in concatenated
 raw profiles

Build the raw profile symbol table from every concatenated header before deserializing value records. This preserves indirect-call and vtable targets whose addresses belong to a later image in the same raw profile buffer.
---
 .../llvm/ProfileData/InstrProfReader.h        |  6 +-
 llvm/lib/ProfileData/InstrProfReader.cpp      | 94 ++++++++++++++++---
 .../tools/llvm-profdata/raw-two-profiles.test | 16 +++-
 3 files changed, 96 insertions(+), 20 deletions(-)

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..5625ef256fecb 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,49 @@ 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 +644,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 +665,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 +771,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..fddbf8fadf11b 100644
--- a/llvm/test/tools/llvm-profdata/raw-two-profiles.test
+++ b/llvm/test/tools/llvm-profdata/raw-two-profiles.test
@@ -29,13 +29,18 @@ 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\0\0\0\0' >> %t-foo.profraw
+RUN: printf '\1\0\0\0\1\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 '\050\0\0\0\1\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 '\201rforpl\377' > %t-bar.profraw
 RUN: printf '\13\0\0\0\0\0\0\0' >> %t-bar.profraw
@@ -62,7 +67,7 @@ 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
@@ -72,14 +77,17 @@ RUN: printf '\101\0\0\0\0\0\0\0' >> %t-bar.profraw
 RUN: printf '\3\0bar\0\0\0' >> %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 | FileCheck %s
 
 CHECK: Counters:
 CHECK:   foo:
 CHECK:     Hash: 0x0000000000000001
 CHECK:     Counters: 1
 CHECK:     Function count: 19
+CHECK:     Indirect Call Site Count: 1
 CHECK:     Block counts: []
+CHECK:     Indirect Target Results:
+CHECK:         [  0, bar,          7 ]
 CHECK:   bar:
 CHECK:     Hash: 0x0000000000000002
 CHECK:     Counters: 2



More information about the llvm-commits mailing list