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