llvmorg-github-actions[bot] wrote:

<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-clang

Author: Dmitry Polukhin (dmpolukhin)

<details>
<summary>Changes</summary>

### 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.

---

Patch is 29.31 KiB, truncated to 20.00 KiB below, full version: 
https://github.com/llvm/llvm-project/pull/224826.diff


9 Files Affected:

- (modified) clang/docs/ReleaseNotes.md (+7) 
- (modified) clang/include/clang/Basic/ABIVersions.def (+3) 
- (modified) clang/include/clang/Basic/AttrDocs.td (+14) 
- (modified) clang/lib/AST/ItaniumMangle.cpp (+162-5) 
- (added) clang/test/CodeGenCXX/mangle-abi-tag-namespace-reopen-order.cpp 
(+130) 
- (added) clang/test/CodeGenCXX/mangle-abi-tag-namespace-reopen-param.cpp (+38) 
- (added) clang/test/CodeGenCXX/mangle-abi-tag-namespace-reopen.cpp (+110) 
- (added) clang/test/CodeGenCXX/mangle-abi-tag-namespace-substitution.cpp (+36) 
- (added) clang/test/Modules/abi-tag-namespace-reopen.cpp (+47) 


``````````diff
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..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,15 +390,26 @@ 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(
             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.
+            Context.addNamespaceAbiTags(NS, NamespaceTagsAsOf, UsedAbiTags);
+          }
           // Don't emit abi tags for namespaces.
           return;
         }
@@ -376,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;
 
@@ -417,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),
@@ -813,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(
@@ -849,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
@@ -1021,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 { sta...
[truncated]

``````````

</details>


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

Reply via email to