Skip to content

Commit ce3485a

Browse files
authored
[llvm][GlobalOpt] Remove empty atexit destructors/handlers (#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.
1 parent 6566ffd commit ce3485a

File tree

5 files changed

+96
-22
lines changed

5 files changed

+96
-22
lines changed

llvm/include/llvm/Analysis/TargetLibraryInfo.def

+5
Original file line numberDiff line numberDiff line change
@@ -471,6 +471,11 @@ TLI_DEFINE_ENUM_INTERNAL(cxa_atexit)
471471
TLI_DEFINE_STRING_INTERNAL("__cxa_atexit")
472472
TLI_DEFINE_SIG_INTERNAL(Int, Ptr, Ptr, Ptr)
473473

474+
/// int atexit(void (*f)(void));
475+
TLI_DEFINE_ENUM_INTERNAL(atexit)
476+
TLI_DEFINE_STRING_INTERNAL("atexit")
477+
TLI_DEFINE_SIG_INTERNAL(Int, Ptr)
478+
474479
/// void __cxa_guard_abort(guard_t *guard);
475480
/// guard_t is int64_t in Itanium ABI or int32_t on ARM eabi.
476481
TLI_DEFINE_ENUM_INTERNAL(cxa_guard_abort)

llvm/lib/Transforms/IPO/GlobalOpt.cpp

+27-18
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ STATISTIC(NumNestRemoved , "Number of nest attributes removed");
8787
STATISTIC(NumAliasesResolved, "Number of global aliases resolved");
8888
STATISTIC(NumAliasesRemoved, "Number of global aliases eliminated");
8989
STATISTIC(NumCXXDtorsRemoved, "Number of global C++ destructors removed");
90+
STATISTIC(NumAtExitRemoved, "Number of atexit handlers removed");
9091
STATISTIC(NumInternalFunc, "Number of internal functions");
9192
STATISTIC(NumColdCC, "Number of functions marked coldcc");
9293
STATISTIC(NumIFuncsResolved, "Number of statically resolved IFuncs");
@@ -2328,36 +2329,38 @@ OptimizeGlobalAliases(Module &M,
23282329
}
23292330

23302331
static Function *
2331-
FindCXAAtExit(Module &M, function_ref<TargetLibraryInfo &(Function &)> GetTLI) {
2332+
FindAtExitLibFunc(Module &M,
2333+
function_ref<TargetLibraryInfo &(Function &)> GetTLI,
2334+
LibFunc Func) {
23322335
// Hack to get a default TLI before we have actual Function.
23332336
auto FuncIter = M.begin();
23342337
if (FuncIter == M.end())
23352338
return nullptr;
23362339
auto *TLI = &GetTLI(*FuncIter);
23372340

2338-
LibFunc F = LibFunc_cxa_atexit;
2339-
if (!TLI->has(F))
2341+
if (!TLI->has(Func))
23402342
return nullptr;
23412343

2342-
Function *Fn = M.getFunction(TLI->getName(F));
2344+
Function *Fn = M.getFunction(TLI->getName(Func));
23432345
if (!Fn)
23442346
return nullptr;
23452347

23462348
// Now get the actual TLI for Fn.
23472349
TLI = &GetTLI(*Fn);
23482350

23492351
// Make sure that the function has the correct prototype.
2350-
if (!TLI->getLibFunc(*Fn, F) || F != LibFunc_cxa_atexit)
2352+
LibFunc F;
2353+
if (!TLI->getLibFunc(*Fn, F) || F != Func)
23512354
return nullptr;
23522355

23532356
return Fn;
23542357
}
23552358

2356-
/// Returns whether the given function is an empty C++ destructor and can
2357-
/// therefore be eliminated.
2358-
/// Note that we assume that other optimization passes have already simplified
2359-
/// the code so we simply check for 'ret'.
2360-
static bool cxxDtorIsEmpty(const Function &Fn) {
2359+
/// Returns whether the given function is an empty C++ destructor or atexit
2360+
/// handler and can therefore be eliminated. Note that we assume that other
2361+
/// optimization passes have already simplified the code so we simply check for
2362+
/// 'ret'.
2363+
static bool IsEmptyAtExitFunction(const Function &Fn) {
23612364
// FIXME: We could eliminate C++ destructors if they're readonly/readnone and
23622365
// nounwind, but that doesn't seem worth doing.
23632366
if (Fn.isDeclaration())
@@ -2373,7 +2376,7 @@ static bool cxxDtorIsEmpty(const Function &Fn) {
23732376
return false;
23742377
}
23752378

2376-
static bool OptimizeEmptyGlobalCXXDtors(Function *CXAAtExitFn) {
2379+
static bool OptimizeEmptyGlobalAtExitDtors(Function *CXAAtExitFn, bool isCXX) {
23772380
/// Itanium C++ ABI p3.3.5:
23782381
///
23792382
/// After constructing a global (or local static) object, that will require
@@ -2386,8 +2389,8 @@ static bool OptimizeEmptyGlobalCXXDtors(Function *CXAAtExitFn) {
23862389
/// registered before this one. It returns zero if registration is
23872390
/// successful, nonzero on failure.
23882391

2389-
// This pass will look for calls to __cxa_atexit where the function is trivial
2390-
// and remove them.
2392+
// This pass will look for calls to __cxa_atexit or atexit where the function
2393+
// is trivial and remove them.
23912394
bool Changed = false;
23922395

23932396
for (User *U : llvm::make_early_inc_range(CXAAtExitFn->users())) {
@@ -2400,14 +2403,17 @@ static bool OptimizeEmptyGlobalCXXDtors(Function *CXAAtExitFn) {
24002403

24012404
Function *DtorFn =
24022405
dyn_cast<Function>(CI->getArgOperand(0)->stripPointerCasts());
2403-
if (!DtorFn || !cxxDtorIsEmpty(*DtorFn))
2406+
if (!DtorFn || !IsEmptyAtExitFunction(*DtorFn))
24042407
continue;
24052408

24062409
// Just remove the call.
24072410
CI->replaceAllUsesWith(Constant::getNullValue(CI->getType()));
24082411
CI->eraseFromParent();
24092412

2410-
++NumCXXDtorsRemoved;
2413+
if (isCXX)
2414+
++NumCXXDtorsRemoved;
2415+
else
2416+
++NumAtExitRemoved;
24112417

24122418
Changed |= true;
24132419
}
@@ -2525,9 +2531,12 @@ optimizeGlobalsInModule(Module &M, const DataLayout &DL,
25252531

25262532
// Try to remove trivial global destructors if they are not removed
25272533
// already.
2528-
Function *CXAAtExitFn = FindCXAAtExit(M, GetTLI);
2529-
if (CXAAtExitFn)
2530-
LocalChange |= OptimizeEmptyGlobalCXXDtors(CXAAtExitFn);
2534+
if (Function *CXAAtExitFn =
2535+
FindAtExitLibFunc(M, GetTLI, LibFunc_cxa_atexit))
2536+
LocalChange |= OptimizeEmptyGlobalAtExitDtors(CXAAtExitFn, true);
2537+
2538+
if (Function *AtExitFn = FindAtExitLibFunc(M, GetTLI, LibFunc_atexit))
2539+
LocalChange |= OptimizeEmptyGlobalAtExitDtors(AtExitFn, false);
25312540

25322541
// Optimize IFuncs whose callee's are statically known.
25332542
LocalChange |= OptimizeStaticIFuncs(M);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 4
2+
; RUN: opt < %s -S -passes=globalopt | FileCheck %s
3+
4+
declare dso_local i32 @atexit(ptr)
5+
6+
define dso_local void @empty_atexit_handler() {
7+
; CHECK-LABEL: define dso_local void @empty_atexit_handler() local_unnamed_addr {
8+
; CHECK-NEXT: ret void
9+
;
10+
ret void
11+
}
12+
13+
; Check that `atexit` is removed if the handler is empty.
14+
; Check that a removed `atexit` call returns `0` which is the value that denotes success.
15+
define dso_local noundef i32 @register_atexit_handler() {
16+
; CHECK-LABEL: define dso_local noundef i32 @register_atexit_handler() local_unnamed_addr {
17+
; CHECK-NEXT: ret i32 0
18+
;
19+
%1 = call i32 @atexit(ptr @empty_atexit_handler)
20+
ret i32 %1
21+
}
22+
23+
declare dso_local void @declared_atexit_handler()
24+
25+
; Check that an atexit handler with only a declaration is not removed.
26+
define dso_local noundef i32 @register_declared_atexit_handler() {
27+
; CHECK-LABEL: define dso_local noundef i32 @register_declared_atexit_handler() local_unnamed_addr {
28+
; CHECK-NEXT: [[TMP1:%.*]] = call i32 @atexit(ptr @declared_atexit_handler)
29+
; CHECK-NEXT: ret i32 [[TMP1]]
30+
;
31+
%1 = call i32 @atexit(ptr @declared_atexit_handler)
32+
ret i32 %1
33+
}
34+
35+
declare dso_local void @external_exit_func()
36+
37+
define dso_local void @nonempty_atexit_handler() {
38+
; CHECK-LABEL: define dso_local void @nonempty_atexit_handler() {
39+
; CHECK-NEXT: call void @external_exit_func()
40+
; CHECK-NEXT: ret void
41+
;
42+
call void @external_exit_func()
43+
ret void
44+
}
45+
46+
; Check that an atexit handler that consists of any instructions other than `ret` is considered nonempty and not removed.
47+
define dso_local noundef i32 @register_nonempty_atexit_handler() {
48+
; CHECK-LABEL: define dso_local noundef i32 @register_nonempty_atexit_handler() local_unnamed_addr {
49+
; CHECK-NEXT: [[TMP1:%.*]] = call i32 @atexit(ptr @nonempty_atexit_handler)
50+
; CHECK-NEXT: ret i32 [[TMP1]]
51+
;
52+
%1 = call i32 @atexit(ptr @nonempty_atexit_handler)
53+
ret i32 %1
54+
}

llvm/test/tools/llvm-tli-checker/ps4-tli-check.yaml

+8-4
Original file line numberDiff line numberDiff line change
@@ -34,21 +34,21 @@
3434
#
3535
# CHECK: << Total TLI yes SDK no: 8
3636
# CHECK: >> Total TLI no SDK yes: 0
37-
# CHECK: == Total TLI yes SDK yes: 238
37+
# CHECK: == Total TLI yes SDK yes: 239
3838
#
3939
# WRONG_DETAIL: << TLI yes SDK no : '_ZdaPv' aka operator delete[](void*)
4040
# WRONG_DETAIL: >> TLI no SDK yes: '_ZdaPvj' aka operator delete[](void*, unsigned int)
4141
# WRONG_DETAIL-COUNT-8: << TLI yes SDK no : {{.*}}__hot_cold_t
4242
# WRONG_SUMMARY: << Total TLI yes SDK no: 9{{$}}
4343
# WRONG_SUMMARY: >> Total TLI no SDK yes: 1{{$}}
44-
# WRONG_SUMMARY: == Total TLI yes SDK yes: 237
44+
# WRONG_SUMMARY: == Total TLI yes SDK yes: 238
4545
#
4646
## The -COUNT suffix doesn't care if there are too many matches, so check
4747
## the exact count first; the two directives should add up to that.
4848
## Yes, this means additions to TLI will fail this test, but the argument
4949
## to -COUNT can't be an expression.
50-
# AVAIL: TLI knows 479 symbols, 246 available
51-
# AVAIL-COUNT-246: {{^}} available
50+
# AVAIL: TLI knows 480 symbols, 247 available
51+
# AVAIL-COUNT-247: {{^}} available
5252
# AVAIL-NOT: {{^}} available
5353
# UNAVAIL-COUNT-233: not available
5454
# UNAVAIL-NOT: not available
@@ -263,6 +263,10 @@ DynamicSymbols:
263263
Type: STT_FUNC
264264
Section: .text
265265
Binding: STB_GLOBAL
266+
- Name: atexit
267+
Type: STT_FUNC
268+
Section: .text
269+
Binding: STB_GLOBAL
266270
- Name: atof
267271
Type: STT_FUNC
268272
Section: .text

llvm/unittests/Analysis/TargetLibraryInfoTest.cpp

+2
Original file line numberDiff line numberDiff line change
@@ -495,6 +495,8 @@ TEST_F(TargetLibraryInfoTest, ValidProto) {
495495
"declare i32 @__cxa_guard_acquire(%struct*)\n"
496496
"declare void @__cxa_guard_release(%struct*)\n"
497497

498+
"declare i32 @atexit(void ()*)\n"
499+
498500
"declare i32 @__nvvm_reflect(i8*)\n"
499501

500502
"declare i8* @__memcpy_chk(i8*, i8*, i64, i64)\n"

0 commit comments

Comments
 (0)