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@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


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

Reply via email to