[llvm] [llvm][GlobalOpt] Remove empty atexit destructors/handlers (PR #88836)
Max Winkler via llvm-commits
llvm-commits at lists.llvm.org
Mon Apr 15 20:37:16 PDT 2024
https://github.com/MaxEW707 created https://github.com/llvm/llvm-project/pull/88836
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.
>From f9d7735dd5bbd224e7451efdf9520b77138ad2bb Mon Sep 17 00:00:00 2001
From: MaxEW707 <max.enrico.winkler at gmail.com>
Date: Mon, 15 Apr 2024 22:04:18 -0400
Subject: [PATCH 1/5] Remove empty atexit destructors
---
.../llvm/Analysis/TargetLibraryInfo.def | 5 ++
llvm/lib/Transforms/IPO/GlobalOpt.cpp | 72 +++++++++++--------
llvm/test/Transforms/GlobalOpt/atexit-dtor.ll | 31 ++++++++
3 files changed, 80 insertions(+), 28 deletions(-)
create mode 100644 llvm/test/Transforms/GlobalOpt/atexit-dtor.ll
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..d6bc1eda5180fb 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,36 +2322,46 @@ OptimizeGlobalAliases(Module &M,
}
static Function *
-FindCXAAtExit(Module &M, function_ref<TargetLibraryInfo &(Function &)> GetTLI) {
- // 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);
+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;
- if (!TLI->has(F))
- return nullptr;
+ LibFunc F = Func;
+ if (!TLI->has(F))
+ return nullptr;
- Function *Fn = M.getFunction(TLI->getName(F));
- if (!Fn)
- return nullptr;
+ Function *Fn = M.getFunction(TLI->getName(F));
+ if (!Fn)
+ return nullptr;
- // Now get the actual TLI for Fn.
- TLI = &GetTLI(*Fn);
+ // Now get the actual TLI for Fn.
+ TLI = &GetTLI(*Fn);
- // Make sure that the function has the correct prototype.
- if (!TLI->getLibFunc(*Fn, F) || F != LibFunc_cxa_atexit)
- return nullptr;
+ // Make sure that the function has the correct prototype.
+ if (!TLI->getLibFunc(*Fn, F) || F != Func)
+ return nullptr;
+
+ return Fn;
+}
- return Fn;
+static Function *
+FindCXAAtExit(Module &M, function_ref<TargetLibraryInfo &(Function &)> GetTLI) {
+ return FindAtExitLibFunc(M, GetTLI, LibFunc_cxa_atexit);
}
-/// Returns whether the given function is an empty C++ destructor and can
-/// therefore be eliminated.
+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..b3806af51ab5e7
--- /dev/null
+++ b/llvm/test/Transforms/GlobalOpt/atexit-dtor.ll
@@ -0,0 +1,31 @@
+; 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"() #0 {
+ %1 = call i32 @atexit(ptr @"??__Fg@@YAXXZ") #2
+ 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"() #0 {
+ call void @"??1A@@QEAA at XZ"(ptr @"?g@@3UA@@A")
+ ret void
+}
+
+declare dso_local i32 @atexit(ptr) #2
+
+define internal void @_GLOBAL__sub_I_atexit-dtor() #0 {
+ call void @"??__Eg@@YAXXZ"()
+ ret void
+}
\ No newline at end of file
>From 714880159156ec661475040799cdc72276336d6d Mon Sep 17 00:00:00 2001
From: MaxEW707 <max.enrico.winkler at gmail.com>
Date: Mon, 15 Apr 2024 22:19:21 -0400
Subject: [PATCH 2/5] formatting
---
llvm/lib/Transforms/IPO/GlobalOpt.cpp | 46 +++++++++++++--------------
1 file changed, 23 insertions(+), 23 deletions(-)
diff --git a/llvm/lib/Transforms/IPO/GlobalOpt.cpp b/llvm/lib/Transforms/IPO/GlobalOpt.cpp
index d6bc1eda5180fb..bf277efc892bff 100644
--- a/llvm/lib/Transforms/IPO/GlobalOpt.cpp
+++ b/llvm/lib/Transforms/IPO/GlobalOpt.cpp
@@ -2323,38 +2323,38 @@ OptimizeGlobalAliases(Module &M,
static Function *
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);
+ // 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 = Func;
- if (!TLI->has(F))
- return nullptr;
+ LibFunc F = Func;
+ if (!TLI->has(F))
+ return nullptr;
- Function *Fn = M.getFunction(TLI->getName(F));
- if (!Fn)
- return nullptr;
+ Function *Fn = M.getFunction(TLI->getName(F));
+ if (!Fn)
+ return nullptr;
- // Now get the actual TLI for Fn.
- TLI = &GetTLI(*Fn);
+ // Now get the actual TLI for Fn.
+ TLI = &GetTLI(*Fn);
- // Make sure that the function has the correct prototype.
- if (!TLI->getLibFunc(*Fn, F) || F != Func)
- return nullptr;
+ // Make sure that the function has the correct prototype.
+ if (!TLI->getLibFunc(*Fn, F) || F != Func)
+ return nullptr;
- return Fn;
+ return Fn;
}
static Function *
FindCXAAtExit(Module &M, function_ref<TargetLibraryInfo &(Function &)> GetTLI) {
- return FindAtExitLibFunc(M, GetTLI, LibFunc_cxa_atexit);
+ return FindAtExitLibFunc(M, GetTLI, LibFunc_cxa_atexit);
}
static Function *
FindAtExit(Module &M, function_ref<TargetLibraryInfo &(Function &)> GetTLI) {
- return FindAtExitLibFunc(M, GetTLI, LibFunc_atexit);
+ return FindAtExitLibFunc(M, GetTLI, LibFunc_atexit);
}
/// Returns whether the given function is an empty C++ destructor or atexit handler
@@ -2412,9 +2412,9 @@ static bool OptimizeEmptyGlobalAtExitDtors(Function *CXAAtExitFn, bool isCXX) {
CI->eraseFromParent();
if (isCXX)
- ++NumCXXDtorsRemoved;
+ ++NumCXXDtorsRemoved;
else
- ++NumAtExitRemoved;
+ ++NumAtExitRemoved;
Changed |= true;
}
@@ -2533,10 +2533,10 @@ optimizeGlobalsInModule(Module &M, const DataLayout &DL,
// Try to remove trivial global destructors if they are not removed
// already.
if (Function *CXAAtExitFn = FindCXAAtExit(M, GetTLI))
- LocalChange |= OptimizeEmptyGlobalAtExitDtors(CXAAtExitFn, true);
+ LocalChange |= OptimizeEmptyGlobalAtExitDtors(CXAAtExitFn, true);
if (Function *AtExitFn = FindAtExit(M, GetTLI))
- LocalChange |= OptimizeEmptyGlobalAtExitDtors(AtExitFn, false);
+ LocalChange |= OptimizeEmptyGlobalAtExitDtors(AtExitFn, false);
// Optimize IFuncs whose callee's are statically known.
LocalChange |= OptimizeStaticIFuncs(M);
>From 8c38bcaef7f4e461eb877f52578ef23d9649e859 Mon Sep 17 00:00:00 2001
From: MaxEW707 <max.enrico.winkler at gmail.com>
Date: Mon, 15 Apr 2024 22:23:41 -0400
Subject: [PATCH 3/5] eol
---
llvm/test/Transforms/GlobalOpt/atexit-dtor.ll | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/llvm/test/Transforms/GlobalOpt/atexit-dtor.ll b/llvm/test/Transforms/GlobalOpt/atexit-dtor.ll
index b3806af51ab5e7..83c7482aca1e7b 100644
--- a/llvm/test/Transforms/GlobalOpt/atexit-dtor.ll
+++ b/llvm/test/Transforms/GlobalOpt/atexit-dtor.ll
@@ -28,4 +28,4 @@ declare dso_local i32 @atexit(ptr) #2
define internal void @_GLOBAL__sub_I_atexit-dtor() #0 {
call void @"??__Eg@@YAXXZ"()
ret void
-}
\ No newline at end of file
+}
>From 544738e6fc6d665a1f57d07546b87b5c762045b8 Mon Sep 17 00:00:00 2001
From: MaxEW707 <max.enrico.winkler at gmail.com>
Date: Mon, 15 Apr 2024 23:05:30 -0400
Subject: [PATCH 4/5] unit test for explicit at exit registration
---
llvm/test/Transforms/GlobalOpt/atexit-dtor.ll | 25 +++++++++++++++----
1 file changed, 20 insertions(+), 5 deletions(-)
diff --git a/llvm/test/Transforms/GlobalOpt/atexit-dtor.ll b/llvm/test/Transforms/GlobalOpt/atexit-dtor.ll
index 83c7482aca1e7b..aa1f4998630097 100644
--- a/llvm/test/Transforms/GlobalOpt/atexit-dtor.ll
+++ b/llvm/test/Transforms/GlobalOpt/atexit-dtor.ll
@@ -9,8 +9,8 @@ $"??1A@@QEAA at XZ" = comdat any
; CHECK-NOT: call i32 @atexit
-define internal void @"??__Eg@@YAXXZ"() #0 {
- %1 = call i32 @atexit(ptr @"??__Fg@@YAXXZ") #2
+define internal void @"??__Eg@@YAXXZ"() {
+ %1 = call i32 @atexit(ptr @"??__Fg@@YAXXZ")
ret void
}
@@ -18,14 +18,29 @@ define linkonce_odr dso_local void @"??1A@@QEAA at XZ"(ptr noundef nonnull align 4
ret void
}
-define internal void @"??__Fg@@YAXXZ"() #0 {
+define internal void @"??__Fg@@YAXXZ"() {
call void @"??1A@@QEAA at XZ"(ptr @"?g@@3UA@@A")
ret void
}
-declare dso_local i32 @atexit(ptr) #2
+declare dso_local i32 @atexit(ptr)
-define internal void @_GLOBAL__sub_I_atexit-dtor() #0 {
+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
+}
\ No newline at end of file
>From 6bbd308c2ce12a02dee176ba4a950e8ad1f820fc Mon Sep 17 00:00:00 2001
From: MaxEW707 <max.enrico.winkler at gmail.com>
Date: Mon, 15 Apr 2024 23:06:33 -0400
Subject: [PATCH 5/5] eol
---
llvm/test/Transforms/GlobalOpt/atexit-dtor.ll | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/llvm/test/Transforms/GlobalOpt/atexit-dtor.ll b/llvm/test/Transforms/GlobalOpt/atexit-dtor.ll
index aa1f4998630097..f504fcc9ae29d3 100644
--- a/llvm/test/Transforms/GlobalOpt/atexit-dtor.ll
+++ b/llvm/test/Transforms/GlobalOpt/atexit-dtor.ll
@@ -43,4 +43,4 @@ define dso_local noundef i32 @register_atexit_handler() {
%2 = call i32 @atexit(ptr @"atexit_handler")
; CHECK: ret i32 0
ret i32 %2
-}
\ No newline at end of file
+}
More information about the llvm-commits
mailing list