[Lldb-commits] [lldb] [llvm] [CodeView] Resolve all forward referenced types from TPI stream (PR #193064)

via lldb-commits lldb-commits at lists.llvm.org
Mon Apr 20 13:55:47 PDT 2026


https://github.com/Nerixyz updated https://github.com/llvm/llvm-project/pull/193064

>From f8eb20441023e88637f4be6b6ceaf6870a9421d3 Mon Sep 17 00:00:00 2001
From: Nerixyz <nerixdev at outlook.de>
Date: Mon, 20 Apr 2026 20:28:27 +0200
Subject: [PATCH] [CodeView] Resolve all forward referenced types from TPI
 stream

---
 .../NativePDB/PdbAstBuilderClang.cpp          |  11 +-
 .../Plugins/SymbolFile/NativePDB/PdbUtil.cpp  |  22 ++-
 .../NativePDB/SymbolFileNativePDB.cpp         |  14 +-
 .../llvm/DebugInfo/PDB/Native/TpiStream.h     |  17 ++-
 llvm/lib/DebugInfo/PDB/Native/SymbolCache.cpp |  16 ++-
 llvm/lib/DebugInfo/PDB/Native/TpiStream.cpp   |  15 ++-
 .../tools/llvm-pdbutil/dump-forward-refs.test | 126 ++++++++++++++++++
 llvm/tools/llvm-pdbutil/MinimalTypeDumper.cpp |  36 +++--
 .../llvm-pdbutil/TypeReferenceTracker.cpp     |  14 +-
 9 files changed, 228 insertions(+), 43 deletions(-)
 create mode 100644 llvm/test/tools/llvm-pdbutil/dump-forward-refs.test

diff --git a/lldb/source/Plugins/SymbolFile/NativePDB/PdbAstBuilderClang.cpp b/lldb/source/Plugins/SymbolFile/NativePDB/PdbAstBuilderClang.cpp
index b07302bc7c7bf..b39e6051c0fe9 100644
--- a/lldb/source/Plugins/SymbolFile/NativePDB/PdbAstBuilderClang.cpp
+++ b/lldb/source/Plugins/SymbolFile/NativePDB/PdbAstBuilderClang.cpp
@@ -904,10 +904,13 @@ clang::FunctionDecl *PdbAstBuilderClang::CreateFunctionDecl(
     TagRecord tag_record = CVTagRecord::create(parent_cvt).asTag();
     // If it's a forward reference, try to get the real TypeIndex.
     if (tag_record.isForwardRef()) {
-      llvm::Expected<TypeIndex> eti =
-          index.tpi().findFullDeclForForwardRef(class_index);
-      if (eti) {
-        tag_record = CVTagRecord::create(index.tpi().getType(*eti)).asTag();
+      llvm::SmallVector<TypeIndex, 2> tis;
+      llvm::Error err =
+          index.tpi().findFullDeclsForForwardRef(class_index, tis);
+      if (!err && !tis.empty()) {
+        // FIXME: Find correct type if `tis` has more than one type index.
+        tag_record =
+            CVTagRecord::create(index.tpi().getType(tis.front())).asTag();
       }
     }
 
diff --git a/lldb/source/Plugins/SymbolFile/NativePDB/PdbUtil.cpp b/lldb/source/Plugins/SymbolFile/NativePDB/PdbUtil.cpp
index 5860d90eca689..b51539c122ed1 100644
--- a/lldb/source/Plugins/SymbolFile/NativePDB/PdbUtil.cpp
+++ b/lldb/source/Plugins/SymbolFile/NativePDB/PdbUtil.cpp
@@ -1133,7 +1133,15 @@ PdbTypeSymId lldb_private::npdb::GetBestPossibleDecl(PdbTypeSymId id,
   if (!IsForwardRefUdt(cvt))
     return id;
 
-  return llvm::cantFail(tpi.findFullDeclForForwardRef(id.index));
+  llvm::SmallVector<TypeIndex, 2> tis;
+  llvm::Error err = tpi.findFullDeclsForForwardRef(id.index, tis);
+  if (err)
+    llvm::consumeError(std::move(err));
+  if (tis.empty())
+    return id;
+
+  // FIXME: Find correct type if `tis` has more than one type index.
+  return tis.front();
 }
 
 template <typename RecordType> static size_t GetSizeOfTypeInternal(CVType cvt) {
@@ -1162,8 +1170,16 @@ size_t lldb_private::npdb::GetSizeOfType(PdbTypeSymId id,
   }
 
   TypeIndex index = id.index;
-  if (IsForwardRefUdt(index, tpi))
-    index = llvm::cantFail(tpi.findFullDeclForForwardRef(index));
+  if (IsForwardRefUdt(index, tpi)) {
+    llvm::SmallVector<TypeIndex, 2> ids;
+    llvm::Error err = tpi.findFullDeclsForForwardRef(index, ids);
+    if (err)
+      llvm::consumeError(std::move(err));
+    if (!ids.empty()) {
+      // FIXME: Find correct type if `tis` has more than one type index.
+      index = ids.front();
+    }
+  }
 
   CVType cvt = tpi.getType(index);
   switch (cvt.kind()) {
diff --git a/lldb/source/Plugins/SymbolFile/NativePDB/SymbolFileNativePDB.cpp b/lldb/source/Plugins/SymbolFile/NativePDB/SymbolFileNativePDB.cpp
index 176f1f992c02d..ebe294eccadf9 100644
--- a/lldb/source/Plugins/SymbolFile/NativePDB/SymbolFileNativePDB.cpp
+++ b/lldb/source/Plugins/SymbolFile/NativePDB/SymbolFileNativePDB.cpp
@@ -959,12 +959,14 @@ TypeSP SymbolFileNativePDB::CreateAndCacheType(PdbTypeSymId type_id) {
   // decl and just map the forward ref uid to the full decl record.
   std::optional<PdbTypeSymId> full_decl_uid;
   if (IsForwardRefUdt(type_id, m_index->tpi())) {
-    auto expected_full_ti =
-        m_index->tpi().findFullDeclForForwardRef(type_id.index);
-    if (!expected_full_ti)
-      llvm::consumeError(expected_full_ti.takeError());
-    else if (*expected_full_ti != type_id.index) {
-      full_decl_uid = PdbTypeSymId(*expected_full_ti, false);
+    llvm::SmallVector<TypeIndex, 2> full_tis;
+    llvm::Error err =
+        m_index->tpi().findFullDeclsForForwardRef(type_id.index, full_tis);
+    if (err)
+      llvm::consumeError(std::move(err));
+    else if (!full_tis.empty()) {
+      // FIXME: Find correct type if `full_tis` has more than one type index.
+      full_decl_uid = PdbTypeSymId(full_tis.front(), false);
 
       // It's possible that a lookup would occur for the full decl causing it
       // to be cached, then a second lookup would occur for the forward decl.
diff --git a/llvm/include/llvm/DebugInfo/PDB/Native/TpiStream.h b/llvm/include/llvm/DebugInfo/PDB/Native/TpiStream.h
index deaa313c61329..ab7f8789f3d18 100644
--- a/llvm/include/llvm/DebugInfo/PDB/Native/TpiStream.h
+++ b/llvm/include/llvm/DebugInfo/PDB/Native/TpiStream.h
@@ -61,8 +61,21 @@ class TpiStream {
 
   codeview::LazyRandomTypeCollection &typeCollection() { return *Types; }
 
-  LLVM_ABI Expected<codeview::TypeIndex>
-  findFullDeclForForwardRef(codeview::TypeIndex ForwardRefTI) const;
+  /// Find all possible declarations this \p ForwardRefTI is a forward reference
+  /// for and add them to \p Decls.
+  ///
+  /// This method uses the TPI hash table to find the referenced types.
+  ///
+  /// In most cases, this should return at most one declaration. However, in
+  /// incrementally linked PDBs, there might be multiple possible records \p
+  /// ForwardRefTI could reference. From the TPI stream alone, it's not known
+  /// which record is the "real" one.
+  ///
+  /// If no type could be found, \p Decls will remain unmodified and success is
+  /// returned.
+  LLVM_ABI Error
+  findFullDeclsForForwardRef(codeview::TypeIndex ForwardRefTI,
+                             SmallVectorImpl<codeview::TypeIndex> &Decls) const;
 
   LLVM_ABI std::vector<codeview::TypeIndex>
   findRecordsByName(StringRef Name) const;
diff --git a/llvm/lib/DebugInfo/PDB/Native/SymbolCache.cpp b/llvm/lib/DebugInfo/PDB/Native/SymbolCache.cpp
index 535893b3f1a4c..3dbbb51d948ba 100644
--- a/llvm/lib/DebugInfo/PDB/Native/SymbolCache.cpp
+++ b/llvm/lib/DebugInfo/PDB/Native/SymbolCache.cpp
@@ -179,13 +179,15 @@ SymIndexId SymbolCache::findSymbolByTypeIndex(codeview::TypeIndex Index) const {
   codeview::CVType CVT = Types.getType(Index);
 
   if (isUdtForwardRef(CVT)) {
-    Expected<TypeIndex> EFD = Tpi->findFullDeclForForwardRef(Index);
-
-    if (!EFD)
-      consumeError(EFD.takeError());
-    else if (*EFD != Index) {
-      assert(!isUdtForwardRef(Types.getType(*EFD)));
-      SymIndexId Result = findSymbolByTypeIndex(*EFD);
+    llvm::SmallVector<TypeIndex, 2> TIs;
+    Error Err = Tpi->findFullDeclsForForwardRef(Index, TIs);
+
+    if (Err)
+      consumeError(std::move(Err));
+    else if (!TIs.empty()) {
+      assert(!isUdtForwardRef(Types.getType(TIs.front())));
+      // FIXME: Find correct type if `TIs` has more than one type index.
+      SymIndexId Result = findSymbolByTypeIndex(TIs.front());
       // Record a mapping from ForwardRef -> SymIndex of complete type so that
       // we'll take the fast path next time.
       assert(TypeIndexToSymbolId.count(Index) == 0);
diff --git a/llvm/lib/DebugInfo/PDB/Native/TpiStream.cpp b/llvm/lib/DebugInfo/PDB/Native/TpiStream.cpp
index 24d2f9e3360ea..a64e2bc8ab409 100644
--- a/llvm/lib/DebugInfo/PDB/Native/TpiStream.cpp
+++ b/llvm/lib/DebugInfo/PDB/Native/TpiStream.cpp
@@ -175,14 +175,14 @@ std::vector<TypeIndex> TpiStream::findRecordsByName(StringRef Name) const {
 
 bool TpiStream::supportsTypeLookup() const { return !HashMap.empty(); }
 
-Expected<TypeIndex>
-TpiStream::findFullDeclForForwardRef(TypeIndex ForwardRefTI) const {
+Error TpiStream::findFullDeclsForForwardRef(
+    TypeIndex ForwardRefTI, SmallVectorImpl<TypeIndex> &Decls) const {
   if (!supportsTypeLookup())
-    const_cast<TpiStream*>(this)->buildHashMap();
+    const_cast<TpiStream *>(this)->buildHashMap();
 
   CVType F = Types->getType(ForwardRefTI);
   if (!isUdtForwardRef(F))
-    return ForwardRefTI;
+    return Error::success();
 
   Expected<TagRecordHash> ForwardTRH = hashTagRecord(F);
   if (!ForwardTRH)
@@ -205,16 +205,17 @@ TpiStream::findFullDeclForForwardRef(TypeIndex ForwardRefTI) const {
 
     if (!ForwardTR.hasUniqueName()) {
       if (ForwardTR.getName() == FullTR.getName())
-        return TI;
+        Decls.emplace_back(TI);
       continue;
     }
 
     if (!FullTR.hasUniqueName())
       continue;
     if (ForwardTR.getUniqueName() == FullTR.getUniqueName())
-      return TI;
+      Decls.emplace_back(TI);
   }
-  return ForwardRefTI;
+
+  return Error::success();
 }
 
 codeview::CVType TpiStream::getType(codeview::TypeIndex Index) {
diff --git a/llvm/test/tools/llvm-pdbutil/dump-forward-refs.test b/llvm/test/tools/llvm-pdbutil/dump-forward-refs.test
new file mode 100644
index 0000000000000..74a8a9c1b7344
--- /dev/null
+++ b/llvm/test/tools/llvm-pdbutil/dump-forward-refs.test
@@ -0,0 +1,126 @@
+# RUN: llvm-pdbutil yaml2pdb %s --pdb=%t.pdb
+# RUN: llvm-pdbutil dump --types %t.pdb | FileCheck %s
+
+# CHECK:        0x1000 | LF_STRUCTURE [size = 36] `Foo`
+# CHECK-NEXT:           unique name: `.?AUFoo@@`
+# CHECK-NEXT:           vtable: <no type>, base list: <no type>, field list: <no type>
+# CHECK-NEXT:           options: forward ref (-> 0x1008, -> 0x100A) | has unique name, sizeof 0
+
+# CHECK:       0x100B | LF_STRUCTURE [size = 36] `Bar`
+# CHECK-NEXT:           unique name: `.?AUBar@@`
+# CHECK-NEXT:           vtable: <no type>, base list: <no type>, field list: <no type>
+# CHECK-NEXT:           options: forward ref (= 0x100B) | has unique name, sizeof 0
+
+---
+TpiStream:
+  Version:         VC80
+  Records:
+    - Kind:            LF_STRUCTURE
+      Class:
+        MemberCount:     0
+        Options:         [ None, ForwardReference, HasUniqueName ]
+        FieldList:       0
+        Name:            Foo
+        UniqueName:      '.?AUFoo@@'
+        DerivationList:  0
+        VTableShape:     0
+        Size:            0
+    - Kind:            LF_POINTER
+      Pointer:
+        ReferentType:    4096
+        Attrs:           65548
+    - Kind:            LF_ARGLIST
+      ArgList:
+        ArgIndices:      [ 4097 ]
+    - Kind:            LF_PROCEDURE
+      Procedure:
+        ReturnType:      116
+        CallConv:        NearC
+        Options:         [ None ]
+        ParameterCount:  1
+        ArgumentList:    4098
+    - Kind:            LF_ARGLIST
+      ArgList:
+        ArgIndices:      [  ]
+    - Kind:            LF_PROCEDURE
+      Procedure:
+        ReturnType:      116
+        CallConv:        NearC
+        Options:         [ None ]
+        ParameterCount:  0
+        ArgumentList:    4100
+    - Kind:            LF_MFUNCTION
+      MemberFunction:
+        ReturnType:      3
+        ClassType:       4096
+        ThisType:        4097
+        CallConv:        NearC
+        Options:         [ None, Constructor ]
+        ParameterCount:  0
+        ArgumentList:    4100
+        ThisPointerAdjustment: 0
+    - Kind:            LF_FIELDLIST
+      FieldList:
+        - Kind:            LF_MEMBER
+          DataMember:
+            Attrs:           3
+            Type:            116
+            FieldOffset:     0
+            Name:            a
+        - Kind:            LF_ONEMETHOD
+          OneMethod:
+            Type:            4102
+            Attrs:           259
+            VFTableOffset:   -1
+            Name:            Foo
+    - Kind:            LF_STRUCTURE
+      Class:
+        MemberCount:     2
+        Options:         [ None, HasConstructorOrDestructor, HasUniqueName ]
+        FieldList:       4103
+        Name:            Foo
+        UniqueName:      '.?AUFoo@@'
+        DerivationList:  0
+        VTableShape:     0
+        Size:            4
+    - Kind:            LF_FIELDLIST
+      FieldList:
+        - Kind:            LF_MEMBER
+          DataMember:
+            Attrs:           3
+            Type:            4097
+            FieldOffset:     0
+            Name:            ptr
+        - Kind:            LF_MEMBER
+          DataMember:
+            Attrs:           3
+            Type:            116
+            FieldOffset:     8
+            Name:            a
+        - Kind:            LF_ONEMETHOD
+          OneMethod:
+            Type:            4102
+            Attrs:           259
+            VFTableOffset:   -1
+            Name:            Foo
+    - Kind:            LF_STRUCTURE
+      Class:
+        MemberCount:     3
+        Options:         [ None, HasConstructorOrDestructor, HasUniqueName ]
+        FieldList:       4105
+        Name:            Foo
+        UniqueName:      '.?AUFoo@@'
+        DerivationList:  0
+        VTableShape:     0
+        Size:            16
+    - Kind:            LF_STRUCTURE
+      Class:
+        MemberCount:     0
+        Options:         [ None, ForwardReference, HasUniqueName ]
+        FieldList:       0
+        Name:            Bar
+        UniqueName:      '.?AUBar@@'
+        DerivationList:  0
+        VTableShape:     0
+        Size:            0
+...
diff --git a/llvm/tools/llvm-pdbutil/MinimalTypeDumper.cpp b/llvm/tools/llvm-pdbutil/MinimalTypeDumper.cpp
index fc29ec46180c4..db2d2c6091ca4 100644
--- a/llvm/tools/llvm-pdbutil/MinimalTypeDumper.cpp
+++ b/llvm/tools/llvm-pdbutil/MinimalTypeDumper.cpp
@@ -41,18 +41,34 @@ static std::string formatClassOptions(uint32_t IndentLevel,
       !opts::dump::DontResolveForwardRefs &&
       ((Options & ClassOptions::ForwardReference) != ClassOptions::None)) {
     // If we're able to resolve forward references, do that.
-    Expected<TypeIndex> ETI =
-        Stream->findFullDeclForForwardRef(CurrentTypeIndex);
-    if (!ETI) {
-      consumeError(ETI.takeError());
+    SmallVector<TypeIndex, 2> TIs;
+    Error Err = Stream->findFullDeclsForForwardRef(CurrentTypeIndex, TIs);
+    if (Err) {
+      consumeError(std::move(Err));
       PUSH_FLAG(ClassOptions, ForwardReference, Options, "forward ref (??\?)");
-    } else {
-      const char *Direction = (*ETI == CurrentTypeIndex)
-                                  ? "="
-                                  : ((*ETI < CurrentTypeIndex) ? "<-" : "->");
-      std::string Formatted =
-          formatv("forward ref ({0} {1})", Direction, *ETI).str();
+    } else if (!TIs.empty()) {
+      std::string Formatted = "forward ref (";
+      bool First = true;
+      for (TypeIndex TI : TIs) {
+        if (First)
+          First = false;
+        else
+          Formatted += ", ";
+
+        if (TI > CurrentTypeIndex)
+          Formatted += "-> ";
+        else if (TI < CurrentTypeIndex)
+          Formatted += "<- ";
+        else
+          Formatted += "= ";
+
+        Formatted += formatv("{0}", TI);
+      }
+      Formatted += ')';
       PUSH_FLAG(ClassOptions, ForwardReference, Options, std::move(Formatted));
+    } else {
+      PUSH_FLAG(ClassOptions, ForwardReference, Options,
+                formatv("forward ref (= {0})", CurrentTypeIndex));
     }
   } else {
     PUSH_FLAG(ClassOptions, ForwardReference, Options, "forward ref");
diff --git a/llvm/tools/llvm-pdbutil/TypeReferenceTracker.cpp b/llvm/tools/llvm-pdbutil/TypeReferenceTracker.cpp
index c7653b7c06d48..9eb3ad5fb3ce3 100644
--- a/llvm/tools/llvm-pdbutil/TypeReferenceTracker.cpp
+++ b/llvm/tools/llvm-pdbutil/TypeReferenceTracker.cpp
@@ -153,10 +153,16 @@ void TypeReferenceTracker::markReferencedTypes() {
       case LF_INTERFACE:
       case LF_STRUCTURE:
       case LF_UNION:
-      case LF_ENUM:
-        addOneTypeRef(TiRefKind::TypeRef,
-                      cantFail(Tpi->findFullDeclForForwardRef(RefTI)));
-        break;
+      case LF_ENUM: {
+        SmallVector<TypeIndex, 2> TIs;
+        cantFail(Tpi->findFullDeclsForForwardRef(RefTI, TIs));
+        if (TIs.empty()) {
+          addOneTypeRef(TiRefKind::TypeRef, RefTI);
+        } else {
+          for (TypeIndex TI : TIs)
+            addOneTypeRef(TiRefKind::TypeRef, TI);
+        }
+      } break;
       }
     }
   }



More information about the lldb-commits mailing list