https://github.com/cherleey created 
https://github.com/llvm/llvm-project/pull/211268

> **An honest note up front.** This contribution came out of a project where I 
> use AI agents working on top of llvm-project; the diagnosis, the patch, the 
> measurements, and this description were produced by AI. I do not understand 
> this change deeply enough — nor is my English good enough — to write the 
> description and comments by hand, as the reviewer asked. I could not do that 
> part, but I did split the PR in two as requested. I am sorry, and if 
> AI-written text is not acceptable here, I will follow the reviewers' decision.

Part 2 of 2 — **stacked on #211178**; the first commit here is that PR, please 
review only the second commit.

**Summary.** `PdbIndex::create` eagerly loads the TPI and IPI type streams — 
usually the largest streams of a big PDB — and builds the TPI hash map for 
every PDB it indexes. This PR loads them lazily on first `tpi()`/`ipi()` access 
(`llvm::call_once`), so symtab-only consumers (e.g. `AddSymbols` from the 
publics stream) never pay for type streams.

Stream *presence* is validated up front by the abilities check from #211178, so 
a load failure at access time means a corrupt stream in a PDB we already 
committed to; that path is a `report_fatal_error` (the pre-lazy behavior 
rejected such a PDB wholesale in `create()`). `PdbIndex.{cpp,h}` only, +49/−8.

**Numbers / Test.** Measured with both PRs applied together — see #211178; 
per-part numbers do not exist yet.

**AI tool use disclosure.** Assisted-by: Claude (Anthropic). Per the [LLVM AI 
Tool Use Policy](https://llvm.org/docs/AIToolPolicy.html) — with the honest 
caveat above about the limits of my own understanding.


>From bfac9baabc50f00dfca7b50167747255571fd492 Mon Sep 17 00:00:00 2001
From: cherleey <[email protected]>
Date: Wed, 22 Jul 2026 22:42:59 +0900
Subject: [PATCH 1/2] [lldb][NativePDB] Defer PdbIndex creation until the
 symbol file is queried

---
 .../NativePDB/SymbolFileNativePDB.cpp         | 93 ++++++++++++++++---
 .../NativePDB/SymbolFileNativePDB.h           | 14 ++-
 2 files changed, 92 insertions(+), 15 deletions(-)

diff --git a/lldb/source/Plugins/SymbolFile/NativePDB/SymbolFileNativePDB.cpp 
b/lldb/source/Plugins/SymbolFile/NativePDB/SymbolFileNativePDB.cpp
index ec6e89b10e776..214ba98d3446f 100644
--- a/lldb/source/Plugins/SymbolFile/NativePDB/SymbolFileNativePDB.cpp
+++ b/lldb/source/Plugins/SymbolFile/NativePDB/SymbolFileNativePDB.cpp
@@ -34,6 +34,7 @@
 #include "llvm/DebugInfo/CodeView/SymbolDeserializer.h"
 #include "llvm/DebugInfo/CodeView/SymbolRecordHelpers.h"
 #include "llvm/DebugInfo/CodeView/TypeDeserializer.h"
+#include "llvm/DebugInfo/MSF/MappedBlockStream.h"
 #include "llvm/DebugInfo/PDB/Native/DbiStream.h"
 #include "llvm/DebugInfo/PDB/Native/GlobalsStream.h"
 #include "llvm/DebugInfo/PDB/Native/InfoStream.h"
@@ -173,6 +174,34 @@ loadMatchingPDBFile(std::string exe_path, 
llvm::BumpPtrAllocator &allocator) {
   return pdb;
 }
 
+// Reads only the DBI stream header to answer isStripped(). Parsing the full
+// DBI stream (let alone PdbIndex::create, which also pulls in the type and
+// symbol-record streams) materializes most of a large PDB on the private
+// heap; abilities probing runs for every candidate module at attach/launch,
+// so it must stay O(header).
+static std::optional<bool> IsDbiStripped(llvm::pdb::PDBFile &pdb) {
+  using namespace llvm::pdb;
+  if (!pdb.hasPDBDbiStream())
+    return std::nullopt;
+  auto stream_or_err = pdb.safelyCreateIndexedStream(
+      static_cast<uint32_t>(SpecialStream::StreamDBI));
+  if (!stream_or_err) {
+    llvm::consumeError(stream_or_err.takeError());
+    return std::nullopt;
+  }
+  std::unique_ptr<llvm::msf::MappedBlockStream> stream =
+      std::move(*stream_or_err);
+  if (stream->getLength() < sizeof(DbiStreamHeader))
+    return std::nullopt;
+  llvm::BinaryStreamReader reader(*stream);
+  const DbiStreamHeader *header = nullptr;
+  if (auto ec = reader.readObject(header)) {
+    llvm::consumeError(std::move(ec));
+    return std::nullopt;
+  }
+  return (header->Flags & DbiFlags::FlagStrippedMask) != 0;
+}
+
 static bool IsFunctionPrologue(const CompilandIndexItem &cci,
                                lldb::addr_t addr) {
   // FIXME: Implement this.
@@ -383,7 +412,7 @@ uint32_t SymbolFileNativePDB::CalculateAbilities() {
   if (!m_objfile_sp)
     return 0;
 
-  if (!m_index) {
+  if (!m_pdb_file) {
     // Lazily load and match the PDB file, but only do this once.
     PDBFile *pdb_file;
     if (auto *pdb = llvm::dyn_cast<ObjectFilePDB>(m_objfile_sp.get())) {
@@ -402,26 +431,53 @@ uint32_t SymbolFileNativePDB::CalculateAbilities() {
         pdb_file->getFilePath(),
         m_objfile_sp->GetModule()->GetObjectFile()->GetFileSpec().GetPath());
 
-    auto expected_index = PdbIndex::create(pdb_file);
-    if (!expected_index) {
-      llvm::consumeError(expected_index.takeError());
-      return 0;
-    }
-    m_index = std::move(*expected_index);
+    m_pdb_file = pdb_file;
   }
-  if (!m_index)
-    return 0;
 
   // We don't especially have to be precise here.  We only distinguish between
-  // stripped and not stripped.
-  abilities = kAllAbilities;
+  // stripped and not stripped. Building the PdbIndex here would eagerly parse
+  // the type and symbol-record streams of every candidate module, so the
+  // stripped check reads just the DBI stream header instead — the index is
+  // built on demand in GetOrCreateIndex().
+  //
+  // PdbIndex::create requires the DBI/TPI/IPI streams; reject PDBs lacking
+  // them here so an unusable PDB does not win plugin selection only to fail
+  // when the index is materialized later.
+  if (!m_pdb_file->hasPDBTpiStream() || !m_pdb_file->hasPDBIpiStream())
+    return 0;
 
-  if (m_index->dbi().isStripped())
+  std::optional<bool> stripped = IsDbiStripped(*m_pdb_file);
+  if (!stripped)
+    return 0;
+
+  abilities = kAllAbilities;
+  if (*stripped)
     abilities &= ~(Blocks | LocalVariables);
   return abilities;
 }
 
+PdbIndex *SymbolFileNativePDB::GetOrCreateIndex() {
+  if (m_index)
+    return m_index.get();
+  if (!m_pdb_file)
+    return nullptr;
+
+  LLDB_LOG(GetLog(LLDBLog::Symbols), "Building PDB index for {0}",
+           m_objfile_sp->GetFileSpec().GetPath());
+
+  auto expected_index = PdbIndex::create(m_pdb_file);
+  if (!expected_index) {
+    LLDB_LOG_ERROR(GetLog(LLDBLog::Symbols), expected_index.takeError(),
+                   "Failed to build PDB index: {0}");
+    return nullptr;
+  }
+  m_index = std::move(*expected_index);
+  return m_index.get();
+}
+
 void SymbolFileNativePDB::InitializeObject() {
+  if (!GetOrCreateIndex())
+    return;
   m_obj_load_address = m_objfile_sp->GetModule()
                            ->GetObjectFile()
                            ->GetBaseAddress()
@@ -442,6 +498,8 @@ void SymbolFileNativePDB::InitializeObject() {
 }
 
 uint32_t SymbolFileNativePDB::CalculateNumCompileUnits() {
+  if (!GetOrCreateIndex())
+    return 0;
   const DbiModuleList &modules = m_index->dbi().modules();
   uint32_t count = modules.getModuleCount();
   if (count == 0)
@@ -1245,6 +1303,11 @@ lldb::LanguageType 
SymbolFileNativePDB::ParseLanguage(CompileUnit &comp_unit) {
 }
 
 void SymbolFileNativePDB::AddSymbols(Symtab &symtab) {
+  // Symtab construction is reachable before InitializeObject (e.g. the
+  // on-demand wrapper serves pre-hydration lookups from the symtab), so the
+  // index must be materialized here as well.
+  if (!GetOrCreateIndex())
+    return;
   auto *section_list =
       m_objfile_sp->GetModule()->GetObjectFile()->GetSectionList();
   if (!section_list)
@@ -2759,7 +2822,11 @@ 
SymbolFileNativePDB::GetTypeSystemForLanguage(lldb::LanguageType language) {
 
 uint64_t SymbolFileNativePDB::GetDebugInfoSize(bool load_all_debug_info) {
   // PDB files are a separate file that contains all debug info.
-  return m_index->pdb().getFileSize();
+  // Reachable before the index is materialized (e.g. `statistics dump` with
+  // on-demand symbol loading), so use the PDB file directly.
+  if (!m_pdb_file)
+    return 0;
+  return m_pdb_file->getFileSize();
 }
 
 void SymbolFileNativePDB::BuildParentMap() {
diff --git a/lldb/source/Plugins/SymbolFile/NativePDB/SymbolFileNativePDB.h 
b/lldb/source/Plugins/SymbolFile/NativePDB/SymbolFileNativePDB.h
index 4d5d9fb58bcac..5707b1acc72b2 100644
--- a/lldb/source/Plugins/SymbolFile/NativePDB/SymbolFileNativePDB.h
+++ b/lldb/source/Plugins/SymbolFile/NativePDB/SymbolFileNativePDB.h
@@ -158,8 +158,8 @@ class SymbolFileNativePDB : public SymbolFileCommon {
 
   llvm::StringRef GetPluginName() override { return GetPluginNameStatic(); }
 
-  llvm::pdb::PDBFile &GetPDBFile() { return m_index->pdb(); }
-  const llvm::pdb::PDBFile &GetPDBFile() const { return m_index->pdb(); }
+  llvm::pdb::PDBFile &GetPDBFile() { return *m_pdb_file; }
+  const llvm::pdb::PDBFile &GetPDBFile() const { return *m_pdb_file; }
 
   PdbIndex &GetIndex() { return *m_index; };
 
@@ -301,7 +301,17 @@ class SymbolFileNativePDB : public SymbolFileCommon {
   // pdb debug info.
   lldb::user_id_t anonymous_id = LLDB_INVALID_UID - 1;
 
+  /// Builds m_index on first use. PdbIndex::create eagerly parses the DBI,
+  /// type (TPI/IPI) and symbol-record streams — for large PDBs that is most
+  /// of the file materialized on the private heap — so it must not run
+  /// during abilities probing, only when debug info is actually consumed
+  /// (InitializeObject / symtab construction).
+  PdbIndex *GetOrCreateIndex();
+
   std::unique_ptr<llvm::pdb::PDBFile> m_file_up;
+  /// The matched PDB file (owned by m_file_up, or by the ObjectFilePDB when
+  /// the module's object file IS the PDB). Set by CalculateAbilities.
+  llvm::pdb::PDBFile *m_pdb_file = nullptr;
   std::unique_ptr<PdbIndex> m_index;
 
   llvm::DenseMap<lldb::user_id_t, lldb::VariableSP> m_global_vars;

>From a3a15f5dc684809bbdef2876ec5e5eb98cf187ba Mon Sep 17 00:00:00 2001
From: cherleey <[email protected]>
Date: Wed, 22 Jul 2026 22:42:20 +0900
Subject: [PATCH 2/2] [lldb][NativePDB] Load TPI/IPI type streams lazily in
 PdbIndex

---
 .../Plugins/SymbolFile/NativePDB/PdbIndex.cpp | 36 +++++++++++++++++--
 .../Plugins/SymbolFile/NativePDB/PdbIndex.h   | 21 ++++++++---
 2 files changed, 49 insertions(+), 8 deletions(-)

diff --git a/lldb/source/Plugins/SymbolFile/NativePDB/PdbIndex.cpp 
b/lldb/source/Plugins/SymbolFile/NativePDB/PdbIndex.cpp
index ea778fc6cca67..b2a39d2344349 100644
--- a/lldb/source/Plugins/SymbolFile/NativePDB/PdbIndex.cpp
+++ b/lldb/source/Plugins/SymbolFile/NativePDB/PdbIndex.cpp
@@ -45,20 +45,50 @@ PdbIndex::create(llvm::pdb::PDBFile *file) {
 
   std::unique_ptr<PdbIndex> result(new PdbIndex());
   ASSIGN_PTR_OR_RETURN(result->m_dbi, file->getPDBDbiStream());
-  ASSIGN_PTR_OR_RETURN(result->m_tpi, file->getPDBTpiStream());
-  ASSIGN_PTR_OR_RETURN(result->m_ipi, file->getPDBIpiStream());
   ASSIGN_PTR_OR_RETURN(result->m_info, file->getPDBInfoStream());
   ASSIGN_PTR_OR_RETURN(result->m_publics, file->getPDBPublicsStream());
   ASSIGN_PTR_OR_RETURN(result->m_globals, file->getPDBGlobalsStream());
   ASSIGN_PTR_OR_RETURN(result->m_symrecords, file->getPDBSymbolStream());
 
-  result->m_tpi->buildHashMap();
+  // The TPI/IPI (type) streams — usually the largest streams of a big PDB —
+  // are materialized lazily in tpi()/ipi(); symtab-only consumers (e.g.
+  // AddSymbols from the publics stream) never pay for them.
 
   result->m_file = file;
 
   return std::move(result);
 }
 
+llvm::pdb::TpiStream &PdbIndex::tpi() {
+  // Symbol parsing can run on multiple threads; materialize exactly once.
+  // Stream *presence* is validated during abilities probing, so a failure
+  // here means a corrupt stream in a PDB we already committed to — callers
+  // hold references, so the only honest exits are success or fatal (the
+  // pre-lazy behavior rejected such a PDB wholesale in create()).
+  llvm::call_once(m_tpi_once, [this] {
+    auto expected_tpi = m_file->getPDBTpiStream();
+    if (!expected_tpi) {
+      llvm::consumeError(expected_tpi.takeError());
+      llvm::report_fatal_error("PdbIndex: failed to load TPI stream");
+    }
+    m_tpi = &*expected_tpi;
+    m_tpi->buildHashMap();
+  });
+  return *m_tpi;
+}
+
+llvm::pdb::TpiStream &PdbIndex::ipi() {
+  llvm::call_once(m_ipi_once, [this] {
+    auto expected_ipi = m_file->getPDBIpiStream();
+    if (!expected_ipi) {
+      llvm::consumeError(expected_ipi.takeError());
+      llvm::report_fatal_error("PdbIndex: failed to load IPI stream");
+    }
+    m_ipi = &*expected_ipi;
+  });
+  return *m_ipi;
+}
+
 lldb::addr_t PdbIndex::MakeVirtualAddress(uint16_t segment,
                                           uint32_t offset) const {
   uint32_t max_section = dbi().getSectionHeaders().size();
diff --git a/lldb/source/Plugins/SymbolFile/NativePDB/PdbIndex.h 
b/lldb/source/Plugins/SymbolFile/NativePDB/PdbIndex.h
index 796aa4c8dfd1b..2a350d82f99c8 100644
--- a/lldb/source/Plugins/SymbolFile/NativePDB/PdbIndex.h
+++ b/lldb/source/Plugins/SymbolFile/NativePDB/PdbIndex.h
@@ -13,6 +13,7 @@
 #include "llvm/ADT/IntervalMap.h"
 #include "llvm/DebugInfo/PDB/Native/PDBFile.h"
 #include "llvm/DebugInfo/PDB/PDBTypes.h"
+#include "llvm/Support/Threading.h"
 
 #include "CompileUnitIndex.h"
 #include "PdbSymUid.h"
@@ -63,6 +64,8 @@ class PdbIndex {
   /// the other way around.
   llvm::pdb::TpiStream *m_tpi = nullptr;
   llvm::pdb::TpiStream *m_ipi = nullptr;
+  llvm::once_flag m_tpi_once;
+  llvm::once_flag m_ipi_once;
 
   /// This is called the "PDB Stream" in the Microsoft reference 
implementation.
   /// It contains information about the structure of the file, as well as 
fields
@@ -121,11 +124,19 @@ class PdbIndex {
   llvm::pdb::DbiStream &dbi() { return *m_dbi; }
   const llvm::pdb::DbiStream &dbi() const { return *m_dbi; }
 
-  llvm::pdb::TpiStream &tpi() { return *m_tpi; }
-  const llvm::pdb::TpiStream &tpi() const { return *m_tpi; }
-
-  llvm::pdb::TpiStream &ipi() { return *m_ipi; }
-  const llvm::pdb::TpiStream &ipi() const { return *m_ipi; }
+  /// The TPI/IPI (type) streams are the largest streams of a big PDB and are
+  /// only needed once debug info (types) is actually consumed — symtab
+  /// construction from publics does not touch them. They are therefore
+  /// materialized lazily on first access rather than in create().
+  llvm::pdb::TpiStream &tpi();
+  const llvm::pdb::TpiStream &tpi() const {
+    return const_cast<PdbIndex *>(this)->tpi();
+  }
+
+  llvm::pdb::TpiStream &ipi();
+  const llvm::pdb::TpiStream &ipi() const {
+    return const_cast<PdbIndex *>(this)->ipi();
+  }
 
   llvm::pdb::InfoStream &info() { return *m_info; }
   const llvm::pdb::InfoStream &info() const { return *m_info; }

_______________________________________________
lldb-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to