[libcxx-commits] [clang] [libcxxabi] [lldb] [llvm] [lldb][Expression] Add structor variant to LLDB's function call labels (PR #149827)

Michael Buch via libcxx-commits libcxx-commits at lists.llvm.org
Thu Aug 21 02:55:11 PDT 2025


https://github.com/Michael137 updated https://github.com/llvm/llvm-project/pull/149827

>From d55e41fa03d09b2ddfc9484c4a70a7d21ed9a994 Mon Sep 17 00:00:00 2001
From: Michael Buch <michaelbuch12 at gmail.com>
Date: Mon, 18 Aug 2025 15:12:45 +0100
Subject: [PATCH 01/20] [llvm][DebugInfo] Support DW_AT_linkage_names that are
 different between declaration and definition

(cherry picked from commit 62641a7cc6b439c747be0a9ae91b9b266d67816e)
---
 llvm/lib/CodeGen/AsmPrinter/DwarfUnit.cpp     |  7 +-
 .../structor-declaration-linkage-names.ll     | 68 +++++++++++++++++++
 2 files changed, 70 insertions(+), 5 deletions(-)
 create mode 100644 llvm/test/DebugInfo/Generic/structor-declaration-linkage-names.ll

diff --git a/llvm/lib/CodeGen/AsmPrinter/DwarfUnit.cpp b/llvm/lib/CodeGen/AsmPrinter/DwarfUnit.cpp
index b03fac2d22a52..4904ad03199c7 100644
--- a/llvm/lib/CodeGen/AsmPrinter/DwarfUnit.cpp
+++ b/llvm/lib/CodeGen/AsmPrinter/DwarfUnit.cpp
@@ -1403,11 +1403,8 @@ bool DwarfUnit::applySubprogramDefinitionAttributes(const DISubprogram *SP,
 
   // Add the linkage name if we have one and it isn't in the Decl.
   StringRef LinkageName = SP->getLinkageName();
-  assert(((LinkageName.empty() || DeclLinkageName.empty()) ||
-          LinkageName == DeclLinkageName) &&
-         "decl has a linkage name and it is different");
-  if (DeclLinkageName.empty() &&
-      // Always emit it for abstract subprograms.
+  // Always emit linkage name for abstract subprograms.
+  if (DeclLinkageName != LinkageName &&
       (DD->useAllLinkageNames() || DU->getAbstractScopeDIEs().lookup(SP)))
     addLinkageName(SPDie, LinkageName);
 
diff --git a/llvm/test/DebugInfo/Generic/structor-declaration-linkage-names.ll b/llvm/test/DebugInfo/Generic/structor-declaration-linkage-names.ll
new file mode 100644
index 0000000000000..9b1f2a5b2a186
--- /dev/null
+++ b/llvm/test/DebugInfo/Generic/structor-declaration-linkage-names.ll
@@ -0,0 +1,68 @@
+; RUN: %llc_dwarf < %s -filetype=obj | llvm-dwarfdump -debug-info - | FileCheck %s
+
+; Make sure we attach DW_AT_linkage_name on function declarations but only
+; attach it on definitions if the value is different than on the declaration.
+
+target triple = "arm64-apple-macosx"
+
+define void @_Z11SameLinkagev() !dbg !4 {
+entry:
+  ret void
+}
+
+; CHECK:     DW_AT_linkage_name ("_Z11SameLinkagev")
+; CHECK:     DW_AT_declaration (true)
+; CHECK-NOT: DW_AT_linkage_name ("_Z11SameLinkagev")
+
+define void @_Z11DiffLinkagev() !dbg !8 {
+entry:
+  ret void
+}
+
+; CHECK: DW_AT_linkage_name ("SomeName")
+; CHECK: DW_AT_declaration (true)
+; CHECK: DW_AT_linkage_name ("_Z11DiffLinkagev")
+
+define void @_Z15EmptyDefLinkagev() !dbg !10 {
+entry:
+  ret void
+}
+
+; CHECK:     DW_AT_linkage_name ("_Z15EmptyDefLinkagev")
+; CHECK:     DW_AT_declaration (true)
+; CHECK-NOT: DW_AT_linkage_name
+
+define void @_Z16EmptyDeclLinkagev() !dbg !12 {
+entry:
+  ret void
+}
+
+; CHECK: DW_AT_declaration (true)
+; CHECK: DW_AT_linkage_name ("_Z16EmptyDeclLinkagev")
+
+define void @_Z13EmptyLinkagesv() !dbg !14 {
+entry:
+  ret void
+}
+
+; CHECK-NOT: DW_AT_linkage_name
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!2, !3}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus_14, file: !1, producer: "clang", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: Apple, sysroot: "/")
+!1 = !DIFile(filename: "foo.cpp", directory: "/tmp")
+!2 = !{i32 7, !"Dwarf Version", i32 5}
+!3 = !{i32 2, !"Debug Info Version", i32 3}
+!4 = distinct !DISubprogram(name: "SameLinkage", linkageName: "_Z11SameLinkagev", scope: !1, file: !1, line: 3, type: !5, scopeLine: 3, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0, declaration: !7)
+!5 = !DISubroutineType(types: !6)
+!6 = !{null}
+!7 = !DISubprogram(name: "SameLinkage", linkageName: "_Z11SameLinkagev", scope: !1, file: !1, line: 3, type: !5, scopeLine: 3, flags: DIFlagPrototyped, spFlags: 0)
+!8 = distinct !DISubprogram(name: "DiffLinkage", linkageName: "_Z11DiffLinkagev", scope: !1, file: !1, line: 5, type: !5, scopeLine: 5, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0, declaration: !9)
+!9 = !DISubprogram(name: "DiffLinkage", linkageName: "SomeName", scope: !1, file: !1, line: 3, type: !5, scopeLine: 3, flags: DIFlagPrototyped, spFlags: 0)
+!10 = distinct !DISubprogram(name: "EmptyDefLinkage", linkageName: "", scope: !1, file: !1, line: 5, type: !5, scopeLine: 5, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0, declaration: !11)
+!11 = !DISubprogram(name: "EmptyDefLinkage", linkageName: "_Z15EmptyDefLinkagev", scope: !1, file: !1, line: 3, type: !5, scopeLine: 3, flags: DIFlagPrototyped, spFlags: 0)
+!12 = distinct !DISubprogram(name: "EmptyDeclLinkage", linkageName: "_Z16EmptyDeclLinkagev", scope: !1, file: !1, line: 5, type: !5, scopeLine: 5, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0, declaration: !13)
+!13 = !DISubprogram(name: "EmptyDeclLinkage", linkageName: "", scope: !1, file: !1, line: 3, type: !5, scopeLine: 3, flags: DIFlagPrototyped, spFlags: 0)
+!14 = distinct !DISubprogram(name: "EmptyLinkages", linkageName: "", scope: !1, file: !1, line: 5, type: !5, scopeLine: 5, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0, declaration: !15)
+!15 = !DISubprogram(name: "EmptyLinkages", linkageName: "", scope: !1, file: !1, line: 3, type: !5, scopeLine: 3, flags: DIFlagPrototyped, spFlags: 0)

>From 3ce391791dc7264791f75690d64ebd96a44c9c6b Mon Sep 17 00:00:00 2001
From: Michael Buch <michaelbuch12 at gmail.com>
Date: Mon, 18 Aug 2025 15:14:40 +0100
Subject: [PATCH 02/20] [clang][DebugInfo] Emit unified (Itanium) mangled name
 to structor declarations

---
 clang/include/clang/Basic/ABI.h               | 10 +--
 clang/lib/AST/ItaniumMangle.cpp               | 10 +++
 clang/lib/AST/MicrosoftMangle.cpp             |  2 +
 clang/lib/CodeGen/CGDebugInfo.cpp             | 32 +++++++--
 clang/lib/CodeGen/CGDebugInfo.h               |  4 ++
 clang/lib/CodeGen/MicrosoftCXXABI.cpp         |  4 ++
 .../debug-info-structor-linkage-names.cpp     | 72 +++++++++++++++++++
 7 files changed, 124 insertions(+), 10 deletions(-)
 create mode 100644 clang/test/CodeGenCXX/debug-info-structor-linkage-names.cpp

diff --git a/clang/include/clang/Basic/ABI.h b/clang/include/clang/Basic/ABI.h
index 231bad799a42c..8279529c316cf 100644
--- a/clang/include/clang/Basic/ABI.h
+++ b/clang/include/clang/Basic/ABI.h
@@ -27,14 +27,16 @@ enum CXXCtorType {
   Ctor_Comdat,         ///< The COMDAT used for ctors
   Ctor_CopyingClosure, ///< Copying closure variant of a ctor
   Ctor_DefaultClosure, ///< Default closure variant of a ctor
+  Ctor_Unified,        ///< GCC-style unified dtor
 };
 
 /// C++ destructor types.
 enum CXXDtorType {
-    Dtor_Deleting, ///< Deleting dtor
-    Dtor_Complete, ///< Complete object dtor
-    Dtor_Base,     ///< Base object dtor
-    Dtor_Comdat    ///< The COMDAT used for dtors
+  Dtor_Deleting, ///< Deleting dtor
+  Dtor_Complete, ///< Complete object dtor
+  Dtor_Base,     ///< Base object dtor
+  Dtor_Comdat,   ///< The COMDAT used for dtors
+  Dtor_Unified,  ///< GCC-style unified dtor
 };
 
 } // end namespace clang
diff --git a/clang/lib/AST/ItaniumMangle.cpp b/clang/lib/AST/ItaniumMangle.cpp
index 112678fb2714a..f6ff9861508a2 100644
--- a/clang/lib/AST/ItaniumMangle.cpp
+++ b/clang/lib/AST/ItaniumMangle.cpp
@@ -6029,6 +6029,8 @@ void CXXNameMangler::mangleCXXCtorType(CXXCtorType T,
   //                  ::= CI2 <type> # base inheriting constructor
   //
   // In addition, C5 is a comdat name with C1 and C2 in it.
+  // C4 represents a ctor declaration and is used by debuggers to look up
+  // the various ctor variants.
   Out << 'C';
   if (InheritedFrom)
     Out << 'I';
@@ -6039,6 +6041,9 @@ void CXXNameMangler::mangleCXXCtorType(CXXCtorType T,
   case Ctor_Base:
     Out << '2';
     break;
+  case Ctor_Unified:
+    Out << '4';
+    break;
   case Ctor_Comdat:
     Out << '5';
     break;
@@ -6056,6 +6061,8 @@ void CXXNameMangler::mangleCXXDtorType(CXXDtorType T) {
   //                  ::= D2  # base object destructor
   //
   // In addition, D5 is a comdat name with D1, D2 and, if virtual, D0 in it.
+  // D4 represents a dtor declaration and is used by debuggers to look up
+  // the various dtor variants.
   switch (T) {
   case Dtor_Deleting:
     Out << "D0";
@@ -6066,6 +6073,9 @@ void CXXNameMangler::mangleCXXDtorType(CXXDtorType T) {
   case Dtor_Base:
     Out << "D2";
     break;
+  case Dtor_Unified:
+    Out << "D4";
+    break;
   case Dtor_Comdat:
     Out << "D5";
     break;
diff --git a/clang/lib/AST/MicrosoftMangle.cpp b/clang/lib/AST/MicrosoftMangle.cpp
index fc79ab1de24ff..d214db76e419b 100644
--- a/clang/lib/AST/MicrosoftMangle.cpp
+++ b/clang/lib/AST/MicrosoftMangle.cpp
@@ -1496,6 +1496,8 @@ void MicrosoftCXXNameMangler::mangleCXXDtorType(CXXDtorType T) {
   // it.
   case Dtor_Comdat:
     llvm_unreachable("not expecting a COMDAT");
+  case Dtor_Unified:
+    llvm_unreachable("not expecting a unified dtor type");
   }
   llvm_unreachable("Unsupported dtor type?");
 }
diff --git a/clang/lib/CodeGen/CGDebugInfo.cpp b/clang/lib/CodeGen/CGDebugInfo.cpp
index 994bdbdae860f..f32e1e8185337 100644
--- a/clang/lib/CodeGen/CGDebugInfo.cpp
+++ b/clang/lib/CodeGen/CGDebugInfo.cpp
@@ -2177,24 +2177,44 @@ static bool isFunctionLocalClass(const CXXRecordDecl *RD) {
   return false;
 }
 
-llvm::DISubprogram *CGDebugInfo::CreateCXXMemberFunction(
-    const CXXMethodDecl *Method, llvm::DIFile *Unit, llvm::DIType *RecordTy) {
+llvm::StringRef
+CGDebugInfo::GetMethodLinkageName(const CXXMethodDecl *Method) const {
+  assert(Method);
+
   bool IsCtorOrDtor =
       isa<CXXConstructorDecl>(Method) || isa<CXXDestructorDecl>(Method);
 
+  // In some ABIs (particularly Itanium) a single ctor/dtor
+  // corresponds to multiple functions. Attach a "unified"
+  // linkage name for those (which is the convention GCC uses).
+  // Otherwise, attach no linkage name.
+  if (IsCtorOrDtor && !CGM.getTarget().getCXXABI().hasConstructorVariants())
+    return {};
+
+  if (const auto *Ctor = llvm::dyn_cast<CXXConstructorDecl>(Method))
+    return CGM.getMangledName(GlobalDecl(Ctor, CXXCtorType::Ctor_Unified));
+
+  if (const auto *Dtor = llvm::dyn_cast<CXXDestructorDecl>(Method))
+    return CGM.getMangledName(GlobalDecl(Dtor, CXXDtorType::Dtor_Unified));
+
+  return CGM.getMangledName(Method);
+}
+
+llvm::DISubprogram *CGDebugInfo::CreateCXXMemberFunction(
+    const CXXMethodDecl *Method, llvm::DIFile *Unit, llvm::DIType *RecordTy) {
+  assert(Method);
+
   StringRef MethodName = getFunctionName(Method);
   llvm::DISubroutineType *MethodTy = getOrCreateMethodType(Method, Unit);
 
-  // Since a single ctor/dtor corresponds to multiple functions, it doesn't
-  // make sense to give a single ctor/dtor a linkage name.
   StringRef MethodLinkageName;
   // FIXME: 'isFunctionLocalClass' seems like an arbitrary/unintentional
   // property to use here. It may've been intended to model "is non-external
   // type" but misses cases of non-function-local but non-external classes such
   // as those in anonymous namespaces as well as the reverse - external types
   // that are function local, such as those in (non-local) inline functions.
-  if (!IsCtorOrDtor && !isFunctionLocalClass(Method->getParent()))
-    MethodLinkageName = CGM.getMangledName(Method);
+  if (!isFunctionLocalClass(Method->getParent()))
+    MethodLinkageName = GetMethodLinkageName(Method);
 
   // Get the location for the method.
   llvm::DIFile *MethodDefUnit = nullptr;
diff --git a/clang/lib/CodeGen/CGDebugInfo.h b/clang/lib/CodeGen/CGDebugInfo.h
index 497d3a6ab17b1..55c528031368d 100644
--- a/clang/lib/CodeGen/CGDebugInfo.h
+++ b/clang/lib/CodeGen/CGDebugInfo.h
@@ -899,6 +899,10 @@ class CGDebugInfo {
       std::memcpy(Data + A.size(), B.data(), B.size());
     return StringRef(Data, A.size() + B.size());
   }
+
+  /// If one exists, returns the linkage name of the specified \
+  /// (non-null) \c Method. Returns empty string otherwise.
+  llvm::StringRef GetMethodLinkageName(const CXXMethodDecl *Method) const;
 };
 
 /// A scoped helper to set the current debug location to the specified
diff --git a/clang/lib/CodeGen/MicrosoftCXXABI.cpp b/clang/lib/CodeGen/MicrosoftCXXABI.cpp
index 88f0648660965..94190a149e859 100644
--- a/clang/lib/CodeGen/MicrosoftCXXABI.cpp
+++ b/clang/lib/CodeGen/MicrosoftCXXABI.cpp
@@ -77,6 +77,8 @@ class MicrosoftCXXABI : public CGCXXABI {
         return false;
 
       case Dtor_Comdat: llvm_unreachable("emitting dtor comdat as function?");
+      case Dtor_Unified:
+        llvm_unreachable("unexpected unified dtor type");
       }
       llvm_unreachable("bad dtor kind");
     }
@@ -1417,6 +1419,8 @@ llvm::GlobalValue::LinkageTypes MicrosoftCXXABI::getCXXDestructorLinkage(
     // and are emitted everywhere they are used. They are internal if the class
     // is internal.
     return llvm::GlobalValue::LinkOnceODRLinkage;
+  case Dtor_Unified:
+    llvm_unreachable("MS C++ ABI does not support unified dtors");
   case Dtor_Comdat:
     llvm_unreachable("MS C++ ABI does not support comdat dtors");
   }
diff --git a/clang/test/CodeGenCXX/debug-info-structor-linkage-names.cpp b/clang/test/CodeGenCXX/debug-info-structor-linkage-names.cpp
new file mode 100644
index 0000000000000..88b4ee75d4565
--- /dev/null
+++ b/clang/test/CodeGenCXX/debug-info-structor-linkage-names.cpp
@@ -0,0 +1,72 @@
+// Tests that we emit unified constructor/destructor linkage names
+// for ABIs that support it.
+
+// RUN: %clang_cc1 -triple aarch64-apple-macosx -emit-llvm -debug-info-kind=standalone %s -o - | FileCheck %s --check-prefixes=CHECK,ITANIUM
+// RUN: %clang_cc1 -triple x86_64-windows-msvc -emit-llvm -debug-info-kind=standalone %s -o - | FileCheck %s --check-prefixes=CHECK,MSABI
+
+struct Base {
+  Base(int x);
+  ~Base();
+};
+
+Base::Base(int x) {}
+Base::~Base() {}
+
+// Check that we emit unified ctor/dtor (C4/D4) on Itanium but not for MS-ABI.
+
+// CHECK: ![[BASE_CTOR_DECL:[0-9]+]] = !DISubprogram(name: "Base"
+// MSABI-NOT:                                        linkageName:
+// ITANIUM-SAME:                                     linkageName: "_ZN4BaseC4Ei"
+// CHECK-SAME:                                       spFlags: 0
+
+// CHECK: ![[BASE_DTOR_DECL:[0-9]+]] = !DISubprogram(name: "~Base"
+// MSABI-NOT:                                        linkageName:
+// ITANIUM-SAME:                                     linkageName: "_ZN4BaseD4Ev"
+// CHECK-SAME:                                       spFlags: 0
+
+// Check that the ctor/dtor definitions have linkage names that aren't
+// the ones on the declaration.
+
+// CHECK: !DISubprogram(name: "Base"
+// MSABI-SAME:          linkageName:
+// ITANIUM-SAME:        linkageName: "_ZN4BaseC2Ei"
+// CHECK-SAME:          spFlags: DISPFlagDefinition
+// CHECK-SAME:          declaration: ![[BASE_CTOR_DECL]]
+
+// ITANIUM: !DISubprogram(name: "Base"
+// ITANIUM-SAME:          linkageName: "_ZN4BaseC1Ei"
+// ITANIUM-SAME:          spFlags: DISPFlagDefinition
+// ITANIUM-SAME:          declaration: ![[BASE_CTOR_DECL]]
+
+// CHECK: !DISubprogram(name: "~Base"
+// MSABI-SAME:          linkageName:
+// ITANIUM-SAME:        linkageName: "_ZN4BaseD2Ev"
+// CHECK-SAME:          spFlags: DISPFlagDefinition
+// CHECK-SAME:          declaration: ![[BASE_DTOR_DECL]]
+
+// ITANIUM: !DISubprogram(name: "~Base"
+// ITANIUM-SAME:          linkageName: "_ZN4BaseD1Ev"
+// ITANIUM-SAME:          spFlags: DISPFlagDefinition
+// ITANIUM-SAME:          declaration: ![[BASE_DTOR_DECL]]
+
+struct Derived : public Base {
+    using Base::Base;
+} d(5);
+
+// CHECK: !DISubprogram(name: "Base"
+// MSABI-SAME:          linkageName:
+// ITANIUM-SAME:        linkageName: "_ZN7DerivedCI14BaseEi"
+// CHECK-SAME:          spFlags: {{.*}}DISPFlagDefinition
+// CHECK-SAME:          declaration: ![[BASE_INHERIT_CTOR_DECL:[0-9]+]]
+
+// CHECK: [[BASE_INHERIT_CTOR_DECL]] = !DISubprogram(name: "Base"
+// MSABI-NOT:                                        linkageName:
+// ITANIUM-SAME:                                     linkageName: "_ZN7DerivedCI44BaseEi"
+// CHECK-SAME                                        spFlags: 0
+
+// ITANIUM: !DISubprogram(name: "Base"
+// ITANIUM-SAME:          linkageName: "_ZN7DerivedCI24BaseEi"
+// ITANIUM-SAME:          spFlags: DISPFlagDefinition
+// ITANIUM-SAME:          declaration: ![[BASE_INHERIT_CTOR_DECL:[0-9]+]]
+
+// MSABI: !DISubprogram(name: "~Derived"

>From 847afb07c6803dd642bed506b45a7b11a6c4c305 Mon Sep 17 00:00:00 2001
From: Michael Buch <michaelbuch12 at gmail.com>
Date: Mon, 18 Aug 2025 17:07:42 +0100
Subject: [PATCH 03/20] fixup! switch-statement warnings

---
 clang/lib/CodeGen/CGClass.cpp       | 2 ++
 clang/lib/CodeGen/ItaniumCXXABI.cpp | 5 +++++
 2 files changed, 7 insertions(+)

diff --git a/clang/lib/CodeGen/CGClass.cpp b/clang/lib/CodeGen/CGClass.cpp
index e9a92ae0f01cb..ba587943384a6 100644
--- a/clang/lib/CodeGen/CGClass.cpp
+++ b/clang/lib/CodeGen/CGClass.cpp
@@ -1502,6 +1502,8 @@ void CodeGenFunction::EmitDestructorBody(FunctionArgList &Args) {
   // we'd introduce *two* handler blocks.  In the Microsoft ABI, we
   // always delegate because we might not have a definition in this TU.
   switch (DtorType) {
+  case Dtor_Unified:
+    llvm_unreachable("not expecting a unified dtor");
   case Dtor_Comdat: llvm_unreachable("not expecting a COMDAT");
   case Dtor_Deleting: llvm_unreachable("already handled deleting case");
 
diff --git a/clang/lib/CodeGen/ItaniumCXXABI.cpp b/clang/lib/CodeGen/ItaniumCXXABI.cpp
index 4ed3775f156c9..4e6907ba68acf 100644
--- a/clang/lib/CodeGen/ItaniumCXXABI.cpp
+++ b/clang/lib/CodeGen/ItaniumCXXABI.cpp
@@ -91,6 +91,8 @@ class ItaniumCXXABI : public CodeGen::CGCXXABI {
 
       case Dtor_Comdat:
         llvm_unreachable("emitting dtor comdat as function?");
+      case Dtor_Unified:
+        llvm_unreachable("emitting unified dtor as function?");
       }
       llvm_unreachable("bad dtor kind");
     }
@@ -108,6 +110,9 @@ class ItaniumCXXABI : public CodeGen::CGCXXABI {
 
       case Ctor_Comdat:
         llvm_unreachable("emitting ctor comdat as function?");
+
+      case Ctor_Unified:
+        llvm_unreachable("emitting unified ctor as function?");
       }
       llvm_unreachable("bad dtor kind");
     }

>From f4a0dd86fe0888848d21d8a8525ccd50f0f4b7f0 Mon Sep 17 00:00:00 2001
From: Michael Buch <michaelbuch12 at gmail.com>
Date: Wed, 20 Aug 2025 10:19:45 +0100
Subject: [PATCH 04/20] fixup! add clang driver option

---
 clang/include/clang/Basic/DebugOptions.def    |  5 ++++-
 clang/include/clang/Driver/Options.td         |  9 +++++++++
 clang/lib/CodeGen/CGDebugInfo.cpp             |  5 ++++-
 clang/lib/Driver/ToolChains/Clang.cpp         |  4 ++++
 .../debug-info-structor-linkage-names.cpp     | 20 +++++++++++++++++--
 5 files changed, 39 insertions(+), 4 deletions(-)

diff --git a/clang/include/clang/Basic/DebugOptions.def b/clang/include/clang/Basic/DebugOptions.def
index c6e736e92744c..c2fb705662c0c 100644
--- a/clang/include/clang/Basic/DebugOptions.def
+++ b/clang/include/clang/Basic/DebugOptions.def
@@ -125,8 +125,11 @@ DEBUGOPT(DebugNameTable, 2, 0, Compatible)
 /// Whether to use DWARF base address specifiers in .debug_ranges.
 DEBUGOPT(DebugRangesBaseAddress, 1, 0, Compatible)
 
+/// Whether to add linkage names to constructor/destructor declarations.
+DEBUGOPT(DebugStructorDeclLinkageNames, 1, 0, Benign)
+
 /// Whether to embed source in DWARF debug line section.
-DEBUGOPT(EmbedSource, 1, 0, Compatible)
+DEBUGOPT(EmbedSource, 1, 1, Compatible)
 
 #undef DEBUGOPT
 #undef ENUM_DEBUGOPT
diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td
index 9cfb1bbcac5c3..ef0097f8ba334 100644
--- a/clang/include/clang/Driver/Options.td
+++ b/clang/include/clang/Driver/Options.td
@@ -4767,6 +4767,15 @@ def gembed_source : Flag<["-"], "gembed-source">, Group<g_flags_Group>,
 def gno_embed_source : Flag<["-"], "gno-embed-source">, Group<g_flags_Group>,
     Flags<[NoXarchOption]>,
     HelpText<"Restore the default behavior of not embedding source text in DWARF debug sections">;
+defm structor_decl_linkage_names
+    : BoolGOption<"structor-decl-linkage-names",
+                  CodeGenOpts<"DebugStructorDeclLinkageNames">, DefaultTrue,
+                  NegFlag<SetFalse>,
+                  PosFlag<SetTrue, [], [],
+                          "Attach linkage names to C++ constructor/destructor "
+                          "declarations in DWARF."
+                          "Implies -g.">,
+                  BothFlags<[], [ClangOption, CLOption, CC1Option]>>;
 defm key_instructions : BoolGOption<"key-instructions",
     CodeGenOpts<"DebugKeyInstructions">, DefaultFalse,
     NegFlag<SetFalse>, PosFlag<SetTrue, [], [],
diff --git a/clang/lib/CodeGen/CGDebugInfo.cpp b/clang/lib/CodeGen/CGDebugInfo.cpp
index f32e1e8185337..2ad19e9581c12 100644
--- a/clang/lib/CodeGen/CGDebugInfo.cpp
+++ b/clang/lib/CodeGen/CGDebugInfo.cpp
@@ -2181,9 +2181,12 @@ llvm::StringRef
 CGDebugInfo::GetMethodLinkageName(const CXXMethodDecl *Method) const {
   assert(Method);
 
-  bool IsCtorOrDtor =
+  const bool IsCtorOrDtor =
       isa<CXXConstructorDecl>(Method) || isa<CXXDestructorDecl>(Method);
 
+  if (IsCtorOrDtor && !CGM.getCodeGenOpts().DebugStructorDeclLinkageNames)
+    return {};
+
   // In some ABIs (particularly Itanium) a single ctor/dtor
   // corresponds to multiple functions. Attach a "unified"
   // linkage name for those (which is the convention GCC uses).
diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp
index 29b7180df5cb5..10a00f268884e 100644
--- a/clang/lib/Driver/ToolChains/Clang.cpp
+++ b/clang/lib/Driver/ToolChains/Clang.cpp
@@ -4594,6 +4594,10 @@ renderDebugOptions(const ToolChain &TC, const Driver &D, const llvm::Triple &T,
                    options::OPT_gno_key_instructions, false))
     CmdArgs.push_back("-gkey-instructions");
 
+  if (!Args.hasFlag(options::OPT_gstructor_decl_linkage_names,
+                    options::OPT_gno_structor_decl_linkage_names, true))
+    CmdArgs.push_back("-gno-structor-decl-linkage-names");
+
   if (EmitCodeView) {
     CmdArgs.push_back("-gcodeview");
 
diff --git a/clang/test/CodeGenCXX/debug-info-structor-linkage-names.cpp b/clang/test/CodeGenCXX/debug-info-structor-linkage-names.cpp
index 88b4ee75d4565..72b0bf39e0bbb 100644
--- a/clang/test/CodeGenCXX/debug-info-structor-linkage-names.cpp
+++ b/clang/test/CodeGenCXX/debug-info-structor-linkage-names.cpp
@@ -1,8 +1,21 @@
 // Tests that we emit unified constructor/destructor linkage names
 // for ABIs that support it.
 
-// RUN: %clang_cc1 -triple aarch64-apple-macosx -emit-llvm -debug-info-kind=standalone %s -o - | FileCheck %s --check-prefixes=CHECK,ITANIUM
-// RUN: %clang_cc1 -triple x86_64-windows-msvc -emit-llvm -debug-info-kind=standalone %s -o - | FileCheck %s --check-prefixes=CHECK,MSABI
+// Check that -gstructor-decl-linkage-names is the default.
+// RUN: %clang_cc1 -triple aarch64-apple-macosx -emit-llvm -debug-info-kind=standalone \
+// RUN:            %s -o - | FileCheck %s --check-prefixes=CHECK,ITANIUM
+//
+// Check with -gstructor-decl-linkage-names.
+// RUN: %clang_cc1 -triple aarch64-apple-macosx -emit-llvm -debug-info-kind=standalone \
+// RUN:            -gstructor-decl-linkage-names %s -o - | FileCheck %s --check-prefixes=CHECK,ITANIUM
+//
+// Check with -gno-structor-decl-linkage-names.
+// RUN: %clang_cc1 -triple aarch64-apple-macosx -emit-llvm -debug-info-kind=standalone \
+// RUN:            -gno-structor-decl-linkage-names %s -o - | FileCheck %s --check-prefixes=CHECK,DISABLE
+//
+// Check ABI without structor variants.
+// RUN: %clang_cc1 -triple x86_64-windows-msvc -emit-llvm -debug-info-kind=standalone \
+// RUN:            -gstructor-decl-linkage-names %s -o - | FileCheck %s --check-prefixes=CHECK,MSABI
 
 struct Base {
   Base(int x);
@@ -16,11 +29,13 @@ Base::~Base() {}
 
 // CHECK: ![[BASE_CTOR_DECL:[0-9]+]] = !DISubprogram(name: "Base"
 // MSABI-NOT:                                        linkageName:
+// DISABLE-NOT:                                      linkageName:
 // ITANIUM-SAME:                                     linkageName: "_ZN4BaseC4Ei"
 // CHECK-SAME:                                       spFlags: 0
 
 // CHECK: ![[BASE_DTOR_DECL:[0-9]+]] = !DISubprogram(name: "~Base"
 // MSABI-NOT:                                        linkageName:
+// DISABLE-NOT:                                      linkageName:
 // ITANIUM-SAME:                                     linkageName: "_ZN4BaseD4Ev"
 // CHECK-SAME:                                       spFlags: 0
 
@@ -61,6 +76,7 @@ struct Derived : public Base {
 
 // CHECK: [[BASE_INHERIT_CTOR_DECL]] = !DISubprogram(name: "Base"
 // MSABI-NOT:                                        linkageName:
+// DISABLE-NOT:                                      linkageName:
 // ITANIUM-SAME:                                     linkageName: "_ZN7DerivedCI44BaseEi"
 // CHECK-SAME                                        spFlags: 0
 

>From f4b32c64425734db78c6751d8153600d2f0538c7 Mon Sep 17 00:00:00 2001
From: Michael Buch <michaelbuch12 at gmail.com>
Date: Wed, 20 Aug 2025 14:23:06 +0100
Subject: [PATCH 05/20] fixup! fix test

---
 clang/test/DebugInfo/CXX/artificial-arg.cpp | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/clang/test/DebugInfo/CXX/artificial-arg.cpp b/clang/test/DebugInfo/CXX/artificial-arg.cpp
index a0cf131f83e15..21b8d047b3456 100644
--- a/clang/test/DebugInfo/CXX/artificial-arg.cpp
+++ b/clang/test/DebugInfo/CXX/artificial-arg.cpp
@@ -25,7 +25,8 @@ int main(int argc, char **argv) {
 // CHECK: ![[CLASSTYPE:.*]] = distinct !DICompositeType(tag: DW_TAG_class_type, name: "A",
 // CHECK-SAME:                                 identifier: "_ZTS1A"
 // CHECK: ![[ARTARG:.*]] = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: ![[CLASSTYPE]],{{.*}} DIFlagArtificial
-// CHECK: !DISubprogram(name: "A", scope: ![[CLASSTYPE]]
+// CHECK: !DISubprogram(name: "A"
+// CHECK-SAME:          scope: ![[CLASSTYPE]]
 // CHECK-SAME:          line: 12
 // CHECK-SAME:          DIFlagPublic
 // CHECK: !DISubroutineType(types: [[FUNCTYPE:![0-9]*]])

>From fd5ca533deaec0c7f5567605b260787b08b52a74 Mon Sep 17 00:00:00 2001
From: Michael Buch <michaelbuch12 at gmail.com>
Date: Wed, 20 Aug 2025 14:25:26 +0100
Subject: [PATCH 06/20] fixup! fix test

---
 clang/test/CodeGenCXX/debug-info-structor-linkage-names.cpp | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/clang/test/CodeGenCXX/debug-info-structor-linkage-names.cpp b/clang/test/CodeGenCXX/debug-info-structor-linkage-names.cpp
index 72b0bf39e0bbb..b7aac198c5180 100644
--- a/clang/test/CodeGenCXX/debug-info-structor-linkage-names.cpp
+++ b/clang/test/CodeGenCXX/debug-info-structor-linkage-names.cpp
@@ -85,4 +85,5 @@ struct Derived : public Base {
 // ITANIUM-SAME:          spFlags: DISPFlagDefinition
 // ITANIUM-SAME:          declaration: ![[BASE_INHERIT_CTOR_DECL:[0-9]+]]
 
-// MSABI: !DISubprogram(name: "~Derived"
+// MSABI:   !DISubprogram(name: "~Derived"
+// DISABLE: !DISubprogram(name: "~Derived"

>From 9ef0564b477c8209986991382e0148951c7e8ce8 Mon Sep 17 00:00:00 2001
From: Michael Buch <michaelbuch12 at gmail.com>
Date: Thu, 21 Aug 2025 08:54:57 +0100
Subject: [PATCH 07/20] fixup! add comment

---
 clang/include/clang/Basic/DebugOptions.def | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/clang/include/clang/Basic/DebugOptions.def b/clang/include/clang/Basic/DebugOptions.def
index c2fb705662c0c..a768b12fa4e0d 100644
--- a/clang/include/clang/Basic/DebugOptions.def
+++ b/clang/include/clang/Basic/DebugOptions.def
@@ -126,10 +126,13 @@ DEBUGOPT(DebugNameTable, 2, 0, Compatible)
 DEBUGOPT(DebugRangesBaseAddress, 1, 0, Compatible)
 
 /// Whether to add linkage names to constructor/destructor declarations.
+/// This is an escape hatch for cases where attaching the additional linkage
+/// names would increase debug-info size (particularly the .debug_str section)
+/// too much.
 DEBUGOPT(DebugStructorDeclLinkageNames, 1, 0, Benign)
 
 /// Whether to embed source in DWARF debug line section.
-DEBUGOPT(EmbedSource, 1, 1, Compatible)
+DEBUGOPT(EmbedSource, 1, 0, Compatible)
 
 #undef DEBUGOPT
 #undef ENUM_DEBUGOPT

>From 5926a49f235b67ef966426c4e61ade4b016c9cf3 Mon Sep 17 00:00:00 2001
From: Michael Buch <michaelbuch12 at gmail.com>
Date: Thu, 21 Aug 2025 09:22:33 +0100
Subject: [PATCH 08/20] fixup! add DocBrief

---
 clang/include/clang/Driver/Options.td | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td
index ef0097f8ba334..f701c4d9e485f 100644
--- a/clang/include/clang/Driver/Options.td
+++ b/clang/include/clang/Driver/Options.td
@@ -4775,7 +4775,10 @@ defm structor_decl_linkage_names
                           "Attach linkage names to C++ constructor/destructor "
                           "declarations in DWARF."
                           "Implies -g.">,
-                  BothFlags<[], [ClangOption, CLOption, CC1Option]>>;
+                  BothFlags<[], [ClangOption, CLOption, CC1Option]>>,
+                  DocBrief<[{On some ABIs (e.g., Itanium), constructors and destructors may have multiple variants. Historically, when generating DWARF, Clang did not attach ``DW_AT_linkage_name``s to structor DIEs because there were multiple possible manglings (depending on the structor variant) that could be used. With ``-gstructor-decl-linkage-names``, for ABIs with structor variants, we attach a "unified" mangled name to structor declarations DIEs which debuggers can use to look up all the definitions for a structor declaration. E.g., a "unified" mangled name ``_ZN3FooC4Ev`` may have multiple definitions associated with it such as ``_ZN3FooC1Ev`` and ``_ZN3FooC2Ev``.
+
+Enabling this flag results in a better interactive debugging experience (both GDB and LLDB have support for understanding these "unified" linkage names). However, it comes with a significant increase in debug-info size (particularly the `.debug_str` section). As an escape hatch, users can disable this feature using ``-gno-structor-decl-linkage-names``.}]>;
 defm key_instructions : BoolGOption<"key-instructions",
     CodeGenOpts<"DebugKeyInstructions">, DefaultFalse,
     NegFlag<SetFalse>, PosFlag<SetTrue, [], [],

>From 74917380ec7c2808857292d2521d02a88eb378f8 Mon Sep 17 00:00:00 2001
From: Michael Buch <michaelbuch12 at gmail.com>
Date: Thu, 21 Aug 2025 09:27:46 +0100
Subject: [PATCH 09/20] fixup! move test

---
 .../CXX}/debug-info-structor-linkage-names.cpp                    | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
 rename clang/test/{CodeGenCXX => DebugInfo/CXX}/debug-info-structor-linkage-names.cpp (100%)

diff --git a/clang/test/CodeGenCXX/debug-info-structor-linkage-names.cpp b/clang/test/DebugInfo/CXX/debug-info-structor-linkage-names.cpp
similarity index 100%
rename from clang/test/CodeGenCXX/debug-info-structor-linkage-names.cpp
rename to clang/test/DebugInfo/CXX/debug-info-structor-linkage-names.cpp

>From c7ce30931fb3d4b80a422bf144d531d9213a8b3b Mon Sep 17 00:00:00 2001
From: Michael Buch <michaelbuch12 at gmail.com>
Date: Mon, 4 Aug 2025 14:20:45 +0100
Subject: [PATCH 10/20] [clang][Mangle] Inject structor type into mangled name
 when mangling for LLDB JIT expressions

This patch adds special handling for `AsmLabel`s created by LLDB. LLDB
uses `AsmLabel`s to encode information about a function declaration to
make it easier to locate function symbols when JITing C++ expressions.
For constructors/destructors LLDB doesn't know at the time of creating
the `AsmLabelAttr` which structor variant the expression evaluator will
need to call (this is decided when compiling the expression). So we make
the Clang mangler inject this information into our custom label when
we're JITting the expression.
---
 clang/lib/AST/Mangle.cpp         | 33 +++++++++++++-
 clang/unittests/AST/DeclTest.cpp | 75 ++++++++++++++++++++++++++++++++
 2 files changed, 107 insertions(+), 1 deletion(-)

diff --git a/clang/lib/AST/Mangle.cpp b/clang/lib/AST/Mangle.cpp
index 0bfb51c11f0a5..1131477fa7200 100644
--- a/clang/lib/AST/Mangle.cpp
+++ b/clang/lib/AST/Mangle.cpp
@@ -152,6 +152,33 @@ bool MangleContext::shouldMangleDeclName(const NamedDecl *D) {
   return shouldMangleCXXName(D);
 }
 
+static llvm::StringRef g_lldb_func_call_label_prefix = "$__lldb_func:";
+
+/// Given an LLDB function call label, this function prints the label
+/// into \c Out, together with the structor type of \c GD (if the
+/// decl is a constructor/destructor). LLDB knows how to handle mangled
+/// names with this encoding.
+///
+/// Example input label:
+///   $__lldb_func::123:456:~Foo
+///
+/// Example output:
+///   $__lldb_func:D1:123:456:~Foo
+///
+static void emitLLDBAsmLabel(llvm::StringRef label, GlobalDecl GD,
+                             llvm::raw_ostream &Out) {
+  assert(label.starts_with(g_lldb_func_call_label_prefix));
+
+  Out << g_lldb_func_call_label_prefix;
+
+  if (llvm::isa<clang::CXXConstructorDecl>(GD.getDecl()))
+    Out << "C" << GD.getCtorType();
+  else if (llvm::isa<clang::CXXDestructorDecl>(GD.getDecl()))
+    Out << "D" << GD.getDtorType();
+
+  Out << label.substr(g_lldb_func_call_label_prefix.size());
+}
+
 void MangleContext::mangleName(GlobalDecl GD, raw_ostream &Out) {
   const ASTContext &ASTContext = getASTContext();
   const NamedDecl *D = cast<NamedDecl>(GD.getDecl());
@@ -185,7 +212,11 @@ void MangleContext::mangleName(GlobalDecl GD, raw_ostream &Out) {
     if (!UserLabelPrefix.empty())
       Out << '\01'; // LLVM IR Marker for __asm("foo")
 
-    Out << ALA->getLabel();
+    if (ALA->getLabel().starts_with(g_lldb_func_call_label_prefix))
+      emitLLDBAsmLabel(ALA->getLabel(), GD, Out);
+    else
+      Out << ALA->getLabel();
+
     return;
   }
 
diff --git a/clang/unittests/AST/DeclTest.cpp b/clang/unittests/AST/DeclTest.cpp
index 6b443918ec137..4bd7886ef9b35 100644
--- a/clang/unittests/AST/DeclTest.cpp
+++ b/clang/unittests/AST/DeclTest.cpp
@@ -16,6 +16,7 @@
 #include "clang/AST/Mangle.h"
 #include "clang/ASTMatchers/ASTMatchFinder.h"
 #include "clang/ASTMatchers/ASTMatchers.h"
+#include "clang/Basic/ABI.h"
 #include "clang/Basic/Diagnostic.h"
 #include "clang/Basic/LLVM.h"
 #include "clang/Basic/TargetInfo.h"
@@ -102,6 +103,80 @@ TEST(Decl, AsmLabelAttr) {
                      "foo");
 }
 
+TEST(Decl, AsmLabelAttr_LLDB) {
+  StringRef Code = R"(
+    struct S {
+      void f() {}
+      S() = default;
+      ~S() = default;
+    };
+  )";
+  auto AST =
+      tooling::buildASTFromCodeWithArgs(Code, {"-target", "i386-apple-darwin"});
+  ASTContext &Ctx = AST->getASTContext();
+  assert(Ctx.getTargetInfo().getUserLabelPrefix() == StringRef("_") &&
+         "Expected target to have a global prefix");
+  DiagnosticsEngine &Diags = AST->getDiagnostics();
+
+  const auto *DeclS =
+      selectFirst<CXXRecordDecl>("d", match(cxxRecordDecl().bind("d"), Ctx));
+
+  auto *DeclF = *DeclS->method_begin();
+  auto *Ctor = *DeclS->ctor_begin();
+  auto *Dtor = DeclS->getDestructor();
+
+  ASSERT_TRUE(DeclF);
+  ASSERT_TRUE(Ctor);
+  ASSERT_TRUE(Dtor);
+
+  DeclF->addAttr(AsmLabelAttr::Create(Ctx, "$__lldb_func::123:123:_Z1fv"));
+  Ctor->addAttr(AsmLabelAttr::Create(Ctx, "$__lldb_func::123:123:S"));
+  Dtor->addAttr(AsmLabelAttr::Create(Ctx, "$__lldb_func::123:123:~S"));
+
+  std::unique_ptr<ItaniumMangleContext> MC(
+      ItaniumMangleContext::create(Ctx, Diags));
+
+  {
+    std::string Mangled;
+    llvm::raw_string_ostream OS_Mangled(Mangled);
+    MC->mangleName(DeclF, OS_Mangled);
+
+    ASSERT_EQ(Mangled, "\x01$__lldb_func::123:123:_Z1fv");
+  };
+
+  {
+    std::string Mangled;
+    llvm::raw_string_ostream OS_Mangled(Mangled);
+    MC->mangleName(GlobalDecl(Ctor, CXXCtorType::Ctor_Complete), OS_Mangled);
+
+    ASSERT_EQ(Mangled, "\x01$__lldb_func:C0:123:123:S");
+  };
+
+  {
+    std::string Mangled;
+    llvm::raw_string_ostream OS_Mangled(Mangled);
+    MC->mangleName(GlobalDecl(Ctor, CXXCtorType::Ctor_Base), OS_Mangled);
+
+    ASSERT_EQ(Mangled, "\x01$__lldb_func:C1:123:123:S");
+  };
+
+  {
+    std::string Mangled;
+    llvm::raw_string_ostream OS_Mangled(Mangled);
+    MC->mangleName(GlobalDecl(Dtor, CXXDtorType::Dtor_Deleting), OS_Mangled);
+
+    ASSERT_EQ(Mangled, "\x01$__lldb_func:D0:123:123:~S");
+  };
+
+  {
+    std::string Mangled;
+    llvm::raw_string_ostream OS_Mangled(Mangled);
+    MC->mangleName(GlobalDecl(Dtor, CXXDtorType::Dtor_Base), OS_Mangled);
+
+    ASSERT_EQ(Mangled, "\x01$__lldb_func:D2:123:123:~S");
+  };
+}
+
 TEST(Decl, MangleDependentSizedArray) {
   StringRef Code = R"(
     template <int ...N>

>From 3b1e5146bd094dd221b829ae1da517d9ac75c9fe Mon Sep 17 00:00:00 2001
From: Michael Buch <michaelbuch12 at gmail.com>
Date: Mon, 4 Aug 2025 07:49:40 +0100
Subject: [PATCH 11/20] [ItaniumDemangle] Add getter for constructor/destructor
 variants

This patch adds a way to retrieve the constructor/destructor variant
from the Itanium demangle tree. This will be used by LLDB.
---
 libcxxabi/src/demangle/ItaniumDemangle.h     |  2 ++
 llvm/include/llvm/Demangle/Demangle.h        |  3 +++
 llvm/include/llvm/Demangle/ItaniumDemangle.h |  2 ++
 llvm/lib/Demangle/ItaniumDemangle.cpp        | 10 +++++++---
 4 files changed, 14 insertions(+), 3 deletions(-)

diff --git a/libcxxabi/src/demangle/ItaniumDemangle.h b/libcxxabi/src/demangle/ItaniumDemangle.h
index 6f27da7b9cadf..7b3983bc89367 100644
--- a/libcxxabi/src/demangle/ItaniumDemangle.h
+++ b/libcxxabi/src/demangle/ItaniumDemangle.h
@@ -1766,6 +1766,8 @@ class CtorDtorName final : public Node {
 
   template<typename Fn> void match(Fn F) const { F(Basename, IsDtor, Variant); }
 
+  int getVariant() const { return Variant; }
+
   void printLeft(OutputBuffer &OB) const override {
     if (IsDtor)
       OB += "~";
diff --git a/llvm/include/llvm/Demangle/Demangle.h b/llvm/include/llvm/Demangle/Demangle.h
index d9b08b2d856dc..6af8fad9ceb86 100644
--- a/llvm/include/llvm/Demangle/Demangle.h
+++ b/llvm/include/llvm/Demangle/Demangle.h
@@ -127,6 +127,9 @@ struct ItaniumPartialDemangler {
   /// If this symbol describes a constructor or destructor.
   DEMANGLE_ABI bool isCtorOrDtor() const;
 
+  /// If this symbol describes a constructor or destructor.
+  std::optional<int> getCtorOrDtorVariant() const;
+
   /// If this symbol describes a function.
   DEMANGLE_ABI bool isFunction() const;
 
diff --git a/llvm/include/llvm/Demangle/ItaniumDemangle.h b/llvm/include/llvm/Demangle/ItaniumDemangle.h
index 62d427c3966bb..c0db02f8e7fef 100644
--- a/llvm/include/llvm/Demangle/ItaniumDemangle.h
+++ b/llvm/include/llvm/Demangle/ItaniumDemangle.h
@@ -1766,6 +1766,8 @@ class CtorDtorName final : public Node {
 
   template<typename Fn> void match(Fn F) const { F(Basename, IsDtor, Variant); }
 
+  int getVariant() const { return Variant; }
+
   void printLeft(OutputBuffer &OB) const override {
     if (IsDtor)
       OB += "~";
diff --git a/llvm/lib/Demangle/ItaniumDemangle.cpp b/llvm/lib/Demangle/ItaniumDemangle.cpp
index 1009cc91ca12a..a5d7a5576fccf 100644
--- a/llvm/lib/Demangle/ItaniumDemangle.cpp
+++ b/llvm/lib/Demangle/ItaniumDemangle.cpp
@@ -560,13 +560,17 @@ bool ItaniumPartialDemangler::hasFunctionQualifiers() const {
 }
 
 bool ItaniumPartialDemangler::isCtorOrDtor() const {
+  return getCtorOrDtorVariant().has_value();
+}
+
+std::optional<int> ItaniumPartialDemangler::getCtorOrDtorVariant() const {
   const Node *N = static_cast<const Node *>(RootNode);
   while (N) {
     switch (N->getKind()) {
     default:
-      return false;
+      return std::nullopt;
     case Node::KCtorDtorName:
-      return true;
+      return static_cast<const CtorDtorName *>(N)->getVariant();
 
     case Node::KAbiTagAttr:
       N = static_cast<const AbiTagAttr *>(N)->Base;
@@ -588,7 +592,7 @@ bool ItaniumPartialDemangler::isCtorOrDtor() const {
       break;
     }
   }
-  return false;
+  return std::nullopt;
 }
 
 bool ItaniumPartialDemangler::isFunction() const {

>From a428f041502bf9ec59ce4465bd215f8bfe326945 Mon Sep 17 00:00:00 2001
From: Michael Buch <michaelbuch12 at gmail.com>
Date: Mon, 4 Aug 2025 14:21:16 +0100
Subject: [PATCH 12/20] [lldb][Expression] Encode structor variant into
 FunctionCallLabel

This patch is an implementation of [this discussion](https://discourse.llvm.org/t/rfc-lldb-handling-abi-tagged-constructors-destructors-in-expression-evaluator/82816/7) about handling ABI-tagged structors during expression evaluation.

**Motivation**

LLDB encodes the mangled name of a `DW_TAG_subprogram` into `AsmLabel`s on function and method Clang AST nodes. This means that when calls to these functions get lowered into IR (when running JITted expressions), the address resolver can locate the appropriate symbol by mangled name (and it's guaranteed to find the symbol because we got the mangled name from debug-info, instead of letting Clang mangle it based on AST structure). However, we don't do this for `CXXConstructorDecl`s/`CXXDestructorDecl`s because these structor declarations in DWARF don't have a linkage name. This is because there can be multiple variants of a structor, each with a distinct mangling in the Itanium ABI. Each structor variant has its own definition `DW_TAG_subprogram`. So LLDB doesn't know which mangled name to put into the `AsmLabel`.

Currently this means using an ABI-tagged constructor in LLDB expressions won't work (see [this RFC](https://discourse.llvm.org/t/rfc-lldb-handling-abi-tagged-constructors-destructors-in-expression-evaluator/82816) for concrete examples).

**Proposed Solution**

The `FunctionCallLabel` encoding that we put into `AsmLabel`s already supports stuffing more info about a DIE into it. So this patch extends the `FunctionCallLabel` to contain an optional discriminator (a sequence of bytes) which the `SymbolFileDWARF` plugin interprets as the constructor/destructor variant of that DIE.

There's a few subtleties here:
1. At the point at which LLDB first constructs the label, it has no way of knowing (just by looking the debug-info declaration), which structor variant the expression evaluator is supposed to call. That's something that gets decided when compiling the expression. So we let the Clang mangler inject the correct structor variant into the `AsmLabel` during JITing. I adjusted the `AsmLabelAttr` mangling for this. An option would've been to create a new Clang attribute which behaved like an `AsmLabel` but with these special semantics for LLDB. My main concern there is that we'd have to adjust all the `AsmLabelAttr` checks around Clang to also now account for this new attribute.
2. The compiler is free to omit the `C1` variant of a constructor if the `C2` variant is sufficient. In that case it may alias `C1` to `C2`, leaving us with only the `C2` `DW_TAG_subprogram` in the object file. Linux is one of the platforms where this occurs. For those cases there's heuristic in `SymbolFileDWARF` where we pick `C2` if we asked for `C1` but it doesn't exist. This may not always be correct (if for some reason the compiler decided to drop `C1` for other reasons).
---
 lldb/include/lldb/Expression/Expression.h     |   8 +-
 lldb/source/Expression/Expression.cpp         |  29 ++--
 .../SymbolFile/DWARF/DWARFASTParserClang.cpp  |  47 +++++-
 .../SymbolFile/DWARF/SymbolFileDWARF.cpp      | 137 ++++++++++++++++--
 .../SymbolFile/DWARF/SymbolFileDWARF.h        |   3 +-
 .../API/lang/cpp/abi_tag_structors/Makefile   |   3 +
 .../abi_tag_structors/TestAbiTagStructors.py  |  30 ++++
 .../API/lang/cpp/abi_tag_structors/main.cpp   |  38 +++++
 lldb/unittests/Expression/ExpressionTest.cpp  |  37 +++--
 lldb/unittests/Symbol/TestTypeSystemClang.cpp |  14 +-
 10 files changed, 291 insertions(+), 55 deletions(-)
 create mode 100644 lldb/test/API/lang/cpp/abi_tag_structors/Makefile
 create mode 100644 lldb/test/API/lang/cpp/abi_tag_structors/TestAbiTagStructors.py
 create mode 100644 lldb/test/API/lang/cpp/abi_tag_structors/main.cpp

diff --git a/lldb/include/lldb/Expression/Expression.h b/lldb/include/lldb/Expression/Expression.h
index 20067f469895b..847226167d584 100644
--- a/lldb/include/lldb/Expression/Expression.h
+++ b/lldb/include/lldb/Expression/Expression.h
@@ -103,11 +103,15 @@ class Expression {
 ///
 /// The format being:
 ///
-///   <prefix>:<module uid>:<symbol uid>:<name>
+///   <prefix>:<discriminator>:<module uid>:<symbol uid>:<name>
 ///
 /// The label string needs to stay valid for the entire lifetime
 /// of this object.
 struct FunctionCallLabel {
+  /// Arbitrary string which language plugins can interpret for their
+  /// own needs.
+  llvm::StringRef discriminator;
+
   /// Unique identifier of the lldb_private::Module
   /// which contains the symbol identified by \c symbol_id.
   lldb::user_id_t module_id;
@@ -133,7 +137,7 @@ struct FunctionCallLabel {
   ///
   /// The representation roundtrips through \c fromString:
   /// \code{.cpp}
-  /// llvm::StringRef encoded = "$__lldb_func:0x0:0x0:_Z3foov";
+  /// llvm::StringRef encoded = "$__lldb_func:blah:0x0:0x0:_Z3foov";
   /// FunctionCallLabel label = *fromString(label);
   ///
   /// assert (label.toString() == encoded);
diff --git a/lldb/source/Expression/Expression.cpp b/lldb/source/Expression/Expression.cpp
index 796851ff15ca3..16ecb1d7deef8 100644
--- a/lldb/source/Expression/Expression.cpp
+++ b/lldb/source/Expression/Expression.cpp
@@ -34,10 +34,10 @@ Expression::Expression(ExecutionContextScope &exe_scope)
 
 llvm::Expected<FunctionCallLabel>
 lldb_private::FunctionCallLabel::fromString(llvm::StringRef label) {
-  llvm::SmallVector<llvm::StringRef, 4> components;
-  label.split(components, ":", /*MaxSplit=*/3);
+  llvm::SmallVector<llvm::StringRef, 5> components;
+  label.split(components, ":", /*MaxSplit=*/4);
 
-  if (components.size() != 4)
+  if (components.size() != 5)
     return llvm::createStringError("malformed function call label.");
 
   if (components[0] != FunctionCallLabelPrefix)
@@ -45,8 +45,10 @@ lldb_private::FunctionCallLabel::fromString(llvm::StringRef label) {
         "expected function call label prefix '{0}' but found '{1}' instead.",
         FunctionCallLabelPrefix, components[0]));
 
-  llvm::StringRef module_label = components[1];
-  llvm::StringRef die_label = components[2];
+  llvm::StringRef discriminator = components[1];
+  llvm::StringRef module_label = components[2];
+  llvm::StringRef die_label = components[3];
+  llvm::StringRef lookup_name = components[4];
 
   lldb::user_id_t module_id = 0;
   if (!llvm::to_integer(module_label, module_id))
@@ -58,20 +60,23 @@ lldb_private::FunctionCallLabel::fromString(llvm::StringRef label) {
     return llvm::createStringError(
         llvm::formatv("failed to parse symbol ID from '{0}'.", die_label));
 
-  return FunctionCallLabel{/*.module_id=*/module_id,
+  return FunctionCallLabel{/*.discriminator=*/discriminator,
+                           /*.module_id=*/module_id,
                            /*.symbol_id=*/die_id,
-                           /*.lookup_name=*/components[3]};
+                           /*.lookup_name=*/lookup_name};
 }
 
 std::string lldb_private::FunctionCallLabel::toString() const {
-  return llvm::formatv("{0}:{1:x}:{2:x}:{3}", FunctionCallLabelPrefix,
-                       module_id, symbol_id, lookup_name)
+  return llvm::formatv("{0}:{1}:{2:x}:{3:x}:{4}", FunctionCallLabelPrefix,
+                       discriminator, module_id, symbol_id, lookup_name)
       .str();
 }
 
 void llvm::format_provider<FunctionCallLabel>::format(
     const FunctionCallLabel &label, raw_ostream &OS, StringRef Style) {
-  OS << llvm::formatv("FunctionCallLabel{ module_id: {0:x}, symbol_id: {1:x}, "
-                      "lookup_name: {2} }",
-                      label.module_id, label.symbol_id, label.lookup_name);
+  OS << llvm::formatv("FunctionCallLabel{ discriminator: {0}, module_id: "
+                      "{1:x}, symbol_id: {2:x}, "
+                      "lookup_name: {3} }",
+                      label.discriminator, label.module_id, label.symbol_id,
+                      label.lookup_name);
 }
diff --git a/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp b/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp
index a429ea848b7f7..cc04f1d7879ed 100644
--- a/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp
+++ b/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp
@@ -251,11 +251,52 @@ static unsigned GetCXXMethodCVQuals(const DWARFDIE &subprogram,
   return cv_quals;
 }
 
+static const char *GetMangledOrStructorName(const DWARFDIE &die) {
+  const char *name = die.GetMangledName(/*substitute_name_allowed*/ false);
+  if (name)
+    return name;
+
+  name = die.GetName();
+  if (!name)
+    return nullptr;
+
+  DWARFDIE parent = die.GetParent();
+  if (!parent.IsStructUnionOrClass())
+    return nullptr;
+
+  const char *parent_name = parent.GetName();
+  if (!parent_name)
+    return nullptr;
+
+  // Constructor.
+  if (::strcmp(parent_name, name) == 0)
+    return name;
+
+  // Destructor.
+  if (name[0] == '~' && ::strcmp(parent_name, name + 1) == 0)
+    return name;
+
+  return nullptr;
+}
+
 static std::string MakeLLDBFuncAsmLabel(const DWARFDIE &die) {
-  char const *name = die.GetMangledName(/*substitute_name_allowed*/ false);
+  char const *name = GetMangledOrStructorName(die);
   if (!name)
     return {};
 
+  auto *cu = die.GetCU();
+  if (!cu)
+    return {};
+
+  // FIXME: When resolving function call labels, we check that
+  // that the definition's DW_AT_specification points to the
+  // declaration that we encoded into the label here. But if the
+  // declaration came from a type-unit (and the definition from
+  // .debug_info), that check won't work. So for now, don't use
+  // function call labels for declaration DIEs from type-units.
+  if (cu->IsTypeUnit())
+    return {};
+
   SymbolFileDWARF *dwarf = die.GetDWARF();
   if (!dwarf)
     return {};
@@ -286,7 +327,9 @@ static std::string MakeLLDBFuncAsmLabel(const DWARFDIE &die) {
   if (die_id == LLDB_INVALID_UID)
     return {};
 
-  return FunctionCallLabel{/*module_id=*/module_id,
+  // Note, discriminator is added by Clang during mangling.
+  return FunctionCallLabel{/*discriminator=*/{},
+                           /*module_id=*/module_id,
                            /*symbol_id=*/die_id,
                            /*.lookup_name=*/name}
       .toString();
diff --git a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp
index 9958af26379b9..30426a037eca9 100644
--- a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp
+++ b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp
@@ -7,7 +7,9 @@
 //===----------------------------------------------------------------------===//
 
 #include "SymbolFileDWARF.h"
+#include "clang/Basic/ABI.h"
 #include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/StringExtras.h"
 #include "llvm/DebugInfo/DWARF/DWARFAddressRange.h"
 #include "llvm/DebugInfo/DWARF/DWARFDebugLoc.h"
 #include "llvm/Support/Casting.h"
@@ -78,6 +80,7 @@
 
 #include "llvm/DebugInfo/DWARF/DWARFContext.h"
 #include "llvm/DebugInfo/DWARF/DWARFDebugAbbrev.h"
+#include "llvm/Demangle/Demangle.h"
 #include "llvm/Support/FileSystem.h"
 #include "llvm/Support/FormatVariadic.h"
 
@@ -2483,28 +2486,132 @@ bool SymbolFileDWARF::ResolveFunction(const DWARFDIE &orig_die,
   return false;
 }
 
-DWARFDIE
-SymbolFileDWARF::FindFunctionDefinition(const FunctionCallLabel &label) {
+static int ClangToItaniumCtorKind(clang::CXXCtorType kind) {
+  switch (kind) {
+  case clang::CXXCtorType::Ctor_Complete:
+    return 1;
+  case clang::CXXCtorType::Ctor_Base:
+    return 2;
+  case clang::CXXCtorType::Ctor_CopyingClosure:
+  case clang::CXXCtorType::Ctor_DefaultClosure:
+  case clang::CXXCtorType::Ctor_Comdat:
+    llvm_unreachable("Unexpected constructor kind.");
+  }
+}
+
+static int ClangToItaniumDtorKind(clang::CXXDtorType kind) {
+  switch (kind) {
+  case clang::CXXDtorType::Dtor_Deleting:
+    return 0;
+  case clang::CXXDtorType::Dtor_Complete:
+    return 1;
+  case clang::CXXDtorType::Dtor_Base:
+    return 2;
+  case clang::CXXDtorType::Dtor_Comdat:
+    llvm_unreachable("Unexpected destructor kind.");
+  }
+}
+
+static std::optional<int>
+GetItaniumCtorDtorVariant(llvm::StringRef discriminator) {
+  const bool is_ctor = discriminator.consume_front("C");
+  if (!is_ctor && !discriminator.consume_front("D"))
+    return std::nullopt;
+
+  uint64_t structor_kind;
+  if (!llvm::to_integer(discriminator, structor_kind))
+    return std::nullopt;
+
+  if (is_ctor) {
+    if (structor_kind > clang::CXXCtorType::Ctor_DefaultClosure)
+      return std::nullopt;
+
+    return ClangToItaniumCtorKind(
+        static_cast<clang::CXXCtorType>(structor_kind));
+  }
+
+  if (structor_kind > clang::CXXDtorType::Dtor_Comdat)
+    return std::nullopt;
+
+  return ClangToItaniumDtorKind(static_cast<clang::CXXDtorType>(structor_kind));
+}
+
+DWARFDIE SymbolFileDWARF::FindFunctionDefinition(const FunctionCallLabel &label,
+                                                 const DWARFDIE &declaration) {
   DWARFDIE definition;
+  llvm::DenseMap<int, DWARFDIE> structor_variant_to_die;
+
+  // eFunctionNameTypeFull for mangled name lookup.
+  // eFunctionNameTypeMethod is required for structor lookups (since we look
+  // those up by DW_AT_name).
   Module::LookupInfo info(ConstString(label.lookup_name),
-                          lldb::eFunctionNameTypeFull,
+                          lldb::eFunctionNameTypeFull |
+                              lldb::eFunctionNameTypeMethod,
                           lldb::eLanguageTypeUnknown);
 
   m_index->GetFunctions(info, *this, {}, [&](DWARFDIE entry) {
     if (entry.GetAttributeValueAsUnsigned(llvm::dwarf::DW_AT_declaration, 0))
       return IterationAction::Continue;
 
-    // We don't check whether the specification DIE for this function
-    // corresponds to the declaration DIE because the declaration might be in
-    // a type-unit but the definition in the compile-unit (and it's
-    // specifcation would point to the declaration in the compile-unit). We
-    // rely on the mangled name within the module to be enough to find us the
-    // unique definition.
-    definition = entry;
-    return IterationAction::Stop;
+    auto spec = entry.GetAttributeValueAsReferenceDIE(DW_AT_specification);
+    if (!spec)
+      return IterationAction::Continue;
+
+    if (spec != declaration)
+      return IterationAction::Continue;
+
+    // We're not picking a specific structor variant DIE, so we're done here.
+    if (label.discriminator.empty()) {
+      definition = entry;
+      return IterationAction::Stop;
+    }
+
+    const char *mangled =
+        entry.GetMangledName(/*substitute_name_allowed=*/false);
+    if (!mangled)
+      return IterationAction::Continue;
+
+    llvm::ItaniumPartialDemangler D;
+    if (D.partialDemangle(mangled))
+      return IterationAction::Continue;
+
+    auto structor_variant = D.getCtorOrDtorVariant();
+    if (!structor_variant)
+      return IterationAction::Continue;
+
+    auto [_, inserted] = structor_variant_to_die.try_emplace(*structor_variant,
+                                                             std::move(entry));
+    assert(inserted);
+
+    // The compiler may choose to alias the constructor variants
+    // (notably this happens on Linux), so we might not have a definition
+    // DIE for some structor variants. Hence we iterate over all variants
+    // and pick the most appropriate one out of those.
+    return IterationAction::Continue;
   });
 
-  return definition;
+  if (definition.IsValid())
+    return definition;
+
+  auto label_variant = GetItaniumCtorDtorVariant(label.discriminator);
+  if (!label_variant)
+    return {};
+
+  auto it = structor_variant_to_die.find(*label_variant);
+
+  // Found the exact variant.
+  if (it != structor_variant_to_die.end())
+    return it->getSecond();
+
+  // We need a C1 constructor. If debug-info only contains a DIE for C2,
+  // assume C1 was aliased to C2.
+  if (!label.lookup_name.starts_with("~") && label_variant == 1) {
+    if (auto it = structor_variant_to_die.find(2);
+        it != structor_variant_to_die.end())
+      return it->getSecond();
+  }
+
+  return {};
 }
 
 llvm::Expected<SymbolContext>
@@ -2519,11 +2626,9 @@ SymbolFileDWARF::ResolveFunctionCallLabel(const FunctionCallLabel &label) {
   // Label was created using a declaration DIE. Need to fetch the definition
   // to resolve the function call.
   if (die.GetAttributeValueAsUnsigned(llvm::dwarf::DW_AT_declaration, 0)) {
-    auto definition = FindFunctionDefinition(label);
-    if (!definition)
+    die = FindFunctionDefinition(label, die);
+    if (!die.IsValid())
       return llvm::createStringError("failed to find definition DIE");
-
-    die = std::move(definition);
   }
 
   SymbolContextList sc_list;
diff --git a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.h b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.h
index d7db8a3c0869f..6ba2592314c36 100644
--- a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.h
+++ b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.h
@@ -378,7 +378,8 @@ class SymbolFileDWARF : public SymbolFileCommon {
   /// SymbolFile.
   ///
   /// \returns A valid definition DIE on success.
-  DWARFDIE FindFunctionDefinition(const FunctionCallLabel &label);
+  DWARFDIE FindFunctionDefinition(const FunctionCallLabel &label,
+                                  const DWARFDIE &declaration);
 
 protected:
   SymbolFileDWARF(const SymbolFileDWARF &) = delete;
diff --git a/lldb/test/API/lang/cpp/abi_tag_structors/Makefile b/lldb/test/API/lang/cpp/abi_tag_structors/Makefile
new file mode 100644
index 0000000000000..99998b20bcb05
--- /dev/null
+++ b/lldb/test/API/lang/cpp/abi_tag_structors/Makefile
@@ -0,0 +1,3 @@
+CXX_SOURCES := main.cpp
+
+include Makefile.rules
diff --git a/lldb/test/API/lang/cpp/abi_tag_structors/TestAbiTagStructors.py b/lldb/test/API/lang/cpp/abi_tag_structors/TestAbiTagStructors.py
new file mode 100644
index 0000000000000..b549736c1405d
--- /dev/null
+++ b/lldb/test/API/lang/cpp/abi_tag_structors/TestAbiTagStructors.py
@@ -0,0 +1,30 @@
+"""
+Test that we can call structors/destructors
+annotated (and thus mangled) with ABI tags.
+"""
+
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+
+
+class AbiTagStructorsTestCase(TestBase):
+    def test_abi_tag_lookup(self):
+        self.build()
+        lldbutil.run_to_source_breakpoint(
+            self, "Break here", lldb.SBFileSpec("main.cpp", False)
+        )
+
+        self.expect_expr("Tagged()", result_type="Tagged")
+        self.expect_expr("t1 = t2", result_type="Tagged")
+
+        self.expect("expr Tagged t3(t1)", error=False)
+        self.expect("expr t1.~Tagged()", error=False)
+
+        # Calls to deleting and base object destructor variants (D0 and D2 in Itanium ABI)
+        self.expect_expr(
+            "struct D : public HasVirtualDtor {}; D d; d.func()",
+            result_type="int",
+            result_value="10",
+        )
diff --git a/lldb/test/API/lang/cpp/abi_tag_structors/main.cpp b/lldb/test/API/lang/cpp/abi_tag_structors/main.cpp
new file mode 100644
index 0000000000000..b7783ccc49460
--- /dev/null
+++ b/lldb/test/API/lang/cpp/abi_tag_structors/main.cpp
@@ -0,0 +1,38 @@
+#include <cstdio>
+
+struct Tagged {
+  [[gnu::abi_tag("Default")]] Tagged() { std::puts(__func__); }
+  [[gnu::abi_tag("Copy")]] Tagged(const Tagged &) { std::puts(__func__); }
+  [[gnu::abi_tag("CopyAssign")]] Tagged &operator=(const Tagged &) {
+    std::puts(__func__);
+    return *this;
+  }
+  [[gnu::abi_tag("Dtor")]] ~Tagged() { std::puts(__func__); }
+};
+
+struct Base {
+  virtual ~Base() { std::puts(__func__); }
+  virtual int func() { return 5; }
+};
+
+struct HasVirtualDtor : public Base {
+  int func() override { return 10; }
+
+  [[gnu::abi_tag("VirtualDtor")]] ~HasVirtualDtor() override {
+    std::puts(__func__);
+  }
+};
+
+int main() {
+  Tagged t1;
+  Tagged t2(t1);
+  t1 = t2;
+
+  Base b;
+  HasVirtualDtor vdtor;
+  vdtor.func();
+
+  std::puts("Break here");
+
+  return 0;
+}
diff --git a/lldb/unittests/Expression/ExpressionTest.cpp b/lldb/unittests/Expression/ExpressionTest.cpp
index 12f6dd515fd11..ceb567c28ab99 100644
--- a/lldb/unittests/Expression/ExpressionTest.cpp
+++ b/lldb/unittests/Expression/ExpressionTest.cpp
@@ -23,15 +23,15 @@ struct LabelTestCase {
 
 static LabelTestCase g_label_test_cases[] = {
     // Failure modes
-    {"bar:0x0:0x0:_Z3foov",
+    {"bar:blah:0x0:0x0:_Z3foov",
      {},
      {"expected function call label prefix '$__lldb_func' but found 'bar' "
       "instead."}},
-    {"$__lldb_func :0x0:0x0:_Z3foov",
+    {"$__lldb_func :blah:0x0:0x0:_Z3foov",
      {},
      {"expected function call label prefix '$__lldb_func' but found "
       "'$__lldb_func ' instead."}},
-    {"$__lldb_funcc:0x0:0x0:_Z3foov",
+    {"$__lldb_funcc:blah:0x0:0x0:_Z3foov",
      {},
      {"expected function call label prefix '$__lldb_func' but found "
       "'$__lldb_funcc' instead."}},
@@ -39,47 +39,52 @@ static LabelTestCase g_label_test_cases[] = {
     {"foo", {}, {"malformed function call label."}},
     {"$__lldb_func", {}, {"malformed function call label."}},
     {"$__lldb_func:", {}, {"malformed function call label."}},
-    {"$__lldb_func:0x0:0x0", {}, {"malformed function call label."}},
-    {"$__lldb_func:abc:0x0:_Z3foov",
+    {"$__lldb_func:blah", {}, {"malformed function call label."}},
+    {"$__lldb_func:blah:0x0", {}, {"malformed function call label."}},
+    {"$__lldb_func:111:0x0:0x0", {}, {"malformed function call label."}},
+    {"$__lldb_func:111:abc:0x0:_Z3foov",
      {},
      {"failed to parse module ID from 'abc'."}},
-    {"$__lldb_func:-1:0x0:_Z3foov",
+    {"$__lldb_func:111:-1:0x0:_Z3foov",
      {},
      {"failed to parse module ID from '-1'."}},
-    {"$__lldb_func:0x0invalid:0x0:_Z3foov",
+    {"$__lldb_func:111:0x0invalid:0x0:_Z3foov",
      {},
      {"failed to parse module ID from '0x0invalid'."}},
-    {"$__lldb_func:0x0 :0x0:_Z3foov",
+    {"$__lldb_func:111:0x0 :0x0:_Z3foov",
      {},
      {"failed to parse module ID from '0x0 '."}},
-    {"$__lldb_func:0x0:abc:_Z3foov",
+    {"$__lldb_func:blah:0x0:abc:_Z3foov",
      {},
      {"failed to parse symbol ID from 'abc'."}},
-    {"$__lldb_func:0x5:-1:_Z3foov",
+    {"$__lldb_func:blah:0x5:-1:_Z3foov",
      {},
      {"failed to parse symbol ID from '-1'."}},
-    {"$__lldb_func:0x5:0x0invalid:_Z3foov",
+    {"$__lldb_func:blah:0x5:0x0invalid:_Z3foov",
      {},
      {"failed to parse symbol ID from '0x0invalid'."}},
-    {"$__lldb_func:0x5:0x0 :_Z3foov",
+    {"$__lldb_func:blah:0x5:0x0 :_Z3foov",
      {},
      {"failed to parse symbol ID from '0x0 '."}},
-    {"$__lldb_func:0x0:0x0:_Z3foov",
+    {"$__lldb_func:blah:0x0:0x0:_Z3foov",
      {
+         /*.discriminator=*/"blah",
          /*.module_id=*/0x0,
          /*.symbol_id=*/0x0,
          /*.lookup_name=*/"_Z3foov",
      },
      {}},
-    {"$__lldb_func:0x0:0x0:abc:def:::a",
+    {"$__lldb_func::0x0:0x0:abc:def:::a",
      {
+         /*.discriminator=*/"",
          /*.module_id=*/0x0,
          /*.symbol_id=*/0x0,
          /*.lookup_name=*/"abc:def:::a",
      },
      {}},
-    {"$__lldb_func:0xd2:0xf0:$__lldb_func",
+    {"$__lldb_func:0x45:0xd2:0xf0:$__lldb_func",
      {
+         /*.discriminator=*/"0x45",
          /*.module_id=*/0xd2,
          /*.symbol_id=*/0xf0,
          /*.lookup_name=*/"$__lldb_func",
@@ -106,6 +111,7 @@ TEST_P(ExpressionTestFixture, FunctionCallLabel) {
   EXPECT_EQ(decoded_or_err->toString(), encoded);
   EXPECT_EQ(label_str, encoded);
 
+  EXPECT_EQ(decoded_or_err->discriminator, label.discriminator);
   EXPECT_EQ(decoded_or_err->module_id, label.module_id);
   EXPECT_EQ(decoded_or_err->symbol_id, label.symbol_id);
   EXPECT_EQ(decoded_or_err->lookup_name, label.lookup_name);
@@ -113,6 +119,7 @@ TEST_P(ExpressionTestFixture, FunctionCallLabel) {
   auto roundtrip_or_err = FunctionCallLabel::fromString(label_str);
   EXPECT_THAT_EXPECTED(roundtrip_or_err, llvm::Succeeded());
 
+  EXPECT_EQ(roundtrip_or_err->discriminator, label.discriminator);
   EXPECT_EQ(roundtrip_or_err->module_id, label.module_id);
   EXPECT_EQ(roundtrip_or_err->symbol_id, label.symbol_id);
   EXPECT_EQ(roundtrip_or_err->lookup_name, label.lookup_name);
diff --git a/lldb/unittests/Symbol/TestTypeSystemClang.cpp b/lldb/unittests/Symbol/TestTypeSystemClang.cpp
index b993b82612497..f673cceae00dd 100644
--- a/lldb/unittests/Symbol/TestTypeSystemClang.cpp
+++ b/lldb/unittests/Symbol/TestTypeSystemClang.cpp
@@ -1150,12 +1150,12 @@ TEST_F(TestTypeSystemClang, AsmLabel_CtorDtor) {
       is_explicit, is_attr_used, is_artificial);
 
   auto *ctor = m_ast->AddMethodToCXXRecordType(
-      t.GetOpaqueQualType(), "S", /*asm_label=*/"$__lldb_func:0x0:0x0:S",
+      t.GetOpaqueQualType(), "S", /*asm_label=*/"$__lldb_func::0x0:0x0:S",
       function_type, lldb::AccessType::eAccessPublic, is_virtual, is_static,
       is_inline, is_explicit, is_attr_used, is_artificial);
 
   auto *dtor = m_ast->AddMethodToCXXRecordType(
-      t.GetOpaqueQualType(), "~S", /*asm_label=*/"$__lldb_func:0x0:0x0:~S",
+      t.GetOpaqueQualType(), "~S", /*asm_label=*/"$__lldb_func::0x0:0x0:~S",
       function_type, lldb::AccessType::eAccessPublic, is_virtual, is_static,
       is_inline, is_explicit, is_attr_used, is_artificial);
 
@@ -1181,11 +1181,11 @@ TEST_F(TestTypeSystemClang, AsmLabel_CtorDtor) {
   EXPECT_STREQ(llvm::GlobalValue::dropLLVMManglingEscape(
                    m_ast->DeclGetMangledName(ctor).GetStringRef())
                    .data(),
-               "$__lldb_func:0x0:0x0:S");
+               "$__lldb_func:C0:0x0:0x0:S");
   EXPECT_STREQ(llvm::GlobalValue::dropLLVMManglingEscape(
                    m_ast->DeclGetMangledName(dtor).GetStringRef())
                    .data(),
-               "$__lldb_func:0x0:0x0:~S");
+               "$__lldb_func:D1:0x0:0x0:~S");
 }
 
 struct AsmLabelTestCase {
@@ -1215,10 +1215,10 @@ class TestTypeSystemClangAsmLabel
 };
 
 static AsmLabelTestCase g_asm_label_test_cases[] = {
-    {/*mangled=*/"$__lldb_func:0x0:0x0:_Z3foov",
+    {/*mangled=*/"$__lldb_func::0x0:0x0:_Z3foov",
      /*expected=*/"_Z3foov"},
-    {/*mangled=*/"$__lldb_func:0x0:0x0:foo",
-     /*expected=*/"$__lldb_func:0x0:0x0:foo"},
+    {/*mangled=*/"$__lldb_func::0x0:0x0:foo",
+     /*expected=*/"$__lldb_func::0x0:0x0:foo"},
     {/*mangled=*/"foo",
      /*expected=*/"foo"},
     {/*mangled=*/"_Z3foov",

>From f425690222d806bf253d53c8157da1e0b2aeca48 Mon Sep 17 00:00:00 2001
From: Michael Buch <michaelbuch12 at gmail.com>
Date: Fri, 8 Aug 2025 09:25:40 +0100
Subject: [PATCH 13/20] fixup! remove type-unit special-case

---
 .../Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp     | 9 ---------
 1 file changed, 9 deletions(-)

diff --git a/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp b/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp
index cc04f1d7879ed..324d1c2b068ec 100644
--- a/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp
+++ b/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp
@@ -288,15 +288,6 @@ static std::string MakeLLDBFuncAsmLabel(const DWARFDIE &die) {
   if (!cu)
     return {};
 
-  // FIXME: When resolving function call labels, we check that
-  // that the definition's DW_AT_specification points to the
-  // declaration that we encoded into the label here. But if the
-  // declaration came from a type-unit (and the definition from
-  // .debug_info), that check won't work. So for now, don't use
-  // function call labels for declaration DIEs from type-units.
-  if (cu->IsTypeUnit())
-    return {};
-
   SymbolFileDWARF *dwarf = die.GetDWARF();
   if (!dwarf)
     return {};

>From c58569b1917d1a795ffde7c4abed739a73ac5ca4 Mon Sep 17 00:00:00 2001
From: Michael Buch <michaelbuch12 at gmail.com>
Date: Fri, 8 Aug 2025 09:37:25 +0100
Subject: [PATCH 14/20] fixup! clean up error handling

---
 .../SymbolFile/DWARF/SymbolFileDWARF.cpp      | 26 ++++++++++++++-----
 .../SymbolFile/DWARF/SymbolFileDWARF.h        |  5 ++--
 2 files changed, 22 insertions(+), 9 deletions(-)

diff --git a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp
index 30426a037eca9..2a715674fee4a 100644
--- a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp
+++ b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp
@@ -2536,8 +2536,9 @@ GetItaniumCtorDtorVariant(llvm::StringRef discriminator) {
   return ClangToItaniumDtorKind(static_cast<clang::CXXDtorType>(structor_kind));
 }
 
-DWARFDIE SymbolFileDWARF::FindFunctionDefinition(const FunctionCallLabel &label,
-                                                 const DWARFDIE &declaration) {
+llvm::Expected<DWARFDIE>
+SymbolFileDWARF::FindFunctionDefinition(const FunctionCallLabel &label,
+                                        const DWARFDIE &declaration) {
   DWARFDIE definition;
   llvm::DenseMap<int, DWARFDIE> structor_variant_to_die;
 
@@ -2571,6 +2572,8 @@ DWARFDIE SymbolFileDWARF::FindFunctionDefinition(const FunctionCallLabel &label,
     if (!mangled)
       return IterationAction::Continue;
 
+    // FIXME: we should make DWARF encode the structor variant instead of
+    // needing to re-demangle.
     llvm::ItaniumPartialDemangler D;
     if (D.partialDemangle(mangled))
       return IterationAction::Continue;
@@ -2595,7 +2598,9 @@ DWARFDIE SymbolFileDWARF::FindFunctionDefinition(const FunctionCallLabel &label,
 
   auto label_variant = GetItaniumCtorDtorVariant(label.discriminator);
   if (!label_variant)
-    return {};
+    return llvm::createStringError(
+        llvm::formatv("failed to retrieve structor variant from label: {0}",
+                      label.discriminator));
 
   auto it = structor_variant_to_die.find(*label_variant);
 
@@ -2605,13 +2610,16 @@ DWARFDIE SymbolFileDWARF::FindFunctionDefinition(const FunctionCallLabel &label,
 
   // We need a C1 constructor. If debug-info only contains a DIE for C2,
   // assume C1 was aliased to C2.
+  //
+  // FIXME: can DWARF encode this information for us?
   if (!label.lookup_name.starts_with("~") && label_variant == 1) {
     if (auto it = structor_variant_to_die.find(2);
         it != structor_variant_to_die.end())
       return it->getSecond();
   }
 
-  return {};
+  return llvm::createStringError(llvm::formatv(
+      "failed to find structor variant DIE for label: {0}", label));
 }
 
 llvm::Expected<SymbolContext>
@@ -2626,9 +2634,13 @@ SymbolFileDWARF::ResolveFunctionCallLabel(const FunctionCallLabel &label) {
   // Label was created using a declaration DIE. Need to fetch the definition
   // to resolve the function call.
   if (die.GetAttributeValueAsUnsigned(llvm::dwarf::DW_AT_declaration, 0)) {
-    die = FindFunctionDefinition(label, die);
-    if (!die.IsValid())
-      return llvm::createStringError("failed to find definition DIE");
+    auto die_or_err = FindFunctionDefinition(label, die);
+    if (!die_or_err)
+      return llvm::joinErrors(
+          llvm::createStringError("failed to find definition DIE"),
+          die_or_err.takeError());
+
+    die = std::move(*die_or_err);
   }
 
   SymbolContextList sc_list;
diff --git a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.h b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.h
index 6ba2592314c36..e29ed1fc73902 100644
--- a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.h
+++ b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.h
@@ -378,8 +378,9 @@ class SymbolFileDWARF : public SymbolFileCommon {
   /// SymbolFile.
   ///
   /// \returns A valid definition DIE on success.
-  DWARFDIE FindFunctionDefinition(const FunctionCallLabel &label,
-                                  const DWARFDIE &declaration);
+  llvm::Expected<DWARFDIE>
+  FindFunctionDefinition(const FunctionCallLabel &label,
+                         const DWARFDIE &declaration);
 
 protected:
   SymbolFileDWARF(const SymbolFileDWARF &) = delete;

>From 13413b680ea3c2d28cd9495f75ad156613256995 Mon Sep 17 00:00:00 2001
From: Michael Buch <michaelbuch12 at gmail.com>
Date: Fri, 8 Aug 2025 10:05:43 +0100
Subject: [PATCH 15/20] fixup! more adjustments to error messages

---
 lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp
index 2a715674fee4a..cbd086c95620a 100644
--- a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp
+++ b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp
@@ -2619,7 +2619,7 @@ SymbolFileDWARF::FindFunctionDefinition(const FunctionCallLabel &label,
   }
 
   return llvm::createStringError(llvm::formatv(
-      "failed to find structor variant DIE for label: {0}", label));
+      "failed to find DIE for structor variant: {0}", label.discriminator));
 }
 
 llvm::Expected<SymbolContext>
@@ -2637,7 +2637,7 @@ SymbolFileDWARF::ResolveFunctionCallLabel(const FunctionCallLabel &label) {
     auto die_or_err = FindFunctionDefinition(label, die);
     if (!die_or_err)
       return llvm::joinErrors(
-          llvm::createStringError("failed to find definition DIE"),
+          llvm::createStringError("failed to find definition DIE:"),
           die_or_err.takeError());
 
     die = std::move(*die_or_err);

>From fb89e55d1f923c186177568a1c85c6531a9d0d1d Mon Sep 17 00:00:00 2001
From: Michael Buch <michaelbuch12 at gmail.com>
Date: Fri, 8 Aug 2025 10:06:22 +0100
Subject: [PATCH 16/20] fixup! add test for ctor declaration separate from
 definition

---
 .../cpp/expr-definition-in-dylib/TestExprDefinitionInDylib.py   | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lldb/test/API/lang/cpp/expr-definition-in-dylib/TestExprDefinitionInDylib.py b/lldb/test/API/lang/cpp/expr-definition-in-dylib/TestExprDefinitionInDylib.py
index 70d7fd096c7cf..447cf7adfe14e 100644
--- a/lldb/test/API/lang/cpp/expr-definition-in-dylib/TestExprDefinitionInDylib.py
+++ b/lldb/test/API/lang/cpp/expr-definition-in-dylib/TestExprDefinitionInDylib.py
@@ -31,10 +31,10 @@ def test(self):
         )
 
         self.expect_expr("f.method()", result_value="-72", result_type="int")
+
         self.expect_expr("Foo()", result_type="Foo")
 
         # FIXME: mangled name lookup for ABI-tagged ctors fails because
         # the debug-info AST doesn't have ABI-tag information.
         self.expect(
             "expr Bar()", error=True, substrs=["error: Couldn't look up symbols"]
-        )

>From 72f78594c6442fe949c5dd8a842630cf97750719 Mon Sep 17 00:00:00 2001
From: Michael Buch <michaelbuch12 at gmail.com>
Date: Thu, 21 Aug 2025 09:42:22 +0100
Subject: [PATCH 17/20] [DO-NOT-MERGE] Unified structor mangling changes

---
 .../SymbolFile/DWARF/DWARFASTParserClang.cpp  | 34 +------------------
 .../SymbolFile/DWARF/SymbolFileDWARF.cpp      | 21 ++++--------
 .../abi_tag_structors/TestAbiTagStructors.py  |  2 +-
 .../cpp/constructors/TestCppConstructors.py   | 20 +++++------
 .../TestExprDefinitionInDylib.py              |  8 ++---
 .../lang/cpp/expr-definition-in-dylib/lib.cpp |  4 +++
 .../lang/cpp/expr-definition-in-dylib/lib.h   |  7 +++-
 .../cpp/expr-definition-in-dylib/main.cpp     |  3 +-
 8 files changed, 34 insertions(+), 65 deletions(-)

diff --git a/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp b/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp
index 324d1c2b068ec..bc13e7875c4a6 100644
--- a/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp
+++ b/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp
@@ -251,43 +251,11 @@ static unsigned GetCXXMethodCVQuals(const DWARFDIE &subprogram,
   return cv_quals;
 }
 
-static const char *GetMangledOrStructorName(const DWARFDIE &die) {
-  const char *name = die.GetMangledName(/*substitute_name_allowed*/ false);
-  if (name)
-    return name;
-
-  name = die.GetName();
-  if (!name)
-    return nullptr;
-
-  DWARFDIE parent = die.GetParent();
-  if (!parent.IsStructUnionOrClass())
-    return nullptr;
-
-  const char *parent_name = parent.GetName();
-  if (!parent_name)
-    return nullptr;
-
-  // Constructor.
-  if (::strcmp(parent_name, name) == 0)
-    return name;
-
-  // Destructor.
-  if (name[0] == '~' && ::strcmp(parent_name, name + 1) == 0)
-    return name;
-
-  return nullptr;
-}
-
 static std::string MakeLLDBFuncAsmLabel(const DWARFDIE &die) {
-  char const *name = GetMangledOrStructorName(die);
+  const char *name = die.GetMangledName(/*substitute_name_allowed*/ false);
   if (!name)
     return {};
 
-  auto *cu = die.GetCU();
-  if (!cu)
-    return {};
-
   SymbolFileDWARF *dwarf = die.GetDWARF();
   if (!dwarf)
     return {};
diff --git a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp
index cbd086c95620a..beac6477dcf92 100644
--- a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp
+++ b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp
@@ -2492,6 +2492,8 @@ static int ClangToItaniumCtorKind(clang::CXXCtorType kind) {
     return 1;
   case clang::CXXCtorType::Ctor_Base:
     return 2;
+  case clang::CXXCtorType::Ctor_Unified:
+    return 4;
   case clang::CXXCtorType::Ctor_CopyingClosure:
   case clang::CXXCtorType::Ctor_DefaultClosure:
   case clang::CXXCtorType::Ctor_Comdat:
@@ -2507,6 +2509,8 @@ static int ClangToItaniumDtorKind(clang::CXXDtorType kind) {
     return 1;
   case clang::CXXDtorType::Dtor_Base:
     return 2;
+  case clang::CXXDtorType::Dtor_Unified:
+    return 4;
   case clang::CXXDtorType::Dtor_Comdat:
     llvm_unreachable("Unexpected destructor kind.");
   }
@@ -2523,14 +2527,14 @@ GetItaniumCtorDtorVariant(llvm::StringRef discriminator) {
     return std::nullopt;
 
   if (is_ctor) {
-    if (structor_kind > clang::CXXCtorType::Ctor_DefaultClosure)
+    if (structor_kind > clang::CXXCtorType::Ctor_Unified)
       return std::nullopt;
 
     return ClangToItaniumCtorKind(
         static_cast<clang::CXXCtorType>(structor_kind));
   }
 
-  if (structor_kind > clang::CXXDtorType::Dtor_Comdat)
+  if (structor_kind > clang::CXXDtorType::Dtor_Unified)
     return std::nullopt;
 
   return ClangToItaniumDtorKind(static_cast<clang::CXXDtorType>(structor_kind));
@@ -2542,25 +2546,14 @@ SymbolFileDWARF::FindFunctionDefinition(const FunctionCallLabel &label,
   DWARFDIE definition;
   llvm::DenseMap<int, DWARFDIE> structor_variant_to_die;
 
-  // eFunctionNameTypeFull for mangled name lookup.
-  // eFunctionNameTypeMethod is required for structor lookups (since we look
-  // those up by DW_AT_name).
   Module::LookupInfo info(ConstString(label.lookup_name),
-                          lldb::eFunctionNameTypeFull |
-                              lldb::eFunctionNameTypeMethod,
+                          lldb::eFunctionNameTypeFull,
                           lldb::eLanguageTypeUnknown);
 
   m_index->GetFunctions(info, *this, {}, [&](DWARFDIE entry) {
     if (entry.GetAttributeValueAsUnsigned(llvm::dwarf::DW_AT_declaration, 0))
       return IterationAction::Continue;
 
-    auto spec = entry.GetAttributeValueAsReferenceDIE(DW_AT_specification);
-    if (!spec)
-      return IterationAction::Continue;
-
-    if (spec != declaration)
-      return IterationAction::Continue;
-
     // We're not picking a specific structor variant DIE, so we're done here.
     if (label.discriminator.empty()) {
       definition = entry;
diff --git a/lldb/test/API/lang/cpp/abi_tag_structors/TestAbiTagStructors.py b/lldb/test/API/lang/cpp/abi_tag_structors/TestAbiTagStructors.py
index b549736c1405d..5e7c03f8e0dfe 100644
--- a/lldb/test/API/lang/cpp/abi_tag_structors/TestAbiTagStructors.py
+++ b/lldb/test/API/lang/cpp/abi_tag_structors/TestAbiTagStructors.py
@@ -10,7 +10,7 @@
 
 
 class AbiTagStructorsTestCase(TestBase):
-    def test_abi_tag_lookup(self):
+    def test(self):
         self.build()
         lldbutil.run_to_source_breakpoint(
             self, "Break here", lldb.SBFileSpec("main.cpp", False)
diff --git a/lldb/test/API/lang/cpp/constructors/TestCppConstructors.py b/lldb/test/API/lang/cpp/constructors/TestCppConstructors.py
index baf06e4c59fbb..3d957fe3c0ba9 100644
--- a/lldb/test/API/lang/cpp/constructors/TestCppConstructors.py
+++ b/lldb/test/API/lang/cpp/constructors/TestCppConstructors.py
@@ -39,16 +39,16 @@ def test_constructors(self):
         )
 
         # FIXME: Calling deleted constructors should fail before linking.
-        self.expect(
-            "expr ClassWithDeletedCtor(1).value",
-            error=True,
-            substrs=["Couldn't look up symbols:"],
-        )
-        self.expect(
-            "expr ClassWithDeletedDefaultCtor().value",
-            error=True,
-            substrs=["Couldn't look up symbols:", "function", "optimized out"],
-        )
+        #self.expect(
+        #    "expr ClassWithDeletedCtor(1).value",
+        #    error=True,
+        #    substrs=["Couldn't look up symbols:"],
+        #)
+        #self.expect(
+        #    "expr ClassWithDeletedDefaultCtor().value",
+        #    error=True,
+        #    substrs=["Couldn't look up symbols:", "function", "optimized out"],
+        #)
 
     @skipIfWindows  # Can't find operator new.
     @skipIfLinux  # Fails on some Linux systems with SIGABRT.
diff --git a/lldb/test/API/lang/cpp/expr-definition-in-dylib/TestExprDefinitionInDylib.py b/lldb/test/API/lang/cpp/expr-definition-in-dylib/TestExprDefinitionInDylib.py
index 447cf7adfe14e..b6998764b6922 100644
--- a/lldb/test/API/lang/cpp/expr-definition-in-dylib/TestExprDefinitionInDylib.py
+++ b/lldb/test/API/lang/cpp/expr-definition-in-dylib/TestExprDefinitionInDylib.py
@@ -5,7 +5,6 @@
 
 
 class ExprDefinitionInDylibTestCase(TestBase):
-    NO_DEBUG_INFO_TESTCASE = True
 
     @skipIfWindows
     def test(self):
@@ -34,7 +33,6 @@ def test(self):
 
         self.expect_expr("Foo()", result_type="Foo")
 
-        # FIXME: mangled name lookup for ABI-tagged ctors fails because
-        # the debug-info AST doesn't have ABI-tag information.
-        self.expect(
-            "expr Bar()", error=True, substrs=["error: Couldn't look up symbols"]
+        self.expect_expr("Base()", result_type="Base")
+
+        self.expect_expr("Bar()", result_type="Bar")
diff --git a/lldb/test/API/lang/cpp/expr-definition-in-dylib/lib.cpp b/lldb/test/API/lang/cpp/expr-definition-in-dylib/lib.cpp
index 1a08817f5cda1..1ddd28021dffc 100644
--- a/lldb/test/API/lang/cpp/expr-definition-in-dylib/lib.cpp
+++ b/lldb/test/API/lang/cpp/expr-definition-in-dylib/lib.cpp
@@ -11,3 +11,7 @@ Foo::~Foo() { std::puts(__func__); }
 Bar::Bar() { std::puts(__func__); }
 
 Bar::~Bar() { std::puts(__func__); }
+
+Base::Base() { std::puts(__func__); }
+
+Base::~Base() { std::puts(__func__); }
diff --git a/lldb/test/API/lang/cpp/expr-definition-in-dylib/lib.h b/lldb/test/API/lang/cpp/expr-definition-in-dylib/lib.h
index 5ec227946cba0..be2206103c17e 100644
--- a/lldb/test/API/lang/cpp/expr-definition-in-dylib/lib.h
+++ b/lldb/test/API/lang/cpp/expr-definition-in-dylib/lib.h
@@ -7,7 +7,12 @@ struct Foo {
   ~Foo();
 };
 
-struct Bar {
+struct Base {
+  [[gnu::abi_tag("BaseCtor")]] Base();
+  [[gnu::abi_tag("BaseDtor")]] ~Base();
+};
+
+struct Bar : public Base {
   [[gnu::abi_tag("Ctor")]] Bar();
   [[gnu::abi_tag("Dtor")]] ~Bar();
 };
diff --git a/lldb/test/API/lang/cpp/expr-definition-in-dylib/main.cpp b/lldb/test/API/lang/cpp/expr-definition-in-dylib/main.cpp
index 4d6bece21ecac..b2e8700257716 100644
--- a/lldb/test/API/lang/cpp/expr-definition-in-dylib/main.cpp
+++ b/lldb/test/API/lang/cpp/expr-definition-in-dylib/main.cpp
@@ -2,6 +2,7 @@
 
 int main() {
   Foo f;
-  Bar b;
+  Base b1;
+  Bar b2;
   return f.method();
 }

>From d506fe42d0c31ee35095c967a05cf6da289357f8 Mon Sep 17 00:00:00 2001
From: Michael Buch <michaelbuch12 at gmail.com>
Date: Thu, 14 Aug 2025 10:14:24 +0100
Subject: [PATCH 18/20] [DO-NOT-MERGE] Expose ManglingSubstitutor as static
 helpers

Allows us to use the mangling substitution facilities in
CPlusPlusLanguage but also SymbolFileDWARF.
---
 .../Language/CPlusPlus/CPlusPlusLanguage.cpp  | 322 ++++++++++--------
 .../Language/CPlusPlus/CPlusPlusLanguage.h    |  13 +
 2 files changed, 201 insertions(+), 134 deletions(-)

diff --git a/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp b/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
index b4207439f5285..9d9880edd2454 100644
--- a/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
+++ b/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
@@ -604,126 +604,6 @@ bool CPlusPlusLanguage::ExtractContextAndIdentifier(
   return false;
 }
 
-namespace {
-class NodeAllocator {
-  llvm::BumpPtrAllocator Alloc;
-
-public:
-  void reset() { Alloc.Reset(); }
-
-  template <typename T, typename... Args> T *makeNode(Args &&...args) {
-    return new (Alloc.Allocate(sizeof(T), alignof(T)))
-        T(std::forward<Args>(args)...);
-  }
-
-  void *allocateNodeArray(size_t sz) {
-    return Alloc.Allocate(sizeof(llvm::itanium_demangle::Node *) * sz,
-                          alignof(llvm::itanium_demangle::Node *));
-  }
-};
-
-template <typename Derived>
-class ManglingSubstitutor
-    : public llvm::itanium_demangle::AbstractManglingParser<Derived,
-                                                            NodeAllocator> {
-  using Base =
-      llvm::itanium_demangle::AbstractManglingParser<Derived, NodeAllocator>;
-
-public:
-  ManglingSubstitutor() : Base(nullptr, nullptr) {}
-
-  template <typename... Ts>
-  ConstString substitute(llvm::StringRef Mangled, Ts &&...Vals) {
-    this->getDerived().reset(Mangled, std::forward<Ts>(Vals)...);
-    return substituteImpl(Mangled);
-  }
-
-protected:
-  void reset(llvm::StringRef Mangled) {
-    Base::reset(Mangled.begin(), Mangled.end());
-    Written = Mangled.begin();
-    Result.clear();
-    Substituted = false;
-  }
-
-  ConstString substituteImpl(llvm::StringRef Mangled) {
-    Log *log = GetLog(LLDBLog::Language);
-    if (this->parse() == nullptr) {
-      LLDB_LOG(log, "Failed to substitute mangling in {0}", Mangled);
-      return ConstString();
-    }
-    if (!Substituted)
-      return ConstString();
-
-    // Append any trailing unmodified input.
-    appendUnchangedInput();
-    LLDB_LOG(log, "Substituted mangling {0} -> {1}", Mangled, Result);
-    return ConstString(Result);
-  }
-
-  void trySubstitute(llvm::StringRef From, llvm::StringRef To) {
-    if (!llvm::StringRef(currentParserPos(), this->numLeft()).starts_with(From))
-      return;
-
-    // We found a match. Append unmodified input up to this point.
-    appendUnchangedInput();
-
-    // And then perform the replacement.
-    Result += To;
-    Written += From.size();
-    Substituted = true;
-  }
-
-private:
-  /// Input character until which we have constructed the respective output
-  /// already.
-  const char *Written = "";
-
-  llvm::SmallString<128> Result;
-
-  /// Whether we have performed any substitutions.
-  bool Substituted = false;
-
-  const char *currentParserPos() const { return this->First; }
-
-  void appendUnchangedInput() {
-    Result +=
-        llvm::StringRef(Written, std::distance(Written, currentParserPos()));
-    Written = currentParserPos();
-  }
-};
-
-/// Given a mangled function `Mangled`, replace all the primitive function type
-/// arguments of `Search` with type `Replace`.
-class TypeSubstitutor : public ManglingSubstitutor<TypeSubstitutor> {
-  llvm::StringRef Search;
-  llvm::StringRef Replace;
-
-public:
-  void reset(llvm::StringRef Mangled, llvm::StringRef Search,
-             llvm::StringRef Replace) {
-    ManglingSubstitutor::reset(Mangled);
-    this->Search = Search;
-    this->Replace = Replace;
-  }
-
-  llvm::itanium_demangle::Node *parseType() {
-    trySubstitute(Search, Replace);
-    return ManglingSubstitutor::parseType();
-  }
-};
-
-class CtorDtorSubstitutor : public ManglingSubstitutor<CtorDtorSubstitutor> {
-public:
-  llvm::itanium_demangle::Node *
-  parseCtorDtorName(llvm::itanium_demangle::Node *&SoFar, NameState *State) {
-    trySubstitute("C1", "C2");
-    trySubstitute("D1", "D2");
-    return ManglingSubstitutor::parseCtorDtorName(SoFar, State);
-  }
-};
-} // namespace
-
 std::vector<ConstString> CPlusPlusLanguage::GenerateAlternateFunctionManglings(
     const ConstString mangled_name) const {
   std::vector<ConstString> alternates;
@@ -751,29 +631,49 @@ std::vector<ConstString> CPlusPlusLanguage::GenerateAlternateFunctionManglings(
     alternates.push_back(ConstString(fixed_scratch));
   }
 
-  TypeSubstitutor TS;
+  auto *log = GetLog(LLDBLog::Language);
+
   // `char` is implementation defined as either `signed` or `unsigned`.  As a
   // result a char parameter has 3 possible manglings: 'c'-char, 'a'-signed
   // char, 'h'-unsigned char.  If we're looking for symbols with a signed char
   // parameter, try finding matches which have the general case 'c'.
-  if (ConstString char_fixup =
-          TS.substitute(mangled_name.GetStringRef(), "a", "c"))
-    alternates.push_back(char_fixup);
+  if (auto char_fixup_or_err =
+          SubstituteType_ItaniumMangle(mangled_name.GetStringRef(), "a", "c")) {
+    // LLDB_LOG(log, "Substituted mangling {0} -> {1}", Mangled, Result);
+    if (*char_fixup_or_err)
+      alternates.push_back(*char_fixup_or_err);
+  } else
+    LLDB_LOG_ERROR(log, char_fixup_or_err.takeError(),
+                   "Failed to substitute 'char' type mangling: {0}");
 
   // long long parameter mangling 'x', may actually just be a long 'l' argument
-  if (ConstString long_fixup =
-          TS.substitute(mangled_name.GetStringRef(), "x", "l"))
-    alternates.push_back(long_fixup);
+  if (auto long_fixup_or_err =
+          SubstituteType_ItaniumMangle(mangled_name.GetStringRef(), "x", "l")) {
+    if (*long_fixup_or_err)
+      alternates.push_back(*long_fixup_or_err);
+  } else
+    LLDB_LOG_ERROR(log, long_fixup_or_err.takeError(),
+                   "Failed to substitute 'long long' type mangling: {0}");
 
   // unsigned long long parameter mangling 'y', may actually just be unsigned
   // long 'm' argument
-  if (ConstString ulong_fixup =
-          TS.substitute(mangled_name.GetStringRef(), "y", "m"))
-    alternates.push_back(ulong_fixup);
-
-  if (ConstString ctor_fixup =
-          CtorDtorSubstitutor().substitute(mangled_name.GetStringRef()))
-    alternates.push_back(ctor_fixup);
+  if (auto ulong_fixup_or_err =
+          SubstituteType_ItaniumMangle(mangled_name.GetStringRef(), "y", "m")) {
+    if (*ulong_fixup_or_err)
+      alternates.push_back(*ulong_fixup_or_err);
+  } else
+    LLDB_LOG_ERROR(
+        log, ulong_fixup_or_err.takeError(),
+        "Failed to substitute 'unsigned long long' type mangling: {0}");
+
+  if (auto ctor_fixup_or_err = SubstituteStructorAliases_ItaniumMangle(
+          mangled_name.GetStringRef())) {
+    if (*ctor_fixup_or_err) {
+      alternates.push_back(*ctor_fixup_or_err);
+    }
+  } else
+    LLDB_LOG_ERROR(log, ctor_fixup_or_err.takeError(),
+                   "Failed to substitute structor alias manglings: {0}");
 
   return alternates;
 }
@@ -2442,6 +2342,160 @@ bool CPlusPlusLanguage::HandleFrameFormatVariable(
   }
 }
 
+namespace {
+class NodeAllocator {
+  llvm::BumpPtrAllocator Alloc;
+
+public:
+  void reset() { Alloc.Reset(); }
+
+  template <typename T, typename... Args> T *makeNode(Args &&...args) {
+    return new (Alloc.Allocate(sizeof(T), alignof(T)))
+        T(std::forward<Args>(args)...);
+  }
+
+  void *allocateNodeArray(size_t sz) {
+    return Alloc.Allocate(sizeof(llvm::itanium_demangle::Node *) * sz,
+                          alignof(llvm::itanium_demangle::Node *));
+  }
+};
+
+template <typename Derived>
+class ManglingSubstitutor
+    : public llvm::itanium_demangle::AbstractManglingParser<Derived,
+                                                            NodeAllocator> {
+  using Base =
+      llvm::itanium_demangle::AbstractManglingParser<Derived, NodeAllocator>;
+
+public:
+  ManglingSubstitutor() : Base(nullptr, nullptr) {}
+
+  template <typename... Ts>
+  llvm::Expected<ConstString> substitute(llvm::StringRef Mangled,
+                                         Ts &&...Vals) {
+    this->getDerived().reset(Mangled, std::forward<Ts>(Vals)...);
+    return substituteImpl(Mangled);
+  }
+
+protected:
+  void reset(llvm::StringRef Mangled) {
+    Base::reset(Mangled.begin(), Mangled.end());
+    Written = Mangled.begin();
+    Result.clear();
+    Substituted = false;
+  }
+
+  llvm::Expected<ConstString> substituteImpl(llvm::StringRef Mangled) {
+    if (this->parse() == nullptr)
+      return llvm::createStringError(
+          llvm::formatv("Failed to substitute mangling in {0}", Mangled));
+
+    if (!Substituted)
+      return ConstString();
+
+    // Append any trailing unmodified input.
+    appendUnchangedInput();
+    return ConstString(Result);
+  }
+
+  void trySubstitute(llvm::StringRef From, llvm::StringRef To) {
+    if (!llvm::StringRef(currentParserPos(), this->numLeft()).starts_with(From))
+      return;
+
+    // We found a match. Append unmodified input up to this point.
+    appendUnchangedInput();
+
+    // And then perform the replacement.
+    Result += To;
+    Written += From.size();
+    Substituted = true;
+  }
+
+private:
+  /// Input character until which we have constructed the respective output
+  /// already.
+  const char *Written = "";
+
+  llvm::SmallString<128> Result;
+
+  /// Whether we have performed any substitutions.
+  bool Substituted = false;
+
+  const char *currentParserPos() const { return this->First; }
+
+  void appendUnchangedInput() {
+    Result +=
+        llvm::StringRef(Written, std::distance(Written, currentParserPos()));
+    Written = currentParserPos();
+  }
+};
+
+/// Given a mangled function `Mangled`, replace all the primitive function type
+/// arguments of `Search` with type `Replace`.
+class TypeSubstitutor : public ManglingSubstitutor<TypeSubstitutor> {
+  llvm::StringRef Search;
+  llvm::StringRef Replace;
+
+public:
+  void reset(llvm::StringRef Mangled, llvm::StringRef Search,
+             llvm::StringRef Replace) {
+    ManglingSubstitutor::reset(Mangled);
+    this->Search = Search;
+    this->Replace = Replace;
+  }
+
+  llvm::itanium_demangle::Node *parseType() {
+    trySubstitute(Search, Replace);
+    return ManglingSubstitutor::parseType();
+  }
+};
+
+class CtorDtorSubstitutor : public ManglingSubstitutor<CtorDtorSubstitutor> {
+  llvm::StringRef Search;
+  llvm::StringRef Replace;
+
+public:
+  void reset(llvm::StringRef Mangled, llvm::StringRef Search,
+             llvm::StringRef Replace) {
+    ManglingSubstitutor::reset(Mangled);
+    this->Search = Search;
+    this->Replace = Replace;
+  }
+
+  void reset(llvm::StringRef Mangled) { ManglingSubstitutor::reset(Mangled); }
+
+  llvm::itanium_demangle::Node *
+  parseCtorDtorName(llvm::itanium_demangle::Node *&SoFar, NameState *State) {
+    if (!Search.empty() && !Replace.empty()) {
+      trySubstitute(Search, Replace);
+    } else {
+      trySubstitute("D1", "D2");
+      trySubstitute("C1", "C2");
+    }
+    return ManglingSubstitutor::parseCtorDtorName(SoFar, State);
+  }
+};
+} // namespace
+
+llvm::Expected<ConstString>
+CPlusPlusLanguage::SubstituteType_ItaniumMangle(llvm::StringRef mangled_name,
+                                                llvm::StringRef subst_from,
+                                                llvm::StringRef subst_to) {
+  return TypeSubstitutor().substitute(mangled_name, subst_from, subst_to);
+}
+
+llvm::Expected<ConstString> CPlusPlusLanguage::SubstituteStructor_ItaniumMangle(
+    llvm::StringRef mangled_name, llvm::StringRef subst_from,
+    llvm::StringRef subst_to) {
+  return CtorDtorSubstitutor().substitute(mangled_name, subst_from, subst_to);
+}
+
+llvm::Expected<ConstString>
+CPlusPlusLanguage::SubstituteStructorAliases_ItaniumMangle(
+    llvm::StringRef mangled_name) {
+  return CtorDtorSubstitutor().substitute(mangled_name);
+}
+
 #define LLDB_PROPERTIES_language_cplusplus
 #include "LanguageCPlusPlusProperties.inc"
 
diff --git a/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.h b/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.h
index 4a30299dd2658..67b28bf202c3a 100644
--- a/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.h
+++ b/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.h
@@ -164,6 +164,19 @@ class CPlusPlusLanguage : public Language {
   ConstString FindBestAlternateFunctionMangledName(
       const Mangled mangled, const SymbolContext &sym_ctx) const override;
 
+  static llvm::Expected<ConstString>
+  SubstituteType_ItaniumMangle(llvm::StringRef mangled_name,
+                               llvm::StringRef subst_from,
+                               llvm::StringRef subst_to);
+
+  static llvm::Expected<ConstString>
+  SubstituteStructor_ItaniumMangle(llvm::StringRef mangled_name,
+                                   llvm::StringRef subst_from,
+                                   llvm::StringRef subst_to);
+
+  static llvm::Expected<ConstString>
+  SubstituteStructorAliases_ItaniumMangle(llvm::StringRef mangled_name);
+
   llvm::StringRef GetInstanceVariableName() override { return "this"; }
 
   FormatEntity::Entry GetFunctionNameFormat() const override;

>From 7b49e533143c01497177f0991bf6e0d4d23c75a8 Mon Sep 17 00:00:00 2001
From: Michael Buch <michaelbuch12 at gmail.com>
Date: Thu, 14 Aug 2025 12:53:59 +0100
Subject: [PATCH 19/20] [DO-NOT-MERGE] Substitute unified structor mangling
 with discriminator during lookup

Now that we emit a unified linkage name to structor declarations we can
use it to perform lookups. Before performing the lookup we substitute the
discriminator into the linkage name, and we also try substituting
for potential structor aliases. This solves the case where declarations
and definitions of structors live in separate SymbolFile's because the
fallback global lookup that the IRExecutionUnit does will use the
substituted mangled name to do so.
---
 lldb/include/lldb/Symbol/SymbolFile.h         |   4 +-
 lldb/source/Expression/IRExecutionUnit.cpp    |   2 +-
 .../SymbolFile/DWARF/SymbolFileDWARF.cpp      | 140 +++++++++---------
 .../SymbolFile/DWARF/SymbolFileDWARF.h        |   2 +-
 .../DWARF/SymbolFileDWARFDebugMap.cpp         |   4 +-
 .../DWARF/SymbolFileDWARFDebugMap.h           |   2 +-
 6 files changed, 74 insertions(+), 80 deletions(-)

diff --git a/lldb/include/lldb/Symbol/SymbolFile.h b/lldb/include/lldb/Symbol/SymbolFile.h
index bbc615d9fdc38..a84f081b340eb 100644
--- a/lldb/include/lldb/Symbol/SymbolFile.h
+++ b/lldb/include/lldb/Symbol/SymbolFile.h
@@ -332,12 +332,12 @@ class SymbolFile : public PluginInterface {
   /// Resolves the function corresponding to the specified LLDB function
   /// call \c label.
   ///
-  /// \param[in] label The FunctionCallLabel to be resolved.
+  /// \param[in,out] label The FunctionCallLabel to be resolved.
   ///
   /// \returns An llvm::Error if the specified \c label couldn't be resolved.
   ///          Returns the resolved function (as a SymbolContext) otherwise.
   virtual llvm::Expected<SymbolContext>
-  ResolveFunctionCallLabel(const FunctionCallLabel &label) {
+  ResolveFunctionCallLabel(FunctionCallLabel &label) {
     return llvm::createStringError("Not implemented");
   }
 
diff --git a/lldb/source/Expression/IRExecutionUnit.cpp b/lldb/source/Expression/IRExecutionUnit.cpp
index d557084acb745..d3dd163edd034 100644
--- a/lldb/source/Expression/IRExecutionUnit.cpp
+++ b/lldb/source/Expression/IRExecutionUnit.cpp
@@ -778,7 +778,7 @@ class LoadAddressResolver {
 /// Returns address of the function referred to by the special function call
 /// label \c label.
 static llvm::Expected<lldb::addr_t>
-ResolveFunctionCallLabel(const FunctionCallLabel &label,
+ResolveFunctionCallLabel(FunctionCallLabel &label,
                          const lldb_private::SymbolContext &sc,
                          bool &symbol_was_missing_weak) {
   symbol_was_missing_weak = false;
diff --git a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp
index beac6477dcf92..4b0e81bb9079f 100644
--- a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp
+++ b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp
@@ -13,6 +13,7 @@
 #include "llvm/DebugInfo/DWARF/DWARFAddressRange.h"
 #include "llvm/DebugInfo/DWARF/DWARFDebugLoc.h"
 #include "llvm/Support/Casting.h"
+#include "llvm/Support/Error.h"
 #include "llvm/Support/FileUtilities.h"
 #include "llvm/Support/FormatAdapters.h"
 #include "llvm/Support/Threading.h"
@@ -24,6 +25,7 @@
 #include "lldb/Core/Progress.h"
 #include "lldb/Core/Section.h"
 #include "lldb/Core/Value.h"
+#include "lldb/Expression/Expression.h"
 #include "lldb/Utility/ArchSpec.h"
 #include "lldb/Utility/LLDBLog.h"
 #include "lldb/Utility/RegularExpression.h"
@@ -2486,14 +2488,14 @@ bool SymbolFileDWARF::ResolveFunction(const DWARFDIE &orig_die,
   return false;
 }
 
-static int ClangToItaniumCtorKind(clang::CXXCtorType kind) {
+static llvm::StringRef ClangToItaniumCtorKind(clang::CXXCtorType kind) {
   switch (kind) {
   case clang::CXXCtorType::Ctor_Complete:
-    return 1;
+    return "C1";
   case clang::CXXCtorType::Ctor_Base:
-    return 2;
+    return "C2";
   case clang::CXXCtorType::Ctor_Unified:
-    return 4;
+    return "C4";
   case clang::CXXCtorType::Ctor_CopyingClosure:
   case clang::CXXCtorType::Ctor_DefaultClosure:
   case clang::CXXCtorType::Ctor_Comdat:
@@ -2501,41 +2503,41 @@ static int ClangToItaniumCtorKind(clang::CXXCtorType kind) {
   }
 }
 
-static int ClangToItaniumDtorKind(clang::CXXDtorType kind) {
+static llvm::StringRef ClangToItaniumDtorKind(clang::CXXDtorType kind) {
   switch (kind) {
   case clang::CXXDtorType::Dtor_Deleting:
-    return 0;
+    return "D0";
   case clang::CXXDtorType::Dtor_Complete:
-    return 1;
+    return "D1";
   case clang::CXXDtorType::Dtor_Base:
-    return 2;
+    return "D2";
   case clang::CXXDtorType::Dtor_Unified:
-    return 4;
+    return "D4";
   case clang::CXXDtorType::Dtor_Comdat:
     llvm_unreachable("Unexpected destructor kind.");
   }
 }
 
-static std::optional<int>
+static llvm::StringRef
 GetItaniumCtorDtorVariant(llvm::StringRef discriminator) {
   const bool is_ctor = discriminator.consume_front("C");
   if (!is_ctor && !discriminator.consume_front("D"))
-    return std::nullopt;
+    return {};
 
   uint64_t structor_kind;
   if (!llvm::to_integer(discriminator, structor_kind))
-    return std::nullopt;
+    return {};
 
   if (is_ctor) {
     if (structor_kind > clang::CXXCtorType::Ctor_Unified)
-      return std::nullopt;
+      return {};
 
     return ClangToItaniumCtorKind(
         static_cast<clang::CXXCtorType>(structor_kind));
   }
 
   if (structor_kind > clang::CXXDtorType::Dtor_Unified)
-    return std::nullopt;
+    return {};
 
   return ClangToItaniumDtorKind(static_cast<clang::CXXDtorType>(structor_kind));
 }
@@ -2543,82 +2545,74 @@ GetItaniumCtorDtorVariant(llvm::StringRef discriminator) {
 llvm::Expected<DWARFDIE>
 SymbolFileDWARF::FindFunctionDefinition(const FunctionCallLabel &label,
                                         const DWARFDIE &declaration) {
-  DWARFDIE definition;
-  llvm::DenseMap<int, DWARFDIE> structor_variant_to_die;
+  auto do_lookup = [this](llvm::StringRef lookup_name) -> DWARFDIE {
+    DWARFDIE found;
+    Module::LookupInfo info(ConstString(lookup_name),
+                            lldb::eFunctionNameTypeFull,
+                            lldb::eLanguageTypeUnknown);
 
-  Module::LookupInfo info(ConstString(label.lookup_name),
-                          lldb::eFunctionNameTypeFull,
-                          lldb::eLanguageTypeUnknown);
+    m_index->GetFunctions(info, *this, {}, [&](DWARFDIE entry) {
+      if (entry.GetAttributeValueAsUnsigned(llvm::dwarf::DW_AT_declaration, 0))
+        return IterationAction::Continue;
 
-  m_index->GetFunctions(info, *this, {}, [&](DWARFDIE entry) {
-    if (entry.GetAttributeValueAsUnsigned(llvm::dwarf::DW_AT_declaration, 0))
-      return IterationAction::Continue;
-
-    // We're not picking a specific structor variant DIE, so we're done here.
-    if (label.discriminator.empty()) {
-      definition = entry;
+      found = entry;
       return IterationAction::Stop;
-    }
-
-    const char *mangled =
-        entry.GetMangledName(/*substitute_name_allowed=*/false);
-    if (!mangled)
-      return IterationAction::Continue;
+    });
 
-    // FIXME: we should make DWARF encode the structor variant instead of
-    // needing to re-demangle.
-    llvm::ItaniumPartialDemangler D;
-    if (D.partialDemangle(mangled))
-      return IterationAction::Continue;
-
-    auto structor_variant = D.getCtorOrDtorVariant();
-    if (!structor_variant)
-      return IterationAction::Continue;
-
-    auto [_, inserted] = structor_variant_to_die.try_emplace(*structor_variant,
-                                                             std::move(entry));
-    assert(inserted);
-
-    // The compiler may choose to alias the constructor variants
-    // (notably this happens on Linux), so we might not have a definition
-    // DIE for some structor variants. Hence we iterate over all variants
-    // and pick the most appropriate one out of those.
-    return IterationAction::Continue;
-  });
+    return found;
+  };
 
+  DWARFDIE definition = do_lookup(label.lookup_name);
   if (definition.IsValid())
     return definition;
 
-  auto label_variant = GetItaniumCtorDtorVariant(label.discriminator);
-  if (!label_variant)
+  // This is not a structor lookup. Nothing else to be done here.
+  if (label.discriminator.empty())
     return llvm::createStringError(
-        llvm::formatv("failed to retrieve structor variant from label: {0}",
-                      label.discriminator));
-
-  auto it = structor_variant_to_die.find(*label_variant);
-
-  // Found the exact variant.
-  if (it != structor_variant_to_die.end())
-    return it->getSecond();
+        "no definition DIE found in this SymbolFile");
 
-  // We need a C1 constructor. If debug-info only contains a DIE for C2,
-  // assume C1 was aliased to C2.
+  // We're doing a structor lookup. Maybe we didn't find the structor variant
+  // because the complete object structor was aliased to the base object
+  // structor. Try finding the alias instead.
   //
-  // FIXME: can DWARF encode this information for us?
-  if (!label.lookup_name.starts_with("~") && label_variant == 1) {
-    if (auto it = structor_variant_to_die.find(2);
-        it != structor_variant_to_die.end())
-      return it->getSecond();
-  }
+  // TODO: there are other reasons for why a subprogram definition might be
+  // missing. Ideally DWARF would tell us more details about which structor
+  // variant a DIE corresponds to and whether it's an alias.
+  auto subst_or_err =
+      CPlusPlusLanguage::SubstituteStructorAliases_ItaniumMangle(
+          label.lookup_name);
+  if (!subst_or_err)
+    return subst_or_err.takeError();
+
+  definition = do_lookup(*subst_or_err);
+
+  if (!definition.IsValid())
+    return llvm::createStringError(
+        "failed to find definition DIE for structor alias in fallback lookup");
 
-  return llvm::createStringError(llvm::formatv(
-      "failed to find DIE for structor variant: {0}", label.discriminator));
+  return definition;
 }
 
 llvm::Expected<SymbolContext>
-SymbolFileDWARF::ResolveFunctionCallLabel(const FunctionCallLabel &label) {
+SymbolFileDWARF::ResolveFunctionCallLabel(FunctionCallLabel &label) {
   std::lock_guard<std::recursive_mutex> guard(GetModuleMutex());
 
+  if (!label.discriminator.empty()) {
+    llvm::StringRef from = label.discriminator[0] == 'C' ? "C4" : "D4";
+
+    llvm::StringRef variant = GetItaniumCtorDtorVariant(label.discriminator);
+    if (variant.empty())
+      return llvm::createStringError(
+          "failed to get Itanium variant for discriminator");
+
+    auto subst_or_err = CPlusPlusLanguage::SubstituteStructor_ItaniumMangle(
+        label.lookup_name, from, variant);
+    if (subst_or_err && *subst_or_err)
+      label.lookup_name = subst_or_err->GetStringRef();
+    else
+      llvm::consumeError(subst_or_err.takeError());
+  }
+
   DWARFDIE die = GetDIE(label.symbol_id);
   if (!die.IsValid())
     return llvm::createStringError(
diff --git a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.h b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.h
index e29ed1fc73902..608130cd068ce 100644
--- a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.h
+++ b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.h
@@ -447,7 +447,7 @@ class SymbolFileDWARF : public SymbolFileCommon {
                                         DIEArray &&variable_dies);
 
   llvm::Expected<SymbolContext>
-  ResolveFunctionCallLabel(const FunctionCallLabel &label) override;
+  ResolveFunctionCallLabel(FunctionCallLabel &label) override;
 
   // Given a die_offset, figure out the symbol context representing that die.
   bool ResolveFunction(const DWARFDIE &die, bool include_inlines,
diff --git a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.cpp b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.cpp
index 9d7452a1988fa..8b8229a7020c5 100644
--- a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.cpp
+++ b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.cpp
@@ -1603,8 +1603,8 @@ void SymbolFileDWARFDebugMap::GetCompileOptions(
   });
 }
 
-llvm::Expected<SymbolContext> SymbolFileDWARFDebugMap::ResolveFunctionCallLabel(
-    const FunctionCallLabel &label) {
+llvm::Expected<SymbolContext>
+SymbolFileDWARFDebugMap::ResolveFunctionCallLabel(FunctionCallLabel &label) {
   const uint64_t oso_idx = GetOSOIndexFromUserID(label.symbol_id);
   SymbolFileDWARF *oso_dwarf = GetSymbolFileByOSOIndex(oso_idx);
   if (!oso_dwarf)
diff --git a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.h b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.h
index e1f1df23951c6..bce1ed2671af0 100644
--- a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.h
+++ b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.h
@@ -145,7 +145,7 @@ class SymbolFileDWARFDebugMap : public SymbolFileCommon {
   GetCompileOptions(std::unordered_map<lldb::CompUnitSP, Args> &args) override;
 
   llvm::Expected<SymbolContext>
-  ResolveFunctionCallLabel(const FunctionCallLabel &label) override;
+  ResolveFunctionCallLabel(FunctionCallLabel &label) override;
 
 protected:
   enum { kHaveInitializedOSOs = (1 << 0), kNumFlags };

>From a22baa8d2a0af37536b43c0689dfec9c9c169430 Mon Sep 17 00:00:00 2001
From: Michael Buch <michaelbuch12 at gmail.com>
Date: Thu, 21 Aug 2025 10:02:36 +0100
Subject: [PATCH 20/20] Revert "[ItaniumDemangle] Add getter for
 constructor/destructor variants"

This reverts commit 3b1e5146bd094dd221b829ae1da517d9ac75c9fe.
---
 libcxxabi/src/demangle/ItaniumDemangle.h     |  2 --
 llvm/include/llvm/Demangle/Demangle.h        |  3 ---
 llvm/include/llvm/Demangle/ItaniumDemangle.h |  2 --
 llvm/lib/Demangle/ItaniumDemangle.cpp        | 10 +++-------
 4 files changed, 3 insertions(+), 14 deletions(-)

diff --git a/libcxxabi/src/demangle/ItaniumDemangle.h b/libcxxabi/src/demangle/ItaniumDemangle.h
index 7b3983bc89367..6f27da7b9cadf 100644
--- a/libcxxabi/src/demangle/ItaniumDemangle.h
+++ b/libcxxabi/src/demangle/ItaniumDemangle.h
@@ -1766,8 +1766,6 @@ class CtorDtorName final : public Node {
 
   template<typename Fn> void match(Fn F) const { F(Basename, IsDtor, Variant); }
 
-  int getVariant() const { return Variant; }
-
   void printLeft(OutputBuffer &OB) const override {
     if (IsDtor)
       OB += "~";
diff --git a/llvm/include/llvm/Demangle/Demangle.h b/llvm/include/llvm/Demangle/Demangle.h
index 6af8fad9ceb86..d9b08b2d856dc 100644
--- a/llvm/include/llvm/Demangle/Demangle.h
+++ b/llvm/include/llvm/Demangle/Demangle.h
@@ -127,9 +127,6 @@ struct ItaniumPartialDemangler {
   /// If this symbol describes a constructor or destructor.
   DEMANGLE_ABI bool isCtorOrDtor() const;
 
-  /// If this symbol describes a constructor or destructor.
-  std::optional<int> getCtorOrDtorVariant() const;
-
   /// If this symbol describes a function.
   DEMANGLE_ABI bool isFunction() const;
 
diff --git a/llvm/include/llvm/Demangle/ItaniumDemangle.h b/llvm/include/llvm/Demangle/ItaniumDemangle.h
index c0db02f8e7fef..62d427c3966bb 100644
--- a/llvm/include/llvm/Demangle/ItaniumDemangle.h
+++ b/llvm/include/llvm/Demangle/ItaniumDemangle.h
@@ -1766,8 +1766,6 @@ class CtorDtorName final : public Node {
 
   template<typename Fn> void match(Fn F) const { F(Basename, IsDtor, Variant); }
 
-  int getVariant() const { return Variant; }
-
   void printLeft(OutputBuffer &OB) const override {
     if (IsDtor)
       OB += "~";
diff --git a/llvm/lib/Demangle/ItaniumDemangle.cpp b/llvm/lib/Demangle/ItaniumDemangle.cpp
index a5d7a5576fccf..1009cc91ca12a 100644
--- a/llvm/lib/Demangle/ItaniumDemangle.cpp
+++ b/llvm/lib/Demangle/ItaniumDemangle.cpp
@@ -560,17 +560,13 @@ bool ItaniumPartialDemangler::hasFunctionQualifiers() const {
 }
 
 bool ItaniumPartialDemangler::isCtorOrDtor() const {
-  return getCtorOrDtorVariant().has_value();
-}
-
-std::optional<int> ItaniumPartialDemangler::getCtorOrDtorVariant() const {
   const Node *N = static_cast<const Node *>(RootNode);
   while (N) {
     switch (N->getKind()) {
     default:
-      return std::nullopt;
+      return false;
     case Node::KCtorDtorName:
-      return static_cast<const CtorDtorName *>(N)->getVariant();
+      return true;
 
     case Node::KAbiTagAttr:
       N = static_cast<const AbiTagAttr *>(N)->Base;
@@ -592,7 +588,7 @@ std::optional<int> ItaniumPartialDemangler::getCtorOrDtorVariant() const {
       break;
     }
   }
-  return std::nullopt;
+  return false;
 }
 
 bool ItaniumPartialDemangler::isFunction() const {



More information about the libcxx-commits mailing list