Skip to content

[llvm][GlobalOpt] Remove empty atexit destructors/handlers #88836

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Apr 30, 2024

Conversation

MaxEW707
Copy link
Contributor

https://godbolt.org/z/frjhqMKqc for an example.

Removal of allocations due to empty __cxa_atexit destructor calls is done by the following globalopt pass.
This pass currently does not look for atexit handlers generated for platforms that do not use __cxa_atexit.
By default Win32 and AIX use atexit.

I don't see an easy way to only remove atexit calls that the compiler generated without looking at the generated mangled name of the atexit handler that is being registered.
However we can easily remove all atexit calls that register empty handlers since it is trivial to ensure the removed call still returns 0 which is the value for success.

@llvmbot
Copy link
Member

llvmbot commented Apr 16, 2024

@llvm/pr-subscribers-llvm-analysis

@llvm/pr-subscribers-llvm-transforms

Author: Max Winkler (MaxEW707)

Changes

https://godbolt.org/z/frjhqMKqc for an example.

Removal of allocations due to empty __cxa_atexit destructor calls is done by the following globalopt pass.
This pass currently does not look for atexit handlers generated for platforms that do not use __cxa_atexit.
By default Win32 and AIX use atexit.

I don't see an easy way to only remove atexit calls that the compiler generated without looking at the generated mangled name of the atexit handler that is being registered.
However we can easily remove all atexit calls that register empty handlers since it is trivial to ensure the removed call still returns 0 which is the value for success.


Full diff: https://github.com/llvm/llvm-project/pull/88836.diff

3 Files Affected:

  • (modified) llvm/include/llvm/Analysis/TargetLibraryInfo.def (+5)
  • (modified) llvm/lib/Transforms/IPO/GlobalOpt.cpp (+29-13)
  • (added) llvm/test/Transforms/GlobalOpt/atexit-dtor.ll (+46)
diff --git a/llvm/include/llvm/Analysis/TargetLibraryInfo.def b/llvm/include/llvm/Analysis/TargetLibraryInfo.def
index 37221eb9e47115..717693a7cf63c1 100644
--- a/llvm/include/llvm/Analysis/TargetLibraryInfo.def
+++ b/llvm/include/llvm/Analysis/TargetLibraryInfo.def
@@ -471,6 +471,11 @@ TLI_DEFINE_ENUM_INTERNAL(cxa_atexit)
 TLI_DEFINE_STRING_INTERNAL("__cxa_atexit")
 TLI_DEFINE_SIG_INTERNAL(Int, Ptr, Ptr, Ptr)
 
+/// int atexit(void (*f)(void));
+TLI_DEFINE_ENUM_INTERNAL(atexit)
+TLI_DEFINE_STRING_INTERNAL("atexit")
+TLI_DEFINE_SIG_INTERNAL(Int, Ptr)
+
 /// void __cxa_guard_abort(guard_t *guard);
 /// guard_t is int64_t in Itanium ABI or int32_t on ARM eabi.
 TLI_DEFINE_ENUM_INTERNAL(cxa_guard_abort)
diff --git a/llvm/lib/Transforms/IPO/GlobalOpt.cpp b/llvm/lib/Transforms/IPO/GlobalOpt.cpp
index da714c9a75701b..bf277efc892bff 100644
--- a/llvm/lib/Transforms/IPO/GlobalOpt.cpp
+++ b/llvm/lib/Transforms/IPO/GlobalOpt.cpp
@@ -87,6 +87,7 @@ STATISTIC(NumNestRemoved   , "Number of nest attributes removed");
 STATISTIC(NumAliasesResolved, "Number of global aliases resolved");
 STATISTIC(NumAliasesRemoved, "Number of global aliases eliminated");
 STATISTIC(NumCXXDtorsRemoved, "Number of global C++ destructors removed");
+STATISTIC(NumAtExitRemoved, "Number of atexit handlers removed");
 STATISTIC(NumInternalFunc, "Number of internal functions");
 STATISTIC(NumColdCC, "Number of functions marked coldcc");
 STATISTIC(NumIFuncsResolved, "Number of statically resolved IFuncs");
@@ -2321,14 +2322,14 @@ OptimizeGlobalAliases(Module &M,
 }
 
 static Function *
-FindCXAAtExit(Module &M, function_ref<TargetLibraryInfo &(Function &)> GetTLI) {
+FindAtExitLibFunc(Module &M, function_ref<TargetLibraryInfo &(Function &)> GetTLI, LibFunc Func) {
   // Hack to get a default TLI before we have actual Function.
   auto FuncIter = M.begin();
   if (FuncIter == M.end())
     return nullptr;
   auto *TLI = &GetTLI(*FuncIter);
 
-  LibFunc F = LibFunc_cxa_atexit;
+  LibFunc F = Func;
   if (!TLI->has(F))
     return nullptr;
 
@@ -2340,17 +2341,27 @@ FindCXAAtExit(Module &M, function_ref<TargetLibraryInfo &(Function &)> GetTLI) {
   TLI = &GetTLI(*Fn);
 
   // Make sure that the function has the correct prototype.
-  if (!TLI->getLibFunc(*Fn, F) || F != LibFunc_cxa_atexit)
+  if (!TLI->getLibFunc(*Fn, F) || F != Func)
     return nullptr;
 
   return Fn;
 }
 
-/// Returns whether the given function is an empty C++ destructor and can
-/// therefore be eliminated.
+static Function *
+FindCXAAtExit(Module &M, function_ref<TargetLibraryInfo &(Function &)> GetTLI) {
+  return FindAtExitLibFunc(M, GetTLI, LibFunc_cxa_atexit);
+}
+
+static Function *
+FindAtExit(Module &M, function_ref<TargetLibraryInfo &(Function &)> GetTLI) {
+  return FindAtExitLibFunc(M, GetTLI, LibFunc_atexit);
+}
+
+/// Returns whether the given function is an empty C++ destructor or atexit handler
+/// and can therefore be eliminated.
 /// Note that we assume that other optimization passes have already simplified
 /// the code so we simply check for 'ret'.
-static bool cxxDtorIsEmpty(const Function &Fn) {
+static bool IsEmptyAtExitFunction(const Function &Fn) {
   // FIXME: We could eliminate C++ destructors if they're readonly/readnone and
   // nounwind, but that doesn't seem worth doing.
   if (Fn.isDeclaration())
@@ -2366,7 +2377,7 @@ static bool cxxDtorIsEmpty(const Function &Fn) {
   return false;
 }
 
-static bool OptimizeEmptyGlobalCXXDtors(Function *CXAAtExitFn) {
+static bool OptimizeEmptyGlobalAtExitDtors(Function *CXAAtExitFn, bool isCXX) {
   /// Itanium C++ ABI p3.3.5:
   ///
   ///   After constructing a global (or local static) object, that will require
@@ -2379,7 +2390,7 @@ static bool OptimizeEmptyGlobalCXXDtors(Function *CXAAtExitFn) {
   ///   registered before this one. It returns zero if registration is
   ///   successful, nonzero on failure.
 
-  // This pass will look for calls to __cxa_atexit where the function is trivial
+  // This pass will look for calls to __cxa_atexit or atexit where the function is trivial
   // and remove them.
   bool Changed = false;
 
@@ -2393,14 +2404,17 @@ static bool OptimizeEmptyGlobalCXXDtors(Function *CXAAtExitFn) {
 
     Function *DtorFn =
       dyn_cast<Function>(CI->getArgOperand(0)->stripPointerCasts());
-    if (!DtorFn || !cxxDtorIsEmpty(*DtorFn))
+    if (!DtorFn || !IsEmptyAtExitFunction(*DtorFn))
       continue;
 
     // Just remove the call.
     CI->replaceAllUsesWith(Constant::getNullValue(CI->getType()));
     CI->eraseFromParent();
 
-    ++NumCXXDtorsRemoved;
+    if (isCXX)
+      ++NumCXXDtorsRemoved;
+    else
+      ++NumAtExitRemoved;
 
     Changed |= true;
   }
@@ -2518,9 +2532,11 @@ optimizeGlobalsInModule(Module &M, const DataLayout &DL,
 
     // Try to remove trivial global destructors if they are not removed
     // already.
-    Function *CXAAtExitFn = FindCXAAtExit(M, GetTLI);
-    if (CXAAtExitFn)
-      LocalChange |= OptimizeEmptyGlobalCXXDtors(CXAAtExitFn);
+    if (Function *CXAAtExitFn = FindCXAAtExit(M, GetTLI))
+      LocalChange |= OptimizeEmptyGlobalAtExitDtors(CXAAtExitFn, true);
+
+    if (Function *AtExitFn = FindAtExit(M, GetTLI))
+      LocalChange |= OptimizeEmptyGlobalAtExitDtors(AtExitFn, false);
 
     // Optimize IFuncs whose callee's are statically known.
     LocalChange |= OptimizeStaticIFuncs(M);
diff --git a/llvm/test/Transforms/GlobalOpt/atexit-dtor.ll b/llvm/test/Transforms/GlobalOpt/atexit-dtor.ll
new file mode 100644
index 00000000000000..f504fcc9ae29d3
--- /dev/null
+++ b/llvm/test/Transforms/GlobalOpt/atexit-dtor.ll
@@ -0,0 +1,46 @@
+; RUN: opt < %s -S -passes='cgscc(inline),function(early-cse),globalopt' | FileCheck %s
+
+%struct.A = type { i32 }
+
+$"??1A@@QEAA@XZ" = comdat any
+
+@"?g@@3UA@@A" = dso_local global %struct.A zeroinitializer, align 4
+@llvm.global_ctors = appending global [1 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 65535, ptr @_GLOBAL__sub_I_atexit-dtor, ptr null }]
+
+; CHECK-NOT: call i32 @atexit
+
+define internal void @"??__Eg@@YAXXZ"() {
+  %1 = call i32 @atexit(ptr @"??__Fg@@YAXXZ")
+  ret void
+}
+
+define linkonce_odr dso_local void @"??1A@@QEAA@XZ"(ptr noundef nonnull align 4 dereferenceable(4) %0) unnamed_addr #1 comdat align 2 {
+  ret void
+}
+
+define internal void @"??__Fg@@YAXXZ"() {
+  call void @"??1A@@QEAA@XZ"(ptr @"?g@@3UA@@A")
+  ret void
+}
+
+declare dso_local i32 @atexit(ptr)
+
+define internal void @_GLOBAL__sub_I_atexit-dtor() {
+  call void @"??__Eg@@YAXXZ"()
+  ret void
+}
+
+define dso_local void @atexit_handler() {
+  ret void
+}
+
+; CHECK-NOT: call i32 @atexit
+
+; Check that a removed `atexit` call returns `0` which is the value that denotes success.
+define dso_local noundef i32 @register_atexit_handler() {
+  %1 = alloca i32, align 4
+  store i32 0, ptr %1, align 4
+  %2 = call i32 @atexit(ptr @"atexit_handler")
+; CHECK: ret i32 0
+  ret i32 %2
+}

Copy link

github-actions bot commented Apr 16, 2024

✅ With the latest revision this PR passed the C/C++ code formatter.

@MaxEW707 MaxEW707 force-pushed the mew/atexit-dtor-opt branch from c2b100f to f1b585f Compare April 17, 2024 01:14
@@ -263,6 +263,10 @@ DynamicSymbols:
Type: STT_FUNC
Section: .text
Binding: STB_GLOBAL
- Name: atexit
Copy link
Contributor Author

@MaxEW707 MaxEW707 Apr 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pogo59 @sks75220 Let me know for ps4/ps5 if you prefer that this library function be set unavailable.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

atexit should be available for PS4/PS5, thanks!

@MaxEW707 MaxEW707 requested a review from nikic April 20, 2024 20:13
Copy link
Contributor

@nikic nikic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@MaxEW707 MaxEW707 merged commit ce3485a into llvm:main Apr 30, 2024
4 checks passed
@MaxEW707 MaxEW707 deleted the mew/atexit-dtor-opt branch April 30, 2024 00:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants