diff --git a/libc/config/linux/api.td b/libc/config/linux/api.td --- a/libc/config/linux/api.td +++ b/libc/config/linux/api.td @@ -159,7 +159,8 @@ "lldiv_t", "size_t", "__bsearchcompare_t", - "__qsortcompare_t" + "__qsortcompare_t", + "__atexithandler_t", ]; } diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt --- a/libc/config/linux/x86_64/entrypoints.txt +++ b/libc/config/linux/x86_64/entrypoints.txt @@ -226,6 +226,8 @@ # 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 diff --git a/libc/include/CMakeLists.txt b/libc/include/CMakeLists.txt --- a/libc/include/CMakeLists.txt +++ b/libc/include/CMakeLists.txt @@ -141,6 +141,7 @@ .llvm-libc-types.ldiv_t .llvm-libc-types.lldiv_t .llvm-libc-types.size_t + .llvm-libc-types.__atexithandler_t ) add_gen_header( diff --git a/libc/include/llvm-libc-types/CMakeLists.txt b/libc/include/llvm-libc-types/CMakeLists.txt --- a/libc/include/llvm-libc-types/CMakeLists.txt +++ b/libc/include/llvm-libc-types/CMakeLists.txt @@ -23,3 +23,4 @@ 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) diff --git a/libc/test/src/stdlib/_Exit_test.cpp b/libc/include/llvm-libc-types/__atexithandler_t.h copy from libc/test/src/stdlib/_Exit_test.cpp copy to libc/include/llvm-libc-types/__atexithandler_t.h --- a/libc/test/src/stdlib/_Exit_test.cpp +++ b/libc/include/llvm-libc-types/__atexithandler_t.h @@ -1,4 +1,4 @@ -//===-- Unittests for _Exit -----------------------------------------------===// +//===-- 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. @@ -6,11 +6,9 @@ // //===----------------------------------------------------------------------===// -#include "include/stdlib.h" -#include "src/stdlib/_Exit.h" -#include "utils/UnitTest/Test.h" +#ifndef __LLVM_LIBC_TYPES_ATEXITHANDLER_T_H__ +#define __LLVM_LIBC_TYPES_ATEXITHANDLER_T_H__ -TEST(LlvmLibcStdlib, _Exit) { - EXPECT_EXITS([] { __llvm_libc::_Exit(1); }, 1); - EXPECT_EXITS([] { __llvm_libc::_Exit(65); }, 65); -} +typedef void (*__atexithandler_t)(void); + +#endif // __LLVM_LIBC_TYPES_ATEXITHANDLER_T_H__ diff --git a/libc/spec/spec.td b/libc/spec/spec.td --- a/libc/spec/spec.td +++ b/libc/spec/spec.td @@ -94,6 +94,8 @@ 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">; diff --git a/libc/spec/stdc.td b/libc/spec/stdc.td --- a/libc/spec/stdc.td +++ b/libc/spec/stdc.td @@ -502,6 +502,7 @@ SizeTType, BSearchCompareT, QSortCompareT, + AtexitHandlerT, ], // Types [], // Enumerations [ @@ -538,6 +539,8 @@ FunctionSpec<"free", RetValSpec, [ArgSpec]>, FunctionSpec<"_Exit", RetValSpec, [ArgSpec]>, + FunctionSpec<"exit", RetValSpec, [ArgSpec]>, + FunctionSpec<"atexit", RetValSpec, [ArgSpec]>, ] >; diff --git a/libc/src/stdlib/CMakeLists.txt b/libc/src/stdlib/CMakeLists.txt --- a/libc/src/stdlib/CMakeLists.txt +++ b/libc/src/stdlib/CMakeLists.txt @@ -270,6 +270,30 @@ .${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 diff --git a/libc/src/stdlib/atexit.h b/libc/src/stdlib/atexit.h new file mode 100644 --- /dev/null +++ b/libc/src/stdlib/atexit.h @@ -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 diff --git a/libc/src/stdlib/atexit.cpp b/libc/src/stdlib/atexit.cpp new file mode 100644 --- /dev/null +++ b/libc/src/stdlib/atexit.cpp @@ -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 where it will +// allocate at least N before needing dynamic allocation? +static cpp::vector 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 diff --git a/libc/src/stdlib/exit.h b/libc/src/stdlib/exit.h new file mode 100644 --- /dev/null +++ b/libc/src/stdlib/exit.h @@ -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 + +#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 diff --git a/libc/test/src/stdlib/_Exit_test.cpp b/libc/src/stdlib/exit.cpp copy from libc/test/src/stdlib/_Exit_test.cpp copy to libc/src/stdlib/exit.cpp --- a/libc/test/src/stdlib/_Exit_test.cpp +++ b/libc/src/stdlib/exit.cpp @@ -1,4 +1,4 @@ -//===-- Unittests for _Exit -----------------------------------------------===// +//===-- 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. @@ -6,11 +6,19 @@ // //===----------------------------------------------------------------------===// -#include "include/stdlib.h" +#include "src/stdlib/exit.h" +#include "src/__support/common.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); +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 diff --git a/libc/test/src/stdlib/CMakeLists.txt b/libc/test/src/stdlib/CMakeLists.txt --- a/libc/test/src/stdlib/CMakeLists.txt +++ b/libc/test/src/stdlib/CMakeLists.txt @@ -110,35 +110,6 @@ 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 @@ 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() diff --git a/libc/test/src/stdlib/_Exit_test.cpp b/libc/test/src/stdlib/_Exit_test.cpp --- a/libc/test/src/stdlib/_Exit_test.cpp +++ b/libc/test/src/stdlib/_Exit_test.cpp @@ -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); } diff --git a/libc/test/src/stdlib/atexit_test.cpp b/libc/test/src/stdlib/atexit_test.cpp new file mode 100644 --- /dev/null +++ b/libc/test/src/stdlib/atexit_test.cpp @@ -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 arr; + +template +void register_atexit_handlers(__llvm_libc::cpp::IntegerSequence) { + (__llvm_libc::atexit(+[] { arr[size++] = Ts; }), ...); +} + +template 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{}); + __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); +}