-
Notifications
You must be signed in to change notification settings - Fork 13.6k
[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
Conversation
@llvm/pr-subscribers-llvm-analysis @llvm/pr-subscribers-llvm-transforms Author: Max Winkler (MaxEW707) Changeshttps://godbolt.org/z/frjhqMKqc for an example. Removal of allocations due to empty I don't see an easy way to only remove Full diff: https://github.com/llvm/llvm-project/pull/88836.diff 3 Files Affected:
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
+}
|
✅ With the latest revision this PR passed the C/C++ code formatter. |
c2b100f
to
f1b585f
Compare
@@ -263,6 +263,10 @@ DynamicSymbols: | |||
Type: STT_FUNC | |||
Section: .text | |||
Binding: STB_GLOBAL | |||
- Name: atexit |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
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!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
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 returns0
which is the value for success.