[clang] 7d812d2 - [Clang] Retain constructor/destructor variants when symbol must be kept (#226572)
via cfe-commits
cfe-commits at lists.llvm.org
Wed Sep 30 10:59:04 PDT 2026
Author: Zaara Syeda
Date: 2026-09-30T13:58:56-04:00
New Revision: 7d812d2b8aad397ed65a664ca2304500ba311b3f
URL: https://github.com/llvm/llvm-project/commit/7d812d2b8aad397ed65a664ca2304500ba311b3f
DIFF: https://github.com/llvm/llvm-project/commit/7d812d2b8aad397ed65a664ca2304500ba311b3f.diff
LOG: [Clang] Retain constructor/destructor variants when symbol must be kept (#226572)
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
Added:
Modified:
clang/docs/ReleaseNotes.md
clang/lib/CodeGen/ItaniumCXXABI.cpp
clang/test/CodeGen/keep-inline-functions.cpp
clang/test/CodeGenCXX/attr-used.cpp
Removed:
################################################################################
diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index ca0846e917ac4..81e4d1fc8360f 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -271,6 +271,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
@@ -779,6 +784,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
diff --git a/clang/lib/CodeGen/ItaniumCXXABI.cpp b/clang/lib/CodeGen/ItaniumCXXABI.cpp
index 699649046d244..35964b8e0d0b1 100644
--- a/clang/lib/CodeGen/ItaniumCXXABI.cpp
+++ b/clang/lib/CodeGen/ItaniumCXXABI.cpp
@@ -4737,7 +4737,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)
@@ -4757,7 +4769,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/CodeGenCXX/attr-used.cpp b/clang/test/CodeGenCXX/attr-used.cpp
index 8ca37f6194ec2..26fe86a82a223 100644
--- a/clang/test/CodeGenCXX/attr-used.cpp
+++ b/clang/test/CodeGenCXX/attr-used.cpp
@@ -1,12 +1,13 @@
// RUN: %clang_cc1 -emit-llvm -triple %itanium_abi_triple -o - %s | FileCheck %s
-
-// 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() {}
-};
+// 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
// PR19743: not emitting __attribute__((used)) inline methods in nested classes.
struct X1 {
@@ -25,3 +26,57 @@ 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
+
+
+// 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
+// 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
+
+// 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
+// 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
More information about the cfe-commits
mailing list