[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:
parent
f374c8ddf2
commit
d66983861a
|
@ -159,7 +159,8 @@ def StdlibAPI : PublicAPI<"stdlib.h"> {
|
|||
"lldiv_t",
|
||||
"size_t",
|
||||
"__bsearchcompare_t",
|
||||
"__qsortcompare_t"
|
||||
"__qsortcompare_t",
|
||||
"__atexithandler_t",
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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__
|
|
@ -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">;
|
||||
|
||||
|
|
|
@ -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>]>,
|
||||
]
|
||||
>;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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()
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
Loading…
Reference in New Issue