[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