Skip to content

Conversation

Michael137
Copy link
Member

@Michael137 Michael137 commented Aug 18, 2025

Depends on #154137

This patch is motivated by #149827, where we plan on using mangled names on structor declarations to find the exact structor definition that LLDB's expression evaluator should call.

Given a DW_TAG_subprogram for a function declaration, the most convenient way for a debugger to find the corresponding definition is to use the DW_AT_linkage_name (i.e., the mangled name). However, we currently can't do that for constructors/destructors because Clang doesn't attach linkage names to them. This is because, depending on ABI, there can be multiple definitions for a single constructor/destructor declaration. The way GCC works around this is by producing a C4/D4 "unified" mangling for structor declarations (see godbolt). GDB uses this to locate the relevant definitions.

This patch aligns Clang with GCC's DWARF output and allows us to implement the same lookup scheme in LLDB.

@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" clang:codegen IR generation bugs: mangling, exceptions, etc. llvm:codegen debuginfo labels Aug 18, 2025
@llvmbot
Copy link
Member

llvmbot commented Aug 18, 2025

@llvm/pr-subscribers-clang-driver
@llvm/pr-subscribers-clang-codegen

@llvm/pr-subscribers-debuginfo

Author: Michael Buch (Michael137)

Changes

Depends on #154137

This patch is motivated by #149827, where we plan on using mangled names on structor declarations to find the exact structor definition that LLDB's expression evaluator should call.

Given a DW_TAG_subprogram for a function declaration, the most convenient way for a debugger to find the corresponding definition is to use the DW_AT_linkage_name (i.e., the mangled name). However, we currently can't do that for constructors/destructors because Clang doesn't attach linkage names to them. This is because, depending on ABI, there can be multiple definitions for a single constructor/destructor declaration. The way GCC works around this is by producing a C4/D4 "unified" mangling for structor declarations. GDB uses this to locate the relevant definitions.

This patch aligns Clang with GCC's DWARF output and allows us to implement the same lookup scheme in LLDB.


Full diff: https://github.com/llvm/llvm-project/pull/154142.diff

9 Files Affected:

  • (modified) clang/include/clang/Basic/ABI.h (+6-4)
  • (modified) clang/lib/AST/ItaniumMangle.cpp (+10)
  • (modified) clang/lib/AST/MicrosoftMangle.cpp (+2)
  • (modified) clang/lib/CodeGen/CGDebugInfo.cpp (+26-6)
  • (modified) clang/lib/CodeGen/CGDebugInfo.h (+4)
  • (modified) clang/lib/CodeGen/MicrosoftCXXABI.cpp (+4)
  • (added) clang/test/CodeGenCXX/debug-info-structor-linkage-names.cpp (+72)
  • (modified) llvm/lib/CodeGen/AsmPrinter/DwarfUnit.cpp (+2-5)
  • (added) llvm/test/DebugInfo/Generic/structor-declaration-linkage-names.ll (+68)
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 a7380a19e3607..484f1b12f3588 100644
--- a/clang/lib/AST/ItaniumMangle.cpp
+++ b/clang/lib/AST/ItaniumMangle.cpp
@@ -6014,6 +6014,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';
@@ -6024,6 +6026,9 @@ void CXXNameMangler::mangleCXXCtorType(CXXCtorType T,
   case Ctor_Base:
     Out << '2';
     break;
+  case Ctor_Unified:
+    Out << '4';
+    break;
   case Ctor_Comdat:
     Out << '5';
     break;
@@ -6041,6 +6046,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";
@@ -6051,6 +6058,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 241c7c35fcc83..fe188de81b20f 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"
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)

@llvmbot
Copy link
Member

llvmbot commented Aug 18, 2025

@llvm/pr-subscribers-clang

Author: Michael Buch (Michael137)

Changes

Depends on #154137

This patch is motivated by #149827, where we plan on using mangled names on structor declarations to find the exact structor definition that LLDB's expression evaluator should call.

Given a DW_TAG_subprogram for a function declaration, the most convenient way for a debugger to find the corresponding definition is to use the DW_AT_linkage_name (i.e., the mangled name). However, we currently can't do that for constructors/destructors because Clang doesn't attach linkage names to them. This is because, depending on ABI, there can be multiple definitions for a single constructor/destructor declaration. The way GCC works around this is by producing a C4/D4 "unified" mangling for structor declarations. GDB uses this to locate the relevant definitions.

This patch aligns Clang with GCC's DWARF output and allows us to implement the same lookup scheme in LLDB.


Full diff: https://github.com/llvm/llvm-project/pull/154142.diff

9 Files Affected:

  • (modified) clang/include/clang/Basic/ABI.h (+6-4)
  • (modified) clang/lib/AST/ItaniumMangle.cpp (+10)
  • (modified) clang/lib/AST/MicrosoftMangle.cpp (+2)
  • (modified) clang/lib/CodeGen/CGDebugInfo.cpp (+26-6)
  • (modified) clang/lib/CodeGen/CGDebugInfo.h (+4)
  • (modified) clang/lib/CodeGen/MicrosoftCXXABI.cpp (+4)
  • (added) clang/test/CodeGenCXX/debug-info-structor-linkage-names.cpp (+72)
  • (modified) llvm/lib/CodeGen/AsmPrinter/DwarfUnit.cpp (+2-5)
  • (added) llvm/test/DebugInfo/Generic/structor-declaration-linkage-names.ll (+68)
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 a7380a19e3607..484f1b12f3588 100644
--- a/clang/lib/AST/ItaniumMangle.cpp
+++ b/clang/lib/AST/ItaniumMangle.cpp
@@ -6014,6 +6014,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';
@@ -6024,6 +6026,9 @@ void CXXNameMangler::mangleCXXCtorType(CXXCtorType T,
   case Ctor_Base:
     Out << '2';
     break;
+  case Ctor_Unified:
+    Out << '4';
+    break;
   case Ctor_Comdat:
     Out << '5';
     break;
@@ -6041,6 +6046,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";
@@ -6051,6 +6058,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 241c7c35fcc83..fe188de81b20f 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"
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)

@Michael137
Copy link
Member Author

I provided some debug-info size numbers in #149827 but I'll regenerate them with this PR because the implementation changed slightly.

@llvmbot llvmbot added the clang:driver 'clang' and 'clang++' user-facing binaries. Not 'clang-cl' label Aug 20, 2025
@Michael137 Michael137 force-pushed the debuginfo/mangled-structor-declarations branch from a430cef to cf9f73b Compare August 20, 2025 13:23
@Michael137 Michael137 requested a review from jmorse August 20, 2025 13:38
@Michael137
Copy link
Member Author

Here is the debug-info increase when I run bloaty on all the Clang/LLDB/LLVM object files:

$ bloaty `find builds-lldb/with-patch/ -name '*.o'` -- `find builds-lldb/no-patch/ -name '*.o'`
    FILE SIZE        VM SIZE    
 --------------  -------------- 
   +15%  +207Mi  [ = ]       0    .debug_str
  +9.6% +56.2Mi  [ = ]       0    .rela.debug_str_offsets
  +9.6% +9.37Mi  [ = ]       0    .debug_str_offsets
  +0.7% +4.29Mi  [ = ]       0    .debug_info
  +0.0%    +404  [ = ]       0    .group
  +0.0%    +230  +0.0%    +210    .rodata.str1.1
  +0.0%     +12  [ = ]       0    .debug_addr
  +0.1%      +8  [ = ]       0    .data.rel.ro._ZTVN4llvm2cl15OptionValueCopyIbEE
  +1.7%      +8  [ = ]       0    .data.rel.ro._ZTVN5clang4Sema27ContextualImplicitConverterE
  +150%      +6  [ = ]       0    .rodata._ZSt14in_place_indexILm10EE
 -66.7%      -2  [ = ]       0    .rodata._ZN4llvm7IsaPredIJN5clang17OpenACCBindClauseEEEE
  -0.9%      -4  [ = ]       0    .rodata._ZN4llvm14GlobalVariable11AllocMarkerE
  -1.3%      -6  [ = ]       0    .rodata._ZSt14in_place_indexILm0EE
  -3.0%     -12  [ = ]       0    .rodata._ZN4llvm3LLT28PointerAddressSpaceFieldInfoE
  -0.8%     -42  [ = ]       0    .rodata._ZSt7nullopt
  -0.0%    -191  [ = ]       0    .comment
  -0.1% -17.8Ki  [ = ]       0    .debug_line_str
  -1.9%  -243Ki  [ = ]       0    .debug_abbrev
  +4.9%  +277Mi  +0.0%    +210    TOTAL

Google used an earlier version of this patch to check how their binaries are affected. The .debug_str section exceeds the 4GB limit for some of the larger binaries. So I added a command-line option to switch this behaviour off. @cmtice @labath does that alleviate some of your concerns?

Also CCing @jmorse as a heads up for Sony in case you have any concerns.

@Michael137
Copy link
Member Author

Michael137 commented Aug 20, 2025

Test failure is an XPASS in TestExprDefinitionInDylib because the LLDB AsmLabel now has a mangled name in it and the lookup by mangled name succeeds. Though it's technically not correct because we're not guaranteed to pick the right constructor variant (we probably just pick the first one we find). I'm planning to un-XFAIL it in the LLDB patch that I'll land at the same time as this PR

Copy link
Collaborator

@rnk rnk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for adding the flag! I think this could've turned into a big fire drill for us if we had detected a 15% regression in .debug_str size during internal qualification later. 😥

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can add DocBrief here to expand on what this flag does, see this example:
https://clang.llvm.org/docs/ClangCommandLineReference.html#cmdoption-clang-frecord-command-line

This seems like a good place to mention that enabling the flag causes a significant increase (estimated 15%) in generated debug_str object size, that it's experimental, and that it enables a better interactive debugging experience, particularly in LLDB, because it enables calling constructors and destructors.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whoops this must've been by accident

@Michael137 Michael137 force-pushed the debuginfo/mangled-structor-declarations branch 2 times, most recently from a9e9151 to 7491738 Compare August 21, 2025 08:27
@jmorse
Copy link
Member

jmorse commented Aug 21, 2025

Similar thanks for the heads up,

The change broadly LGTMs, although this isn't a zone of debug-info that I'm really familiar with.

@Michael137 Michael137 force-pushed the debuginfo/mangled-structor-declarations branch from d6cb62e to 91da739 Compare August 26, 2025 09:01
@Michael137
Copy link
Member Author

I'll merge this with the LLDB test failure because it'll be fixed by the final PR in the patch series #149827

@Michael137 Michael137 merged commit 06d202b into llvm:main Sep 9, 2025
8 of 9 checks passed
@Michael137 Michael137 deleted the debuginfo/mangled-structor-declarations branch September 9, 2025 08:50
@llvm-ci
Copy link
Collaborator

llvm-ci commented Sep 9, 2025

LLVM Buildbot has detected a new failure on builder lldb-x86_64-debian running on lldb-x86_64-debian while building clang at step 6 "test".

Full details are available at: https://lab.llvm.org/buildbot/#/builders/162/builds/30695

Here is the relevant piece of the build log for the reference
Step 6 (test) failure: build (failure)
...
PASS: lldb-api :: tools/lldb-dap/server/TestDAP_server.py (80 of 3184)
PASS: lldb-api :: functionalities/data-formatter/data-formatter-stl/generic/map/TestDataFormatterStdMap.py (81 of 3184)
PASS: lldb-api :: source-manager/TestSourceManager.py (82 of 3184)
PASS: lldb-api :: tools/lldb-dap/module/TestDAP_module.py (83 of 3184)
PASS: lldb-api :: tools/lldb-dap/restart/TestDAP_restart.py (84 of 3184)
PASS: lldb-api :: functionalities/completion/TestCompletion.py (85 of 3184)
PASS: lldb-api :: functionalities/inline-stepping/TestInlineStepping.py (86 of 3184)
PASS: lldb-api :: python_api/process/TestProcessAPI.py (87 of 3184)
PASS: lldb-api :: tools/lldb-dap/step/TestDAP_step.py (88 of 3184)
PASS: lldb-api :: tools/lldb-dap/coreFile/TestDAP_coreFile.py (89 of 3184)
FAIL: lldb-api :: functionalities/limit-debug-info/TestLimitDebugInfo.py (90 of 3184)
******************** TEST 'lldb-api :: functionalities/limit-debug-info/TestLimitDebugInfo.py' FAILED ********************
Script:
--
/usr/bin/python3 /home/worker/2.0.1/lldb-x86_64-debian/llvm-project/lldb/test/API/dotest.py -u CXXFLAGS -u CFLAGS --env LLVM_LIBS_DIR=/home/worker/2.0.1/lldb-x86_64-debian/build/./lib --env LLVM_INCLUDE_DIR=/home/worker/2.0.1/lldb-x86_64-debian/build/include --env LLVM_TOOLS_DIR=/home/worker/2.0.1/lldb-x86_64-debian/build/./bin --arch x86_64 --build-dir /home/worker/2.0.1/lldb-x86_64-debian/build/lldb-test-build.noindex --lldb-module-cache-dir /home/worker/2.0.1/lldb-x86_64-debian/build/lldb-test-build.noindex/module-cache-lldb/lldb-api --clang-module-cache-dir /home/worker/2.0.1/lldb-x86_64-debian/build/lldb-test-build.noindex/module-cache-clang/lldb-api --executable /home/worker/2.0.1/lldb-x86_64-debian/build/./bin/lldb --compiler /home/worker/2.0.1/lldb-x86_64-debian/build/./bin/clang --dsymutil /home/worker/2.0.1/lldb-x86_64-debian/build/./bin/dsymutil --make /usr/bin/gmake --llvm-tools-dir /home/worker/2.0.1/lldb-x86_64-debian/build/./bin --lldb-obj-root /home/worker/2.0.1/lldb-x86_64-debian/build/tools/lldb --lldb-libs-dir /home/worker/2.0.1/lldb-x86_64-debian/build/./lib --cmake-build-type Release -t /home/worker/2.0.1/lldb-x86_64-debian/llvm-project/lldb/test/API/functionalities/limit-debug-info -p TestLimitDebugInfo.py
--
Exit Code: 1

Command Output (stdout):
--
lldb version 22.0.0git (https://github.com/llvm/llvm-project.git revision 06d202b6cb13b38165af03dbf12a3beaac0b38ea)
  clang revision 06d202b6cb13b38165af03dbf12a3beaac0b38ea
  llvm revision 06d202b6cb13b38165af03dbf12a3beaac0b38ea
Skipping the following test categories: ['libc++', 'msvcstl', 'dsym', 'gmodules', 'debugserver', 'objc']

--
Command Output (stderr):
--
Change dir to: /home/worker/2.0.1/lldb-x86_64-debian/llvm-project/lldb/test/API/functionalities/limit-debug-info
UNSUPPORTED: LLDB (/home/worker/2.0.1/lldb-x86_64-debian/build/bin/clang-x86_64) :: test_one_and_two_debug_dsym (TestLimitDebugInfo.LimitDebugInfoTestCase.test_one_and_two_debug_dsym) (test case does not fall in any category of interest for this run) 
runCmd: settings clear --all

output: 

runCmd: settings set symbols.enable-external-lookup false

output: 

runCmd: settings set target.inherit-tcc true

output: 

runCmd: settings set target.disable-aslr false

output: 

runCmd: settings set target.detach-on-error false

output: 

Michael137 added a commit that referenced this pull request Sep 9, 2025
#149827)

Depends on
* #148877
* #155483
* #155485
* #154137
* #154142

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 is 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 ABI-tagged structors 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. So when searching for the
definition DIE, LLDB will include the structor variant in its heuristic
for determining a match.

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 at 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 in
#155485. 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 I added
a heuristic in `SymbolFileDWARF` where we pick `C2` if we asked for `C1`
but it doesn't exist. This may not always be correct (e.g., if the
compiler decided to drop `C1` for other reasons).
3. In #154142 Clang will emit
`C4`/`D4` variants of ctors/dtors on declarations. When resolving the
`FunctionCallLabel` we will now substitute the actual variant that Clang
told us we need to call into the mangled name. We do this using LLDB's
`ManglingSubstitutor`. That way we find the definition DIE exactly the
same way we do for regular function calls.
4. In cases where declarations and definitions live in separate modules,
the DIE ID encoded in the function call label may not be enough to find
the definition DIE in the encoded module ID. For those cases we fall
back to how LLDB used to work: look up in all images of the target. To
make sure we don't use the unified mangled name for the fallback lookup,
we change the lookup name to whatever mangled name the FunctionCallLabel
resolved to.

rdar://104968288
llvm-sync bot pushed a commit to arm/arm-toolchain that referenced this pull request Sep 9, 2025
… call labels (#149827)

Depends on
* llvm/llvm-project#148877
* llvm/llvm-project#155483
* llvm/llvm-project#155485
* llvm/llvm-project#154137
* llvm/llvm-project#154142

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 is 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 ABI-tagged structors 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. So when searching for the
definition DIE, LLDB will include the structor variant in its heuristic
for determining a match.

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 at 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 in
llvm/llvm-project#155485. 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 I added
a heuristic in `SymbolFileDWARF` where we pick `C2` if we asked for `C1`
but it doesn't exist. This may not always be correct (e.g., if the
compiler decided to drop `C1` for other reasons).
3. In llvm/llvm-project#154142 Clang will emit
`C4`/`D4` variants of ctors/dtors on declarations. When resolving the
`FunctionCallLabel` we will now substitute the actual variant that Clang
told us we need to call into the mangled name. We do this using LLDB's
`ManglingSubstitutor`. That way we find the definition DIE exactly the
same way we do for regular function calls.
4. In cases where declarations and definitions live in separate modules,
the DIE ID encoded in the function call label may not be enough to find
the definition DIE in the encoded module ID. For those cases we fall
back to how LLDB used to work: look up in all images of the target. To
make sure we don't use the unified mangled name for the fallback lookup,
we change the lookup name to whatever mangled name the FunctionCallLabel
resolved to.

rdar://104968288
Michael137 added a commit to swiftlang/llvm-project that referenced this pull request Sep 9, 2025
…clarations (llvm#154142)

Depends on llvm#154137

This patch is motivated by
llvm#149827, where we plan on using
mangled names on structor declarations to find the exact structor
definition that LLDB's expression evaluator should call.

Given a `DW_TAG_subprogram` for a function declaration, the most
convenient way for a debugger to find the corresponding definition is to
use the `DW_AT_linkage_name` (i.e., the mangled name). However, we
currently can't do that for constructors/destructors because Clang
doesn't attach linkage names to them. This is because, depending on ABI,
there can be multiple definitions for a single constructor/destructor
declaration. The way GCC works around this is by producing a `C4`/`D4`
"unified" mangling for structor declarations (see
[godbolt](https://godbolt.org/z/Wds6cja9K)). GDB uses this to locate the
relevant definitions.

This patch aligns Clang with GCC's DWARF output and allows us to
implement the same lookup scheme in LLDB.

(cherry picked from commit 06d202b)
Michael137 added a commit to swiftlang/llvm-project that referenced this pull request Sep 9, 2025
llvm#149827)

Depends on
* llvm#148877
* llvm#155483
* llvm#155485
* llvm#154137
* llvm#154142

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 is 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 ABI-tagged structors 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. So when searching for the
definition DIE, LLDB will include the structor variant in its heuristic
for determining a match.

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 at 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 in
llvm#155485. 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 I added
a heuristic in `SymbolFileDWARF` where we pick `C2` if we asked for `C1`
but it doesn't exist. This may not always be correct (e.g., if the
compiler decided to drop `C1` for other reasons).
3. In llvm#154142 Clang will emit
`C4`/`D4` variants of ctors/dtors on declarations. When resolving the
`FunctionCallLabel` we will now substitute the actual variant that Clang
told us we need to call into the mangled name. We do this using LLDB's
`ManglingSubstitutor`. That way we find the definition DIE exactly the
same way we do for regular function calls.
4. In cases where declarations and definitions live in separate modules,
the DIE ID encoded in the function call label may not be enough to find
the definition DIE in the encoded module ID. For those cases we fall
back to how LLDB used to work: look up in all images of the target. To
make sure we don't use the unified mangled name for the fallback lookup,
we change the lookup name to whatever mangled name the FunctionCallLabel
resolved to.

rdar://104968288
(cherry picked from commit 57a7907)
andykaylor added a commit to andykaylor/llvm-project that referenced this pull request Sep 9, 2025
A recent change introduced a new dtor kind, which caused a build
warning in the CIR code where this dtor kind wasn't handled in a switch.
This fixes the problem.
andykaylor added a commit that referenced this pull request Sep 9, 2025
A recent change introduced a new dtor kind, which caused a build warning
in the CIR code where this dtor kind wasn't handled in a switch. This
fixes the problem.
@alexfh
Copy link
Contributor

alexfh commented Oct 9, 2025

Is it expected that -gno-structor-decl-linkage-names enables debug information (i.e. it has an effect of -g -gno-structor-decl-linkage-names)? I was quite surprised by this, but maybe it's a common behavior of clang options controlling debug information?

https://gcc.godbolt.org/z/bhbKq5W8W

@rnk
Copy link
Collaborator

rnk commented Oct 9, 2025

Is it expected that -gno-structor-decl-linkage-names enables debug information (i.e. it has an effect of -g -gno-structor-decl-linkage-names)? I was quite surprised by this, but maybe it's a common behavior of clang options controlling debug information?

https://gcc.godbolt.org/z/bhbKq5W8W

I think it's a bug, I think it's just a consequence of the use of g_Group, and I'm filing an issue, maybe I can make a patch.

@Michael137
Copy link
Member Author

Is it expected that -gno-structor-decl-linkage-names enables debug information (i.e. it has an effect of -g -gno-structor-decl-linkage-names)? I was quite surprised by this, but maybe it's a common behavior of clang options controlling debug information?

https://gcc.godbolt.org/z/bhbKq5W8W

Thanks for flagging! Yes that was unintentional.
Thanks for the quick fix @rnk

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

clang:codegen IR generation bugs: mangling, exceptions, etc. clang:driver 'clang' and 'clang++' user-facing binaries. Not 'clang-cl' clang:frontend Language frontend issues, e.g. anything involving "Sema" clang Clang issues not falling into any other category debuginfo llvm:codegen

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants