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