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 @@ -251,6 +251,7 @@ libc.src.pthread.pthread_create libc.src.pthread.pthread_detach libc.src.pthread.pthread_equal + libc.src.pthread.pthread_exit libc.src.pthread.pthread_getname_np libc.src.pthread.pthread_join libc.src.pthread.pthread_self @@ -325,6 +326,7 @@ libc.src.threads.thrd_current libc.src.threads.thrd_detach libc.src.threads.thrd_equal + libc.src.threads.thrd_exit libc.src.threads.thrd_join # time.h entrypoints diff --git a/libc/loader/linux/x86_64/start.cpp b/libc/loader/linux/x86_64/start.cpp --- a/libc/loader/linux/x86_64/start.cpp +++ b/libc/loader/linux/x86_64/start.cpp @@ -205,6 +205,8 @@ __llvm_libc::syscall(SYS_exit, 1); __llvm_libc::self.attrib = &__llvm_libc::main_thread_attrib; + __llvm_libc::main_thread_attrib.atexit_callback_mgr = + __llvm_libc::internal::get_thread_atexit_callback_mgr(); // We want the fini array callbacks to be run after other atexit // callbacks are run. So, we register them before running the init diff --git a/libc/spec/posix.td b/libc/spec/posix.td --- a/libc/spec/posix.td +++ b/libc/spec/posix.td @@ -474,6 +474,11 @@ RetValSpec, [ArgSpec] >, + FunctionSpec< + "pthread_exit", + RetValSpec, + [ArgSpec] + >, FunctionSpec< "pthread_self", RetValSpec, diff --git a/libc/spec/stdc.td b/libc/spec/stdc.td --- a/libc/spec/stdc.td +++ b/libc/spec/stdc.td @@ -824,7 +824,12 @@ "thrd_equal", RetValSpec, [ArgSpec, ArgSpec] - > + >, + FunctionSpec< + "thrd_exit", + RetValSpec, + [ArgSpec] + >, ] >; diff --git a/libc/src/__support/threads/CMakeLists.txt b/libc/src/__support/threads/CMakeLists.txt --- a/libc/src/__support/threads/CMakeLists.txt +++ b/libc/src/__support/threads/CMakeLists.txt @@ -35,6 +35,8 @@ SRCS thread.cpp DEPENDS + .mutex .${LIBC_TARGET_OS}.thread + libc.src.__support.fixedvector ) endif() diff --git a/libc/src/__support/threads/linux/thread.cpp b/libc/src/__support/threads/linux/thread.cpp --- a/libc/src/__support/threads/linux/thread.cpp +++ b/libc/src/__support/threads/linux/thread.cpp @@ -118,30 +118,19 @@ auto *start_args = reinterpret_cast(get_start_args_addr()); auto *attrib = start_args->thread_attrib; self.attrib = attrib; + self.attrib->atexit_callback_mgr = internal::get_thread_atexit_callback_mgr(); - long retval; if (attrib->style == ThreadStyle::POSIX) { attrib->retval.posix_retval = start_args->runner.posix_runner(start_args->arg); - retval = long(attrib->retval.posix_retval); + thread_exit(ThreadReturnValue(attrib->retval.posix_retval), + ThreadStyle::POSIX); } else { attrib->retval.stdc_retval = start_args->runner.stdc_runner(start_args->arg); - retval = long(attrib->retval.stdc_retval); + thread_exit(ThreadReturnValue(attrib->retval.stdc_retval), + ThreadStyle::STDC); } - - uint32_t joinable_state = uint32_t(DetachState::JOINABLE); - if (!attrib->detach_state.compare_exchange_strong( - joinable_state, uint32_t(DetachState::EXITING))) { - // Thread is detached so cleanup the resources. - cleanup_thread_resources(attrib); - - // Set the CLEAR_TID address to nullptr to prevent the kernel - // from signalling at a non-existent futex location. - __llvm_libc::syscall(SYS_set_tid_address, 0); - } - - __llvm_libc::syscall(SYS_exit, retval); } int Thread::run(ThreadStyle style, ThreadRunner runner, void *arg, void *stack, @@ -375,4 +364,34 @@ return 0; } +void thread_exit(ThreadReturnValue retval, ThreadStyle style) { + auto attrib = self.attrib; + + // The very first thing we do is to call the thread's atexit callbacks. + // These callbacks could be the ones registered by the language runtimes, + // for example, the destructors of thread local objects. They can also + // be destructors of the TSS objects set using API like pthread_setspecific. + // NOTE: We cannot call the atexit callbacks as part of the + // cleanup_thread_resources function as that function can be called from a + // different thread. The destructors of thread local and TSS objects should + // be called by the thread which owns them. + internal::call_atexit_callbacks(attrib); + + uint32_t joinable_state = uint32_t(DetachState::JOINABLE); + if (!attrib->detach_state.compare_exchange_strong( + joinable_state, uint32_t(DetachState::EXITING))) { + // Thread is detached so cleanup the resources. + cleanup_thread_resources(attrib); + + // Set the CLEAR_TID address to nullptr to prevent the kernel + // from signalling at a non-existent futex location. + __llvm_libc::syscall(SYS_set_tid_address, 0); + } + + if (style == ThreadStyle::POSIX) + __llvm_libc::syscall(SYS_exit, retval.posix_retval); + else + __llvm_libc::syscall(SYS_exit, retval.stdc_retval); +} + } // namespace __llvm_libc diff --git a/libc/src/__support/threads/thread.h b/libc/src/__support/threads/thread.h --- a/libc/src/__support/threads/thread.h +++ b/libc/src/__support/threads/thread.h @@ -31,6 +31,8 @@ void *posix_retval; int stdc_retval; constexpr ThreadReturnValue() : posix_retval(nullptr) {} + constexpr ThreadReturnValue(int r) : stdc_retval(r) {} + constexpr ThreadReturnValue(void *r) : posix_retval(r) {} }; #if (defined(LLVM_LIBC_ARCH_AARCH64) || defined(LLVM_LIBC_ARCH_X86_64)) @@ -56,6 +58,8 @@ CLEANUP = 2 }; +class ThreadAtExitCallbackMgr; + // A data type to hold common thread attributes which have to be stored as // thread state. Note that this is different from public attribute types like // pthread_attr_t which might contain information which need not be saved as @@ -91,12 +95,14 @@ int tid; ThreadStyle style; ThreadReturnValue retval; + ThreadAtExitCallbackMgr *atexit_callback_mgr; void *platform_data; constexpr ThreadAttributes() : detach_state(uint32_t(DetachState::DETACHED)), stack(nullptr), stack_size(0), tls(0), tls_size(0), owned_stack(false), tid(-1), - style(ThreadStyle::POSIX), retval(), platform_data(nullptr) {} + style(ThreadStyle::POSIX), retval(), atexit_callback_mgr(nullptr), + platform_data(nullptr) {} }; struct Thread { @@ -177,6 +183,25 @@ extern thread_local Thread self; +// Platforms should implement this function. +void thread_exit(ThreadReturnValue retval, ThreadStyle style); + +namespace internal { +// Internal namespace containing utilities which are to be used by platform +// implementations of threads. + +// Return the current thread's atexit callback manager. After thread startup +// but before running the thread function, platform implementations should +// set the "atexit_callback_mgr" field of the thread's attributes to the value +// returned by this function. +ThreadAtExitCallbackMgr *get_thread_atexit_callback_mgr(); + +// Call the currently registered thread specific atexit callbacks. Useful for +// implementing the thread_exit function. +void call_atexit_callbacks(ThreadAttributes *attrib); + +} // namespace internal + } // namespace __llvm_libc #endif // LLVM_LIBC_SRC_SUPPORT_THREADS_THREAD_H diff --git a/libc/src/__support/threads/thread.cpp b/libc/src/__support/threads/thread.cpp --- a/libc/src/__support/threads/thread.cpp +++ b/libc/src/__support/threads/thread.cpp @@ -7,9 +7,75 @@ //===----------------------------------------------------------------------===// #include "thread.h" +#include "mutex.h" + +#include "src/__support/fixedvector.h" namespace __llvm_libc { thread_local Thread self; +namespace { + +using AtExitCallback = void(void *); + +struct AtExitUnit { + AtExitCallback *callback = nullptr; + void *obj = nullptr; + constexpr AtExitUnit() = default; + constexpr AtExitUnit(AtExitCallback *cb, void *o) : callback(cb), obj(o) {} +}; + +} // anonymous namespace + +class ThreadAtExitCallbackMgr { + Mutex mtx; + // TODO: Use a BlockStore when compiled for production. + FixedVector callback_list; + +public: + constexpr ThreadAtExitCallbackMgr() : mtx(false, false, false) {} + + int add_callback(AtExitCallback *callback, void *obj) { + MutexLock lock(&mtx); + return callback_list.push_back({callback, obj}); + } + + void call() { + mtx.lock(); + while (!callback_list.empty()) { + auto atexit_unit = callback_list.back(); + callback_list.pop_back(); + mtx.unlock(); + atexit_unit.callback(atexit_unit.obj); + mtx.lock(); + } + } +}; + +static thread_local ThreadAtExitCallbackMgr atexit_callback_mgr; + +// The function __cxa_thread_atexit is provided by C++ runtimes like libcxxabi. +// It is used by thread local object runtime to register destructor calls. To +// actually register destructor call with the threading library, it calls +// __cxa_thread_atexit_impl, which is to be provided by the threading library. +// The semantics are very similar to the __cxa_atexit function except for the +// fact that the registered callback is thread specific. +extern "C" int __cxa_thread_atexit_impl(AtExitCallback *callback, void *obj, + void *) { + return atexit_callback_mgr.add_callback(callback, obj); +} + +namespace internal { + +ThreadAtExitCallbackMgr *get_thread_atexit_callback_mgr() { + return &atexit_callback_mgr; +} + +void call_atexit_callbacks(ThreadAttributes *attrib) { + attrib->atexit_callback_mgr->call(); +} + +} // namespace internal + } // namespace __llvm_libc diff --git a/libc/src/pthread/CMakeLists.txt b/libc/src/pthread/CMakeLists.txt --- a/libc/src/pthread/CMakeLists.txt +++ b/libc/src/pthread/CMakeLists.txt @@ -291,6 +291,17 @@ libc.src.__support.threads.thread ) +add_entrypoint_object( + pthread_exit + SRCS + pthread_exit.cpp + HDRS + pthread_exit.h + DEPENDS + libc.include.threads + libc.src.__support.threads.thread +) + add_entrypoint_object( pthread_self SRCS diff --git a/libc/src/__support/threads/thread.cpp b/libc/src/pthread/pthread_exit.h copy from libc/src/__support/threads/thread.cpp copy to libc/src/pthread/pthread_exit.h --- a/libc/src/__support/threads/thread.cpp +++ b/libc/src/pthread/pthread_exit.h @@ -1,4 +1,4 @@ -//===--- Definitions of common thread items ---------------------*- C++ -*-===// +//===-- Implementation header for pthread_exit function ---------*- C++ -*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -6,10 +6,15 @@ // //===----------------------------------------------------------------------===// -#include "thread.h" +#ifndef LLVM_LIBC_SRC_THREADS_PTHREAD_EXIT_H +#define LLVM_LIBC_SRC_THREADS_PTHREAD_EXIT_H + +#include namespace __llvm_libc { -thread_local Thread self; +void pthread_exit(void *retval); } // namespace __llvm_libc + +#endif // LLVM_LIBC_SRC_THREADS_PTHREAD_EXIT_H diff --git a/libc/src/pthread/pthread_exit.cpp b/libc/src/pthread/pthread_exit.cpp new file mode 100644 --- /dev/null +++ b/libc/src/pthread/pthread_exit.cpp @@ -0,0 +1,25 @@ +//===-- Implementation of the pthread_exit function -----------------------===// +// +// 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 "pthread_exit.h" + +#include "src/__support/common.h" +#include "src/__support/threads/thread.h" + +#include // For pthread_* type definitions. + +namespace __llvm_libc { + +static_assert(sizeof(pthread_t) == sizeof(__llvm_libc::Thread), + "Mismatch between pthread_t and internal Thread."); + +LLVM_LIBC_FUNCTION(void, pthread_exit, (void *retval)) { + thread_exit(ThreadReturnValue(retval), ThreadStyle::POSIX); +} + +} // namespace __llvm_libc diff --git a/libc/src/threads/CMakeLists.txt b/libc/src/threads/CMakeLists.txt --- a/libc/src/threads/CMakeLists.txt +++ b/libc/src/threads/CMakeLists.txt @@ -68,6 +68,17 @@ libc.src.__support.threads.thread ) +add_entrypoint_object( + thrd_exit + SRCS + thrd_exit.cpp + HDRS + thrd_exit.h + DEPENDS + libc.include.threads + libc.src.__support.threads.thread +) + add_entrypoint_object( mtx_init SRCS diff --git a/libc/src/__support/threads/thread.cpp b/libc/src/threads/thrd_exit.h copy from libc/src/__support/threads/thread.cpp copy to libc/src/threads/thrd_exit.h --- a/libc/src/__support/threads/thread.cpp +++ b/libc/src/threads/thrd_exit.h @@ -1,4 +1,4 @@ -//===--- Definitions of common thread items ---------------------*- C++ -*-===// +//===-- Implementation header for thrd_exit function ------------*- C++ -*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -6,10 +6,15 @@ // //===----------------------------------------------------------------------===// -#include "thread.h" +#ifndef LLVM_LIBC_SRC_THREADS_THRD_EXIT_H +#define LLVM_LIBC_SRC_THREADS_THRD_EXIT_H + +#include "include/threads.h" namespace __llvm_libc { -thread_local Thread self; +void thrd_exit(int retval); } // namespace __llvm_libc + +#endif // LLVM_LIBC_SRC_THREADS_THRD_EXIT_H diff --git a/libc/src/threads/thrd_exit.cpp b/libc/src/threads/thrd_exit.cpp new file mode 100644 --- /dev/null +++ b/libc/src/threads/thrd_exit.cpp @@ -0,0 +1,24 @@ +//===-- Linux implementation of the thrd_exit function --------------------===// +// +// 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/threads/thrd_exit.h" +#include "src/__support/common.h" +#include "src/__support/threads/thread.h" + +#include // For thrd_* type definitions. + +namespace __llvm_libc { + +static_assert(sizeof(thrd_t) == sizeof(__llvm_libc::Thread), + "Mismatch between thrd_t and internal Thread."); + +LLVM_LIBC_FUNCTION(void, thrd_exit, (int retval)) { + thread_exit(ThreadReturnValue(retval), ThreadStyle::STDC); +} + +} // namespace __llvm_libc diff --git a/libc/test/integration/src/pthread/CMakeLists.txt b/libc/test/integration/src/pthread/CMakeLists.txt --- a/libc/test/integration/src/pthread/CMakeLists.txt +++ b/libc/test/integration/src/pthread/CMakeLists.txt @@ -76,3 +76,18 @@ libc.src.pthread.pthread_self libc.src.pthread.pthread_setname_np ) + +add_integration_test( + pthread_exit_test + SUITE + libc-pthread-integration-tests + SRCS + pthread_exit_test.cpp + LOADER + libc.loader.linux.crt1 + DEPENDS + libc.include.pthread + libc.src.pthread.pthread_create + libc.src.pthread.pthread_exit + libc.src.pthread.pthread_join +) diff --git a/libc/test/integration/src/pthread/pthread_exit_test.cpp b/libc/test/integration/src/pthread/pthread_exit_test.cpp new file mode 100644 --- /dev/null +++ b/libc/test/integration/src/pthread/pthread_exit_test.cpp @@ -0,0 +1,65 @@ +//===-- Tests for pthread_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/pthread/pthread_create.h" +#include "src/pthread/pthread_exit.h" +#include "src/pthread/pthread_join.h" +#include "utils/IntegrationTest/test.h" + +#include + +bool dtor_called = false; + +class A { + int val; + +public: + A(int i) { val = i; } + + void set(int i) { val = i; } + + ~A() { + val = 0; + dtor_called = true; + } +}; + +thread_local A thread_local_a(123); + +void *func(void *) { + // Touch the thread local variable so that it gets initialized and a callback + // for its destructor gets registered with __cxa_thread_atexit. + thread_local_a.set(321); + __llvm_libc::pthread_exit(nullptr); + return nullptr; +} + +TEST_MAIN() { + pthread_t th; + void *retval; + + ASSERT_EQ(__llvm_libc::pthread_create(&th, nullptr, func, nullptr), 0); + ASSERT_EQ(__llvm_libc::pthread_join(th, &retval), 0); + + ASSERT_TRUE(dtor_called); + __llvm_libc::pthread_exit(nullptr); + return 0; +} + +extern "C" { + +using Destructor = void(void *); + +int __cxa_thread_atexit_impl(Destructor *, void *, void *); + +// We do not link integration tests to C++ runtime pieces like the libcxxabi. +// So, we provide our own simple __cxa_thread_atexit implementation. +int __cxa_thread_atexit(Destructor *dtor, void *obj, void *) { + return __cxa_thread_atexit_impl(dtor, obj, nullptr); +} +} diff --git a/libc/test/integration/src/threads/CMakeLists.txt b/libc/test/integration/src/threads/CMakeLists.txt --- a/libc/test/integration/src/threads/CMakeLists.txt +++ b/libc/test/integration/src/threads/CMakeLists.txt @@ -54,6 +54,21 @@ libc.src.threads.thrd_join ) +add_integration_test( + thrd_exit_test + SUITE + libc-threads-integration-tests + SRCS + thrd_exit_test.cpp + LOADER + libc.loader.linux.crt1 + DEPENDS + libc.include.threads + libc.src.threads.thrd_create + libc.src.threads.thrd_exit + libc.src.threads.thrd_join +) + add_integration_test( call_once_test SUITE diff --git a/libc/test/integration/src/threads/thrd_exit_test.cpp b/libc/test/integration/src/threads/thrd_exit_test.cpp new file mode 100644 --- /dev/null +++ b/libc/test/integration/src/threads/thrd_exit_test.cpp @@ -0,0 +1,63 @@ +//===-- Tests for thrd_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/threads/thrd_create.h" +#include "src/threads/thrd_exit.h" +#include "src/threads/thrd_join.h" +#include "utils/IntegrationTest/test.h" + +#include + +bool dtor_called = false; + +class A { + int val; + +public: + A(int i) { val = i; } + + void set(int i) { val = i; } + + ~A() { + val = 0; + dtor_called = true; + } +}; + +thread_local A thread_local_a(123); + +int func(void *) { + thread_local_a.set(321); + __llvm_libc::thrd_exit(0); + return 0; +} + +TEST_MAIN() { + thrd_t th; + int retval; + + ASSERT_EQ(__llvm_libc::thrd_create(&th, func, nullptr), thrd_success); + ASSERT_EQ(__llvm_libc::thrd_join(&th, &retval), thrd_success); + + ASSERT_TRUE(dtor_called); + __llvm_libc::thrd_exit(0); + return 0; +} + +extern "C" { + +using Destructor = void(void *); + +int __cxa_thread_atexit_impl(Destructor *, void *, void *); + +// We do not link integration tests to C++ runtime pieces like the libcxxabi. +// So, we provide our own simple __cxa_thread_atexit implementation. +int __cxa_thread_atexit(Destructor *dtor, void *obj, void *) { + return __cxa_thread_atexit_impl(dtor, obj, nullptr); +} +}