[llvm] [ProfileData] Preserve cross-image targets in concatenated raw profiles (PR #212715)
Karim Alweheshy via llvm-commits
llvm-commits at lists.llvm.org
Mon Aug 3 01:28:43 PDT 2026
https://github.com/karim-alweheshy updated https://github.com/llvm/llvm-project/pull/212715
>From 5b551eca1b83b380f4bb5caf8702fb9cef3642ba Mon Sep 17 00:00:00 2001
From: Karim Alweheshy <karim.alweheshy at gmail.com>
Date: Wed, 29 Jul 2026 10:45:23 +0200
Subject: [PATCH 1/2] [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 | 93 ++++++++++++++++---
.../tools/llvm-profdata/raw-two-profiles.test | 16 +++-
3 files changed, 95 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..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..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
>From 5ca2e05aa3cad0b7caa46ba516f72c2227a33db9 Mon Sep 17 00:00:00 2001
From: Karim Alweheshy <karim.alweheshy at gmail.com>
Date: Sun, 2 Aug 2026 14:50:38 +0200
Subject: [PATCH 2/2] [ProfileData] Test cross-image vtable targets and
malformed sizes
---
.../tools/llvm-profdata/raw-two-profiles.test | 30 ++++++++++++++-----
1 file changed, 23 insertions(+), 7 deletions(-)
diff --git a/llvm/test/tools/llvm-profdata/raw-two-profiles.test b/llvm/test/tools/llvm-profdata/raw-two-profiles.test
index fddbf8fadf11b..04d101c60d3f5 100644
--- a/llvm/test/tools/llvm-profdata/raw-two-profiles.test
+++ b/llvm/test/tools/llvm-profdata/raw-two-profiles.test
@@ -22,7 +22,7 @@ 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
@@ -32,15 +32,19 @@ 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 '\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 '\1\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 '\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
@@ -58,9 +62,9 @@ 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
@@ -75,9 +79,16 @@ 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 -ic-targets | 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:
@@ -85,9 +96,12 @@ 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
@@ -97,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
More information about the llvm-commits
mailing list