Skip to content

Conversation

@Michael137
Copy link
Member

Depends on:

When compiling with -glldb, we repoint the DW_AT_type of a DIE to be a typedef that refers to the preferred_name. I.e.,:

template <typename T> structure t7;
using t7i = t7<int>;
template <typename T> struct __attribute__((__preferred_name__(t7i))) t7 {};
template <typename... Ts> void f1()

int main() { f1<t7i>(); }

would produce following (minified) DWARF:

DW_TAG_subprogram
  DW_AT_name      ("_STN|f1|<t7<int> >")
  DW_TAG_template_type_parameter
    DW_AT_type  (0x0000299c "t7i")
...
DW_TAG_typedef
  DW_AT_type      (0x000029a7 "t7<int>")
  DW_AT_name      ("t7i")

Note how the DW_AT_type of the template parameter is a typedef itself (instead of the canonical type). The DWARFTypePrinter would take the DW_AT_name of this typedef when reconstructing the name of f1, so we would end up with a verifier failure:

error: Simplified template DW_AT_name could not be reconstituted:
         original: f1<t7<int> >
    reconstituted: f1<t7i>

Fixing this allows us to un-XFAIL the simplified-template-names.cpp test in cross-project-tests. Unfortunately this is only tested on Darwin, where LLDB tuning is the default. AFAIK, there is no other case where the template parameter type wouldn't be canonical.

@llvmbot
Copy link
Member

llvmbot commented Nov 19, 2025

@llvm/pr-subscribers-debuginfo

Author: Michael Buch (Michael137)

Changes

Depends on:

When compiling with -glldb, we repoint the DW_AT_type of a DIE to be a typedef that refers to the preferred_name. I.e.,:

template &lt;typename T&gt; structure t7;
using t7i = t7&lt;int&gt;;
template &lt;typename T&gt; struct __attribute__((__preferred_name__(t7i))) t7 {};
template &lt;typename... Ts&gt; void f1()

int main() { f1&lt;t7i&gt;(); }

would produce following (minified) DWARF:

DW_TAG_subprogram
  DW_AT_name      ("_STN|f1|&lt;t7&lt;int&gt; &gt;")
  DW_TAG_template_type_parameter
    DW_AT_type  (0x0000299c "t7i")
...
DW_TAG_typedef
  DW_AT_type      (0x000029a7 "t7&lt;int&gt;")
  DW_AT_name      ("t7i")

Note how the DW_AT_type of the template parameter is a typedef itself (instead of the canonical type). The DWARFTypePrinter would take the DW_AT_name of this typedef when reconstructing the name of f1, so we would end up with a verifier failure:

error: Simplified template DW_AT_name could not be reconstituted:
         original: f1&lt;t7&lt;int&gt; &gt;
    reconstituted: f1&lt;t7i&gt;

Fixing this allows us to un-XFAIL the simplified-template-names.cpp test in cross-project-tests. Unfortunately this is only tested on Darwin, where LLDB tuning is the default. AFAIK, there is no other case where the template parameter type wouldn't be canonical.


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

5 Files Affected:

  • (added) cross-project-tests/debuginfo-tests/clang_llvm_roundtrip/Inputs/simplified_template_names.cpp (+272)
  • (added) cross-project-tests/debuginfo-tests/clang_llvm_roundtrip/simplified_template_names-debug-types.test (+9)
  • (removed) cross-project-tests/debuginfo-tests/clang_llvm_roundtrip/simplified_template_names.cpp (-360)
  • (added) cross-project-tests/debuginfo-tests/clang_llvm_roundtrip/simplified_template_names.test (+5)
  • (modified) llvm/include/llvm/DebugInfo/DWARF/DWARFTypePrinter.h (+19-3)
diff --git a/cross-project-tests/debuginfo-tests/clang_llvm_roundtrip/Inputs/simplified_template_names.cpp b/cross-project-tests/debuginfo-tests/clang_llvm_roundtrip/Inputs/simplified_template_names.cpp
new file mode 100644
index 0000000000000..344005ee98141
--- /dev/null
+++ b/cross-project-tests/debuginfo-tests/clang_llvm_roundtrip/Inputs/simplified_template_names.cpp
@@ -0,0 +1,272 @@
+#include <cstddef>
+#include <cstdint>
+template <typename... Ts> struct t1 {};
+template <typename... Ts> struct t2;
+struct udt {};
+namespace ns {
+struct udt {};
+namespace inner {
+template <typename T> struct ttp {};
+struct udt {};
+} // namespace inner
+template <template <typename> class T> void ttp_user() {}
+enum Enumeration : int { Enumerator1, Enumerator2, Enumerator3 = 1 };
+enum class EnumerationClass { Enumerator1, Enumerator2, Enumerator3 = 1 };
+enum : int { AnonEnum1, AnonEnum2, AnonEnum3 = 1 };
+enum EnumerationSmall : unsigned char { kNeg = 0xff };
+} // namespace ns
+template <typename... Ts> void f1() {
+  t1<Ts...> v1;
+  t2<Ts...> *v2;
+}
+template <bool b, int i> void f2() {}
+template <typename T, T... A> void f3() {}
+template <typename T, unsigned = 3> void f4() {}
+template <typename T, bool b = false> struct t3 {};
+extern template class t3<int>;
+template class t3<int>;
+struct outer_class {
+  struct inner_class {};
+};
+int i = 3;
+template <unsigned N> struct t4 {};
+namespace {
+struct t5 {};
+enum LocalEnum { LocalEnum1 };
+} // namespace
+template <typename... T1, typename T2 = int> void f5() {}
+template <typename T1, typename... T2> void f6() {}
+struct t6 {
+  template <typename T> void operator<<(int) {}
+  template <typename T> void operator<(int) {}
+  template <typename T> void operator<=(int) {}
+  template <typename T = int> operator t1<float> *() { return nullptr; }
+  template <typename T> void operator-(int) {}
+  template <typename T> void operator*(int) {}
+  template <typename T> void operator/(int) {}
+  template <typename T> void operator%(int) {}
+  template <typename T> void operator^(int) {}
+  template <typename T> void operator&(int) {}
+  template <typename T> void operator|(int) {}
+  template <typename T> void operator~() {}
+  template <typename T> void operator!() {}
+  template <typename T> void operator=(int) {}
+  template <typename T> void operator>(int) {}
+  template <typename T> void operator,(int) {}
+  template <typename T> void operator()() {}
+  template <typename T> void operator[](int) {}
+  template <typename T> void operator<=>(int) {}
+  template <typename T> void *operator new(std::size_t, T) {
+    __builtin_unreachable();
+  }
+  template <typename T> void operator delete(void *, T) {}
+  template <typename T> void *operator new[](std::size_t, T) {
+    __builtin_unreachable();
+  }
+  template <typename T> void operator delete[](void *, T) {}
+  template <typename T> int operator co_await() { __builtin_unreachable(); }
+};
+void operator"" _suff(unsigned long long) {}
+template <template <typename...> class T> void f7() {}
+template <template <typename...> class T, typename T2> void f8() {}
+template <typename T> struct t7;
+using t7i = t7<int>;
+template <typename T> struct __attribute__((__preferred_name__(t7i))) t7 {};
+struct t8 {
+  void mem();
+};
+namespace ns {
+inline namespace inl {
+template <typename T> struct t9 {};
+} // namespace inl
+} // namespace ns
+template <typename T> void (*f9())() { return nullptr; }
+struct t10 {
+  template <typename T = void> t10() {}
+};
+
+template <typename T> void operator_not_really() {}
+
+template <typename T, T... A> struct t11 {};
+
+struct t12 {
+  t11<LocalEnum, LocalEnum1> v1;
+};
+
+template <decltype(ns::AnonEnum1)> void f10() {}
+
+int main() {
+  struct {
+  } A;
+  auto L = [] {};
+  f1<int>();
+  f1<float>();
+  f1<bool>();
+  f1<double>();
+  f1<long>();
+  f1<short>();
+  f1<unsigned>();
+  f1<unsigned long long>();
+  f1<long long>();
+  f1<udt>();
+  f1<ns::udt>();
+  f1<ns::udt *>();
+  f1<ns::inner::udt>();
+  f1<t1<int>>();
+  f1<int, float>();
+  f1<int *>();
+  f1<int &>();
+  f1<int &&>();
+  f1<const int>();
+  f1<int[3]>();
+  f1<void>();
+  f1<outer_class::inner_class>();
+  f1<unsigned long>();
+  f2<true, 3>();
+  f3<ns::Enumeration, ns::Enumerator3, (ns::Enumeration)2>();
+  f3<ns::EnumerationClass, ns::EnumerationClass::Enumerator3,
+     (ns::EnumerationClass)2>();
+  f3<ns::EnumerationSmall, ns::kNeg>();
+  f3<decltype(ns::AnonEnum1), ns::AnonEnum3, (decltype(ns::AnonEnum1))2>();
+  f3<LocalEnum, LocalEnum1>();
+  f3<int *, &i>();
+  f3<int *, nullptr>();
+  t4<3> v2;
+  f3<unsigned long, 1>();
+  f3<unsigned long long, 1>();
+  f3<long, 1>();
+  f3<unsigned int, 1>();
+  f3<short, 1>();
+  f3<unsigned char, (char)0>();
+  f3<signed char, (char)0>();
+  f3<unsigned short, 1, 2>();
+  f3<char, 0, 1, 6, 7, 13, 14, 31, 32, 33, (char)127, (char)128>();
+  f3<__int128, ((__int128)9223372036854775807) * 2>();
+  f4<unsigned int>();
+  f1<t3<int>>();
+  f1<t3<t3<int>>>();
+  f1<decltype(L)>();
+  t3<decltype(L)> v1;
+  f1<t3<t3<decltype(L)>>>();
+  f1<int(float)>();
+  f1<void(...)>();
+  f1<void(int, ...)>();
+  f1<const int &>();
+  f1<const int *&>();
+  f1<t5>();
+  f1<decltype(nullptr)>();
+  f1<long *, long *>();
+  f1<long *, udt *>();
+  f1<void *const>();
+  f1<const void *const *>();
+  f1<void()>();
+  f1<void (*)()>();
+  f1<decltype(&L)>();
+  f1<decltype(A)>();
+  f1<decltype(&A)>();
+  f5<t1<int>>();
+  f5<>();
+  f6<t1<int>>();
+  f1<>();
+  f1<const void *, const void *>();
+  f1<t1<int *> *>();
+  f1<int *[]>();
+  t6 v6;
+  v6.operator<< <int>(1);
+  v6.operator< <int>(1);
+  v6.operator<= <int>(1);
+  v6.operator t1<float> *();
+  v6.operator- <int>(3);
+  v6.operator* <int>(3);
+  v6.operator/ <int>(3);
+  v6.operator% <int>(3);
+  v6.operator^ <int>(3);
+  v6.operator& <int>(3);
+  v6.operator| <int>(3);
+  v6.operator~ <int>();
+  v6.operator! <int>();
+  v6.operator= <int>(3);
+  v6.operator><int>(3);
+  v6.operator, <int>(3);
+  v6.operator()<int>();
+  v6.operator[]<int>(3);
+  v6.operator<=> <int>(3);
+  t6::operator new(0, 0);
+  t6::operator new[](0, 0);
+  t6::operator delete(nullptr, 0);
+  t6::operator delete[](nullptr, 0);
+  v6.operator co_await <int>();
+  42_suff;
+  struct t7 {};
+  f1<t7>();
+  f1<int (&)[3]>();
+  f1<int (*)[3]>();
+  f7<t1>();
+  f8<t1, int>();
+  using namespace ns;
+  ttp_user<inner::ttp>();
+  f1<int *, decltype(nullptr) *>();
+  t7i x;
+  f1<t7i>();
+  f7<ns::inl::t9>();
+  f1<_Atomic(int)>();
+  f1<int, long, volatile char>();
+  f1<__attribute__((__vector_size__(sizeof(int) * 2))) int>();
+  f1<int *const volatile>();
+  f1<const volatile void>();
+  f1<t1<decltype(L)>>();
+  t10 v3;
+  f1<void (::udt::*)() const>();
+  f1<void (::udt::*)() volatile &>();
+  f1<void (::udt::*)() const volatile &&>();
+  f9<int>();
+  f1<void (*const)()>();
+  f1<char const(&)[1]>();
+  f1<void() const &>();
+  f1<void() volatile &&>();
+  f1<void() const volatile>();
+  f1<int *const[1]>();
+  f1<int *const(&)[1]>();
+  f1<void (::udt::*const &)()>();
+  f1<void (*(int))(float)>();
+  f1<t1<int>[1]>();
+  f1<void (*)() noexcept>();
+  f1<void(decltype(A))>();
+  struct t8 {
+    decltype(A) m;
+  };
+  f1<void(t8, decltype(A))>();
+  f1<void(t8)>();
+  operator_not_really<int>();
+  t12 v4;
+  f1<_BitInt(3)>();
+  f1<const unsigned _BitInt(5)>();
+  f1<void(t1<>, t1<>)>();
+  f1<int t1<>::*>();
+  void fcc() __attribute__((swiftcall));
+  f1<decltype(fcc)>();
+  int fnrt() __attribute__((noreturn));
+  f1<decltype(fnrt)>();
+  f10<ns::AnonEnum1>();
+}
+void t8::mem() {
+  struct t7 {};
+  f1<t7>();
+  f1<decltype(&t8::mem)>();
+}
+namespace complex_type_units {
+void external_function();
+namespace {
+struct internal_type;
+}
+template <void (*)() = external_function> struct t2;
+template <typename = t2<>> class t3 {};
+template <typename = internal_type, typename = t3<>> struct t4 {};
+struct t5 {
+  t4<> v1;
+};
+void f1() {
+  t5 v1;
+  t3<> v2;
+}
+} // namespace complex_type_units
diff --git a/cross-project-tests/debuginfo-tests/clang_llvm_roundtrip/simplified_template_names-debug-types.test b/cross-project-tests/debuginfo-tests/clang_llvm_roundtrip/simplified_template_names-debug-types.test
new file mode 100644
index 0000000000000..74ce8f466b9ae
--- /dev/null
+++ b/cross-project-tests/debuginfo-tests/clang_llvm_roundtrip/simplified_template_names-debug-types.test
@@ -0,0 +1,9 @@
+// debug-types are not available for MachO.
+//
+// UNSUPPORTED: system-darwin
+//
+// RUN: %clang %target_itanium_abi_host_triple %p/Inputs/simplified_template_names.cpp -c -o - -gdwarf-4 -Xclang -gsimple-template-names=mangled -Xclang -debug-forward-template-params -std=c++20 -fdebug-types-section \
+// RUN:   | llvm-dwarfdump --verify -
+//
+// RUN: %clang %target_itanium_abi_host_triple %p/Inputs/simplified_template_names.cpp -c -o - -gdwarf-5 -Xclang -gsimple-template-names=mangled -Xclang -debug-forward-template-params -std=c++20 -fdebug-types-section \
+// RUN:   | llvm-dwarfdump --verify -
diff --git a/cross-project-tests/debuginfo-tests/clang_llvm_roundtrip/simplified_template_names.cpp b/cross-project-tests/debuginfo-tests/clang_llvm_roundtrip/simplified_template_names.cpp
deleted file mode 100644
index 956c7e37f0225..0000000000000
--- a/cross-project-tests/debuginfo-tests/clang_llvm_roundtrip/simplified_template_names.cpp
+++ /dev/null
@@ -1,360 +0,0 @@
-// UNSUPPORTED: system-darwin
-// RUN: %clang %target_itanium_abi_host_triple %s -c -o - -gdwarf-4 -Xclang -gsimple-template-names=mangled -Xclang -debug-forward-template-params -std=c++20 \
-// RUN:   | llvm-dwarfdump --verify -
-// RUN: %clang %target_itanium_abi_host_triple %s -c -o - -gdwarf-4 -Xclang -gsimple-template-names=mangled -Xclang -debug-forward-template-params -std=c++20 -gmlt -fdebug-info-for-profiling \
-// RUN:   | llvm-dwarfdump --verify -
-// RUN: %clang %target_itanium_abi_host_triple %s -c -o - -gdwarf-4 -Xclang -gsimple-template-names=mangled -Xclang -debug-forward-template-params -std=c++20 -fdebug-types-section \
-// RUN:   | llvm-dwarfdump --verify -
-// RUN: %clang %target_itanium_abi_host_triple %s -c -o - -gdwarf-5 -Xclang -gsimple-template-names=mangled -Xclang -debug-forward-template-params -std=c++20 -fdebug-types-section \
-// RUN:   | llvm-dwarfdump --verify -
-
-#include <cstdint>
-#include <cstddef>
-template<typename ...Ts>
-struct t1 {
-};
-template<typename ...Ts>
-struct t2;
-struct udt {
-};
-namespace ns {
-struct udt {
-};
-namespace inner {
-template<typename T> struct ttp { };
-struct udt { };
-}
-template<template<typename> class T>
-void ttp_user() { }
-enum Enumeration : int { Enumerator1, Enumerator2, Enumerator3 = 1 };
-enum class EnumerationClass { Enumerator1, Enumerator2, Enumerator3 = 1 };
-enum : int { AnonEnum1, AnonEnum2, AnonEnum3 = 1 };
-enum EnumerationSmall : unsigned char { kNeg = 0xff };
-}
-template <typename... Ts>
-void f1() {
-  t1<Ts...> v1;
-  t2<Ts...> *v2;
-}
-template<bool b, int i>
-void f2() {
-}
-template<typename T, T ...A>
-void f3() {
-}
-template<typename T, unsigned = 3>
-void f4() {
-}
-template<typename T, bool b = false>
-struct t3 { };
-extern template class t3<int>;
-template class t3<int>;
-struct outer_class {
-  struct inner_class {
-  };
-};
-int i = 3;
-template<unsigned N>
-struct t4 { };
-namespace {
-struct t5 { };
-enum LocalEnum { LocalEnum1 };
-}
-template<typename ...T1, typename T2 = int>
-void f5() { }
-template<typename T1, typename ...T2>
-void f6() { }
-struct t6 {
-  template<typename T>
-  void operator<<(int) {
-  }
-  template<typename T>
-  void operator<(int) {
-  }
-  template<typename T>
-  void operator<=(int) {
-  }
-  template<typename T = int>
-  operator t1<float>*() {
-    return nullptr;
-  }
-  template<typename T>
-  void operator-(int) {
-  }
-  template<typename T>
-  void operator*(int) {
-  }
-  template<typename T>
-  void operator/(int) {
-  }
-  template<typename T>
-  void operator%(int) {
-  }
-  template<typename T>
-  void operator^(int) {
-  }
-  template<typename T>
-  void operator&(int) {
-  }
-  template<typename T>
-  void operator|(int) {
-  }
-  template<typename T>
-  void operator~() {
-  }
-  template<typename T>
-  void operator!() {
-  }
-  template<typename T>
-  void operator=(int) {
-  }
-  template<typename T>
-  void operator>(int) {
-  }
-  template<typename T>
-  void operator,(int) {
-  }
-  template<typename T>
-  void operator()() {
-  }
-  template<typename T>
-  void operator[](int) {
-  }
-  template<typename T>
-  void operator<=>(int) {
-  }
-  template<typename T>
-  void* operator new(std::size_t, T) {
-    __builtin_unreachable();
-  }
-  template<typename T>
-  void operator delete(void*, T) {
-  }
-  template<typename T>
-  void* operator new[](std::size_t, T) {
-    __builtin_unreachable();
-  }
-  template<typename T>
-  void operator delete[](void*, T) {
-  }
-  template<typename T>
-  int operator co_await() { __builtin_unreachable(); }
-
-};
-void operator"" _suff(unsigned long long) {}
-template<template<typename...> class T> void f7() { }
-template<template<typename...> class T, typename T2> void f8() { }
-template<typename T>
-struct t7;
-using t7i = t7<int>;
-template<typename T>
-struct
-__attribute__((__preferred_name__(t7i)))
-t7 {
-};
-struct t8 {
-  void mem();
-};
-namespace ns {
-inline namespace inl {
-template<typename T> struct t9 { };
-}
-}
-template<typename T>
-void (*f9())() {
-  return nullptr;
-}
-struct t10 {
-  template<typename T = void>
-  t10() { }
-};
-
-template<typename T>
-void operator_not_really() {
-}
-
-template<typename T, T ...A>
-struct t11 {
-};
-
-struct t12 {
-  t11<LocalEnum, LocalEnum1> v1;
-};
-
-template<decltype(ns::AnonEnum1)>
-void f10() {
-}
-
-int main() {
-  struct { } A;
-  auto L = []{};
-  f1<int>();
-  f1<float>();
-  f1<bool>();
-  f1<double>();
-  f1<long>();
-  f1<short>();
-  f1<unsigned>();
-  f1<unsigned long long>();
-  f1<long long>();
-  f1<udt>();
-  f1<ns::udt>();
-  f1<ns::udt*>();
-  f1<ns::inner::udt>();
-  f1<t1<int>>();
-  f1<int, float>();
-  f1<int *>();
-  f1<int &>();
-  f1<int &&>();
-  f1<const int>();
-  f1<int[3]>();
-  f1<void>();
-  f1<outer_class::inner_class>();
-  f1<unsigned long>();
-  f2<true, 3>();
-  f3<ns::Enumeration, ns::Enumerator3, (ns::Enumeration)2>();
-  f3<ns::EnumerationClass, ns::EnumerationClass::Enumerator3, (ns::EnumerationClass)2>();
-  f3<ns::EnumerationSmall, ns::kNeg>();
-  f3<decltype(ns::AnonEnum1), ns::AnonEnum3, (decltype(ns::AnonEnum1))2>();
-  f3<LocalEnum, LocalEnum1>();
-  f3<int*, &i>();
-  f3<int*, nullptr>();
-  t4<3> v2;
-  f3<unsigned long, 1>();
-  f3<unsigned long long, 1>();
-  f3<long, 1>();
-  f3<unsigned int, 1>();
-  f3<short, 1>();
-  f3<unsigned char, (char)0>();
-  f3<signed char, (char)0>();
-  f3<unsigned short, 1, 2>();
-  f3<char, 0, 1, 6, 7, 13, 14, 31, 32, 33, (char)127, (char)128>();
-  f3<__int128, ((__int128)9223372036854775807) * 2>();
-  f4<unsigned int>();
-  f1<t3<int>>();
-  f1<t3<t3<int>>>();
-  f1<decltype(L)>();
-  t3<decltype(L)> v1;
-  f1<t3<t3<decltype(L)>>>();
-  f1<int(float)>();
-  f1<void(...)>();
-  f1<void(int, ...)>();
-  f1<const int &>();
-  f1<const int *&>();
-  f1<t5>();
-  f1<decltype(nullptr)>();
-  f1<long*, long*>();
-  f1<long*, udt*>();
-  f1<void *const>();
-  f1<const void *const *>();
-  f1<void()>();
-  f1<void(*)()>();
-  f1<decltype(&L)>();
-  f1<decltype(A)>();
-  f1<decltype(&A)>();
-  f5<t1<int>>();
-  f5<>();
-  f6<t1<int>>();
-  f1<>();
-  f1<const void*, const void*>();
-  f1<t1<int*>*>();
-  f1<int *[]>();
-  t6 v6;
-  v6.operator<< <int>(1);
-  v6.operator< <int>(1);
-  v6.operator<= <int>(1);
-  v6.operator t1<float>*();
-  v6.operator- <int>(3);
-  v6.operator* <int>(3);
-  v6.operator/ <int>(3);
-  v6.operator% <int>(3);
-  v6.operator^ <int>(3);
-  v6.operator& <int>(3);
-  v6.operator| <int>(3);
-  v6.operator~ <int>();
-  v6.operator! <int>();
-  v6.operator= <int>(3);
-  v6.operator> <int>(3);
-  v6.operator, <int>(3);
-  v6.operator() <int>();
-  v6.operator[] <int>(3);
-  v6.operator<=> <int>(3);
-  t6::operator new(0, 0);
-  t6::operator new[](0, 0);
-  t6::operator delete(nullptr, 0);
-  t6::operator delete[](nullptr, 0);
-  v6.operator co_await<int>();
-  42_suff;
-  struct t7 { };
-  f1<t7>();
-  f1<int(&)[3]>();
-  f1<int(*)[3]>();
-  f7<t1>();
-  f8<t1, int>();
-  using namespace ns;
-  ttp_user<inner::ttp>();
-  f1<int*, decltype(nullptr)*>();
-  t7i x;
-  f1<t7i>();
-  f7<ns::inl::t9>();
-  f1<_Atomic(int)>();
-  f1<int, long, volatile char>();
-  f1<__attribute__((__vector_size__(sizeof(int) * 2))) int>();
-  f1<int *const volatile>();
-  f1<const volatile void>();
-  f1<t1<decltype(L)>>();
-  t10 v3;
-  f1<void (::udt::*)() const>();
-  f1<void (::udt::*)() volatile &>();
-  f1<void (::udt::*)() const volatile &&>();
-  f9<int>();
-  f1<void (*const)()>();
-  f1<char const (&)[1]>();
-  f1<void () const &>();
-  f1<void () volatile &&>();
-  f1<void () const volatile>();
-  f1<int *const[1]>();
-  f1<int *const(&)[1]>();
-  f1<void (::udt::* const&)()>();
-  f1<void (*(int))(float)>();
-  f1<t1<int>[1]>();
-  f1<void (*)() noexcept>();
-  f1<void (decltype(A))>();
-  struct t8 { decltype(A) m; };
-  f1<void(t8, decltype(A))>();
-  f1<void(t8)>();
-  operator_not_really<int>();
-  t12 v4;
-  f1<_BitInt(3)>();
-  f1<const unsigned _BitInt(5)>();
-  f1<void(t1<>, t1<>)>();
-  f1<int t1<>::*>();
-  void fcc() __attribute__((swiftcall));
-  f1<decltype(fcc)>();
-  int fnrt() __attribute__((noreturn));
-  f1<decltype(fnrt)>();
-  f10<ns::AnonEnum1>();
-}
-void t8::mem() {
-  struct t7 { };
-  f1<t7>();
-  f1<decltype(&t8::mem)>();
-}
-namespace complex_type_units {
-void external_function();
-namespace {
-struct internal_type;
-}
-template <void (*)() = external_function> struct t2;
-template <typename = t2<>> class t3 {};
-template <typename = internal_type, typename = t3<>>
-struct t4 {
-};
-struct t5 {
-    t4<> v1;
-};
-void f1() {
-  t5 v1;
-  t3<> v2;
-}
-}
diff --git a/cross-project-tests/debuginfo-tests/clang_llvm_roundtrip/simplified_template_names.test b/cross-project-tests/debuginfo-tests/clang_llvm_roundtrip/simplified_template_names.test
new file mode 100644
index 0000000000000..5a0da903e2a8b
--- /dev/null
+++ b/cross-project-tests/debuginfo-tests/clang_llvm_roundtrip/simplified_template_names.test
@@ -0,0 +1,5 @@
+// RUN: %clang %target_itanium_abi_host_triple %p/Inputs/simplified_template_names.cpp -c -o - -gdwarf-4 -Xclang -gsimple-template-names=mangled -Xclang -debug-forward-template-params -std=c++20 -gtemplate-alias \
+// RUN:   | llvm-dwarfdump --verify -
+//
+// RUN: %clang %target_itanium_abi_host_triple %p/Inputs/simplified_template_names.cpp -c -o - -gdwarf-4 -Xclang -gsimple-template-names=mangled -Xclang -debug-forward-template-params -std=c++20 -gmlt -fdebug-info-for-profiling -gtemplate-alias \
+// RUN:   | llvm-dwarfdump --verify -
diff --git a/llvm/include/llvm/DebugInfo/DWARF/DWARFTypePrinter.h b/llvm/include/llvm/DebugInfo/DWARF/DWARFTypePrinter.h
index a760f773055d2..b26d0fc013da4 100644
--- a/llvm/include/llvm/DebugInfo/DWARF/DWARFTypePrinter.h
+++ b/llvm/include/llvm/DebugInfo/DWARF/DWARFTypePrinter.h
@@ -161,6 +161,23 @@ const char *toString(std::optional<DWARFFormValueType> F) {
   }
   return nullptr;
 }
+
+/// Resolve the DW_AT_type of \c D until we reach a DIE that is not a
+/// DW_TAG_typedef.
+template <typename DieType> DieType unwrapReferencedTypedefType(DieType D) {
+  auto TypeAttr = D.find(dwarf::DW_AT_type);
+  if (!TypeAttr)
+    return DieType();
+
+  auto Unwrapped = detail::resolveReferencedType(D, *TypeAttr);
+  if (!Unwrapped)
+    return DieType();
+
+  if (Unwrapped.getTag() == dwarf::DW_TAG_typedef)
+    return unwrapReferencedTypedefType(Unwrapped);
+
+  return Unwrapped;
+}
 } // namespace detail
 
 template <typename DieType>
@@ -553,10 +570,9 @@ bool DWARFTypePrinter<DieType>::appendTemplateParameters(DieType D,
     }
     if (C.getTag() != dwarf::DW_TAG_template_type_parameter)
       continue;
-    auto TypeAttr = C.find(dwarf::DW_AT_type);
     Sep();
-    appendQualifiedName(TypeAttr ? detail::resolveReferencedType(C, *TypeAttr)
-                                 : DieType());
+
+    appendQualifiedName(detail::unwrapReferencedTypedefType(C));
   }
   if (IsTemplate && *FirstParameter && FirstParameter == &FirstParameterValue) {
     OS << '<';

appendQualifiedName(TypeAttr ? detail::resolveReferencedType(C, *TypeAttr)
: DieType());

appendQualifiedName(detail::unwrapReferencedTypedefType(C));
Copy link
Member Author

Choose a reason for hiding this comment

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

Wasn't entirely sure if this was the right place. We could just unwrap the DIE in appendUnqualifiedNameBefore for DW_TAG_typedef. But that felt wrong (though that didn't fail any tests, so maybe we usually never get to appendUnqualifiedNameBefore for typedef types?). But still felt a bit off

@github-actions
Copy link

github-actions bot commented Nov 19, 2025

🐧 Linux x64 Test Results

  • 186404 tests passed
  • 4866 tests skipped

@dwblaikie
Copy link
Collaborator

Hmm, probably for good reason, but: Why do it this way (having the canonical type printing ignore the preferred name), rather than the other way (fully embrace the preferred name, and use it when printing the DW_AT_name (when unsimplified or mangled)?)

@Michael137
Copy link
Member Author

Michael137 commented Nov 19, 2025

Hmm, probably for good reason, but: Why do it this way (having the canonical type printing ignore the preferred name), rather than the other way (fully embrace the preferred name, and use it when printing the DW_AT_name (when unsimplified or mangled)?)

Here's the commit that disabled printing it for debug-info:

commit 2ff049b12ee3fb60581835a28bf9d0acc1723f23                             
Author: David Blaikie <dblaikie@gmail.com>                                  
Date:   Sun Sep 19 20:34:45 2021 -0700                                      
                                                                            
    DebugInfo: Don't use preferred template names in debug info             
                                                                            
    Using the preferred name creates a mismatch between the textual name of 
    a type and the DWARF tags describing the parameters as well as possible 
    inconsistency between DWARF producers (like Clang and GCC, or           
    older/newer Clang versions, etc).                                       

Is this still an important property to maintain?

Also, the reason we did the typedef indirection trick for LLDB was that we don't have a way to create a PreferredNameAttr in the Clang AST that LLDB creates. So the extra typedef allows us to print typenames based on the AST, without relying on DWARF extensions telling us that we are dealing with a preferred name. I suspect even if we reverted the above commit, LLDB would still depend on that extra typedef to print typenames with preferred names.

@dwblaikie
Copy link
Collaborator

Hmm, probably for good reason, but: Why do it this way (having the canonical type printing ignore the preferred name), rather than the other way (fully embrace the preferred name, and use it when printing the DW_AT_name (when unsimplified or mangled)?)

Here's the commit that disabled printing it for debug-info:

commit 2ff049b12ee3fb60581835a28bf9d0acc1723f23                             
Author: David Blaikie <dblaikie@gmail.com>                                  
Date:   Sun Sep 19 20:34:45 2021 -0700                                      
                                                                            
    DebugInfo: Don't use preferred template names in debug info             
                                                                            
    Using the preferred name creates a mismatch between the textual name of 
    a type and the DWARF tags describing the parameters as well as possible 
    inconsistency between DWARF producers (like Clang and GCC, or           
    older/newer Clang versions, etc).                                       

Is this still an important property to maintain?

The first point, maintaining correspondence between textual (DW_AT_name) and structural (DW_AT_template_type_parameter) is important for simplified template names verification - ensures there's a simple guarantee we're striving for: the ability to reconstruct the textual from the structural, character for character.
But we can maintain that invariant /and/ use the preferred name when we're targeting lldb which uses the preferred name for the DW_AT_template_type_parameter. ie: we should either use the preferred name for both structural and textual, or neither. So we can do it for both for lldb, and for neither otherwise. For now we do it for neither otherwise, but we split the difference when tuning for lldb.

The second point - inconsistency between DWARF producers - yeah, that still is a real thing... not sure what the right tradeoff is there...

Also, the reason we did the typedef indirection trick for LLDB was that we don't have a way to create a PreferredNameAttr in the Clang AST that LLDB creates. So the extra typedef allows us to print typenames based on the AST, without relying on DWARF extensions telling us that we are dealing with a preferred name. I suspect even if we reverted the above commit, LLDB would still depend on that extra typedef to print typenames with preferred names.

Ah :/ bit unfortunate.

But I'm not sure if I explained the problem I was pointing out - not that the typedef is a problem, but the fact that the DWARF produced for the template specialization varies depending on whether the typedef was produced for other reasons. We should probably not do that - we should probably produce and use the typedef always when it's the preferred name.

Copy link
Collaborator

@dwblaikie dwblaikie left a comment

Choose a reason for hiding this comment

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

As discussed - it does seem like it could be nice if the type printed with the preferred name for consistency with the structural description - but the GCC/Clang compatibility is not /not/ a concern... so can't exactly fault this direction either.

@Michael137 Michael137 force-pushed the llvm/simplified-template-names-preferred-name-2 branch from 5cc4ae9 to ff6d6d3 Compare November 20, 2025 00:00
@Michael137
Copy link
Member Author

ie: we should either use the preferred name for both structural and textual, or neither. So we can do it for both for lldb, and for neither otherwise. For now we do it for neither otherwise, but we split the difference when tuning for lldb.

Thanks for elaborating, this makes sense! Not great we have to do this for LLDB, but workable I suppose (given the whole AST restriction LLDB is working with -- i think we discussed using the demangled names for typenames, but decided using the TypePrinter gives us some nice options for presenting typenames in a user-friendly way, which the demangler currently can't do).

But I'm not sure if I explained the problem I was pointing out - not that the typedef is a problem, but the fact that the DWARF produced for the template specialization varies depending on whether the typedef was produced for other reasons. We should probably not do that - we should probably produce and use the typedef always when it's the preferred name.

Aah I see, yes I didn't catch this point the first time I read the issue. I'll take a look at this when I get time

@Michael137 Michael137 merged commit e96cc99 into llvm:main Nov 20, 2025
10 checks passed
@Michael137 Michael137 deleted the llvm/simplified-template-names-preferred-name-2 branch November 20, 2025 17:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants