[libcxx-commits] [clang] [libcxx] [CodeGen] Added '*' to internal linkage types (PR #220864)

via libcxx-commits libcxx-commits at lists.llvm.org
Tue Oct 20 08:00:15 PDT 2026


https://github.com/matthew-j-code updated https://github.com/llvm/llvm-project/pull/220864

>From 1fd1f90fa7edeafbb5c68d6a602d37283d6857d0 Mon Sep 17 00:00:00 2001
From: "Jones, Matthew (DI SW SIM FRWK PLAT SIMFW HPC)"
 <jones.matthew at siemens.com>
Date: Tue, 11 Aug 2026 03:13:24 -0700
Subject: [PATCH 01/10] [CodeGen] Added '*' to internal linkage types

RTTI typename for internal-linkage types lacked '*', which broke
type_info::operator== and dynamic_cast with libstdc++

Fixes #34255
---
 clang/docs/ReleaseNotes.md                    |  6 ++-
 clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp | 20 ++++++--
 clang/lib/CodeGen/ItaniumCXXABI.cpp           | 23 ++++++---
 .../CIR-internal-linkage-typeinfo-name.cpp    | 51 +++++++++++++++++++
 clang/test/CIR/CodeGenCXX/vtable-linkage.cpp  | 12 ++---
 .../internal-linkage-typeinfo-name.cpp        | 51 +++++++++++++++++++
 6 files changed, 145 insertions(+), 18 deletions(-)
 create mode 100644 clang/test/CIR/CodeGen/CIR-internal-linkage-typeinfo-name.cpp
 create mode 100644 clang/test/CodeGenCXX/internal-linkage-typeinfo-name.cpp

diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index 79de46f09a8352..175ebac96ccde3 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -131,6 +131,11 @@ features cannot lower the translation-unit ABI level;
   accesses to these fields. `__attribute__((packed))` on the field or the
   enclosing record still reduces the alignment. (#GH208256)
 
+- Fixed RTTI name for internal-linkage types lacking '*', fixed breaking 
+  type_info::operator== and dynamic_cast with libstdc++. Fixed libc++ NonUnique
+  implementation incorrectly comparing internal-linakge types equal via strcmp. 
+  (#GH34255)
+
 ### AST Dumping Potentially Breaking Changes
 
 ### Clang Frontend Potentially Breaking Changes
@@ -752,7 +757,6 @@ features cannot lower the translation-unit ABI level;
 - Fixed an assertion failure on use of an uninitialized token in dependency directives lexing in clang-scan-deps.
 - Fixed auto type inference being rejected when the initializer is a bit-field. (#GH226280)
 - Fixed failed assertion when creating a compound literal with erroneous initializer at file scope. (#GH229888)
-
 #### Bug Fixes to Compiler Builtins
 
 - Fixed a crash when classifying a call to a builtin with dependent arguments,
diff --git a/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp b/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp
index 9c952dd6054686..b184f192c3858f 100644
--- a/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp
@@ -848,12 +848,20 @@ CIRGenItaniumRTTIBuilder::getAddrOfTypeName(mlir::Location loc, QualType ty,
   llvm::raw_svector_ostream out(name);
   cgm.getCXXABI().getMangleContext().mangleCXXRTTIName(ty, out);
 
-  // We know that the mangled name of the type starts at index 4 of the
-  // mangled name of the typename, so we can just index into it in order to
-  // get the mangled name of the type.
+  // RTTI type-name symbol has form "_ZTS<mangled-string>". The string stored in
+  // type_info object excludes the "_ZTS" prefix. So we skip past the first 4
+  // characters. For types that do not have externally visible Clang/C++
+  // linkage, '*' is prepended to the type-name string so that within libstdc++
+  // RTTI names compare correctly via strcmp across translation units. LLVM
+  // internal linkage marks incomplete types as internal linkage, resulting in
+  // '*' being prepended incorrectly, thus Clang/C++ linkage is used, through
+  // isExternallyVisible.
+  SmallString<256> typeName;
+  if (!isExternallyVisible(ty->getLinkage()))
+    typeName += '*';
+  typeName += StringRef(name).substr(4);
   mlir::Attribute init = builder.getString(
-      name.substr(4), cgm.convertType(cgm.getASTContext().CharTy),
-      std::nullopt);
+      typeName, cgm.convertType(cgm.getASTContext().CharTy), std::nullopt);
 
   CharUnits align =
       cgm.getASTContext().getTypeAlignInChars(cgm.getASTContext().CharTy);
@@ -863,6 +871,8 @@ CIRGenItaniumRTTIBuilder::getAddrOfTypeName(mlir::Location loc, QualType ty,
   // So cast Init to a ConstArrayAttr should be safe.
   auto initStr = cast<cir::ConstArrayAttr>(init);
 
+  // RTTI type-name object is still emitted with prefix "_ZTS" in the symbol
+  // name, so that it can be found by the linker.
   cir::GlobalOp gv = cgm.createOrReplaceCXXRuntimeVariable(
       loc, name, initStr.getType(), linkage, align);
   CIRGenModule::setInitializer(gv, init);
diff --git a/clang/lib/CodeGen/ItaniumCXXABI.cpp b/clang/lib/CodeGen/ItaniumCXXABI.cpp
index 5bbb742a7b1efa..e812bcea06ac2a 100644
--- a/clang/lib/CodeGen/ItaniumCXXABI.cpp
+++ b/clang/lib/CodeGen/ItaniumCXXABI.cpp
@@ -3566,22 +3566,33 @@ llvm::GlobalVariable *ItaniumRTTIBuilder::GetAddrOfTypeName(
   llvm::raw_svector_ostream Out(Name);
   CGM.getCXXABI().getMangleContext().mangleCXXRTTIName(Ty, Out);
 
-  // We know that the mangled name of the type starts at index 4 of the
-  // mangled name of the typename, so we can just index into it in order to
-  // get the mangled name of the type.
+  // RTTI type-name symbol has form "_ZTS<mangled-string>". The string stored in
+  // type_info object excludes the "_ZTS" prefix. So we skip past the first 4
+  // characters. For types that do not have externally visible Clang/C++
+  // linkage, '*' is prepended to the type-name string so that within libstdc++
+  // RTTI names compare correctly via strcmp across translation units. LLVM
+  // internal linkage marks incomplete types as internal linkage, resulting in
+  // '*' being prepended incorrectly, thus Clang/C++ linkage is used, through
+  // isExternal
+  SmallString<256> TypeName;
+  if (!isExternallyVisible(Ty->getLinkage()))
+    TypeName += '*';
+  TypeName += StringRef(Name).substr(4);
   llvm::Constant *Init;
   if (CGM.getTriple().isOSzOS()) {
     // On z/OS, typename is stored as 2 encodings: EBCDIC followed by ASCII.
     SmallString<256> DualEncodedName;
-    llvm::ConverterEBCDIC::convertToEBCDIC(Name.substr(4), DualEncodedName);
+    llvm::ConverterEBCDIC::convertToEBCDIC(TypeName, DualEncodedName);
     DualEncodedName += '\0';
-    DualEncodedName += Name.substr(4);
+    DualEncodedName += TypeName;
     Init = llvm::ConstantDataArray::getString(VMContext, DualEncodedName);
   } else
-    Init = llvm::ConstantDataArray::getString(VMContext, Name.substr(4));
+    Init = llvm::ConstantDataArray::getString(VMContext, TypeName);
 
   auto Align = CGM.getContext().getTypeAlignInChars(CGM.getContext().CharTy);
 
+  // RTTI type-name object is still emitted with prefix "_ZTS" in the symbol
+  // name, so that it can be found by the linker.
   llvm::GlobalVariable *GV = CGM.CreateOrReplaceCXXRuntimeVariable(
       Name, Init->getType(), Linkage, Align.getAsAlign());
 
diff --git a/clang/test/CIR/CodeGen/CIR-internal-linkage-typeinfo-name.cpp b/clang/test/CIR/CodeGen/CIR-internal-linkage-typeinfo-name.cpp
new file mode 100644
index 00000000000000..3a43ea283ab153
--- /dev/null
+++ b/clang/test/CIR/CodeGen/CIR-internal-linkage-typeinfo-name.cpp
@@ -0,0 +1,51 @@
+//RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++11 -fclangir -emit-cir -o - %s | FileCheck %s
+
+// Check that RTTI type-name strings use a leading '*' for types that do not have externally visible Clang/C++ linkage,
+// and for it to be omitted from externally visible types.
+
+namespace std { class type_info; }
+
+//The following types have internal linkage, so their typeinfo names should have a leading '*'
+namespace { struct A {}; }
+const std::type_info &t1() { return typeid(A); }
+
+const std::type_info &t2() { struct L {}; return typeid(L); }
+
+const std::type_info &t3() { return typeid(A*); }
+
+template <class T> struct B {};
+const std::type_info &t4() { return typeid(B<A>); }
+
+namespace { struct C { int x; }; }
+const std::type_info &t5() { return typeid(int A::*); }
+
+//Following should not have a '*' prefix in the typeinfo name, since they have external linkage
+struct Ext {};
+
+const std::type_info &t6() { return typeid(Ext); }
+
+const std::type_info &t7() { return typeid(Ext*); }
+
+namespace NS2 { struct D {}; }
+
+const std::type_info &t8() { return typeid(NS2::D); }
+
+struct Fwd;
+const std::type_info &t9() { return typeid(Fwd*); }
+
+// The following checks emitted RTTI type-names. The global name is the ABI-mangled type-name object, 
+// while the string constant is the mangled type-name itself. For types without externally visible linkage,
+// Clang emits these as 'internal'. {{.*}} matches for array sizes, which is irrelevant to this test.
+
+// These checks are for types with internal linkage, which should have a '*' prefix in the typeinfo name.
+//CHECK-DAG: cir.global{{.*}}internal{{.*}}@_ZTSN12_GLOBAL__N_11AE = #cir.const_array<"*N12_GLOBAL__N_11AE"{{.*}}>
+//CHECK-DAG: cir.global{{.*}}internal{{.*}}@_ZTSZ2t2vE1L = #cir.const_array<"*Z2t2vE1L"{{.*}}>
+//CHECK-DAG: cir.global{{.*}}internal{{.*}}@_ZTSPN12_GLOBAL__N_11AE = #cir.const_array<"*PN12_GLOBAL__N_11AE"{{.*}}>
+//CHECK-DAG: cir.global{{.*}}internal{{.*}}@_ZTS1BIN12_GLOBAL__N_11AEE = #cir.const_array<"*1BIN12_GLOBAL__N_11AEE"{{.*}}>
+//CHECK-DAG: cir.global{{.*}}internal{{.*}}@_ZTSMN12_GLOBAL__N_11AEi = #cir.const_array<"*MN12_GLOBAL__N_11AEi"{{.*}}>
+
+// These checks are for types with external linkage, which should not have a '*' prefix in the typeinfo name.
+//CHECK-DAG: cir.global{{.*}}@_ZTS3Ext = #cir.const_array<"3Ext"{{.*}}>
+//CHECK-DAG: cir.global{{.*}}@_ZTSP3Ext = #cir.const_array<"P3Ext"{{.*}}>
+//CHECK-DAG: cir.global{{.*}}@_ZTSN3NS21DE = #cir.const_array<"N3NS21DE"{{.*}}>
+//CHECK-DAG: cir.global{{.*}}@_ZTSP3Fwd = #cir.const_array<"P3Fwd"{{.*}}>
\ No newline at end of file
diff --git a/clang/test/CIR/CodeGenCXX/vtable-linkage.cpp b/clang/test/CIR/CodeGenCXX/vtable-linkage.cpp
index ece1a9675761a2..fcd49b8d950fd2 100644
--- a/clang/test/CIR/CodeGenCXX/vtable-linkage.cpp
+++ b/clang/test/CIR/CodeGenCXX/vtable-linkage.cpp
@@ -197,19 +197,19 @@ void use_F() {
 // The anonymous struct for e has no linkage, so the vtable should have
 // internal linkage.
 // CIR-DAG: cir.global "private" constant internal dso_local @_ZTV3$_0 = #cir.vtable<{#cir.const_array<[#cir.ptr<null> : !cir.ptr<!u8i>, #cir.global_view<@_ZTI3$_0> : !cir.ptr<!u8i>, #cir.global_view<@_ZN1D1fEv> : !cir.ptr<!u8i>]> : !cir.array<!cir.ptr<!u8i> x 3>}> : !{{.*}}align(8)
-// CIR-DAG: cir.global constant internal dso_local @_ZTS3$_0 = #cir.const_array<"3$_0" : !cir.array<!s8i x 4>, trailing_zeros> : !cir.array<!s8i x 5> align(1)
+// CIR-DAG: cir.global constant internal dso_local @_ZTS3$_0 = #cir.const_array<"*3$_0" : !cir.array<!s8i x 5>, trailing_zeros> : !cir.array<!s8i x 6> align(1)
 // CIR-DAG: cir.global constant internal @_ZTI3$_0 = #cir.typeinfo<{#cir.global_view<@_ZTVN10__cxxabiv120__si_class_type_infoE, [2 : i32]> : !cir.ptr<!u8i>, #cir.global_view<@_ZTS3$_0> : !cir.ptr<!u8i>, #cir.global_view<@_ZTI1D> : !cir.ptr<!u8i>}> : !{{.*}}align(8)
-// LLVM-DAG: @"_ZTV3$_0" = internal constant { [3 x ptr] } { [3 x ptr] [ptr null, ptr @"_ZTI3$_0", ptr @_ZN1D1fEv] }, align 8
-// LLVM-DAG: @"_ZTS3$_0" = internal constant [5 x i8] c"3$_0\00", align 1
+// LLVM-DAG: @"_ZTV3$_0" = internal constant {{.*}}{ [3 x ptr] } { [3 x ptr] [ptr null, ptr @"_ZTI3$_0", ptr @_ZN1D1fEv] }, align 8
+// LLVM-DAG: @"_ZTS3$_0" = internal constant {{.*}}[6 x i8] c"*3$_0\00", align 1
 // LLVM-DAG: @"_ZTI3$_0" = internal constant { ptr, ptr, ptr } { ptr getelementptr {{.*}}({{.*}}, ptr @_ZTVN10__cxxabiv120__si_class_type_infoE, i64 {{.*}}), ptr @"_ZTS3$_0", ptr @_ZTI1D }, align 8
 
 // The A vtable should have internal linkage since it is inside an anonymous 
 // namespace.
 // CIR-DAG: cir.global "private" constant internal dso_local @_ZTVN12_GLOBAL__N_11AE = #cir.vtable<{#cir.const_array<[#cir.ptr<null> : !cir.ptr<!u8i>, #cir.global_view<@_ZTIN12_GLOBAL__N_11AE> : !cir.ptr<!u8i>, #cir.global_view<@_ZN12_GLOBAL__N_11A1fEv> : !cir.ptr<!u8i>]> : !cir.array<!cir.ptr<!u8i> x 3>}> : !{{.*}}align(8)
-// CIR-DAG: cir.global constant internal dso_local @_ZTSN12_GLOBAL__N_11AE = #cir.const_array<"N12_GLOBAL__N_11AE" : !cir.array<!s8i x 18>, trailing_zeros> : !cir.array<!s8i x 19> align(1)
+// CIR-DAG: cir.global constant internal dso_local @_ZTSN12_GLOBAL__N_11AE = #cir.const_array<"*N12_GLOBAL__N_11AE" : !cir.array<!s8i x 19>, trailing_zeros> : !cir.array<!s8i x 20> align(1)
 // CIR-DAG: cir.global constant internal @_ZTIN12_GLOBAL__N_11AE = #cir.typeinfo<{#cir.global_view<@_ZTVN10__cxxabiv117__class_type_infoE, [2 : i32]> : !cir.ptr<!u8i>, #cir.global_view<@_ZTSN12_GLOBAL__N_11AE> : !cir.ptr<!u8i>}> : !{{.*}}align(8)
-// LLVM-DAG: @_ZTVN12_GLOBAL__N_11AE = internal constant { [3 x ptr] } { [3 x ptr] [ptr null, ptr @_ZTIN12_GLOBAL__N_11AE, ptr @_ZN12_GLOBAL__N_11A1fEv] }, align 8
-// LLVM-DAG: @_ZTSN12_GLOBAL__N_11AE = internal constant [19 x i8] c"N12_GLOBAL__N_11AE\00", align 1
+// LLVM-DAG: @_ZTVN12_GLOBAL__N_11AE = internal constant {{.*}}{ [3 x ptr] } { [3 x ptr] [ptr null, ptr @_ZTIN12_GLOBAL__N_11AE, ptr @_ZN12_GLOBAL__N_11A1fEv] }, align 8
+// LLVM-DAG: @_ZTSN12_GLOBAL__N_11AE = internal constant {{.*}}[20 x i8] c"*N12_GLOBAL__N_11AE\00", align 1
 // LLVM-DAG: @_ZTIN12_GLOBAL__N_11AE = internal constant { ptr, ptr } { ptr getelementptr {{.*}}({{.*}}, ptr @_ZTVN10__cxxabiv117__class_type_infoE, i64 {{.*}}), ptr @_ZTSN12_GLOBAL__N_11AE }, align 8
 
 // F<char> is an explicit specialization without a key function, so
diff --git a/clang/test/CodeGenCXX/internal-linkage-typeinfo-name.cpp b/clang/test/CodeGenCXX/internal-linkage-typeinfo-name.cpp
new file mode 100644
index 00000000000000..3c8d1c1d0aa62a
--- /dev/null
+++ b/clang/test/CodeGenCXX/internal-linkage-typeinfo-name.cpp
@@ -0,0 +1,51 @@
+//RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++11 -emit-llvm -o - %s | FileCheck %s
+
+// Check that RTTI type-name strings use a leading '*' for types that do not have externally visible Clang/C++ linkage,
+// and for it to be omitted from externally visible types.
+
+namespace std { class type_info; }
+
+//The following types have internal linkage, so their typeinfo names should have a leading '*'
+namespace { struct A {}; }
+const std::type_info &t1() { return typeid(A); }
+
+const std::type_info &t2() { struct L {}; return typeid(L); }
+
+const std::type_info &t3() { return typeid(A*); }
+
+template <class T> struct B {};
+const std::type_info &t4() { return typeid(B<A>); }
+
+namespace { struct C { int x; }; }
+const std::type_info &t5() { return typeid(int A::*); }
+
+//Following should not have a '*' prefix in the typeinfo name, since they have external linkage
+struct Ext {};
+
+const std::type_info &t6() { return typeid(Ext); }
+
+const std::type_info &t7() { return typeid(Ext*); }
+
+namespace NS2 { struct D {}; }
+
+const std::type_info &t8() { return typeid(NS2::D); }
+
+struct Fwd;
+const std::type_info &t9() { return typeid(Fwd*); }
+
+// The following checks emitted RTTI type-names. The global name is the ABI-mangled type-name object, 
+// while the string constant is the mangled type-name itself. For types without externally visible linkage,
+// Clang emits these as 'internal constant'. {{.*}} matches for array sizes, which is irrelevant to this test.
+
+// These checks are for types with internal linkage, which should have a '*' prefix in the typeinfo name.
+//CHECK-DAG: @_ZTSN12_GLOBAL__N_11AE = internal constant {{.*}}c"*N12_GLOBAL__N_11AE\00"
+//CHECK-DAG: @_ZTSZ2t2vE1L = internal constant {{.*}}c"*Z2t2vE1L\00"
+//CHECK-DAG: @_ZTSPN12_GLOBAL__N_11AE = internal constant {{.*}}c"*PN12_GLOBAL__N_11AE\00"
+//CHECK-DAG: @_ZTS1BIN12_GLOBAL__N_11AEE = internal constant {{.*}}c"*1BIN12_GLOBAL__N_11AEE\00"
+//CHECK-DAG: @_ZTSMN12_GLOBAL__N_11AEi = internal constant {{.*}}c"*MN12_GLOBAL__N_11AEi\00"
+
+// These checks are for types with external linkage, which should not have a '*' prefix in the typeinfo name.
+//CHECK-DAG: @_ZTS3Ext = {{.*}}constant {{.*}}c"3Ext\00"
+//CHECK-DAG: @_ZTSP3Ext = {{.*}}constant {{.*}}c"P3Ext\00"
+//CHECK-DAG: @_ZTSN3NS21DE = {{.*}}constant {{.*}}c"N3NS21DE\00"
+//CHECK-DAG: @_ZTSP3Fwd = {{.*}}constant {{.*}}c"P3Fwd\00"
\ No newline at end of file

>From 839b7b273ac4abafdd8360524ad6d212b42dba84 Mon Sep 17 00:00:00 2001
From: "Jones, Matthew (DI SW SIM FRWK PLAT SIMFW HPC)"
 <jones.matthew at siemens.com>
Date: Tue, 22 Sep 2026 08:59:28 -0700
Subject: [PATCH 02/10] removed * prefix from rtti name() to match libstdc++,
 resulting in no change to libc++. Also added a test to verify this

---
 libcxx/include/typeinfo                       |  3 +-
 .../type_info.name.internal_linkage.pass.cpp  | 30 +++++++++++++++++++
 2 files changed, 32 insertions(+), 1 deletion(-)
 create mode 100644 libcxx/test/libcxx/language.support/support.rtti/type.info/type_info.name.internal_linkage.pass.cpp

diff --git a/libcxx/include/typeinfo b/libcxx/include/typeinfo
index aca6b91768d095..58ae6ece281855 100644
--- a/libcxx/include/typeinfo
+++ b/libcxx/include/typeinfo
@@ -309,7 +309,8 @@ protected:
 public:
   virtual ~type_info();
   [[__nodiscard__]] _LIBCPP_HIDE_FROM_ABI const char* name() const _NOEXCEPT {
-    return __impl::__type_name_to_string(__type_name);
+    const char* __str = __impl::__type_name_to_string(__type_name);
+    return __str[0] == '*' ? __str + 1 : __str;
   }
 
   [[__nodiscard__]] _LIBCPP_HIDE_FROM_ABI bool before(const type_info& __arg) const _NOEXCEPT {
diff --git a/libcxx/test/libcxx/language.support/support.rtti/type.info/type_info.name.internal_linkage.pass.cpp b/libcxx/test/libcxx/language.support/support.rtti/type.info/type_info.name.internal_linkage.pass.cpp
new file mode 100644
index 00000000000000..8d5e487fd598e7
--- /dev/null
+++ b/libcxx/test/libcxx/language.support/support.rtti/type.info/type_info.name.internal_linkage.pass.cpp
@@ -0,0 +1,30 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: no-rtti
+
+// Compilers may prefix the type_info::name() result with a '*' to indicate that the type has internal linkage. This test checks that libc++ strips the '*' prefix from the name() result.
+
+#include <typeinfo>
+#include <cassert>
+
+namespace {
+struct AnonymousType {};
+} // namespace
+
+const std::type_info& local_type() {
+  struct LocalType {};
+  return typeid(LocalType);
+}
+
+int main(int, char**) {
+  assert(typeid(AnonymousType).name()[0] != '*');
+  assert(typeid(AnonymousType*).name()[0] != '*');
+  assert(local_type().name()[0] != '*');
+  return 0;
+}

>From f1cac03050bea1c5d374e0bb0d2bac5560f5581d Mon Sep 17 00:00:00 2001
From: "Jones, Matthew (DI SW SIM FRWK PLAT SIMFW HPC)"
 <jones.matthew at siemens.com>
Date: Wed, 23 Sep 2026 07:21:49 -0700
Subject: [PATCH 03/10] fixed non_unique_impl incorrectly compared internally
 linked types via strcmp, fixed test which incorrectly sets two internally
 linked typeinfo names as equal

---
 libcxx/include/typeinfo                             | 13 +++++++++++--
 .../type.info/type_info.comparison.unmerged.sh.cpp  |  2 +-
 2 files changed, 12 insertions(+), 3 deletions(-)

diff --git a/libcxx/include/typeinfo b/libcxx/include/typeinfo
index 58ae6ece281855..9afec27ea4e6ea 100644
--- a/libcxx/include/typeinfo
+++ b/libcxx/include/typeinfo
@@ -221,10 +221,19 @@ struct __non_unique_impl : __string_impl_base {
     return __hash;
   }
   _LIBCPP_HIDE_FROM_ABI _LIBCPP_ALWAYS_INLINE static bool __eq(__type_name_t __lhs, __type_name_t __rhs) _NOEXCEPT {
-    return __lhs == __rhs || __builtin_strcmp(__lhs, __rhs) == 0;
+    // Internal linkage types (marked with '*') cannot compare equal via strcmp.
+    // They are only equal if they refer to the same RTTI object.
+    return __lhs == __rhs || (__lhs[0] != '*' && __builtin_strcmp(__lhs, __rhs) == 0);
   }
   _LIBCPP_HIDE_FROM_ABI _LIBCPP_ALWAYS_INLINE static bool __lt(__type_name_t __lhs, __type_name_t __rhs) _NOEXCEPT {
-    return __builtin_strcmp(__lhs, __rhs) < 0;
+    int __r = __builtin_strcmp(__lhs, __rhs);
+    if (__r != 0)
+      return __r < 0;
+    // Internal linkage types (marked with '*') are ordered via pointer address.
+    if (__lhs[0] == '*') {
+      return reinterpret_cast<uintptr_t>(__lhs) < reinterpret_cast<uintptr_t>(__rhs);
+    }
+    return false;
   }
 };
 
diff --git a/libcxx/test/libcxx/language.support/support.rtti/type.info/type_info.comparison.unmerged.sh.cpp b/libcxx/test/libcxx/language.support/support.rtti/type.info/type_info.comparison.unmerged.sh.cpp
index 9b94fcbc1c92a8..937b5057a1f984 100644
--- a/libcxx/test/libcxx/language.support/support.rtti/type.info/type_info.comparison.unmerged.sh.cpp
+++ b/libcxx/test/libcxx/language.support/support.rtti/type.info/type_info.comparison.unmerged.sh.cpp
@@ -37,7 +37,7 @@ void register2();
     register2();
 
     assert(registry.size() == 2);
-    assert(registry[0] == registry[1]);
+    assert(registry[0] != registry[1]);
     return 0;
   }
 #else

>From 0628b670412b894e10ca51a20581279ce21b241c Mon Sep 17 00:00:00 2001
From: "Jones, Matthew (DI SW SIM FRWK PLAT SIMFW HPC)"
 <jones.matthew at siemens.com>
Date: Wed, 23 Sep 2026 08:19:19 -0700
Subject: [PATCH 04/10] [libc++] Make unmerged type_info test depend on whether
 internal-linkage RTTI names are marked

---
 .../type_info.comparison.unmerged.sh.cpp          | 15 +++++++++++++--
 1 file changed, 13 insertions(+), 2 deletions(-)

diff --git a/libcxx/test/libcxx/language.support/support.rtti/type.info/type_info.comparison.unmerged.sh.cpp b/libcxx/test/libcxx/language.support/support.rtti/type.info/type_info.comparison.unmerged.sh.cpp
index 937b5057a1f984..89f77efb199ea7 100644
--- a/libcxx/test/libcxx/language.support/support.rtti/type.info/type_info.comparison.unmerged.sh.cpp
+++ b/libcxx/test/libcxx/language.support/support.rtti/type.info/type_info.comparison.unmerged.sh.cpp
@@ -18,6 +18,15 @@
 #include <typeindex>
 #include <vector>
 
+#include "../../../../support/test_macros.h"
+
+// Itanium ABI implementations prefix the RTTI name of a type with internal linkage with a '*',
+// which lets this implementation distinguish between two internally linked types
+#if !defined(_LIBCPP_ABI_MICROSOFT) &&                                                                                 \
+    (defined(TEST_COMPILER_GCC) || (defined(TEST_CLANG_VER) && TEST_CLANG_VER >= 2400))
+#  define INTERNAL_LINKAGE_RTTI_NAMES_ARE_MARKED
+#endif
+
 extern std::vector<std::type_index> registry;
 
 void register1();
@@ -37,9 +46,11 @@ void register2();
     register2();
 
     assert(registry.size() == 2);
+#  if defined(INTERNAL_LINKAGE_RTTI_NAMES_ARE_MARKED)
     assert(registry[0] != registry[1]);
+#  else
+    assert(registry[0] == registry[1]);
+#  endif
     return 0;
   }
-#else
-# error
 #endif

>From 923293bf98ca4f86de48a426cda1513dd215fef9 Mon Sep 17 00:00:00 2001
From: "Jones, Matthew (DI SW SIM FRWK PLAT SIMFW HPC)"
 <jones.matthew at siemens.com>
Date: Wed, 23 Sep 2026 08:24:57 -0700
Subject: [PATCH 05/10] formatting

---
 .../support.rtti/type.info/type_info.comparison.unmerged.sh.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/libcxx/test/libcxx/language.support/support.rtti/type.info/type_info.comparison.unmerged.sh.cpp b/libcxx/test/libcxx/language.support/support.rtti/type.info/type_info.comparison.unmerged.sh.cpp
index 89f77efb199ea7..5f6b990b1442e5 100644
--- a/libcxx/test/libcxx/language.support/support.rtti/type.info/type_info.comparison.unmerged.sh.cpp
+++ b/libcxx/test/libcxx/language.support/support.rtti/type.info/type_info.comparison.unmerged.sh.cpp
@@ -18,7 +18,7 @@
 #include <typeindex>
 #include <vector>
 
-#include "../../../../support/test_macros.h"
+#include "test_macros.h"
 
 // Itanium ABI implementations prefix the RTTI name of a type with internal linkage with a '*',
 // which lets this implementation distinguish between two internally linked types

>From a9a1fd889f5bc3e1027960b8783a4e1bc45d325a Mon Sep 17 00:00:00 2001
From: "Jones, Matthew (DI SW SIM FRWK PLAT SIMFW HPC)"
 <jones.matthew at siemens.com>
Date: Mon, 19 Oct 2026 06:34:19 -0700
Subject: [PATCH 06/10] formatting

---
 clang/test/CIR/CodeGen/CIR-internal-linkage-typeinfo-name.cpp | 2 +-
 clang/test/CodeGenCXX/internal-linkage-typeinfo-name.cpp      | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/clang/test/CIR/CodeGen/CIR-internal-linkage-typeinfo-name.cpp b/clang/test/CIR/CodeGen/CIR-internal-linkage-typeinfo-name.cpp
index 3a43ea283ab153..4f4f63478d4e30 100644
--- a/clang/test/CIR/CodeGen/CIR-internal-linkage-typeinfo-name.cpp
+++ b/clang/test/CIR/CodeGen/CIR-internal-linkage-typeinfo-name.cpp
@@ -48,4 +48,4 @@ const std::type_info &t9() { return typeid(Fwd*); }
 //CHECK-DAG: cir.global{{.*}}@_ZTS3Ext = #cir.const_array<"3Ext"{{.*}}>
 //CHECK-DAG: cir.global{{.*}}@_ZTSP3Ext = #cir.const_array<"P3Ext"{{.*}}>
 //CHECK-DAG: cir.global{{.*}}@_ZTSN3NS21DE = #cir.const_array<"N3NS21DE"{{.*}}>
-//CHECK-DAG: cir.global{{.*}}@_ZTSP3Fwd = #cir.const_array<"P3Fwd"{{.*}}>
\ No newline at end of file
+//CHECK-DAG: cir.global{{.*}}@_ZTSP3Fwd = #cir.const_array<"P3Fwd"{{.*}}>
diff --git a/clang/test/CodeGenCXX/internal-linkage-typeinfo-name.cpp b/clang/test/CodeGenCXX/internal-linkage-typeinfo-name.cpp
index 3c8d1c1d0aa62a..c4d739f0768751 100644
--- a/clang/test/CodeGenCXX/internal-linkage-typeinfo-name.cpp
+++ b/clang/test/CodeGenCXX/internal-linkage-typeinfo-name.cpp
@@ -48,4 +48,4 @@ const std::type_info &t9() { return typeid(Fwd*); }
 //CHECK-DAG: @_ZTS3Ext = {{.*}}constant {{.*}}c"3Ext\00"
 //CHECK-DAG: @_ZTSP3Ext = {{.*}}constant {{.*}}c"P3Ext\00"
 //CHECK-DAG: @_ZTSN3NS21DE = {{.*}}constant {{.*}}c"N3NS21DE\00"
-//CHECK-DAG: @_ZTSP3Fwd = {{.*}}constant {{.*}}c"P3Fwd\00"
\ No newline at end of file
+//CHECK-DAG: @_ZTSP3Fwd = {{.*}}constant {{.*}}c"P3Fwd\00"

>From fed78dd9a9ba62cdbf69d02d72b62b7176b5a979 Mon Sep 17 00:00:00 2001
From: "Jones, Matthew (DI SW SIM FRWK PLAT SIMFW HPC)"
 <jones.matthew at siemens.com>
Date: Tue, 20 Oct 2026 01:47:22 -0700
Subject: [PATCH 07/10] [CodeGen] updated comments

---
 clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp | 8 ++------
 clang/lib/CodeGen/ItaniumCXXABI.cpp           | 8 ++------
 2 files changed, 4 insertions(+), 12 deletions(-)

diff --git a/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp b/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp
index b184f192c3858f..1ae06736559511 100644
--- a/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp
@@ -852,10 +852,8 @@ CIRGenItaniumRTTIBuilder::getAddrOfTypeName(mlir::Location loc, QualType ty,
   // type_info object excludes the "_ZTS" prefix. So we skip past the first 4
   // characters. For types that do not have externally visible Clang/C++
   // linkage, '*' is prepended to the type-name string so that within libstdc++
-  // RTTI names compare correctly via strcmp across translation units. LLVM
-  // internal linkage marks incomplete types as internal linkage, resulting in
-  // '*' being prepended incorrectly, thus Clang/C++ linkage is used, through
-  // isExternallyVisible.
+  // RTTI names compare correctly via strcmp across translation units. Linkage
+  // of the type name is used, not the LLVM linkage of the typeinfo.
   SmallString<256> typeName;
   if (!isExternallyVisible(ty->getLinkage()))
     typeName += '*';
@@ -871,8 +869,6 @@ CIRGenItaniumRTTIBuilder::getAddrOfTypeName(mlir::Location loc, QualType ty,
   // So cast Init to a ConstArrayAttr should be safe.
   auto initStr = cast<cir::ConstArrayAttr>(init);
 
-  // RTTI type-name object is still emitted with prefix "_ZTS" in the symbol
-  // name, so that it can be found by the linker.
   cir::GlobalOp gv = cgm.createOrReplaceCXXRuntimeVariable(
       loc, name, initStr.getType(), linkage, align);
   CIRGenModule::setInitializer(gv, init);
diff --git a/clang/lib/CodeGen/ItaniumCXXABI.cpp b/clang/lib/CodeGen/ItaniumCXXABI.cpp
index e812bcea06ac2a..1c8fb43d027d32 100644
--- a/clang/lib/CodeGen/ItaniumCXXABI.cpp
+++ b/clang/lib/CodeGen/ItaniumCXXABI.cpp
@@ -3570,10 +3570,8 @@ llvm::GlobalVariable *ItaniumRTTIBuilder::GetAddrOfTypeName(
   // type_info object excludes the "_ZTS" prefix. So we skip past the first 4
   // characters. For types that do not have externally visible Clang/C++
   // linkage, '*' is prepended to the type-name string so that within libstdc++
-  // RTTI names compare correctly via strcmp across translation units. LLVM
-  // internal linkage marks incomplete types as internal linkage, resulting in
-  // '*' being prepended incorrectly, thus Clang/C++ linkage is used, through
-  // isExternal
+  // RTTI names compare correctly via strcmp across translation units. Linkage
+  // of the type name is used, not the LLVM linkage of the typeinfo.
   SmallString<256> TypeName;
   if (!isExternallyVisible(Ty->getLinkage()))
     TypeName += '*';
@@ -3591,8 +3589,6 @@ llvm::GlobalVariable *ItaniumRTTIBuilder::GetAddrOfTypeName(
 
   auto Align = CGM.getContext().getTypeAlignInChars(CGM.getContext().CharTy);
 
-  // RTTI type-name object is still emitted with prefix "_ZTS" in the symbol
-  // name, so that it can be found by the linker.
   llvm::GlobalVariable *GV = CGM.CreateOrReplaceCXXRuntimeVariable(
       Name, Init->getType(), Linkage, Align.getAsAlign());
 

>From f76d19b1f06a93dc1ed7fc5b7dfaa1fcddd47404 Mon Sep 17 00:00:00 2001
From: "Jones, Matthew (DI SW SIM FRWK PLAT SIMFW HPC)"
 <jones.matthew at siemens.com>
Date: Tue, 20 Oct 2026 04:25:23 -0700
Subject: [PATCH 08/10] [CodeGen] updated nonunique impl test and split into
 separate test

---
 ...pe_info.comparison.internal_linkage.sh.cpp | 59 +++++++++++++++++++
 .../type_info.comparison.unmerged.sh.cpp      | 57 +++++++-----------
 2 files changed, 81 insertions(+), 35 deletions(-)
 create mode 100644 libcxx/test/libcxx/language.support/support.rtti/type.info/type_info.comparison.internal_linkage.sh.cpp

diff --git a/libcxx/test/libcxx/language.support/support.rtti/type.info/type_info.comparison.internal_linkage.sh.cpp b/libcxx/test/libcxx/language.support/support.rtti/type.info/type_info.comparison.internal_linkage.sh.cpp
new file mode 100644
index 00000000000000..dcd447eea06aa2
--- /dev/null
+++ b/libcxx/test/libcxx/language.support/support.rtti/type.info/type_info.comparison.internal_linkage.sh.cpp
@@ -0,0 +1,59 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: no-rtti
+
+// Older versions don't mark RTTI names of types with internal linkage with a '*'
+// XFAIL: clang-21, clang-22, clang-23
+
+// Itanium ABI implementations prefix the RTTI name of a type with internal linkage with a '*',
+// which lets this implementation distinguish between two internally linked types.
+
+// RUN: %{cxx} %s %{flags} %{compile_flags} -c -o %t.tu1.o -DTU1 -D_LIBCPP_TYPEINFO_COMPARISON_IMPLEMENTATION=2
+// RUN: %{cxx} %s %{flags} %{compile_flags} -c -o %t.tu2.o -DTU2 -D_LIBCPP_TYPEINFO_COMPARISON_IMPLEMENTATION=2
+// RUN: %{cxx} %s %{flags} %{compile_flags} -c -o %t.main.o -DMAIN -D_LIBCPP_TYPEINFO_COMPARISON_IMPLEMENTATION=2
+// RUN: %{cxx} %t.tu1.o %t.tu2.o %t.main.o %{flags} %{link_flags} -o %t.exe
+// RUN: %{exec} %t.exe
+
+#include <cassert>
+#include <typeindex>
+#include <vector>
+
+extern std::vector<std::type_index> registry;
+
+void register1();
+void register2();
+
+#if defined(TU1)
+namespace {
+struct A {
+  bool x;
+};
+} // namespace
+void register1() { registry.push_back(std::type_index(typeid(A))); }
+#elif defined(TU2)
+namespace {
+struct A {
+  int x, y;
+};
+} // namespace
+void register2() { registry.push_back(std::type_index(typeid(A))); }
+#elif defined(MAIN)
+std::vector<std::type_index> registry;
+
+int main(int, char**) {
+  register1();
+  register2();
+
+  assert(registry.size() == 2);
+  assert(registry[0] != registry[1]);
+  return 0;
+}
+#else
+#  error
+#endif
diff --git a/libcxx/test/libcxx/language.support/support.rtti/type.info/type_info.comparison.unmerged.sh.cpp b/libcxx/test/libcxx/language.support/support.rtti/type.info/type_info.comparison.unmerged.sh.cpp
index 5f6b990b1442e5..954299ef21477d 100644
--- a/libcxx/test/libcxx/language.support/support.rtti/type.info/type_info.comparison.unmerged.sh.cpp
+++ b/libcxx/test/libcxx/language.support/support.rtti/type.info/type_info.comparison.unmerged.sh.cpp
@@ -8,49 +8,36 @@
 
 // UNSUPPORTED: no-rtti
 
-// RUN: %{cxx} %s %{flags} %{compile_flags} -c -o %t.tu1.o -DTU1 -D_LIBCPP_TYPEINFO_COMPARISON_IMPLEMENTATION=2
-// RUN: %{cxx} %s %{flags} %{compile_flags} -c -o %t.tu2.o -DTU2 -D_LIBCPP_TYPEINFO_COMPARISON_IMPLEMENTATION=2
+// The type A has external linkage, so its RTTI name is not prefixed with '*'.
+// Its RTTI is emitted separately in the shared library and in the main executable,
+// and libc++ compares the names with strcmp, so the two type_index values must be equal.
+
+// RUN: %{cxx} %s %{flags} %{compile_flags} -fPIC -shared -o %t.lib.so -DTU1 -D_LIBCPP_TYPEINFO_COMPARISON_IMPLEMENTATION=2 %{link_flags}
 // RUN: %{cxx} %s %{flags} %{compile_flags} -c -o %t.main.o -DMAIN -D_LIBCPP_TYPEINFO_COMPARISON_IMPLEMENTATION=2
-// RUN: %{cxx} %t.tu1.o %t.tu2.o %t.main.o %{flags} %{link_flags} -o %t.exe
+// RUN: %{cxx} %t.main.o %t.lib.so %{flags} %{link_flags} -o %t.exe
 // RUN: %{exec} %t.exe
 
 #include <cassert>
 #include <typeindex>
-#include <vector>
-
-#include "test_macros.h"
 
-// Itanium ABI implementations prefix the RTTI name of a type with internal linkage with a '*',
-// which lets this implementation distinguish between two internally linked types
-#if !defined(_LIBCPP_ABI_MICROSOFT) &&                                                                                 \
-    (defined(TEST_COMPILER_GCC) || (defined(TEST_CLANG_VER) && TEST_CLANG_VER >= 2400))
-#  define INTERNAL_LINKAGE_RTTI_NAMES_ARE_MARKED
-#endif
+// Hidden visibility keeps each copy of the RTTI local to its own module.
+struct [[gnu::visibility("hidden")]] A {
+  int x;
+};
 
-extern std::vector<std::type_index> registry;
-
-void register1();
-void register2();
+std::type_index registerA();
 
 #if defined(TU1)
-  namespace { struct A { bool x; }; }
-  void register1() { registry.push_back(std::type_index(typeid(A))); }
-#elif defined(TU2)
-  namespace { struct A { int x, y; }; }
-  void register2() { registry.push_back(std::type_index(typeid(A))); }
+std::type_index registerA() { return std::type_index(typeid(A)); }
+
 #elif defined(MAIN)
-  std::vector<std::type_index> registry;
-
-  int main(int, char**) {
-    register1();
-    register2();
-
-    assert(registry.size() == 2);
-#  if defined(INTERNAL_LINKAGE_RTTI_NAMES_ARE_MARKED)
-    assert(registry[0] != registry[1]);
-#  else
-    assert(registry[0] == registry[1]);
-#  endif
-    return 0;
-  }
+int main(int, char**) {
+  std::type_index executable = std::type_index(typeid(A));
+  std::type_index shared     = registerA();
+
+  assert(executable == shared);
+  return 0;
+}
+#else
+#  error
 #endif

>From cdeee42b5a5f16a4585d4c213914938df9f23908 Mon Sep 17 00:00:00 2001
From: "Jones, Matthew (DI SW SIM FRWK PLAT SIMFW HPC)"
 <jones.matthew at siemens.com>
Date: Tue, 20 Oct 2026 07:56:22 -0700
Subject: [PATCH 09/10] typo

---
 clang/docs/ReleaseNotes.md | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index 175ebac96ccde3..54d48651a57116 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -131,9 +131,9 @@ features cannot lower the translation-unit ABI level;
   accesses to these fields. `__attribute__((packed))` on the field or the
   enclosing record still reduces the alignment. (#GH208256)
 
-- Fixed RTTI name for internal-linkage types lacking '*', fixed breaking 
+- Fixed RTTI name for internal-linkage types lacking '*', fixed breaking
   type_info::operator== and dynamic_cast with libstdc++. Fixed libc++ NonUnique
-  implementation incorrectly comparing internal-linakge types equal via strcmp. 
+  implementation incorrectly comparing internal-linkage types equal via strcmp.
   (#GH34255)
 
 ### AST Dumping Potentially Breaking Changes

>From 4a5085377687a10ee81bb0acbac77a84d4a3876d Mon Sep 17 00:00:00 2001
From: "Jones, Matthew (DI SW SIM FRWK PLAT SIMFW HPC)"
 <jones.matthew at siemens.com>
Date: Tue, 20 Oct 2026 07:57:07 -0700
Subject: [PATCH 10/10] whitespace

---
 clang/docs/ReleaseNotes.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index 54d48651a57116..73fc5367e518f7 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -757,6 +757,7 @@ features cannot lower the translation-unit ABI level;
 - Fixed an assertion failure on use of an uninitialized token in dependency directives lexing in clang-scan-deps.
 - Fixed auto type inference being rejected when the initializer is a bit-field. (#GH226280)
 - Fixed failed assertion when creating a compound literal with erroneous initializer at file scope. (#GH229888)
+
 #### Bug Fixes to Compiler Builtins
 
 - Fixed a crash when classifying a call to a builtin with dependent arguments,



More information about the libcxx-commits mailing list