[llvm] 3c1311e - [ProfileData] Introduce SampleProfileFuncOffsetTable (NFC) (#205045)

via llvm-commits llvm-commits at lists.llvm.org
Sat Jun 27 05:27:00 PDT 2026


Author: Kazu Hirata
Date: 2026-06-27T05:26:55-07:00
New Revision: 3c1311e8f0985b6a399c92b6e4a4ebe7f07c4d8b

URL: https://github.com/llvm/llvm-project/commit/3c1311e8f0985b6a399c92b6e4a4ebe7f07c4d8b
DIFF: https://github.com/llvm/llvm-project/commit/3c1311e8f0985b6a399c92b6e4a4ebe7f07c4d8b.diff

LOG: [ProfileData] Introduce SampleProfileFuncOffsetTable (NFC) (#205045)

This patch introduces SampleProfileFuncOffsetTable, a unified wrapper
representing the function offset table.

Currently, the offset table is always a DenseMap.  To support the
upcoming on-disk hash table (v104) for faster sample profile loading,
this patch abstracts the offset table representation.  The new class
can delegate lookups to either the in-memory DenseMap or the on-disk
OnDiskIterableChainedHashTable.

This patch updates the reader to use the new wrapper's lookup and
insert interfaces.  Since the on-disk path is not yet active, this
change is a non-functional change (NFC).

RFC:
https://discourse.llvm.org/t/rfc-faster-sample-profile-loading/90957/4

Added: 
    

Modified: 
    llvm/include/llvm/ProfileData/SampleProfReader.h
    llvm/lib/ProfileData/SampleProfReader.cpp
    llvm/unittests/ProfileData/SampleProfTest.cpp

Removed: 
    


################################################################################
diff  --git a/llvm/include/llvm/ProfileData/SampleProfReader.h b/llvm/include/llvm/ProfileData/SampleProfReader.h
index e4655fb877d8a..251e6a8f4843a 100644
--- a/llvm/include/llvm/ProfileData/SampleProfReader.h
+++ b/llvm/include/llvm/ProfileData/SampleProfReader.h
@@ -908,6 +908,78 @@ class FuncOffsetHashTableInfo {
   }
 };
 
+/// Tags to select the initialization mode of SampleProfileFuncOffsetTable.
+struct InMemoryModeT {};
+struct OnDiskModeT {};
+inline constexpr InMemoryModeT InMemoryMode{};
+inline constexpr OnDiskModeT OnDiskMode{};
+
+/// A unified wrapper representing the function offset table.
+///
+/// This class abstracts away the physical representation of the offset table,
+/// which can either be:
+///
+/// - An llvm::DenseMap mapping function GUIDs (or context hashes) to their
+///   profile offsets, populated when reading the array of offsets in
+///   context-sensitive (CS) profiles or version 103 profiles.
+///
+/// - An OnDiskIterableChainedHashTable providing the same mapping directly from
+///   the file in (non-context-sensitive) version 104 profiles.
+///
+/// It exposes a single, type-agnostic lookup interface, shielding the reader
+/// from the underlying container types. To prevent hybrid-state corruption, the
+/// table's mode is locked at construction time, and assertions prevent
+/// modification in on-disk mode.
+class SampleProfileFuncOffsetTable {
+public:
+  using OnDiskTableType =
+      llvm::OnDiskIterableChainedHashTable<FuncOffsetHashTableInfo>;
+
+  explicit SampleProfileFuncOffsetTable(InMemoryModeT,
+                                        size_t InitialCapacity = 0) {
+    InMemoryTable.reserve(InitialCapacity);
+  }
+
+  /// Insert a function GUID and its profile offset into the in-memory map.
+  /// Enforces that the on-disk table must not have been set first.
+  void insert(uint64_t GUID, uint64_t Offset) {
+    assert(!OnDiskTable &&
+           "Cannot insert in-memory elements after on-disk table has been set");
+    InMemoryTable[GUID] = Offset;
+  }
+
+  /// Instantiate the on-disk chained hash table using raw stream pointers.
+  SampleProfileFuncOffsetTable(OnDiskModeT, const uint8_t *Buckets,
+                               const uint8_t *Payload, const uint8_t *Base) {
+    OnDiskTable.reset(OnDiskTableType::Create(Buckets, Payload, Base));
+  }
+
+  /// Query the offset table for the profile offset associated with the given
+  /// GUID. Returns the offset if found, or std::nullopt if the key is missing.
+  std::optional<uint64_t> lookup(uint64_t GUID) const {
+    if (OnDiskTable) {
+      auto Iter = OnDiskTable->find(GUID);
+      if (Iter != OnDiskTable->end())
+        return *Iter;
+    } else {
+      auto Iter = InMemoryTable.find(GUID);
+      if (Iter != InMemoryTable.end())
+        return Iter->second;
+    }
+    return std::nullopt;
+  }
+
+  /// Clear the in-memory map and release the on-disk table.
+  void clear() {
+    InMemoryTable.clear();
+    OnDiskTable.reset();
+  }
+
+private:
+  llvm::DenseMap<hash_code, uint64_t> InMemoryTable;
+  std::unique_ptr<OnDiskTableType> OnDiskTable;
+};
+
 /// SampleProfileReaderExtBinaryBase/SampleProfileWriterExtBinaryBase defines
 /// the basic structure of the extensible binary format.
 /// The format is organized in sections except the magic and version number
@@ -971,7 +1043,7 @@ class LLVM_ABI SampleProfileReaderExtBinaryBase
   /// The table mapping from a function context's MD5 to the offset of its
   /// FunctionSample towards file start.
   /// At most one of FuncOffsetTable and FuncOffsetList is populated.
-  DenseMap<hash_code, uint64_t> FuncOffsetTable;
+  std::optional<SampleProfileFuncOffsetTable> FuncOffsetTable;
 
   /// The list version of FuncOffsetTable. This is used if every entry is
   /// being accessed.
@@ -983,7 +1055,9 @@ class LLVM_ABI SampleProfileReaderExtBinaryBase
 public:
   SampleProfileReaderExtBinaryBase(std::unique_ptr<MemoryBuffer> B,
                                    LLVMContext &C, SampleProfileFormat Format)
-      : SampleProfileReaderBinary(std::move(B), C, Format) {}
+      : SampleProfileReaderBinary(std::move(B), C, Format) {
+    FuncOffsetTable.emplace(InMemoryMode);
+  }
 
   /// Read sample profiles in extensible format from the associated file.
   std::error_code readImpl() override;

diff  --git a/llvm/lib/ProfileData/SampleProfReader.cpp b/llvm/lib/ProfileData/SampleProfReader.cpp
index e5d28a75ce69c..14fdaf6581efe 100644
--- a/llvm/lib/ProfileData/SampleProfReader.cpp
+++ b/llvm/lib/ProfileData/SampleProfReader.cpp
@@ -984,7 +984,7 @@ bool SampleProfileReaderExtBinaryBase::collectFuncsFromModule() {
 std::error_code SampleProfileReaderExtBinaryBase::readFuncOffsetTable() {
   // If there are more than one function offset section, the profile associated
   // with the previous section has to be done reading before next one is read.
-  FuncOffsetTable.clear();
+  FuncOffsetTable.reset();
   FuncOffsetList.clear();
 
   auto Size = readNumber<uint64_t>();
@@ -995,7 +995,7 @@ std::error_code SampleProfileReaderExtBinaryBase::readFuncOffsetTable() {
   if (UseFuncOffsetList)
     FuncOffsetList.reserve(*Size);
   else
-    FuncOffsetTable.reserve(*Size);
+    FuncOffsetTable.emplace(InMemoryMode, *Size);
 
   for (uint64_t I = 0; I < *Size; ++I) {
     auto FContextHash(readSampleContextFromTable());
@@ -1012,7 +1012,7 @@ std::error_code SampleProfileReaderExtBinaryBase::readFuncOffsetTable() {
     else
       // Because Porfiles replace existing value with new value if collision
       // happens, we also use the latest offset so that they are consistent.
-      FuncOffsetTable[Hash] = *Offset;
+      FuncOffsetTable->insert(Hash, *Offset);
  }
 
  return sampleprof_error::success;
@@ -1074,12 +1074,11 @@ std::error_code SampleProfileReaderExtBinaryBase::readFuncProfiles(
     assert(!useFuncOffsetList());
     for (auto Name : FuncsToUse) {
       auto GUID = MD5Hash(Name);
-      auto iter = FuncOffsetTable.find(GUID);
-      if (iter == FuncOffsetTable.end())
-        continue;
-      const uint8_t *FuncProfileAddr = Start + iter->second;
-      if (std::error_code EC = readFuncProfile(FuncProfileAddr, Profiles))
-        return EC;
+      if (auto Offset = FuncOffsetTable->lookup(GUID)) {
+        const uint8_t *FuncProfileAddr = Start + *Offset;
+        if (std::error_code EC = readFuncProfile(FuncProfileAddr, Profiles))
+          return EC;
+      }
     }
   } else if (Remapper) {
     assert(useFuncOffsetList());
@@ -1096,13 +1095,11 @@ std::error_code SampleProfileReaderExtBinaryBase::readFuncProfiles(
   } else {
     assert(!useFuncOffsetList());
     for (auto Name : FuncsToUse) {
-
-      auto iter = FuncOffsetTable.find(MD5Hash(Name));
-      if (iter == FuncOffsetTable.end())
-        continue;
-      const uint8_t *FuncProfileAddr = Start + iter->second;
-      if (std::error_code EC = readFuncProfile(FuncProfileAddr, Profiles))
-        return EC;
+      if (auto Offset = FuncOffsetTable->lookup(MD5Hash(Name))) {
+        const uint8_t *FuncProfileAddr = Start + *Offset;
+        if (std::error_code EC = readFuncProfile(FuncProfileAddr, Profiles))
+          return EC;
+      }
     }
   }
 

diff  --git a/llvm/unittests/ProfileData/SampleProfTest.cpp b/llvm/unittests/ProfileData/SampleProfTest.cpp
index 00f5627683616..0158b584f8504 100644
--- a/llvm/unittests/ProfileData/SampleProfTest.cpp
+++ b/llvm/unittests/ProfileData/SampleProfTest.cpp
@@ -534,4 +534,66 @@ TEST_F(SampleProfTest, FuncOffsetHashTable) {
   ASSERT_TRUE(Iter == Table->end());
 }
 
+TEST_F(SampleProfTest, SampleProfileFuncOffsetTableInMemory) {
+  SampleProfileFuncOffsetTable Table(InMemoryMode, 2);
+
+  // Test empty table
+  EXPECT_EQ(Table.lookup(0x1111ULL), std::nullopt);
+
+  // Test insert and lookup
+  Table.insert(0x11112222ULL, 100);
+  Table.insert(0x33334444ULL, 200);
+
+  EXPECT_EQ(Table.lookup(0x11112222ULL), 100);
+  EXPECT_EQ(Table.lookup(0x33334444ULL), 200);
+  EXPECT_EQ(Table.lookup(0x55556666ULL), std::nullopt);
+
+  // Test clear
+  Table.clear();
+  EXPECT_EQ(Table.lookup(0x11112222ULL), std::nullopt);
+}
+
+TEST_F(SampleProfTest, SampleProfileFuncOffsetTableOnDisk) {
+  std::vector<std::pair<uint64_t, uint64_t>> TestData = {
+      {0x1111111122222222ULL, 100},
+      {0x3333333344444444ULL, 250},
+      {0x5555555566666666ULL, 1000},
+  };
+
+  SmallVector<char, 128> Buffer;
+  raw_svector_ostream OS(Buffer);
+
+  FuncOffsetHashTableWriterInfo WriterInfo;
+  OnDiskChainedHashTableGenerator<FuncOffsetHashTableWriterInfo> Generator;
+
+  for (const auto &[Name, Offset] : TestData)
+    Generator.insert(Name, Offset);
+
+  // Add padding to avoid bucket offset 0.
+  OS.write("PAD ", 4);
+  uint32_t BucketTableOffset = Generator.Emit(OS, WriterInfo);
+
+  const unsigned char *Start =
+      reinterpret_cast<const unsigned char *>(Buffer.data());
+
+  const unsigned char *Buckets = Start + BucketTableOffset;
+  const unsigned char *Payload = Start + 4;
+
+  SampleProfileFuncOffsetTable Table(OnDiskMode, Buckets, Payload, Start);
+
+  // Test lookup
+  for (const auto &[Name, Offset] : TestData) {
+    auto LookupResult = Table.lookup(Name);
+    ASSERT_TRUE(LookupResult.has_value());
+    EXPECT_EQ(*LookupResult, Offset);
+  }
+
+  // Test non-existent key
+  EXPECT_EQ(Table.lookup(0x9999999999999999ULL), std::nullopt);
+
+  // Test clear
+  Table.clear();
+  EXPECT_EQ(Table.lookup(0x1111111122222222ULL), std::nullopt);
+}
+
 } // end anonymous namespace


        


More information about the llvm-commits mailing list