https://github.com/dmpolukhin created 
https://github.com/llvm/llvm-project/pull/224826

### Problem

GCC has a single entity per namespace, and every reopening of an inline 
namespace adds its `abi_tag`s to that entity. Clang uses only the first 
`abi_tag` attribute of the first declaration, so tags added on a reopening are 
silently dropped and the mangled names differ from GCC:

```cpp
inline namespace N __attribute__((abi_tag("A"))) { struct S {}; }
inline namespace N __attribute__((abi_tag("B"))) {}
S f();  // GCC: _Z1fB1AB1Bv   Clang: _Z1fB1Av
```

With modules, the "first" declaration depends on deserialization order, so the 
applied tags can change with the order in which declarations are used 
(`_Z2uxB2TXv` vs `_Z2uxv`).

### Solution

- The mangler takes the union of the tags over all redeclarations of the 
namespace, and over all `abi_tag` attributes on each declaration. 
`-fclang-abi-compat=23` restores the old behavior.
- Reopening without the attribute (the libstdc++ `std::__cxx11` idiom), or with 
a subset of the tags, already matched GCC and is unchanged.
- Names stay consistent when a namespace gains a tag after it was used.
  - Implicit tags are recomputed on every mangling. A function emitted before 
such a reopening and a template instantiated after it therefore disagreed on 
the RTTI name of the function's local class, and a `catch` handler was skipped 
at run time.
  - As in GCC, the namespace tags visible to a function or variable are now 
fixed the first time it is mangled.
  - This is done with an epoch that advances only when a reopening adds a new 
tag to a namespace that was already used.
  - Without such reopenings the epoch never changes, so names are identical to 
`-fclang-abi-compat=23` by construction.
- There is no quadratic rescanning.
  - Tags are cached per namespace and extended incrementally.
  - Non-inline namespaces are skipped, since the attribute is only accepted on 
inline ones.
  - Compiling 20k reopenings takes the same time as `-fclang-abi-compat=23`. 
Walking all redeclarations on every mangling took 1.69s against 0.14s, and 
11.7s against 1.8s for a libstdc++-style namespace.
  - On `SemaDecl.cpp` the instruction count is within noise (+0.2%).

### Known limitations

- GCC mangles lazily, mostly at the end of the translation unit, while Clang 
mangles at the definition or first use. If a tag is added after a dependent 
entity was already mangled, the names can still differ (for example a variable 
or an inline function defined before the reopening). GCC itself depends on 
position there.
- The state lives in the mangle context. Other contexts (pointer-auth 
discriminators, abbreviated thunk names, interface stubs) can see different 
tags, but only in a translation unit where a namespace gains a tag after it was 
already used.

### Testing

- Expected manglings in `mangle-abi-tag-namespace-reopen.cpp` were taken from 
GCC trunk, which produced the same names as GCC 16.2.
- New tests cover:
  - modules, in both orders of use;
  - RTTI consistency, comparing the `catch` and `__cxa_throw` operands;
  - tags that arrive through a parameter type;
  - substitution-dependent names, pinned under both ABI modes.
- `check-clang` passes.

Related: #221039 proposes diagnosing these mismatches instead.

>From bfc715d4a7057d6142768c07b7ac32d0f13b8872 Mon Sep 17 00:00:00 2001
From: Dmitry Polukhin <[email protected]>
Date: Fri, 18 Sep 2026 22:21:51 +0100
Subject: [PATCH 1/2] [clang] Use the union of abi_tags from all declarations
 of an inline namespace

GCC has a single entity per namespace and every reopening of an inline
namespace adds its abi_tag attributes to that entity, so the ABI tags of
the namespace are the union of the tags on all of its declarations.

Clang only used the first abi_tag attribute of the first (canonical)
declaration of the namespace. Tags added when reopening the namespace were
silently dropped, which resulted in mangled names different from GCC:

  inline namespace N __attribute__((abi_tag("A"))) { struct S {}; }
  inline namespace N __attribute__((abi_tag("B"))) {}
  S f();  // GCC: _Z1fB1AB1Bv, Clang: _Z1fB1Av

With modules the canonical declaration depends on the deserialization
order, so the applied tags could also change with the order in which
declarations were used.

Collect the tags from all redeclarations of the namespace and from all
abi_tag attributes on each of them. -fclang-abi-compat=23 restores the
previous behavior.

Reopening a namespace without the attribute (the idiom used by libstdc++
for std::__cxx11) or with a subset of the tags already matched GCC and
is not affected.

Note that GCC computes the mangled name lazily, mostly at the end of the
translation unit, while Clang computes it when an entity is defined or
first used. If a tag is added to a namespace after an entity depending on
it was already mangled, the names can still differ from GCC (e.g. for a
variable or an inline function defined before the reopening).
---
 clang/docs/ReleaseNotes.md                    |   7 ++
 clang/include/clang/Basic/ABIVersions.def     |   3 +
 clang/include/clang/Basic/AttrDocs.td         |  14 +++
 clang/lib/AST/ItaniumMangle.cpp               |  16 ++-
 .../mangle-abi-tag-namespace-reopen.cpp       | 110 ++++++++++++++++++
 .../test/Modules/abi-tag-namespace-reopen.cpp |  47 ++++++++
 6 files changed, 195 insertions(+), 2 deletions(-)
 create mode 100644 clang/test/CodeGenCXX/mangle-abi-tag-namespace-reopen.cpp
 create mode 100644 clang/test/Modules/abi-tag-namespace-reopen.cpp

diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index a1f24a8caedae..2cb3afd0cf093 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -112,6 +112,13 @@ features cannot lower the translation-unit ABI level;
   for homogeneous aggregate classification.
   `-fclang-abi-compat=23` restores the previous behavior. (#GH218799)
 
+- The ABI tags of an inline namespace are now the union of the `abi_tag`
+  attributes on all declarations of that namespace, matching GCC. Clang
+  previously only used the first `abi_tag` attribute of the first declaration,
+  so tags added when reopening the namespace were silently dropped, and with
+  modules the applied tags could depend on the order in which declarations
+  were used. `-fclang-abi-compat=23` restores the previous behavior.
+
 ### AST Dumping Potentially Breaking Changes
 
 ### Clang Frontend Potentially Breaking Changes
diff --git a/clang/include/clang/Basic/ABIVersions.def 
b/clang/include/clang/Basic/ABIVersions.def
index 3c434da91bfab..23463eb25cb96 100644
--- a/clang/include/clang/Basic/ABIVersions.def
+++ b/clang/include/clang/Basic/ABIVersions.def
@@ -167,6 +167,9 @@ ABI_VER_MAJOR(22)
 ///     faithfully reproduces Clang 23, including its crash on aggregates such
 ///     as a run of `__int128` bit-fields, where skipping the unnamed field
 ///     leaves part of a wider access unit unclassified.)
+///   - Only use the `abi_tag` attribute of the first declaration of an inline
+///     namespace (and only the first such attribute), instead of the union of
+///     the tags on all declarations of the namespace the way GCC does.
 ABI_VER_MAJOR(23)
 
 /// Conform to the underlying platform's C and C++ ABIs as closely as we can.
diff --git a/clang/include/clang/Basic/AttrDocs.td 
b/clang/include/clang/Basic/AttrDocs.td
index 6170688ae8cf9..80f5350cce8f6 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -6911,6 +6911,20 @@ could have a different set of data members and thus have 
a different size. Using
 the `abi_tag` attribute, it is possible to have different mangled names for
 a global variable of the class type. Therefore, the old code could keep using
 the old mangled name and the new code will use the new mangled name with tags.
+
+On an inline namespace the attribute does not change the mangled name of the
+namespace itself. Instead, functions and variables whose type refers to an
+entity of that namespace get the tags unless they are already part of the
+mangled name. As in GCC, the attribute does not have to be repeated when the
+namespace is reopened, and the tags of the namespace are the union of the tags
+on all of its declarations:
+
+```c++
+inline namespace v1 __attribute__((abi_tag("A"))) { struct S {}; }
+inline namespace v1 { struct T {}; }                        // Still tagged 
"A".
+inline namespace v1 __attribute__((abi_tag("B"))) {}        // Adds tag "B".
+T f();                                                      // _Z1fB1AB1Bv
+```
   }];
 }
 
diff --git a/clang/lib/AST/ItaniumMangle.cpp b/clang/lib/AST/ItaniumMangle.cpp
index 9b3589a527d03..8b2b3e0438cd2 100644
--- a/clang/lib/AST/ItaniumMangle.cpp
+++ b/clang/lib/AST/ItaniumMangle.cpp
@@ -304,8 +304,20 @@ class CXXNameMangler {
             AdditionalAbiTags.empty() &&
             "only function and variables need a list of additional abi tags");
         if (const auto *NS = dyn_cast<NamespaceDecl>(ND)) {
-          if (const auto *AbiTag = NS->getAttr<AbiTagAttr>())
-            llvm::append_range(UsedAbiTags, AbiTag->tags());
+          if (NS->getASTContext().getLangOpts().isCompatibleWith(
+                  LangOptions::ClangABI::Ver23)) {
+            // Clang <= 23 only considered the first declaration of the
+            // namespace (and only its first abi_tag attribute).
+            if (const auto *AbiTag = NS->getAttr<AbiTagAttr>())
+              llvm::append_range(UsedAbiTags, AbiTag->tags());
+          } else {
+            // GCC has a single entity per namespace and every reopening adds
+            // its abi_tags to that entity, so the tags of a namespace are the
+            // union of the tags on all of its declarations.
+            for (const NamespaceDecl *Redecl : NS->redecls())
+              for (const auto *AbiTag : Redecl->specific_attrs<AbiTagAttr>())
+                llvm::append_range(UsedAbiTags, AbiTag->tags());
+          }
           // Don't emit abi tags for namespaces.
           return;
         }
diff --git a/clang/test/CodeGenCXX/mangle-abi-tag-namespace-reopen.cpp 
b/clang/test/CodeGenCXX/mangle-abi-tag-namespace-reopen.cpp
new file mode 100644
index 0000000000000..0e2f7d1e519bf
--- /dev/null
+++ b/clang/test/CodeGenCXX/mangle-abi-tag-namespace-reopen.cpp
@@ -0,0 +1,110 @@
+// RUN: %clang_cc1 -std=c++17 -triple x86_64-linux-gnu -emit-llvm -o - %s | 
FileCheck %s
+// RUN: %clang_cc1 -std=c++17 -triple x86_64-linux-gnu -fclang-abi-compat=23 
-emit-llvm -o - %s | FileCheck %s --check-prefix=V23
+
+// GCC has a single entity per namespace and every reopening of an inline
+// namespace adds its abi_tags to that entity. The expected manglings below are
+// the ones produced by GCC. Clang <= 23 only used the tags from the first
+// declaration of the namespace.
+
+// The first declaration has no tag, a reopening adds one.
+inline namespace AddedLater { struct S0 {}; }
+inline namespace AddedLater __attribute__((abi_tag("X"))) { struct S1 {}; }
+inline namespace AddedLater { struct S2 {}; }
+S0 added0() { return {}; }
+S1 added1() { return {}; }
+S2 added2() { return {}; }
+S1 added_var;
+// CHECK-DAG: @_Z9added_varB1X =
+// CHECK-DAG: define {{.*}} @_Z6added0B1Xv(
+// CHECK-DAG: define {{.*}} @_Z6added1B1Xv(
+// CHECK-DAG: define {{.*}} @_Z6added2B1Xv(
+// V23-DAG: @added_var =
+// V23-DAG: define {{.*}} @_Z6added0v(
+// V23-DAG: define {{.*}} @_Z6added1v(
+// V23-DAG: define {{.*}} @_Z6added2v(
+
+// Reopenings with different tags: every tag applies.
+inline namespace Different __attribute__((abi_tag("A"))) { struct S1 {}; }
+inline namespace Different __attribute__((abi_tag("B"))) { struct S2 {}; }
+inline namespace Different __attribute__((abi_tag("A"))) { struct S3 {}; }
+Different::S1 diff1() { return {}; }
+Different::S2 diff2() { return {}; }
+Different::S3 diff3() { return {}; }
+// CHECK-DAG: define {{.*}} @_Z5diff1B1AB1Bv(
+// CHECK-DAG: define {{.*}} @_Z5diff2B1AB1Bv(
+// CHECK-DAG: define {{.*}} @_Z5diff3B1AB1Bv(
+// V23-DAG: define {{.*}} @_Z5diff1B1Av(
+// V23-DAG: define {{.*}} @_Z5diff2B1Av(
+// V23-DAG: define {{.*}} @_Z5diff3B1Av(
+
+inline namespace Multi __attribute__((abi_tag("A", "B"))) { struct S1 {}; }
+inline namespace Multi __attribute__((abi_tag("X", "Y", "B"))) { struct S2 {}; 
}
+Multi::S1 multi1() { return {}; }
+Multi::S2 multi2() { return {}; }
+// CHECK-DAG: define {{.*}} @_Z6multi1B1AB1BB1XB1Yv(
+// CHECK-DAG: define {{.*}} @_Z6multi2B1AB1BB1XB1Yv(
+// V23-DAG: define {{.*}} @_Z6multi1B1AB1Bv(
+// V23-DAG: define {{.*}} @_Z6multi2B1AB1Bv(
+
+// Reopening without the attribute (the libstdc++ idiom) keeps the tag.
+namespace std2 {
+inline namespace __cxx11 __attribute__((__abi_tag__("cxx11"))) {}
+}
+namespace std2 {
+namespace __cxx11 { template <class C> struct basic_string {}; }
+typedef basic_string<char> string;
+}
+std2::string str() { return {}; }
+std2::string str_var;
+// CHECK-DAG: @_Z7str_varB5cxx11 =
+// CHECK-DAG: define {{.*}} @_Z3strB5cxx11v(
+// V23-DAG: @_Z7str_varB5cxx11 =
+// V23-DAG: define {{.*}} @_Z3strB5cxx11v(
+
+// Reopening with a subset of the tags.
+inline namespace Subset __attribute__((abi_tag("A", "B"))) { struct S1 {}; }
+inline namespace Subset __attribute__((abi_tag("A"))) { struct S2 {}; }
+Subset::S1 subset1() { return {}; }
+Subset::S2 subset2() { return {}; }
+// CHECK-DAG: define {{.*}} @_Z7subset1B1AB1Bv(
+// CHECK-DAG: define {{.*}} @_Z7subset2B1AB1Bv(
+// V23-DAG: define {{.*}} @_Z7subset1B1AB1Bv(
+// V23-DAG: define {{.*}} @_Z7subset2B1AB1Bv(
+
+// Several attributes on one declaration.
+inline namespace TwoAttrs __attribute__((abi_tag("A"))) 
__attribute__((abi_tag("B"))) { struct S {}; }
+TwoAttrs::S two_attrs() { return {}; }
+// CHECK-DAG: define {{.*}} @_Z9two_attrsB1AB1Bv(
+// V23-DAG: define {{.*}} @_Z9two_attrsB1Av(
+
+// Nested inline namespaces, both reopened with new tags.
+inline namespace Outer __attribute__((abi_tag("O1"))) {
+inline namespace Inner __attribute__((abi_tag("I1"))) { struct S {}; }
+}
+inline namespace Outer __attribute__((abi_tag("O2"))) {
+inline namespace Inner __attribute__((abi_tag("I2"))) {}
+}
+Outer::Inner::S nested() { return {}; }
+// CHECK-DAG: define {{.*}} @_Z6nestedB2I1B2I2B2O1B2O2v(
+// V23-DAG: define {{.*}} @_Z6nestedB2I1B2O1v(
+
+// Templates, static data members and explicitly tagged entities.
+template <class T> Different::S1 tmpl(T) { return {}; }
+template Different::S1 tmpl<int>(int);
+template <class T> struct Holder { static Different::S2 member; };
+template <class T> Different::S2 Holder<T>::member;
+template struct Holder<int>;
+__attribute__((abi_tag("Z"))) Different::S1 explicit_tag() { return {}; }
+// CHECK-DAG: define {{.*}} @_Z4tmplIiEN9Different2S1ET_(
+// CHECK-DAG: @_ZN6HolderIiE6memberB1AB1BE =
+// CHECK-DAG: define {{.*}} @_Z12explicit_tagB1AB1BB1Zv(
+// V23-DAG: define {{.*}} @_Z4tmplIiEN9Different2S1ET_(
+// V23-DAG: @_ZN6HolderIiE6memberB1AE =
+// V23-DAG: define {{.*}} @_Z12explicit_tagB1AB1Zv(
+
+// abi_tag on a non-inline namespace is still ignored.
+namespace NonInline {}
+namespace NonInline __attribute__((abi_tag("X"))) { struct S {}; }
+NonInline::S non_inline() { return {}; }
+// CHECK-DAG: define {{.*}} @_Z10non_inlinev(
+// V23-DAG: define {{.*}} @_Z10non_inlinev(
diff --git a/clang/test/Modules/abi-tag-namespace-reopen.cpp 
b/clang/test/Modules/abi-tag-namespace-reopen.cpp
new file mode 100644
index 0000000000000..0feb190472410
--- /dev/null
+++ b/clang/test/Modules/abi-tag-namespace-reopen.cpp
@@ -0,0 +1,47 @@
+// RUN: rm -rf %t
+// RUN: split-file %s %t
+
+// The ABI tags of an inline namespace are the union of the tags on all of its
+// declarations, so the result must not depend on which module's declaration
+// of the namespace happens to be deserialized (and become canonical) first.
+
+// RUN: %clang_cc1 -std=c++17 -triple x86_64-linux-gnu -fmodules \
+// RUN:   -fimplicit-module-maps -fmodules-cache-path=%t/cache -I%t \
+// RUN:   -emit-llvm -o - %t/tagged-first.cpp | FileCheck %s
+// RUN: %clang_cc1 -std=c++17 -triple x86_64-linux-gnu -fmodules \
+// RUN:   -fimplicit-module-maps -fmodules-cache-path=%t/cache -I%t \
+// RUN:   -emit-llvm -o - %t/untagged-first.cpp | FileCheck %s
+
+// CHECK-DAG: define {{.*}} @_Z10use_taggedB1TB1Uv(
+// CHECK-DAG: define {{.*}} @_Z12use_untaggedB1TB1Uv(
+// CHECK-DAG: define {{.*}} @_Z9use_otherB1TB1Uv(
+
+//--- module.modulemap
+module Tagged { header "tagged.h" export * }
+module Untagged { header "untagged.h" export * }
+module Other { header "other.h" export * }
+
+//--- tagged.h
+inline namespace R __attribute__((abi_tag("T"))) { struct FromTagged {}; }
+
+//--- untagged.h
+inline namespace R { struct FromUntagged {}; }
+
+//--- other.h
+inline namespace R __attribute__((abi_tag("U"))) { struct FromOther {}; }
+
+//--- tagged-first.cpp
+#include "tagged.h"
+#include "untagged.h"
+#include "other.h"
+FromTagged use_tagged() { return {}; }
+FromUntagged use_untagged() { return {}; }
+FromOther use_other() { return {}; }
+
+//--- untagged-first.cpp
+#include "tagged.h"
+#include "untagged.h"
+#include "other.h"
+FromUntagged use_untagged() { return {}; }
+FromOther use_other() { return {}; }
+FromTagged use_tagged() { return {}; }

>From f7a53dc58871ad71cb93a6b2c416bcd1a8c05a7c Mon Sep 17 00:00:00 2001
From: Dmitry Polukhin <[email protected]>
Date: Fri, 18 Sep 2026 23:10:40 +0100
Subject: [PATCH 2/2] [clang] Keep names consistent when an inline namespace
 gains abi_tags

Follow-up fixes for the union of abi_tags of an inline namespace.

Keep derived names consistent. The tags of an inline namespace can grow
when it is reopened, and the implicit tags of a function or variable are
recomputed every time its encoding is mangled. A name that embeds the
encoding of a function could therefore change within a translation unit:

  inline namespace N __attribute__((abi_tag("A"))) { struct S {}; }
  template <class T> void thrower() { throw T(); }
  S f() { struct L {}; try { thrower<L>(); } catch (L &) {} return {}; }
  inline namespace N __attribute__((abi_tag("B"))) {}

f is emitted before the reopening, so the catch used _ZTIZ1fB1AvE1L, but
thrower<L> is instantiated at the end of the translation unit and threw
_ZTIZ1fB1AB1BvE1L. The handler did not match at run time.

GCC fixes the implicit tags of a function or variable once per
declaration. The computed list of implicit tags cannot simply be recorded
though: it also depends on the substitutions that are available where the
encoding is embedded (a namespace that is mangled as a substitution does
not contribute its tags), and both GCC and previous versions of Clang
rely on that. Instead, remember which tags the namespaces had when a
declaration was first mangled. The mangle context counts an epoch that
only advances when a reopening adds a new tag to a namespace whose tags
were already used, every cached tag knows the epoch in which it appeared,
and while the encoding of a declaration is mangled only the tags up to the
epoch of its first mangling are visible. Everything else is computed as
before. If no namespace gains a tag after it was used the epoch never
changes, so all names are identical to the previous commit and, for code
without such reopenings, to -fclang-abi-compat=23. Entities that are first
mangled after a reopening still get the added tags like in GCC. This also
covers tags that change on the name or parameter side of a function.

Avoid quadratic behavior. Every mangling of a namespace walked all of its
redeclarations, including for namespaces that cannot have tags. Compiling
20000 reopenings of a namespace with one function each took 1.69s instead
of 0.14s, and 11.7s instead of 1.8s for a tagged inline namespace that is
reopened without the attribute like libstdc++ does. Skip namespaces that
are not inline, as the attribute is only accepted on inline namespaces,
and cache the distinct tags per namespace in the mangle context, extending
them only with the declarations added since the last query. All measured
shapes, including a namespace that repeats the attribute on every
reopening, are back to the time of -fclang-abi-compat=23, and the
instruction count for compiling SemaDecl.cpp is within noise (+0.2%).

Known limitation: the state lives in the mangle context. Other mangle
contexts (e.g. the ones used for pointer authentication discriminators,
abbreviated thunk names or interface stubs) can see a different set of
tags than CodeGen, but only in a translation unit where a namespace gains
a tag after it was already used.
---
 clang/lib/AST/ItaniumMangle.cpp               | 157 +++++++++++++++++-
 .../mangle-abi-tag-namespace-reopen-order.cpp | 130 +++++++++++++++
 .../mangle-abi-tag-namespace-reopen-param.cpp |  38 +++++
 .../mangle-abi-tag-namespace-substitution.cpp |  36 ++++
 4 files changed, 355 insertions(+), 6 deletions(-)
 create mode 100644 
clang/test/CodeGenCXX/mangle-abi-tag-namespace-reopen-order.cpp
 create mode 100644 
clang/test/CodeGenCXX/mangle-abi-tag-namespace-reopen-param.cpp
 create mode 100644 
clang/test/CodeGenCXX/mangle-abi-tag-namespace-substitution.cpp

diff --git a/clang/lib/AST/ItaniumMangle.cpp b/clang/lib/AST/ItaniumMangle.cpp
index 8b2b3e0438cd2..7bde50a0f01ac 100644
--- a/clang/lib/AST/ItaniumMangle.cpp
+++ b/clang/lib/AST/ItaniumMangle.cpp
@@ -80,6 +80,31 @@ class ItaniumMangleContextImpl : public ItaniumMangleContext 
{
 
   bool NeedsUniqueInternalLinkageNames = false;
 
+  /// The abi_tags of an inline namespace: the union of the tags on all of its
+  /// declarations. A namespace can be reopened thousands of times, so the
+  /// union is extended incrementally instead of rescanning all declarations
+  /// every time the namespace is mangled.
+  struct NamespaceAbiTags {
+    /// The most recent declaration that is already accounted for in Tags.
+    const NamespaceDecl *LastScanned = nullptr;
+    /// The distinct tags, each with the value of NamespaceTagsEpoch at which 
it
+    /// became part of the union.
+    SmallVector<std::pair<StringRef, unsigned>, 2> Tags;
+  };
+  llvm::DenseMap<const NamespaceDecl *, NamespaceAbiTags> NamespaceTags;
+
+  /// Incremented whenever a reopening adds a tag to an inline namespace whose
+  /// tags were already used for a mangled name.
+  unsigned NamespaceTagsEpoch = 0;
+
+  /// The number of inline namespaces that were asked for their tags. Used to
+  /// detect whether a mangling depends on the tags of an inline namespace.
+  unsigned NumInlineNamespaceTagQueries = 0;
+
+  /// The value of NamespaceTagsEpoch when a function or variable was first
+  /// mangled, see CXXNameMangler::ImplicitAbiTagsScope.
+  llvm::DenseMap<const NamedDecl *, unsigned> ImplicitAbiTagsEpochs;
+
 public:
   explicit ItaniumMangleContextImpl(
       ASTContext &Context, DiagnosticsEngine &Diags,
@@ -87,6 +112,69 @@ class ItaniumMangleContextImpl : public 
ItaniumMangleContext {
       : ItaniumMangleContext(Context, Diags, IsAux),
         DiscriminatorOverride(DiscriminatorOverride) {}
 
+  /// Appends the union of the abi_tags on all declarations of \p NS to
+  /// \p Tags, leaving out the tags that were added by a reopening after
+  /// NamespaceTagsEpoch had the value \p AsOfEpoch.
+  void addNamespaceAbiTags(const NamespaceDecl *NS, unsigned AsOfEpoch,
+                           SmallVectorImpl<StringRef> &Tags) {
+    // The attribute is only accepted on inline namespaces.
+    if (!NS->isInline())
+      return;
+    ++NumInlineNamespaceTagQueries;
+    // Note that this can deserialize declarations, so do it before taking a
+    // reference into the map.
+    const NamespaceDecl *MostRecent = NS->getMostRecentDecl();
+    NamespaceAbiTags &Entry = NamespaceTags[NS->getFirstDecl()];
+    if (Entry.LastScanned != MostRecent) {
+      // Only look at the declarations added since the last query. If the last
+      // scanned declaration is not found all declarations are visited again,
+      // which is fine because known tags are skipped.
+      bool IsRescan = Entry.LastScanned != nullptr;
+      bool StartedEpoch = false;
+      for (const NamespaceDecl *D = MostRecent; D && D != Entry.LastScanned;
+           D = D->getPreviousDecl()) {
+        for (const auto *AbiTag : D->specific_attrs<AbiTagAttr>()) {
+          for (StringRef Tag : AbiTag->tags()) {
+            if (llvm::is_contained(llvm::make_first_range(Entry.Tags), Tag))
+              continue;
+            // A tag found by a later query was added after the tags of the
+            // namespace were already used.
+            if (IsRescan && !StartedEpoch) {
+              ++NamespaceTagsEpoch;
+              StartedEpoch = true;
+            }
+            Entry.Tags.push_back({Tag, IsRescan ? NamespaceTagsEpoch : 0});
+          }
+        }
+      }
+      Entry.LastScanned = MostRecent;
+    }
+    for (const auto &[Tag, Epoch] : Entry.Tags)
+      if (Epoch <= AsOfEpoch)
+        Tags.push_back(Tag);
+  }
+
+  unsigned getNamespaceTagsEpoch() const { return NamespaceTagsEpoch; }
+
+  unsigned getNumInlineNamespaceTagQueries() const {
+    return NumInlineNamespaceTagQueries;
+  }
+
+  /// Returns the value NamespaceTagsEpoch had when \p D was first mangled, if
+  /// that mangling depended on the tags of an inline namespace.
+  std::optional<unsigned> lookupImplicitAbiTagsEpoch(const NamedDecl *D) const 
{
+    auto It =
+        ImplicitAbiTagsEpochs.find(cast<NamedDecl>(D->getCanonicalDecl()));
+    if (It == ImplicitAbiTagsEpochs.end())
+      return std::nullopt;
+    return It->second;
+  }
+
+  void recordImplicitAbiTagsEpoch(const NamedDecl *D, unsigned Epoch) {
+    ImplicitAbiTagsEpochs.try_emplace(cast<NamedDecl>(D->getCanonicalDecl()),
+                                      Epoch);
+  }
+
   /// @name Mangler Entry Points
   /// @{
 
@@ -229,6 +317,11 @@ class CXXNameMangler {
   /// Also it is required to avoid infinite recursion in some cases.
   bool DisableDerivedAbiTags = false;
 
+  /// Only the abi_tags that inline namespaces had when
+  /// ItaniumMangleContextImpl::NamespaceTagsEpoch had this value are used, see
+  /// ImplicitAbiTagsScope.
+  unsigned NamespaceTagsAsOf = ~0U;
+
   /// The "structor" is the top-level declaration being mangled, if
   /// that's not a template specialization; otherwise it's the pattern
   /// for that specialization.
@@ -297,7 +390,8 @@ class CXXNameMangler {
     ~AbiTagState() { pop(); }
 
     void write(raw_ostream &Out, const NamedDecl *ND,
-               ArrayRef<StringRef> AdditionalAbiTags) {
+               ArrayRef<StringRef> AdditionalAbiTags,
+               ItaniumMangleContextImpl &Context, unsigned NamespaceTagsAsOf) {
       ND = cast<NamedDecl>(ND->getCanonicalDecl());
       if (!isa<FunctionDecl>(ND) && !isa<VarDecl>(ND)) {
         assert(
@@ -314,9 +408,7 @@ class CXXNameMangler {
             // GCC has a single entity per namespace and every reopening adds
             // its abi_tags to that entity, so the tags of a namespace are the
             // union of the tags on all of its declarations.
-            for (const NamespaceDecl *Redecl : NS->redecls())
-              for (const auto *AbiTag : Redecl->specific_attrs<AbiTagAttr>())
-                llvm::append_range(UsedAbiTags, AbiTag->tags());
+            Context.addNamespaceAbiTags(NS, NamespaceTagsAsOf, UsedAbiTags);
           }
           // Don't emit abi tags for namespaces.
           return;
@@ -388,6 +480,51 @@ class CXXNameMangler {
   AbiTagState *AbiTags = nullptr;
   AbiTagState AbiTagsRoot;
 
+  /// GCC computes the implicit abi_tags of a function or variable once and
+  /// records them on the declaration. The tags of an inline namespace can grow
+  /// when it is reopened, but every name that embeds the encoding of the
+  /// declaration (e.g. the RTTI of a local class) has to stay the same. So
+  /// remember which tags the namespaces had when the declaration was first
+  /// mangled and only use these while its encoding is mangled. Apart from the
+  /// tags of the namespaces the implicit tags are computed as usual, as they
+  /// also depend on the substitutions that are available.
+  class ImplicitAbiTagsScope {
+    CXXNameMangler &Mangler;
+    /// The declaration to record, if it was not mangled before.
+    const NamedDecl *ToRecord = nullptr;
+    unsigned SavedAsOf;
+    unsigned SavedNumQueries = 0;
+
+  public:
+    ImplicitAbiTagsScope(CXXNameMangler &Mangler, const NamedDecl *D)
+        : Mangler(Mangler), SavedAsOf(Mangler.NamespaceTagsAsOf) {
+      // A mangler that only collects tags uses the state of its creator.
+      if (Mangler.DisableDerivedAbiTags)
+        return;
+      ItaniumMangleContextImpl &Context = Mangler.Context;
+      if (std::optional<unsigned> Epoch =
+              Context.lookupImplicitAbiTagsEpoch(D)) {
+        Mangler.NamespaceTagsAsOf = std::min(SavedAsOf, *Epoch);
+        return;
+      }
+      ToRecord = D;
+      SavedNumQueries = Context.getNumInlineNamespaceTagQueries();
+    }
+
+    ImplicitAbiTagsScope(const ImplicitAbiTagsScope &) = delete;
+    ImplicitAbiTagsScope &operator=(const ImplicitAbiTagsScope &) = delete;
+
+    ~ImplicitAbiTagsScope() {
+      ItaniumMangleContextImpl &Context = Mangler.Context;
+      // Nothing can change later if no inline namespace was involved.
+      if (ToRecord &&
+          SavedNumQueries != Context.getNumInlineNamespaceTagQueries())
+        Context.recordImplicitAbiTagsEpoch(
+            ToRecord, std::min(SavedAsOf, Context.getNamespaceTagsEpoch()));
+      Mangler.NamespaceTagsAsOf = SavedAsOf;
+    }
+  };
+
   llvm::DenseMap<uintptr_t, unsigned> Substitutions;
   llvm::DenseMap<StringRef, unsigned> ModuleSubstitutions;
 
@@ -429,7 +566,8 @@ class CXXNameMangler {
         NullOut(false), Structor(nullptr), AbiTagsRoot(AbiTags) {}
   CXXNameMangler(CXXNameMangler &Outer, raw_ostream &Out_)
       : Context(Outer.Context), Out(Out_),
-        NormalizeIntegers(Outer.NormalizeIntegers), Structor(Outer.Structor),
+        NormalizeIntegers(Outer.NormalizeIntegers),
+        NamespaceTagsAsOf(Outer.NamespaceTagsAsOf), Structor(Outer.Structor),
         StructorType(Outer.StructorType), SeqID(Outer.SeqID),
         FunctionTypeDepth(Outer.FunctionTypeDepth), AbiTagsRoot(AbiTags),
         Substitutions(Outer.Substitutions),
@@ -825,7 +963,8 @@ void CXXNameMangler::writeAbiTags(const NamedDecl *ND,
   assert(AbiTags && "require AbiTagState");
   AbiTags->write(Out, ND,
                  DisableDerivedAbiTags ? ArrayRef<StringRef>{}
-                                       : AdditionalAbiTags);
+                                       : AdditionalAbiTags,
+                 Context, NamespaceTagsAsOf);
 }
 
 void CXXNameMangler::mangleSourceNameWithAbiTags(
@@ -861,6 +1000,9 @@ void CXXNameMangler::mangleFunctionEncoding(GlobalDecl GD) 
{
     return;
   }
 
+  // The tags of inline namespaces are fixed the first time FD is mangled.
+  ImplicitAbiTagsScope ImplicitTagsScope(*this, FD);
+
   AbiTagList ReturnTypeAbiTags = makeFunctionReturnTypeTags(FD);
   if (ReturnTypeAbiTags.empty()) {
     // There are no tags for return type, the simplest case. Enter the function
@@ -1033,6 +1175,9 @@ static TemplateName asTemplateName(GlobalDecl GD) {
 void CXXNameMangler::mangleName(GlobalDecl GD) {
   const NamedDecl *ND = cast<NamedDecl>(GD.getDecl());
   if (const VarDecl *VD = dyn_cast<VarDecl>(ND)) {
+    // The tags of inline namespaces are fixed the first time VD is mangled.
+    ImplicitAbiTagsScope ImplicitTagsScope(*this, VD);
+
     // Variables should have implicit tags from its type.
     AbiTagList VariableTypeAbiTags = makeVariableTypeTags(VD);
     if (VariableTypeAbiTags.empty()) {
diff --git a/clang/test/CodeGenCXX/mangle-abi-tag-namespace-reopen-order.cpp 
b/clang/test/CodeGenCXX/mangle-abi-tag-namespace-reopen-order.cpp
new file mode 100644
index 0000000000000..7b316c32b03c3
--- /dev/null
+++ b/clang/test/CodeGenCXX/mangle-abi-tag-namespace-reopen-order.cpp
@@ -0,0 +1,130 @@
+// RUN: %clang_cc1 -std=c++17 -triple x86_64-linux-gnu -fcxx-exceptions \
+// RUN:   -fexceptions -emit-llvm -o %t.ll %s
+// RUN: FileCheck %s --input-file=%t.ll
+// RUN: FileCheck %s --input-file=%t.ll --check-prefix=EH
+// RUN: FileCheck %s --input-file=%t.ll --check-prefix=NEG
+// RUN: %clang_cc1 -std=c++17 -triple x86_64-linux-gnu -fcxx-exceptions \
+// RUN:   -fexceptions -fclang-abi-compat=23 -emit-llvm -o - %s | \
+// RUN:   FileCheck %s --check-prefix=V23
+
+// The abi_tags of an inline namespace can grow when the namespace is reopened.
+// As in GCC, the tags that a function or variable gets from inline namespaces
+// are fixed the first time it is mangled. Every name that embeds its encoding
+// keeps seeing the namespaces as they were at that point, even if it is only
+// mangled after a namespace got more tags. Otherwise the local class below
+// would get two different type_info objects and the exception would not be
+// caught.
+
+// None of the names derived from an entity that was mangled before the
+// reopening may pick up the tag that was added later. (FileCheck does not
+// apply --implicit-check-not inside a group of CHECK-DAG lines, hence the
+// separate run.)
+// NEG-NOT: earlyB1AB1B
+// NEG-NOT: early_emptyB1X
+// NEG-NOT: mB1AB1B
+// NEG-NOT: tlB1AB1B
+// NEG-NOT: inline_funcB1AB1B
+// NEG-NOT: inline_lambdaB1AB1B
+
+template <class T> void thrower() { throw T(); }
+
+inline namespace N __attribute__((abi_tag("A"))) { struct S {}; }
+
+// Emitted before the tag "B" is added.
+S early() {
+  struct L {};
+  try {
+    // Instantiated at the end of the translation unit, after "B" was added.
+    thrower<L>();
+  } catch (L &) {
+  }
+  return {};
+}
+
+inline namespace N __attribute__((abi_tag("B"))) {}
+
+// Entities that are first mangled after the reopening get all the tags.
+S late() { return {}; }
+
+// CHECK-DAG: @_ZTIZ5earlyB1AvE1L =
+// CHECK-DAG: @_ZTSZ5earlyB1AvE1L =
+// CHECK-DAG: define {{.*}} @_Z5earlyB1Av(
+// CHECK-DAG: define {{.*}} @_Z7throwerIZ5earlyB1AvE1LEvv(
+// CHECK-DAG: define {{.*}} @_Z4lateB1AB1Bv(
+// V23-DAG: define {{.*}} @_Z5earlyB1Av(
+// V23-DAG: define {{.*}} @_Z7throwerIZ5earlyB1AvE1LEvv(
+// V23-DAG: define {{.*}} @_Z4lateB1Av(
+
+// The handler and the throw expression have to use the same type_info.
+// EH-LABEL: define {{.*}} @_Z5earlyB1Av(
+// EH: catch ptr @_ZTIZ5earlyB1AvE1L
+// EH-LABEL: define {{.*}} @_Z7throwerIZ5earlyB1AvE1LEvv(
+// EH: call void @__cxa_throw(ptr %{{.*}}, ptr @_ZTIZ5earlyB1AvE1L, ptr null)
+
+// A function can have no implicit tags at all when it is first mangled: the
+// namespace only gets its first tag afterwards.
+inline namespace Empty { struct SE {}; }
+SE early_empty() {
+  struct L {};
+  try {
+    thrower<L>();
+  } catch (L &) {
+  }
+  return {};
+}
+inline namespace Empty __attribute__((abi_tag("X"))) {}
+SE late_empty() { return {}; }
+
+// CHECK-DAG: @_ZTIZ11early_emptyvE1L =
+// CHECK-DAG: define {{.*}} @_Z11early_emptyv(
+// CHECK-DAG: define {{.*}} @_Z7throwerIZ11early_emptyvE1LEvv(
+// CHECK-DAG: define {{.*}} @_Z10late_emptyB1Xv(
+// V23-DAG: define {{.*}} @_Z11early_emptyv(
+// V23-DAG: define {{.*}} @_Z10late_emptyv(
+// EH-LABEL: define {{.*}} @_Z11early_emptyv(
+// EH: catch ptr @_ZTIZ11early_emptyvE1L
+// EH-LABEL: define {{.*}} @_Z7throwerIZ11early_emptyvE1LEvv(
+// EH: call void @__cxa_throw(ptr %{{.*}}, ptr @_ZTIZ11early_emptyvE1L, ptr 
null)
+
+// Variables: the name of the variable is mangled at its first use, the guard
+// variable and the thread_local helpers when the definition is instantiated at
+// the end of the translation unit.
+inline namespace NV __attribute__((abi_tag("A"))) { struct SV { SV(); }; }
+template <class T> struct Holder { static SV m; };
+template <class T> SV Holder<T>::m;
+SV *use_m() { return &Holder<int>::m; }
+template <class T> thread_local SV tl;
+SV *use_tl() { return &tl<int>; }
+inline namespace NV __attribute__((abi_tag("B"))) {}
+
+// CHECK-DAG: @_ZN6HolderIiE1mB1AE =
+// CHECK-DAG: @_ZGVN6HolderIiE1mB1AE =
+// CHECK-DAG: @_Z2tlB1AIiE =
+// CHECK-DAG: @_ZGV2tlB1AIiE =
+// CHECK-DAG: @_ZTH2tlB1AIiE =
+// CHECK-DAG: define {{.*}} @_ZTW2tlB1AIiE(
+// V23-DAG: @_ZN6HolderIiE1mB1AE =
+// V23-DAG: @_ZGVN6HolderIiE1mB1AE =
+
+// An inline function is mangled at its definition but its body is only emitted
+// at the end of the translation unit.
+inline namespace NI __attribute__((abi_tag("A"))) { struct SI { SI(); }; }
+inline SI inline_func() {
+  static int counter;
+  ++counter;
+  return {};
+}
+inline SI inline_lambda() {
+  auto l = [] { return 1; };
+  l();
+  return {};
+}
+void use_inline() { inline_func(); inline_lambda(); }
+inline namespace NI __attribute__((abi_tag("B"))) {}
+
+// CHECK-DAG: @_ZZ11inline_funcB1AvE7counter =
+// CHECK-DAG: define {{.*}} @_Z11inline_funcB1Av(
+// CHECK-DAG: define {{.*}} @_Z13inline_lambdaB1Av(
+// CHECK-DAG: define {{.*}} @_ZZ13inline_lambdaB1AvENKUlvE_clEv(
+// V23-DAG: @_ZZ11inline_funcB1AvE7counter =
+// V23-DAG: define {{.*}} @_ZZ13inline_lambdaB1AvENKUlvE_clEv(
diff --git a/clang/test/CodeGenCXX/mangle-abi-tag-namespace-reopen-param.cpp 
b/clang/test/CodeGenCXX/mangle-abi-tag-namespace-reopen-param.cpp
new file mode 100644
index 0000000000000..11843ce78ac0c
--- /dev/null
+++ b/clang/test/CodeGenCXX/mangle-abi-tag-namespace-reopen-param.cpp
@@ -0,0 +1,38 @@
+// RUN: %clang_cc1 -std=c++17 -triple x86_64-linux-gnu -fcxx-exceptions \
+// RUN:   -fexceptions -emit-llvm -o - %s | FileCheck %s
+
+// The tags of an inline namespace do not only matter through the return type
+// of a function. Here "A" is an implicit tag of early_param until the
+// namespace of its parameter type gets the same tag. The names derived from
+// early_param must not change when that happens.
+
+template <class T> void thrower() { throw T(); }
+
+struct __attribute__((abi_tag("A"))) Tagged { Tagged(); };
+inline namespace NP { struct SP {}; }
+
+Tagged early_param(SP) {
+  struct L {};
+  try {
+    thrower<L>();
+  } catch (L &) {
+  }
+  return {};
+}
+
+template <class T> struct Holder { static Tagged m; };
+template <class T> Tagged Holder<T>::m;
+Tagged *use_m() { return &Holder<SP>::m; }
+
+inline namespace NP __attribute__((abi_tag("A"))) {}
+
+Tagged late_param(SP) { return {}; }
+
+// CHECK: @_ZTIZ11early_paramB1AN2NP2SPEE1L = internal constant
+// CHECK: @_ZN6HolderIN2NP2SPEE1mB1AE =
+// CHECK: @_ZGVN6HolderIN2NP2SPEE1mB1AE =
+// CHECK-LABEL: define {{.*}} @_Z11early_paramB1AN2NP2SPE(
+// CHECK: catch ptr @_ZTIZ11early_paramB1AN2NP2SPEE1L
+// CHECK-LABEL: define {{.*}} @_Z7throwerIZ11early_paramB1AN2NP2SPEE1LEvv(
+// CHECK: call void @__cxa_throw(ptr %{{.*}}, ptr 
@_ZTIZ11early_paramB1AN2NP2SPEE1L, ptr null)
+// CHECK-LABEL: define {{.*}} @_Z10late_paramN2NP2SPE(
diff --git a/clang/test/CodeGenCXX/mangle-abi-tag-namespace-substitution.cpp 
b/clang/test/CodeGenCXX/mangle-abi-tag-namespace-substitution.cpp
new file mode 100644
index 0000000000000..ffaf1e0849ec8
--- /dev/null
+++ b/clang/test/CodeGenCXX/mangle-abi-tag-namespace-substitution.cpp
@@ -0,0 +1,36 @@
+// RUN: %clang_cc1 -std=c++17 -triple x86_64-linux-gnu -emit-llvm -o - %s | 
FileCheck %s
+// RUN: %clang_cc1 -std=c++17 -triple x86_64-linux-gnu -fclang-abi-compat=23 
-emit-llvm -o - %s | FileCheck %s
+
+// No namespace gets new tags in this file, so the mangled names must not
+// depend on -fclang-abi-compat. In particular the implicit tags of a function
+// depend on the substitutions that are available where its encoding is
+// embedded: in two<> the namespace was already mangled as part of the first
+// template argument.
+
+namespace std2 {
+inline namespace __cxx11 __attribute__((abi_tag("cxx11"))) { struct string {}; 
}
+}
+template <class X, class Y> void two() {}
+template <class Y> void one() {}
+
+std2::string ret_only() {
+  struct L {};
+  two<std2::string, L>();
+  one<L>();
+  return {};
+}
+
+inline std2::string ret_inline() {
+  auto l = [] {};
+  two<std2::string, decltype(l)>();
+  one<decltype(l)>();
+  return {};
+}
+void use() { ret_inline(); }
+
+// CHECK-DAG: define {{.*}} @_Z8ret_onlyB5cxx11v(
+// CHECK-DAG: define {{.*}} @_Z3twoIN4std27__cxx116stringEZ8ret_onlyvE1LEvv(
+// CHECK-DAG: define {{.*}} @_Z3oneIZ8ret_onlyB5cxx11vE1LEvv(
+// CHECK-DAG: define {{.*}} @_Z10ret_inlineB5cxx11v(
+// CHECK-DAG: define {{.*}} 
@_Z3twoIN4std27__cxx116stringEZ10ret_inlinevEUlvE_Evv(
+// CHECK-DAG: define {{.*}} @_Z3oneIZ10ret_inlineB5cxx11vEUlvE_Evv(

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to