[clang] [Clang] Retain constructor/destructor variants when symbol must be kept (PR #226572)

Zaara Syeda via cfe-commits cfe-commits at lists.llvm.org
Tue Sep 29 13:47:35 PDT 2026


https://github.com/syzaara updated https://github.com/llvm/llvm-project/pull/226572

>From 93a971c8da5f0bec51abdaba3af2c6968ef6021f Mon Sep 17 00:00:00 2001
From: Zaara Syeda <syzaara at ca.ibm.com>
Date: Fri, 25 Sep 2026 15:03:11 -0400
Subject: [PATCH 01/10] [Clang] Retain constructor/destructor variants when
 symbol must be kept

With -mconstructor-aliases, complete constructor and destructor variants
with discardable-if-unused linkage can be silently replaced in the IR
(RAUW) rather than emitted as distinct symbols. This prevents
-fkeep-inline-functions and __attribute__((used)) from retaining the
complete (C1/D1) variants.

Skip RAUW when the declaration requires its symbol to be kept by
introducing structorSymbolMustBeRetained(), which returns true when
__attribute__((used)) is present or -fkeep-inline-functions is active
for an inline definition that is not available_externally.

Assisted-by: IBM Bob
---
 clang/lib/CodeGen/ItaniumCXXABI.cpp           | 15 ++++++-
 clang/test/CodeGen/keep-inline-functions.cpp  | 26 ++++--------
 .../mconstructor-aliases-used-attr.cpp        | 41 +++++++++++++++++++
 3 files changed, 63 insertions(+), 19 deletions(-)
 create mode 100644 clang/test/CodeGen/mconstructor-aliases-used-attr.cpp

diff --git a/clang/lib/CodeGen/ItaniumCXXABI.cpp b/clang/lib/CodeGen/ItaniumCXXABI.cpp
index 754511e6a7d41..1c739e5175d33 100644
--- a/clang/lib/CodeGen/ItaniumCXXABI.cpp
+++ b/clang/lib/CodeGen/ItaniumCXXABI.cpp
@@ -4735,7 +4735,19 @@ ItaniumCXXABI::RTTIUniquenessKind ItaniumCXXABI::classifyRTTIUniqueness(
 // Find out how to codegen the complete destructor and constructor
 namespace {
 enum class StructorCodegen { Emit, RAUW, Alias, COMDAT };
+} // namespace
+
+// Returns true if the complete constructor/destructor variant must be retained
+// as a distinct symbol rather than being silently replaced in the IR (RAUW).
+static bool
+structorSymbolMustBeRetained(CodeGenModule &CGM, const CXXMethodDecl *MD,
+                              llvm::GlobalValue::LinkageTypes Linkage) {
+  if (MD->hasAttr<UsedAttr>())
+    return true;
+  return CGM.getCodeGenOpts().KeepInlineFunctions && MD->isInlined() &&
+         Linkage != llvm::GlobalValue::AvailableExternallyLinkage;
 }
+
 static StructorCodegen getCodegenToUse(CodeGenModule &CGM,
                                        const CXXMethodDecl *MD) {
   if (!CGM.getCodeGenOpts().CXXCtorDtorAliases)
@@ -4755,7 +4767,8 @@ static StructorCodegen getCodegenToUse(CodeGenModule &CGM,
   }
   llvm::GlobalValue::LinkageTypes Linkage = CGM.getFunctionLinkage(AliasDecl);
 
-  if (llvm::GlobalValue::isDiscardableIfUnused(Linkage))
+  if (llvm::GlobalValue::isDiscardableIfUnused(Linkage) &&
+      !structorSymbolMustBeRetained(CGM, MD, Linkage))
     return StructorCodegen::RAUW;
 
   // FIXME: Should we allow available_externally aliases?
diff --git a/clang/test/CodeGen/keep-inline-functions.cpp b/clang/test/CodeGen/keep-inline-functions.cpp
index 4c56fabaecc00..1c64a1d083895 100644
--- a/clang/test/CodeGen/keep-inline-functions.cpp
+++ b/clang/test/CodeGen/keep-inline-functions.cpp
@@ -5,12 +5,18 @@
 // RUN: %clang_cc1 -O2 -fkeep-inline-functions -emit-llvm %s -o - -triple x86_64-pc-windows-msvc | FileCheck %s --check-prefix=MSVC
 // RUN: %clang_cc1 -O2 -fkeep-inline-functions -emit-llvm %s -o - -triple powerpc64-ibm-aix-xcoff | FileCheck %s
 // RUN: %clang_cc1 -O0 -fkeep-inline-functions -emit-llvm %s -o - -triple powerpc64-ibm-aix-xcoff | FileCheck %s
-// RUN: %clang_cc1 -O0 -mconstructor-aliases -fkeep-inline-functions -emit-llvm %s -o - -triple powerpc64-ibm-aix-xcoff | FileCheck %s --check-prefix=CONSTRUCTOR-ALIASES
+
+// The driver enables -mconstructor-aliases by default for this target, but
+// -cc1 does not, so pass it explicitly here.
+// With -fkeep-inline-functions, C1/D1 must be emitted instead of being
+// silently replaced in the IR (RAUW), so they remain available in the used
+// list.
+// RUN: %clang_cc1 -O0 -mconstructor-aliases -fkeep-inline-functions -emit-llvm %s -o - -triple powerpc64-ibm-aix-xcoff | FileCheck %s
+// RUN: %clang_cc1 -O2 -mconstructor-aliases -fkeep-inline-functions -emit-llvm %s -o - -triple powerpc64-ibm-aix-xcoff | FileCheck %s
 
 // -fkeep-inline-functions retains inline function definitions available in
 // this translation unit. Definitions emitted with available_externally
 // linkage are excluded.
-
 // Retained:
 //   f1  explicit inline and referenced
 //   f2  static inline
@@ -77,22 +83,6 @@ int use(S s) {
 // CHECK-DAG: define {{.*}}@_ZN12TestCtorDtorC2Ev
 // CHECK-DAG: define {{.*}}@_ZN12TestCtorDtorD2Ev
 
-// FIXME: -mconstructor-aliases is enabled by default by the driver for this
-// target, but is not enabled by default for -cc1. With -mconstructor-aliases,
-// the C1/D1 constructor and destructor variants are not emitted as separate
-// definitions.
-
-// CONSTRUCTOR-ALIASES: @llvm{{(\.compiler)?}}.used = appending global [8 x ptr]
-
-// CONSTRUCTOR-ALIASES-DAG: define {{.*}}@_Z2f1i
-// CONSTRUCTOR-ALIASES-DAG: define internal {{.*}}@_ZL2f2i
-// CONSTRUCTOR-ALIASES-DAG: define {{.*}}@_Z2f3i
-// CONSTRUCTOR-ALIASES-DAG: define {{.*}}@_ZN1S2f4Ev
-// CONSTRUCTOR-ALIASES-DAG: define {{.*}}@_Z2f7i
-// CONSTRUCTOR-ALIASES-DAG: define {{.*}}@_Z2f8IiET_S0_
-// CONSTRUCTOR-ALIASES-DAG: define {{.*}}@_ZN12TestCtorDtorC2Ev
-// CONSTRUCTOR-ALIASES-DAG: define {{.*}}@_ZN12TestCtorDtorD2Ev
-
 // MSVC: @llvm{{(\.compiler)?}}.used = appending global [8 x ptr]
 
 // MSVC-DAG: define {{.*}}@"?f1@@YAHH at Z"
diff --git a/clang/test/CodeGen/mconstructor-aliases-used-attr.cpp b/clang/test/CodeGen/mconstructor-aliases-used-attr.cpp
new file mode 100644
index 0000000000000..fdff81cb587f0
--- /dev/null
+++ b/clang/test/CodeGen/mconstructor-aliases-used-attr.cpp
@@ -0,0 +1,41 @@
+// Test that __attribute__((used)) on a constructor/destructor retains the
+// C1/D1 complete variants when -mconstructor-aliases is active.
+//
+// Without the fix, -mconstructor-aliases causes C1/D1 to be silently replaced
+// in the IR (RAUW) before SetCommonAttributes can add them to llvm.used, so
+// __attribute__((used)) does not work as expected.
+
+// RUN: %clang_cc1 -O0 -mconstructor-aliases -emit-llvm %s -o - \
+// RUN:   -triple powerpc64-ibm-aix-xcoff \
+// RUN:   | FileCheck %s --check-prefixes=XCOFF,USED
+// RUN: %clang_cc1 -O2 -mconstructor-aliases -emit-llvm %s -o - \
+// RUN:   -triple powerpc64-ibm-aix-xcoff \
+// RUN:   | FileCheck %s --check-prefixes=XCOFF,USED
+// RUN: %clang_cc1 -O0 -mconstructor-aliases -emit-llvm %s -o - \
+// RUN:   -triple x86_64-unknown-linux-gnu \
+// RUN:   | FileCheck %s --check-prefixes=ELF,USED
+
+// Without -mconstructor-aliases the complete variants are always emitted as
+// separate definitions and the normal path handles __attribute__((used)).
+// RUN: %clang_cc1 -O0 -emit-llvm %s -o - \
+// RUN:   -triple powerpc64-ibm-aix-xcoff \
+// RUN:   | FileCheck %s --check-prefixes=XCOFF,USED
+
+struct Foo {
+  __attribute__((used)) Foo() {}
+  __attribute__((used)) ~Foo() {}
+};
+// All four variants must appear in llvm.used/llvm.compiler.used.
+// USED: @llvm{{(\.compiler)?}}.used = appending global [4 x ptr]
+
+// On XCOFF, C1/D1 are full definitions
+// XCOFF-DAG: define {{.*}}@_ZN3FooC1Ev
+// XCOFF-DAG: define {{.*}}@_ZN3FooC2Ev
+// XCOFF-DAG: define {{.*}}@_ZN3FooD1Ev
+// XCOFF-DAG: define {{.*}}@_ZN3FooD2Ev
+
+// On ELF, C1/D1 are aliases to C2/D2
+// ELF-DAG: @_ZN3FooC1Ev = {{.*}}alias{{.*}}@_ZN3FooC2Ev
+// ELF-DAG: @_ZN3FooD1Ev = {{.*}}alias{{.*}}@_ZN3FooD2Ev
+// ELF-DAG: define {{.*}}@_ZN3FooC2Ev
+// ELF-DAG: define {{.*}}@_ZN3FooD2Ev

>From 0e8036741e2aa231753fa3781728676122407260 Mon Sep 17 00:00:00 2001
From: Zaara Syeda <syzaara at ca.ibm.com>
Date: Fri, 25 Sep 2026 15:10:12 -0400
Subject: [PATCH 02/10] clang-format

---
 clang/lib/CodeGen/ItaniumCXXABI.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/clang/lib/CodeGen/ItaniumCXXABI.cpp b/clang/lib/CodeGen/ItaniumCXXABI.cpp
index 1c739e5175d33..460950b35c41e 100644
--- a/clang/lib/CodeGen/ItaniumCXXABI.cpp
+++ b/clang/lib/CodeGen/ItaniumCXXABI.cpp
@@ -4741,7 +4741,7 @@ enum class StructorCodegen { Emit, RAUW, Alias, COMDAT };
 // as a distinct symbol rather than being silently replaced in the IR (RAUW).
 static bool
 structorSymbolMustBeRetained(CodeGenModule &CGM, const CXXMethodDecl *MD,
-                              llvm::GlobalValue::LinkageTypes Linkage) {
+                             llvm::GlobalValue::LinkageTypes Linkage) {
   if (MD->hasAttr<UsedAttr>())
     return true;
   return CGM.getCodeGenOpts().KeepInlineFunctions && MD->isInlined() &&

>From 7a7317acccfae72e48df600514d060055d3f6971 Mon Sep 17 00:00:00 2001
From: Zaara Syeda <syzaara at ca.ibm.com>
Date: Mon, 28 Sep 2026 16:00:00 -0400
Subject: [PATCH 03/10] Address review comments

---
 .../mconstructor-aliases-used-attr.cpp        | 45 +++++++++++++++----
 1 file changed, 36 insertions(+), 9 deletions(-)

diff --git a/clang/test/CodeGen/mconstructor-aliases-used-attr.cpp b/clang/test/CodeGen/mconstructor-aliases-used-attr.cpp
index fdff81cb587f0..bf02a91f530fd 100644
--- a/clang/test/CodeGen/mconstructor-aliases-used-attr.cpp
+++ b/clang/test/CodeGen/mconstructor-aliases-used-attr.cpp
@@ -19,23 +19,50 @@
 // separate definitions and the normal path handles __attribute__((used)).
 // RUN: %clang_cc1 -O0 -emit-llvm %s -o - \
 // RUN:   -triple powerpc64-ibm-aix-xcoff \
-// RUN:   | FileCheck %s --check-prefixes=XCOFF,USED
+// RUN:   | FileCheck %s --check-prefixes=XCOFF-NOALIAS,USED
 
 struct Foo {
   __attribute__((used)) Foo() {}
   __attribute__((used)) ~Foo() {}
 };
-// All four variants must appear in llvm.used/llvm.compiler.used.
-// USED: @llvm{{(\.compiler)?}}.used = appending global [4 x ptr]
 
-// On XCOFF, C1/D1 are full definitions
-// XCOFF-DAG: define {{.*}}@_ZN3FooC1Ev
-// XCOFF-DAG: define {{.*}}@_ZN3FooC2Ev
-// XCOFF-DAG: define {{.*}}@_ZN3FooD1Ev
-// XCOFF-DAG: define {{.*}}@_ZN3FooD2Ev
+namespace {
+struct Bar {
+  __attribute__((used)) Bar() {}
+  __attribute__((used)) ~Bar() {}
+};
+}
+
+// All eight variants (four for Foo, four for Bar) must appear in
+// llvm.used/llvm.compiler.used.
+// USED: @llvm{{(\.compiler)?}}.used = appending global [8 x ptr]
+
+// On XCOFF, C1/D1 are full definitions for externally visible Foo,
+// while they are aliases to C2/D2 for internal-linkage Bar.
+// XCOFF-DAG: define linkonce_odr {{.*}}@_ZN3FooC1Ev
+// XCOFF-DAG: define linkonce_odr {{.*}}@_ZN3FooC2Ev
+// XCOFF-DAG: define linkonce_odr {{.*}}@_ZN3FooD1Ev
+// XCOFF-DAG: define linkonce_odr {{.*}}@_ZN3FooD2Ev
+// XCOFF-DAG: @_ZN12_GLOBAL__N_13BarC1Ev = internal {{.*}}alias{{.*}}@_ZN12_GLOBAL__N_13BarC2Ev
+// XCOFF-DAG: @_ZN12_GLOBAL__N_13BarD1Ev = internal {{.*}}alias{{.*}}@_ZN12_GLOBAL__N_13BarD2Ev
+// XCOFF-DAG: define internal {{.*}}@_ZN12_GLOBAL__N_13BarC2Ev
+// XCOFF-DAG: define internal {{.*}}@_ZN12_GLOBAL__N_13BarD2Ev
+
+// XCOFF-NOALIAS-DAG: define linkonce_odr {{.*}}@_ZN3FooC1Ev
+// XCOFF-NOALIAS-DAG: define linkonce_odr {{.*}}@_ZN3FooC2Ev
+// XCOFF-NOALIAS-DAG: define linkonce_odr {{.*}}@_ZN3FooD1Ev
+// XCOFF-NOALIAS-DAG: define linkonce_odr {{.*}}@_ZN3FooD2Ev
+// XCOFF-NOALIAS-DAG: define internal {{.*}}@_ZN12_GLOBAL__N_13BarC1Ev
+// XCOFF-NOALIAS-DAG: define internal {{.*}}@_ZN12_GLOBAL__N_13BarC2Ev
+// XCOFF-NOALIAS-DAG: define internal {{.*}}@_ZN12_GLOBAL__N_13BarD1Ev
+// XCOFF-NOALIAS-DAG: define internal {{.*}}@_ZN12_GLOBAL__N_13BarD2Ev
 
-// On ELF, C1/D1 are aliases to C2/D2
+// On ELF, C1/D1 are aliases to C2/D2 for both Foo and Bar.
 // ELF-DAG: @_ZN3FooC1Ev = {{.*}}alias{{.*}}@_ZN3FooC2Ev
 // ELF-DAG: @_ZN3FooD1Ev = {{.*}}alias{{.*}}@_ZN3FooD2Ev
 // ELF-DAG: define {{.*}}@_ZN3FooC2Ev
 // ELF-DAG: define {{.*}}@_ZN3FooD2Ev
+// ELF-DAG: @_ZN12_GLOBAL__N_13BarC1Ev = internal {{.*}}alias{{.*}}@_ZN12_GLOBAL__N_13BarC2Ev
+// ELF-DAG: @_ZN12_GLOBAL__N_13BarD1Ev = internal {{.*}}alias{{.*}}@_ZN12_GLOBAL__N_13BarD2Ev
+// ELF-DAG: define internal {{.*}}@_ZN12_GLOBAL__N_13BarC2Ev
+// ELF-DAG: define internal {{.*}}@_ZN12_GLOBAL__N_13BarD2Ev

>From 916390c3e06ed7a8ac1525735ff5190c72cfa871 Mon Sep 17 00:00:00 2001
From: Zaara Syeda <syzaara at ca.ibm.com>
Date: Mon, 28 Sep 2026 16:39:42 -0400
Subject: [PATCH 04/10] Address review comments

---
 .../mconstructor-aliases-used-attr.cpp        | 68 -------------------
 clang/test/CodeGenCXX/attr-used.cpp           | 63 +++++++++++++++++
 2 files changed, 63 insertions(+), 68 deletions(-)
 delete mode 100644 clang/test/CodeGen/mconstructor-aliases-used-attr.cpp

diff --git a/clang/test/CodeGen/mconstructor-aliases-used-attr.cpp b/clang/test/CodeGen/mconstructor-aliases-used-attr.cpp
deleted file mode 100644
index bf02a91f530fd..0000000000000
--- a/clang/test/CodeGen/mconstructor-aliases-used-attr.cpp
+++ /dev/null
@@ -1,68 +0,0 @@
-// Test that __attribute__((used)) on a constructor/destructor retains the
-// C1/D1 complete variants when -mconstructor-aliases is active.
-//
-// Without the fix, -mconstructor-aliases causes C1/D1 to be silently replaced
-// in the IR (RAUW) before SetCommonAttributes can add them to llvm.used, so
-// __attribute__((used)) does not work as expected.
-
-// RUN: %clang_cc1 -O0 -mconstructor-aliases -emit-llvm %s -o - \
-// RUN:   -triple powerpc64-ibm-aix-xcoff \
-// RUN:   | FileCheck %s --check-prefixes=XCOFF,USED
-// RUN: %clang_cc1 -O2 -mconstructor-aliases -emit-llvm %s -o - \
-// RUN:   -triple powerpc64-ibm-aix-xcoff \
-// RUN:   | FileCheck %s --check-prefixes=XCOFF,USED
-// RUN: %clang_cc1 -O0 -mconstructor-aliases -emit-llvm %s -o - \
-// RUN:   -triple x86_64-unknown-linux-gnu \
-// RUN:   | FileCheck %s --check-prefixes=ELF,USED
-
-// Without -mconstructor-aliases the complete variants are always emitted as
-// separate definitions and the normal path handles __attribute__((used)).
-// RUN: %clang_cc1 -O0 -emit-llvm %s -o - \
-// RUN:   -triple powerpc64-ibm-aix-xcoff \
-// RUN:   | FileCheck %s --check-prefixes=XCOFF-NOALIAS,USED
-
-struct Foo {
-  __attribute__((used)) Foo() {}
-  __attribute__((used)) ~Foo() {}
-};
-
-namespace {
-struct Bar {
-  __attribute__((used)) Bar() {}
-  __attribute__((used)) ~Bar() {}
-};
-}
-
-// All eight variants (four for Foo, four for Bar) must appear in
-// llvm.used/llvm.compiler.used.
-// USED: @llvm{{(\.compiler)?}}.used = appending global [8 x ptr]
-
-// On XCOFF, C1/D1 are full definitions for externally visible Foo,
-// while they are aliases to C2/D2 for internal-linkage Bar.
-// XCOFF-DAG: define linkonce_odr {{.*}}@_ZN3FooC1Ev
-// XCOFF-DAG: define linkonce_odr {{.*}}@_ZN3FooC2Ev
-// XCOFF-DAG: define linkonce_odr {{.*}}@_ZN3FooD1Ev
-// XCOFF-DAG: define linkonce_odr {{.*}}@_ZN3FooD2Ev
-// XCOFF-DAG: @_ZN12_GLOBAL__N_13BarC1Ev = internal {{.*}}alias{{.*}}@_ZN12_GLOBAL__N_13BarC2Ev
-// XCOFF-DAG: @_ZN12_GLOBAL__N_13BarD1Ev = internal {{.*}}alias{{.*}}@_ZN12_GLOBAL__N_13BarD2Ev
-// XCOFF-DAG: define internal {{.*}}@_ZN12_GLOBAL__N_13BarC2Ev
-// XCOFF-DAG: define internal {{.*}}@_ZN12_GLOBAL__N_13BarD2Ev
-
-// XCOFF-NOALIAS-DAG: define linkonce_odr {{.*}}@_ZN3FooC1Ev
-// XCOFF-NOALIAS-DAG: define linkonce_odr {{.*}}@_ZN3FooC2Ev
-// XCOFF-NOALIAS-DAG: define linkonce_odr {{.*}}@_ZN3FooD1Ev
-// XCOFF-NOALIAS-DAG: define linkonce_odr {{.*}}@_ZN3FooD2Ev
-// XCOFF-NOALIAS-DAG: define internal {{.*}}@_ZN12_GLOBAL__N_13BarC1Ev
-// XCOFF-NOALIAS-DAG: define internal {{.*}}@_ZN12_GLOBAL__N_13BarC2Ev
-// XCOFF-NOALIAS-DAG: define internal {{.*}}@_ZN12_GLOBAL__N_13BarD1Ev
-// XCOFF-NOALIAS-DAG: define internal {{.*}}@_ZN12_GLOBAL__N_13BarD2Ev
-
-// On ELF, C1/D1 are aliases to C2/D2 for both Foo and Bar.
-// ELF-DAG: @_ZN3FooC1Ev = {{.*}}alias{{.*}}@_ZN3FooC2Ev
-// ELF-DAG: @_ZN3FooD1Ev = {{.*}}alias{{.*}}@_ZN3FooD2Ev
-// ELF-DAG: define {{.*}}@_ZN3FooC2Ev
-// ELF-DAG: define {{.*}}@_ZN3FooD2Ev
-// ELF-DAG: @_ZN12_GLOBAL__N_13BarC1Ev = internal {{.*}}alias{{.*}}@_ZN12_GLOBAL__N_13BarC2Ev
-// ELF-DAG: @_ZN12_GLOBAL__N_13BarD1Ev = internal {{.*}}alias{{.*}}@_ZN12_GLOBAL__N_13BarD2Ev
-// ELF-DAG: define internal {{.*}}@_ZN12_GLOBAL__N_13BarC2Ev
-// ELF-DAG: define internal {{.*}}@_ZN12_GLOBAL__N_13BarD2Ev
diff --git a/clang/test/CodeGenCXX/attr-used.cpp b/clang/test/CodeGenCXX/attr-used.cpp
index 8ca37f6194ec2..dd0dc18b6d62c 100644
--- a/clang/test/CodeGenCXX/attr-used.cpp
+++ b/clang/test/CodeGenCXX/attr-used.cpp
@@ -1,4 +1,16 @@
 // RUN: %clang_cc1 -emit-llvm -triple %itanium_abi_triple -o - %s | FileCheck %s
+// RUN: %clang_cc1 -O0 -mconstructor-aliases -emit-llvm %s -o - \
+// RUN:   -triple powerpc64-ibm-aix-xcoff \
+// RUN:   | FileCheck %s --check-prefixes=XCOFF
+// RUN: %clang_cc1 -O2 -mconstructor-aliases -emit-llvm %s -o - \
+// RUN:   -triple powerpc64-ibm-aix-xcoff \
+// RUN:   | FileCheck %s --check-prefixes=XCOFF
+// RUN: %clang_cc1 -O0 -mconstructor-aliases -emit-llvm %s -o - \
+// RUN:   -triple x86_64-unknown-linux-gnu \
+// RUN:   | FileCheck %s --check-prefixes=ELF
+// RUN: %clang_cc1 -O0 -emit-llvm %s -o - \
+// RUN:   -triple powerpc64-ibm-aix-xcoff \
+// RUN:   | FileCheck %s --check-prefixes=XCOFF-NOALIAS
 
 // clang++ not respecting __attribute__((used)) on destructors
 struct X0 {
@@ -25,3 +37,54 @@ struct X2 {
   // CHECK-DAG: define linkonce_odr {{.*}} @_ZN2X23barEv
   // CHECK-DAG: define linkonce_odr {{.*}} @_ZN2X23fooEv
 };
+
+// Test that __attribute__((used)) on a constructor/destructor retains the
+// C1/D1 complete variants when -mconstructor-aliases is active.
+//
+// Without the fix, -mconstructor-aliases causes C1/D1 to be silently replaced
+// in the IR (RAUW) before SetCommonAttributes can add them to llvm.used, so
+// __attribute__((used)) does not work as expected.
+
+struct Foo {
+  __attribute__((used)) Foo() {}
+  __attribute__((used)) ~Foo() {}
+};
+
+namespace {
+struct Bar {
+  __attribute__((used)) Bar() {}
+  __attribute__((used)) ~Bar() {}
+};
+} // namespace
+
+
+// On XCOFF, C1/D1 are full definitions for externally visible Foo,
+// while they are aliases to C2/D2 for internal-linkage Bar.
+// XCOFF-DAG: define linkonce_odr {{.*}}@_ZN3FooC1Ev
+// XCOFF-DAG: define linkonce_odr {{.*}}@_ZN3FooC2Ev
+// XCOFF-DAG: define linkonce_odr {{.*}}@_ZN3FooD1Ev
+// XCOFF-DAG: define linkonce_odr {{.*}}@_ZN3FooD2Ev
+// XCOFF-DAG: @_ZN12_GLOBAL__N_13BarC1Ev = internal {{.*}}alias{{.*}}@_ZN12_GLOBAL__N_13BarC2Ev
+// XCOFF-DAG: @_ZN12_GLOBAL__N_13BarD1Ev = internal {{.*}}alias{{.*}}@_ZN12_GLOBAL__N_13BarD2Ev
+// XCOFF-DAG: define internal {{.*}}@_ZN12_GLOBAL__N_13BarC2Ev
+// XCOFF-DAG: define internal {{.*}}@_ZN12_GLOBAL__N_13BarD2Ev
+
+// XCOFF without -mconstructor-aliases: all variants are full definitions.
+// XCOFF-NOALIAS-DAG: define linkonce_odr {{.*}}@_ZN3FooC1Ev
+// XCOFF-NOALIAS-DAG: define linkonce_odr {{.*}}@_ZN3FooC2Ev
+// XCOFF-NOALIAS-DAG: define linkonce_odr {{.*}}@_ZN3FooD1Ev
+// XCOFF-NOALIAS-DAG: define linkonce_odr {{.*}}@_ZN3FooD2Ev
+// XCOFF-NOALIAS-DAG: define internal {{.*}}@_ZN12_GLOBAL__N_13BarC1Ev
+// XCOFF-NOALIAS-DAG: define internal {{.*}}@_ZN12_GLOBAL__N_13BarC2Ev
+// XCOFF-NOALIAS-DAG: define internal {{.*}}@_ZN12_GLOBAL__N_13BarD1Ev
+// XCOFF-NOALIAS-DAG: define internal {{.*}}@_ZN12_GLOBAL__N_13BarD2Ev
+
+// On ELF, C1/D1 are aliases to C2/D2 for both Foo and Bar.
+// ELF-DAG: @_ZN3FooC1Ev = {{.*}}alias{{.*}}@_ZN3FooC2Ev
+// ELF-DAG: @_ZN3FooD1Ev = {{.*}}alias{{.*}}@_ZN3FooD2Ev
+// ELF-DAG: define {{.*}}@_ZN3FooC2Ev
+// ELF-DAG: define {{.*}}@_ZN3FooD2Ev
+// ELF-DAG: @_ZN12_GLOBAL__N_13BarC1Ev = internal {{.*}}alias{{.*}}@_ZN12_GLOBAL__N_13BarC2Ev
+// ELF-DAG: @_ZN12_GLOBAL__N_13BarD1Ev = internal {{.*}}alias{{.*}}@_ZN12_GLOBAL__N_13BarD2Ev
+// ELF-DAG: define internal {{.*}}@_ZN12_GLOBAL__N_13BarC2Ev
+// ELF-DAG: define internal {{.*}}@_ZN12_GLOBAL__N_13BarD2Ev

>From 090c6a32f6ea32a9cf940f11e82b5490605f8f0f Mon Sep 17 00:00:00 2001
From: Zaara Syeda <syzaara at ca.ibm.com>
Date: Mon, 28 Sep 2026 17:14:50 -0400
Subject: [PATCH 05/10] Address review comments

---
 clang/test/CodeGenCXX/attr-used.cpp | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/clang/test/CodeGenCXX/attr-used.cpp b/clang/test/CodeGenCXX/attr-used.cpp
index dd0dc18b6d62c..f1bb63d798988 100644
--- a/clang/test/CodeGenCXX/attr-used.cpp
+++ b/clang/test/CodeGenCXX/attr-used.cpp
@@ -58,8 +58,11 @@ struct Bar {
 } // namespace
 
 
-// On XCOFF, C1/D1 are full definitions for externally visible Foo,
-// while they are aliases to C2/D2 for internal-linkage Bar.
+// On XCOFF, Foo's constructors/destructors have linkonce_odr linkage. C1/D1
+// are emitted as full definitions since linkonce_odr does not guarantee that
+// an alias and its target will be retained from the same translation unit.
+// Bar's C1/D1 have internal linkage, so they are confined to the translation
+// unit and can safely be aliases to C2/D2.
 // XCOFF-DAG: define linkonce_odr {{.*}}@_ZN3FooC1Ev
 // XCOFF-DAG: define linkonce_odr {{.*}}@_ZN3FooC2Ev
 // XCOFF-DAG: define linkonce_odr {{.*}}@_ZN3FooD1Ev

>From aa12cfbdf8e00ef916e633422b40802cafd41229 Mon Sep 17 00:00:00 2001
From: Zaara Syeda <syzaara at ca.ibm.com>
Date: Mon, 28 Sep 2026 18:02:02 -0400
Subject: [PATCH 06/10] Address review comments

---
 clang/test/CodeGenCXX/attr-used.cpp | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/clang/test/CodeGenCXX/attr-used.cpp b/clang/test/CodeGenCXX/attr-used.cpp
index f1bb63d798988..ced13cd2cbeac 100644
--- a/clang/test/CodeGenCXX/attr-used.cpp
+++ b/clang/test/CodeGenCXX/attr-used.cpp
@@ -58,11 +58,11 @@ struct Bar {
 } // namespace
 
 
-// On XCOFF, Foo's constructors/destructors have linkonce_odr linkage. C1/D1
-// are emitted as full definitions since linkonce_odr does not guarantee that
-// an alias and its target will be retained from the same translation unit.
-// Bar's C1/D1 have internal linkage, so they are confined to the translation
-// unit and can safely be aliases to C2/D2.
+// C1/D1 are aliases to C2/D2 when the linkage is not weak-for-linker.
+// Bar has internal linkage (not weak-for-linker), so C1/D1 are aliases.
+// Foo has linkonce_odr linkage (weak-for-linker): on ELF this produces a
+// COMDAT, but XCOFF does not support COMDAT so C1/D1 are emitted as full
+// definitions instead.
 // XCOFF-DAG: define linkonce_odr {{.*}}@_ZN3FooC1Ev
 // XCOFF-DAG: define linkonce_odr {{.*}}@_ZN3FooC2Ev
 // XCOFF-DAG: define linkonce_odr {{.*}}@_ZN3FooD1Ev

>From 38fcac5b00deb3e3d83bdf4989dd7cd1bebaa101 Mon Sep 17 00:00:00 2001
From: Zaara Syeda <syzaara at ca.ibm.com>
Date: Mon, 28 Sep 2026 19:21:02 -0400
Subject: [PATCH 07/10] Address review comments

---
 clang/test/CodeGenCXX/attr-used.cpp | 29 +++++++++--------------------
 1 file changed, 9 insertions(+), 20 deletions(-)

diff --git a/clang/test/CodeGenCXX/attr-used.cpp b/clang/test/CodeGenCXX/attr-used.cpp
index ced13cd2cbeac..26fe86a82a223 100644
--- a/clang/test/CodeGenCXX/attr-used.cpp
+++ b/clang/test/CodeGenCXX/attr-used.cpp
@@ -8,17 +8,6 @@
 // RUN: %clang_cc1 -O0 -mconstructor-aliases -emit-llvm %s -o - \
 // RUN:   -triple x86_64-unknown-linux-gnu \
 // RUN:   | FileCheck %s --check-prefixes=ELF
-// RUN: %clang_cc1 -O0 -emit-llvm %s -o - \
-// RUN:   -triple powerpc64-ibm-aix-xcoff \
-// RUN:   | FileCheck %s --check-prefixes=XCOFF-NOALIAS
-
-// clang++ not respecting __attribute__((used)) on destructors
-struct X0 {
-  // CHECK-DAG: define linkonce_odr {{.*}} @_ZN2X0C1Ev
-  __attribute__((used)) X0() {}
-  // CHECK-DAG: define linkonce_odr {{.*}} @_ZN2X0D1Ev
-  __attribute__((used)) ~X0() {}
-};
 
 // PR19743: not emitting __attribute__((used)) inline methods in nested classes.
 struct X1 {
@@ -72,15 +61,15 @@ struct Bar {
 // XCOFF-DAG: define internal {{.*}}@_ZN12_GLOBAL__N_13BarC2Ev
 // XCOFF-DAG: define internal {{.*}}@_ZN12_GLOBAL__N_13BarD2Ev
 
-// XCOFF without -mconstructor-aliases: all variants are full definitions.
-// XCOFF-NOALIAS-DAG: define linkonce_odr {{.*}}@_ZN3FooC1Ev
-// XCOFF-NOALIAS-DAG: define linkonce_odr {{.*}}@_ZN3FooC2Ev
-// XCOFF-NOALIAS-DAG: define linkonce_odr {{.*}}@_ZN3FooD1Ev
-// XCOFF-NOALIAS-DAG: define linkonce_odr {{.*}}@_ZN3FooD2Ev
-// XCOFF-NOALIAS-DAG: define internal {{.*}}@_ZN12_GLOBAL__N_13BarC1Ev
-// XCOFF-NOALIAS-DAG: define internal {{.*}}@_ZN12_GLOBAL__N_13BarC2Ev
-// XCOFF-NOALIAS-DAG: define internal {{.*}}@_ZN12_GLOBAL__N_13BarD1Ev
-// XCOFF-NOALIAS-DAG: define internal {{.*}}@_ZN12_GLOBAL__N_13BarD2Ev
+// Without -mconstructor-aliases: all variants are full definitions.
+// CHECK-DAG: define linkonce_odr {{.*}}@_ZN3FooC1Ev
+// CHECK-DAG: define linkonce_odr {{.*}}@_ZN3FooC2Ev
+// CHECK-DAG: define linkonce_odr {{.*}}@_ZN3FooD1Ev
+// CHECK-DAG: define linkonce_odr {{.*}}@_ZN3FooD2Ev
+// CHECK-DAG: define internal {{.*}}@_ZN12_GLOBAL__N_13BarC1Ev
+// CHECK-DAG: define internal {{.*}}@_ZN12_GLOBAL__N_13BarC2Ev
+// CHECK-DAG: define internal {{.*}}@_ZN12_GLOBAL__N_13BarD1Ev
+// CHECK-DAG: define internal {{.*}}@_ZN12_GLOBAL__N_13BarD2Ev
 
 // On ELF, C1/D1 are aliases to C2/D2 for both Foo and Bar.
 // ELF-DAG: @_ZN3FooC1Ev = {{.*}}alias{{.*}}@_ZN3FooC2Ev

>From eaa00e5b9c822f14a7ba92899d39890640097555 Mon Sep 17 00:00:00 2001
From: Zaara Syeda <syzaara at ca.ibm.com>
Date: Tue, 29 Sep 2026 14:39:00 -0400
Subject: [PATCH 08/10] Address review comments

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

diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index c778703e8cc6f..31f520699e5a4 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -776,6 +776,13 @@ features cannot lower the translation-unit ABI level;
   a new-expression is now correctly rejected in a constant expression because
   it does not select a replaceable global allocation function. (#GH212211)
 
+- Fixed __attribute__((used)) and -fkeep-inline-functions being
+  incorrectly ignored for complete constructors and destructors (C1/D1 variants)
+  when -mconstructor-aliases is active. C1/D1 variants were previously
+  replaced with their base variants (C2/D2) in the IR before retention could
+  take effect, causing them to be dropped from llvm.used and omitted from
+  the object file. (#GH226572)
+
 #### Bug Fixes to AST Handling
 
 - Fixed a non-deterministic ordering of unused local typedefs that made

>From 62cc59ff7667e9d9d37107393d633a37081ff7e5 Mon Sep 17 00:00:00 2001
From: Zaara Syeda <syzaara at ca.ibm.com>
Date: Tue, 29 Sep 2026 16:02:13 -0400
Subject: [PATCH 09/10] Address review comments

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

diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index 31f520699e5a4..d6efaf58d1931 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -268,6 +268,11 @@ features cannot lower the translation-unit ABI level;
   path share one module cache, and is only sound when no module needs the path
   -- a lookup that would have resolved through an ignored path simply fails.
 
+- Added support for -fkeep-inline-functions in Clang. This option forces
+  inline function definitions that are available in the current translation
+  unit to be emitted into the object file, even when they are inlined into all
+  callers or are otherwise unused.
+
 ### Deprecated Compiler Flags
 
 ### Modified Compiler Flags

>From b5f7702882be56cc1859680b2fa2078326e81059 Mon Sep 17 00:00:00 2001
From: Zaara Syeda <syzaara at ca.ibm.com>
Date: Tue, 29 Sep 2026 16:39:30 -0400
Subject: [PATCH 10/10] Address review comments

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

diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index d6efaf58d1931..867b8ad8b5234 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -268,7 +268,7 @@ features cannot lower the translation-unit ABI level;
   path share one module cache, and is only sound when no module needs the path
   -- a lookup that would have resolved through an ignored path simply fails.
 
-- Added support for -fkeep-inline-functions in Clang. This option forces
+- Added support for `-fkeep-inline-functions` in Clang. This option forces
   inline function definitions that are available in the current translation
   unit to be emitted into the object file, even when they are inlined into all
   callers or are otherwise unused.
@@ -781,9 +781,9 @@ features cannot lower the translation-unit ABI level;
   a new-expression is now correctly rejected in a constant expression because
   it does not select a replaceable global allocation function. (#GH212211)
 
-- Fixed __attribute__((used)) and -fkeep-inline-functions being
+- Fixed `__attribute__((used))` and `-fkeep-inline-functions` being
   incorrectly ignored for complete constructors and destructors (C1/D1 variants)
-  when -mconstructor-aliases is active. C1/D1 variants were previously
+  when `-mconstructor-aliases` is active. C1/D1 variants were previously
   replaced with their base variants (C2/D2) in the IR before retention could
   take effect, causing them to be dropped from llvm.used and omitted from
   the object file. (#GH226572)



More information about the cfe-commits mailing list