[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:52:33 PDT 2026


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

>From 62a752e1947eb9c88e4cd218da67fc99c8e32d26 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 e154398b946829a908c362df95035dee08c8782b 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 9f65c32aaa55f09f1ff9658cf1e8ed13438642dc 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 0ff10b558e27d8b1f752a3f45ec74e8e02f47461 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 c6db7b009e165c3a6ac2e347edd7991ded1fa080 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 acf4f1a7d556cef51c658123abe5b8e26fed364b 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 58f0145ac36e1ac52cb2af59e31f98f6d28180e2 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 d4025f6a34347ca59831e32592d9c5ef06fdfb1a 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 a5c72fdd2c18838469d1a6cafdbc46601b403715 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 b9b43c1497546b6d42e44459dda20cbb67d4f716 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