[libc] Add exit and atexit

Often atexit is implemented using __cxa_atexit. I have not implemented __cxa_atexit here because it potentially requires more discussion. It is unique for llvm-libc (I think) that it is an exported symbol that wouldn’t be defined in any spec file because it doesn’t have a header. Implementing it will be trivial given what is here already, but I figured it would be more contentious so it can be implemented later.

Reviewed By: lntue

Differential Revision: https://reviews.llvm.org/D119512
This commit is contained in:
Alex Brachet 2022-02-17 17:21:55 +00:00
parent f374c8ddf2
commit d66983861a
15 changed files with 307 additions and 30 deletions

View File

@ -159,7 +159,8 @@ def StdlibAPI : PublicAPI<"stdlib.h"> {
"lldiv_t",
"size_t",
"__bsearchcompare_t",
"__qsortcompare_t"
"__qsortcompare_t",
"__atexithandler_t",
];
}

View File

@ -226,6 +226,8 @@ if(LLVM_LIBC_FULL_BUILD)
# stdlib.h entrypoints
libc.src.stdlib._Exit
# libc.src.stdlib.abort
libc.src.stdlib.atexit
libc.src.stdlib.exit
# signal.h entrypoints
# TODO: Enable signal.h entrypoints after fixing signal.h

View File

@ -141,6 +141,7 @@ add_gen_header(
.llvm-libc-types.ldiv_t
.llvm-libc-types.lldiv_t
.llvm-libc-types.size_t
.llvm-libc-types.__atexithandler_t
)
add_gen_header(

View File

@ -23,3 +23,4 @@ add_header(struct_tm HDR struct_tm.h)
add_header(thrd_start_t HDR thrd_start_t.h)
add_header(thrd_t HDR thrd_t.h)
add_header(time_t HDR time_t.h)
add_header(__atexithandler_t HDR __atexithandler_t.h)

View File

@ -0,0 +1,14 @@
//===-- Definition of type __atexithandler_t ------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#ifndef __LLVM_LIBC_TYPES_ATEXITHANDLER_T_H__
#define __LLVM_LIBC_TYPES_ATEXITHANDLER_T_H__
typedef void (*__atexithandler_t)(void);
#endif // __LLVM_LIBC_TYPES_ATEXITHANDLER_T_H__

View File

@ -94,6 +94,8 @@ def TimeTType : NamedType<"time_t">;
def BSearchCompareT : NamedType<"__bsearchcompare_t">;
def QSortCompareT : NamedType<"__qsortcompare_t">;
def AtexitHandlerT : NamedType<"__atexithandler_t">;
//added because __assert_fail needs it.
def UnsignedType : NamedType<"unsigned">;

View File

@ -502,6 +502,7 @@ def StdC : StandardSpec<"stdc"> {
SizeTType,
BSearchCompareT,
QSortCompareT,
AtexitHandlerT,
], // Types
[], // Enumerations
[
@ -538,6 +539,8 @@ def StdC : StandardSpec<"stdc"> {
FunctionSpec<"free", RetValSpec<VoidType>, [ArgSpec<VoidPtr>]>,
FunctionSpec<"_Exit", RetValSpec<NoReturn>, [ArgSpec<IntType>]>,
FunctionSpec<"exit", RetValSpec<NoReturn>, [ArgSpec<IntType>]>,
FunctionSpec<"atexit", RetValSpec<IntType>, [ArgSpec<AtexitHandlerT>]>,
]
>;

View File

@ -270,6 +270,30 @@ add_entrypoint_object(
.${LIBC_TARGET_OS}._Exit
)
add_entrypoint_object(
atexit
SRCS
atexit.cpp
HDRS
atexit.h
DEPENDS
libc.src.__support.CPP.vector
libc.src.threads.mtx_init
libc.src.threads.mtx_lock
libc.src.threads.mtx_unlock
)
add_entrypoint_object(
exit
SRCS
exit.cpp
HDRS
exit.h
DEPENDS
._Exit
.atexit
)
# add_entrypoint_object(
# abort
# ALIAS

View File

@ -0,0 +1,54 @@
//===-- Implementation of atexit ------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "src/stdlib/atexit.h"
#include "src/__support/CPP/vector.h"
#include "src/__support/common.h"
#include "src/threads/mtx_init.h"
#include "src/threads/mtx_lock.h"
#include "src/threads/mtx_unlock.h"
namespace __llvm_libc {
namespace {
mtx_t lock;
// TODO need an easier way to use mtx_t internally, or use pthread_mutex_t
// with PTHREAD_MUTEX_INITIALIZER when it lands.
struct Init {
Init() { __llvm_libc::mtx_init(&lock, mtx_plain); }
} init;
// TOOD should we make cpp::vector like llvm::SmallVector<T, N> where it will
// allocate at least N before needing dynamic allocation?
static cpp::vector<void (*)(void)> handlers;
} // namespace
namespace internal {
void call_exit_handlers() {
__llvm_libc::mtx_lock(&lock);
// TODO: implement rbegin() + rend() for cpp::vector
for (int i = handlers.size() - 1; i >= 0; i--) {
__llvm_libc::mtx_unlock(&lock);
handlers[i]();
__llvm_libc::mtx_lock(&lock);
}
}
} // namespace internal
LLVM_LIBC_FUNCTION(int, atexit, (void (*function)())) {
__llvm_libc::mtx_lock(&lock);
handlers.push_back(function);
__llvm_libc::mtx_unlock(&lock);
return 0;
}
} // namespace __llvm_libc

18
libc/src/stdlib/atexit.h Normal file
View File

@ -0,0 +1,18 @@
//===-- Implementation header for atexit ------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_LIBC_SRC_STDLIB_ATEXIT_H
#define LLVM_LIBC_SRC_STDLIB_ATEXIT_H
namespace __llvm_libc {
int atexit(void (*function)());
} // namespace __llvm_libc
#endif // LLVM_LIBC_SRC_STDLIB_ATEXIT_H

24
libc/src/stdlib/exit.cpp Normal file
View File

@ -0,0 +1,24 @@
//===-- Implementation of exit --------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "src/stdlib/exit.h"
#include "src/__support/common.h"
#include "src/stdlib/_Exit.h"
namespace __llvm_libc {
namespace internal {
void call_exit_handlers();
}
LLVM_LIBC_FUNCTION(void, exit, (int status)) {
internal::call_exit_handlers();
_Exit(status);
}
} // namespace __llvm_libc

20
libc/src/stdlib/exit.h Normal file
View File

@ -0,0 +1,20 @@
//===-- Implementation header for exit --------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include <stdlib.h>
#ifndef LLVM_LIBC_SRC_STDLIB_EXIT_H
#define LLVM_LIBC_SRC_STDLIB_EXIT_H
namespace __llvm_libc {
void exit(int status);
} // namespace __llvm_libc
#endif // LLVM_LIBC_SRC_STDLIB_EXIT_H

View File

@ -110,35 +110,6 @@ add_libc_unittest(
libc.src.stdlib.strtoull
)
if(NOT LLVM_LIBC_FULL_BUILD)
return()
endif()
add_libc_unittest(
_Exit_test
SUITE
libc_stdlib_unittests
SRCS
_Exit_test.cpp
DEPENDS
libc.include.stdlib
libc.src.stdlib._Exit
)
# add_libc_unittest(
# abort_test
# SUITE
# libc_stdlib_unittests
# SRCS
# abort_test.cpp
# DEPENDS
# libc.include.stdlib
# libc.include.signal
# libc.src.stdlib.abort
# libc.src.stdlib._Exit
# libc.src.signal.raise
# )
add_libc_unittest(
abs_test
SUITE
@ -229,3 +200,47 @@ add_libc_unittest(
libc.include.stdlib
libc.src.stdlib.qsort
)
if(LLVM_LIBC_FULL_BUILD)
add_libc_unittest(
_Exit_test
SUITE
libc_stdlib_unittests
SRCS
_Exit_test.cpp
DEPENDS
libc.include.stdlib
libc.src.stdlib._Exit
libc.src.stdlib.exit
)
add_libc_unittest(
atexit_test
SUITE
libc_stdlib_unittests
SRCS
atexit_test.cpp
DEPENDS
libc.include.stdlib
libc.src.stdlib._Exit
libc.src.stdlib.exit
libc.src.stdlib.atexit
libc.src.__support.CPP.standalone_cpp
)
# add_libc_unittest(
# abort_test
# SUITE
# libc_stdlib_unittests
# SRCS
# abort_test.cpp
# DEPENDS
# libc.include.stdlib
# libc.include.signal
# libc.src.stdlib.abort
# libc.src.stdlib._Exit
# libc.src.signal.raise
# )
endif()

View File

@ -8,9 +8,13 @@
#include "include/stdlib.h"
#include "src/stdlib/_Exit.h"
#include "src/stdlib/exit.h"
#include "utils/UnitTest/Test.h"
TEST(LlvmLibcStdlib, _Exit) {
EXPECT_EXITS([] { __llvm_libc::_Exit(1); }, 1);
EXPECT_EXITS([] { __llvm_libc::_Exit(65); }, 65);
EXPECT_EXITS([] { __llvm_libc::exit(1); }, 1);
EXPECT_EXITS([] { __llvm_libc::exit(65); }, 65);
}

View File

@ -0,0 +1,94 @@
//===-- Unittests for atexit ----------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "src/__support/CPP/Array.h"
#include "src/__support/CPP/Utility.h"
#include "src/stdlib/atexit.h"
#include "src/stdlib/exit.h"
#include "utils/UnitTest/Test.h"
static int a;
TEST(LlvmLibcAtExit, Basic) {
// In case tests ever run multiple times.
a = 0;
auto test = [] {
int status = __llvm_libc::atexit(+[] {
if (a != 1)
__builtin_trap();
});
status |= __llvm_libc::atexit(+[] { a++; });
if (status)
__builtin_trap();
__llvm_libc::exit(0);
};
EXPECT_EXITS(test, 0);
}
TEST(LlvmLibcAtExit, AtExitCallsSysExit) {
auto test = [] {
__llvm_libc::atexit(+[] { _Exit(1); });
__llvm_libc::exit(0);
};
EXPECT_EXITS(test, 1);
}
static int size;
static __llvm_libc::cpp::Array<int, 256> arr;
template <int... Ts>
void register_atexit_handlers(__llvm_libc::cpp::IntegerSequence<int, Ts...>) {
(__llvm_libc::atexit(+[] { arr[size++] = Ts; }), ...);
}
template <int count> constexpr auto getTest() {
return [] {
__llvm_libc::atexit(+[] {
if (size != count)
__builtin_trap();
for (int i = 0; i < count; i++)
if (arr[i] != count - 1 - i)
__builtin_trap();
});
register_atexit_handlers(
__llvm_libc::cpp::MakeIntegerSequence<int, count>{});
__llvm_libc::exit(0);
};
}
TEST(LlvmLibcAtExit, ReverseOrder) {
// In case tests ever run multiple times.
size = 0;
auto test = getTest<32>();
EXPECT_EXITS(test, 0);
}
TEST(LlvmLibcAtExit, Many) {
// In case tests ever run multiple times.
size = 0;
auto test = getTest<256>();
EXPECT_EXITS(test, 0);
}
// POSIX doesn't specify if an atexit handler can call atexit, it only says it
// is undefined for a handler to call exit(3). The current implementation will
// end up invoking the newly registered function, although glibc does, other
// libc's do not. This just tests that we don't deadlock when an exit handler
// calls atexit.
TEST(LlvmLibcAtExit, HandlerCallsAtExit) {
auto test = [] {
__llvm_libc::atexit(+[] {
__llvm_libc::atexit(+[] { __builtin_trap(); });
__llvm_libc::exit(0);
});
};
EXPECT_EXITS(test, 0);
}