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

via llvm-commits llvm-commits at lists.llvm.org
Mon Apr 15 20:37:48 PDT 2024


llvmbot wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-llvm-transforms

Author: Max Winkler (MaxEW707)

<details>
<summary>Changes</summary>

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
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 at XZ" = comdat any
+
+@"?g@@3UA@@A" = dso_local global %struct.A zeroinitializer, align 4
+ at 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 at 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 at 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
+}

``````````

</details>


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


More information about the llvm-commits mailing list