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
Merged
5 changes: 5 additions & 0 deletions llvm/include/llvm/Analysis/TargetLibraryInfo.def
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
45 changes: 27 additions & 18 deletions llvm/lib/Transforms/IPO/GlobalOpt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down Expand Up @@ -2321,36 +2322,38 @@ 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;
if (!TLI->has(F))
if (!TLI->has(Func))
return nullptr;

Function *Fn = M.getFunction(TLI->getName(F));
Function *Fn = M.getFunction(TLI->getName(Func));
if (!Fn)
return nullptr;

// 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)
LibFunc F;
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.
/// 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) {
/// 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 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())
Expand All @@ -2366,7 +2369,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
Expand All @@ -2379,8 +2382,8 @@ 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
// and remove them.
// This pass will look for calls to __cxa_atexit or atexit where the function
// is trivial and remove them.
bool Changed = false;

for (User *U : llvm::make_early_inc_range(CXAAtExitFn->users())) {
Expand All @@ -2393,14 +2396,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;
}
Expand Down Expand Up @@ -2518,9 +2524,12 @@ 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 =
FindAtExitLibFunc(M, GetTLI, LibFunc_cxa_atexit))
LocalChange |= OptimizeEmptyGlobalAtExitDtors(CXAAtExitFn, true);

if (Function *AtExitFn = FindAtExitLibFunc(M, GetTLI, LibFunc_atexit))
LocalChange |= OptimizeEmptyGlobalAtExitDtors(AtExitFn, false);

// Optimize IFuncs whose callee's are statically known.
LocalChange |= OptimizeStaticIFuncs(M);
Expand Down
54 changes: 54 additions & 0 deletions llvm/test/Transforms/GlobalOpt/atexit-dtor.ll
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 4
; RUN: opt < %s -S -passes=globalopt | FileCheck %s

declare dso_local i32 @atexit(ptr)

define dso_local void @empty_atexit_handler() {
; CHECK-LABEL: define dso_local void @empty_atexit_handler() local_unnamed_addr {
; CHECK-NEXT: ret void
;
ret void
}

; Check that `atexit` is removed if the handler is empty.
; Check that a removed `atexit` call returns `0` which is the value that denotes success.
define dso_local noundef i32 @register_atexit_handler() {
; CHECK-LABEL: define dso_local noundef i32 @register_atexit_handler() local_unnamed_addr {
; CHECK-NEXT: ret i32 0
;
%1 = call i32 @atexit(ptr @empty_atexit_handler)
ret i32 %1
}

declare dso_local void @declared_atexit_handler()

; Check that an atexit handler with only a declaration is not removed.
define dso_local noundef i32 @register_declared_atexit_handler() {
; CHECK-LABEL: define dso_local noundef i32 @register_declared_atexit_handler() local_unnamed_addr {
; CHECK-NEXT: [[TMP1:%.*]] = call i32 @atexit(ptr @declared_atexit_handler)
; CHECK-NEXT: ret i32 [[TMP1]]
;
%1 = call i32 @atexit(ptr @declared_atexit_handler)
ret i32 %1
}

declare dso_local void @external_exit_func()

define dso_local void @nonempty_atexit_handler() {
; CHECK-LABEL: define dso_local void @nonempty_atexit_handler() {
; CHECK-NEXT: call void @external_exit_func()
; CHECK-NEXT: ret void
;
call void @external_exit_func()
ret void
}

; Check that an atexit handler that consists of any instructions other than `ret` is considered nonempty and not removed.
define dso_local noundef i32 @register_nonempty_atexit_handler() {
; CHECK-LABEL: define dso_local noundef i32 @register_nonempty_atexit_handler() local_unnamed_addr {
; CHECK-NEXT: [[TMP1:%.*]] = call i32 @atexit(ptr @nonempty_atexit_handler)
; CHECK-NEXT: ret i32 [[TMP1]]
;
%1 = call i32 @atexit(ptr @nonempty_atexit_handler)
ret i32 %1
}
12 changes: 8 additions & 4 deletions llvm/test/tools/llvm-tli-checker/ps4-tli-check.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -34,21 +34,21 @@
#
# CHECK: << Total TLI yes SDK no: 8
# CHECK: >> Total TLI no SDK yes: 0
# CHECK: == Total TLI yes SDK yes: 238
# CHECK: == Total TLI yes SDK yes: 239
#
# WRONG_DETAIL: << TLI yes SDK no : '_ZdaPv' aka operator delete[](void*)
# WRONG_DETAIL: >> TLI no SDK yes: '_ZdaPvj' aka operator delete[](void*, unsigned int)
# WRONG_DETAIL-COUNT-8: << TLI yes SDK no : {{.*}}__hot_cold_t
# WRONG_SUMMARY: << Total TLI yes SDK no: 9{{$}}
# WRONG_SUMMARY: >> Total TLI no SDK yes: 1{{$}}
# WRONG_SUMMARY: == Total TLI yes SDK yes: 237
# WRONG_SUMMARY: == Total TLI yes SDK yes: 238
#
## The -COUNT suffix doesn't care if there are too many matches, so check
## the exact count first; the two directives should add up to that.
## Yes, this means additions to TLI will fail this test, but the argument
## to -COUNT can't be an expression.
# AVAIL: TLI knows 479 symbols, 246 available
# AVAIL-COUNT-246: {{^}} available
# AVAIL: TLI knows 480 symbols, 247 available
# AVAIL-COUNT-247: {{^}} available
# AVAIL-NOT: {{^}} available
# UNAVAIL-COUNT-233: not available
# UNAVAIL-NOT: not available
Expand Down Expand Up @@ -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!

Type: STT_FUNC
Section: .text
Binding: STB_GLOBAL
- Name: atof
Type: STT_FUNC
Section: .text
Expand Down
2 changes: 2 additions & 0 deletions llvm/unittests/Analysis/TargetLibraryInfoTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -495,6 +495,8 @@ TEST_F(TargetLibraryInfoTest, ValidProto) {
"declare i32 @__cxa_guard_acquire(%struct*)\n"
"declare void @__cxa_guard_release(%struct*)\n"

"declare i32 @atexit(void ()*)\n"

"declare i32 @__nvvm_reflect(i8*)\n"

"declare i8* @__memcpy_chk(i8*, i8*, i64, i64)\n"
Expand Down
Loading