diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt index ed2e248d7652c..6e8cb2c1f1112 100644 --- a/libc/config/linux/x86_64/entrypoints.txt +++ b/libc/config/linux/x86_64/entrypoints.txt @@ -237,6 +237,7 @@ if(LLVM_LIBC_FULL_BUILD) libc.src.pthread.pthread_attr_setstacksize libc.src.pthread.pthread_attr_init libc.src.pthread.pthread_create + libc.src.pthread.pthread_detach libc.src.pthread.pthread_join libc.src.pthread.pthread_mutex_destroy libc.src.pthread.pthread_mutex_init @@ -305,6 +306,7 @@ if(LLVM_LIBC_FULL_BUILD) libc.src.threads.mtx_lock libc.src.threads.mtx_unlock libc.src.threads.thrd_create + libc.src.threads.thrd_detach libc.src.threads.thrd_join # time.h entrypoints diff --git a/libc/spec/posix.td b/libc/spec/posix.td index e53942dd850c4..915a177913402 100644 --- a/libc/spec/posix.td +++ b/libc/spec/posix.td @@ -461,6 +461,11 @@ def POSIX : StandardSpec<"POSIX"> { RetValSpec, [ArgSpec, ArgSpec] >, + FunctionSpec< + "pthread_detach", + RetValSpec, + [ArgSpec] + >, FunctionSpec< "pthread_mutexattr_init", RetValSpec, diff --git a/libc/spec/stdc.td b/libc/spec/stdc.td index e9b9a2d3b42fd..90218443d0991 100644 --- a/libc/spec/stdc.td +++ b/libc/spec/stdc.td @@ -801,6 +801,11 @@ def StdC : StandardSpec<"stdc"> { ArgSpec, ArgSpec, ] + >, + FunctionSpec< + "thrd_detach", + RetValSpec, + [ArgSpec] > ] >; diff --git a/libc/src/__support/threads/CMakeLists.txt b/libc/src/__support/threads/CMakeLists.txt index 26c2387faad38..5dce96cfc2ab2 100644 --- a/libc/src/__support/threads/CMakeLists.txt +++ b/libc/src/__support/threads/CMakeLists.txt @@ -8,6 +8,9 @@ add_header_library( thread_attrib HDRS thread_attrib.h + DEPENDS + libc.src.__support.common + libc.src.__support.CPP.atomic ) if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${LIBC_TARGET_OS}) diff --git a/libc/src/__support/threads/linux/thread.h b/libc/src/__support/threads/linux/thread.h index a354f2d5528c5..b389b8a4c9816 100644 --- a/libc/src/__support/threads/linux/thread.h +++ b/libc/src/__support/threads/linux/thread.h @@ -143,7 +143,8 @@ template struct Thread { attrib = reinterpret_cast *>( adjusted_stack + sizeof(StartArgs)); - attrib->detached = detached; + attrib->detach_state = + uint32_t(detached ? DetachState::DETACHED : DetachState::JOINABLE); attrib->stack = stack; attrib->stack_size = size; attrib->owned_stack = owned_stack; @@ -189,6 +190,48 @@ template struct Thread { } int join(ReturnType *retval) { + wait(); + + *retval = attrib->retval; + if (attrib->owned_stack) + free_stack(attrib->stack, attrib->stack_size); + + return 0; + } + + // Detach a joinable thread. + // + // This method does not have error return value. However, the type of detach + // is returned to help with testing. + int detach() { + uint32_t joinable_state = uint32_t(DetachState::JOINABLE); + if (attrib->detach_state.compare_exchange_strong( + joinable_state, uint32_t(DetachState::DETACHED))) { + return int(DetachType::SIMPLE); + } + + // If the thread was already detached, then the detach method should not + // be called at all. If the thread is exiting, then we wait for it to exit + // and free up resources. + wait(); + + if (attrib->owned_stack) + free_stack(attrib->stack, attrib->stack_size); + return int(DetachType::CLEANUP); + } + + // Wait for the thread to finish. This method can only be called + // if: + // 1. A detached thread is guaranteed to be running. + // 2. A joinable thread has not been detached or joined. As long as it has + // not been detached or joined, wait can be called multiple times. + // + // Also, only one thread can wait and expect to get woken up when the thread + // finishes. + // + // NOTE: This function is to be used for testing only. There is no standard + // which requires exposing it via a public API. + void wait() { // The kernel should set the value at the clear tid address to zero. // If not, it is a spurious wake and we should continue to wait on // the futex. @@ -198,12 +241,6 @@ template struct Thread { __llvm_libc::syscall(SYS_futex, &clear_tid->val, FUTEX_WAIT, CLEAR_TID_VALUE, nullptr); } - - *retval = attrib->retval; - if (!attrib->detached) - free_stack(attrib->stack, attrib->stack_size); - - return 0; } }; @@ -212,12 +249,18 @@ __attribute__((noinline)) void Thread::start_thread() { auto *start_args = reinterpret_cast *>(get_start_args_addr()); auto *thread = start_args->thread; - thread->attrib->retval = start_args->func(start_args->arg); + ReturnType retval = thread->attrib->retval = + start_args->func(start_args->arg); - if (thread->attrib->detached && thread->attrib->owned_stack) - free_stack(thread->attrib->stack, thread->attrib->stack_size); + uint32_t joinable_state = uint32_t(DetachState::JOINABLE); + if (!thread->attrib->detach_state.compare_exchange_strong( + joinable_state, uint32_t(DetachState::EXITING))) { + // Thread is detached so cleanup the resources. + if (thread->attrib->owned_stack) + free_stack(thread->attrib->stack, thread->attrib->stack_size); + } - __llvm_libc::syscall(SYS_exit, thread->attrib->retval); + __llvm_libc::syscall(SYS_exit, retval); } } // namespace __llvm_libc diff --git a/libc/src/__support/threads/thread_attrib.h b/libc/src/__support/threads/thread_attrib.h index c88652163d4da..31d97acd3fb90 100644 --- a/libc/src/__support/threads/thread_attrib.h +++ b/libc/src/__support/threads/thread_attrib.h @@ -9,8 +9,11 @@ #ifndef LLVM_LIBC_SRC_SUPPORT_THREADS_THREAD_ATTRIB_H #define LLVM_LIBC_SRC_SUPPORT_THREADS_THREAD_ATTRIB_H +#include "src/__support/CPP/atomic.h" #include "src/__support/architectures.h" +#include + namespace __llvm_libc { #if (defined(LLVM_LIBC_ARCH_AARCH64) || defined(LLVM_LIBC_ARCH_X86_64)) @@ -18,6 +21,22 @@ constexpr unsigned int STACK_ALIGNMENT = 16; #endif // TODO: Provide stack alignment requirements for other architectures. +enum class DetachState : uint32_t { + JOINABLE = 0x11, + EXITING = 0x22, + DETACHED = 0x33 +}; + +// Detach type is useful in testing the detach operation. +enum class DetachType : int { + // Indicates that the detach operation just set the detach state to DETACHED + // and returned. + SIMPLE = 1, + + // Indicates that the detach operation performed thread cleanup. + CLEANUP = 2 +}; + // 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 @@ -27,7 +46,25 @@ constexpr unsigned int STACK_ALIGNMENT = 16; // for the target architecture. template struct alignas(STACK_ALIGNMENT) ThreadAttributes { - bool detached; + // We want the "detach_state" attribute to be an atomic value as it could be + // updated by one thread while the self thread is reading it. It is a tristate + // variable with the following state transitions: + // 1. The a thread is created in a detached state, then user code should never + // call a detach or join function. Calling either of them can lead to + // undefined behavior. + // The value of |detach_state| is expected to be DetachState::DETACHED for + // its lifetime. + // 2. If a thread is created in a joinable state, |detach_state| will start + // with the value DetachState::JOINABLE. Another thread can detach this + // thread before it exits. The state transitions will as follows: + // (a) If the detach method sees the state as JOINABLE, then it will + // compare exchange to a state of DETACHED. The thread will clean + // itself up after it finishes. + // (b) If the detach method does not see JOINABLE in (a), then it will + // conclude that the thread is EXITING and will wait until the thread + // exits. It will clean up the thread resources once the thread + // exits. + cpp::Atomic detach_state; void *stack; // Pointer to the thread stack unsigned long long stack_size; // Size of the stack unsigned char owned_stack; // Indicates if the thread owns this stack memory diff --git a/libc/src/pthread/CMakeLists.txt b/libc/src/pthread/CMakeLists.txt index d44eef057d320..d786a35155492 100644 --- a/libc/src/pthread/CMakeLists.txt +++ b/libc/src/pthread/CMakeLists.txt @@ -268,3 +268,14 @@ add_entrypoint_object( libc.include.pthread libc.src.__support.threads.thread ) + +add_entrypoint_object( + pthread_detach + SRCS + pthread_detach.cpp + HDRS + pthread_detach.h + DEPENDS + libc.include.pthread + libc.src.__support.threads.thread +) diff --git a/libc/src/pthread/pthread_detach.cpp b/libc/src/pthread/pthread_detach.cpp new file mode 100644 index 0000000000000..c71b12fbdcda5 --- /dev/null +++ b/libc/src/pthread/pthread_detach.cpp @@ -0,0 +1,27 @@ +//===-- Implementation of the pthread_detach 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_detach.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(int, pthread_detach, (pthread_t th)) { + auto *thread = reinterpret_cast *>(&th); + thread->detach(); + return 0; +} + +} // namespace __llvm_libc diff --git a/libc/src/pthread/pthread_detach.h b/libc/src/pthread/pthread_detach.h new file mode 100644 index 0000000000000..2122ed6881a09 --- /dev/null +++ b/libc/src/pthread/pthread_detach.h @@ -0,0 +1,20 @@ +//===-- Implementation header for pthread_detach 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. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_LIBC_SRC_THREADS_PTHREAD_DETACH_H +#define LLVM_LIBC_SRC_THREADS_PTHREAD_DETACH_H + +#include + +namespace __llvm_libc { + +int pthread_detach(pthread_t thread); + +} // namespace __llvm_libc + +#endif // LLVM_LIBC_SRC_THREADS_PTHREAD_DETACH_H diff --git a/libc/src/threads/CMakeLists.txt b/libc/src/threads/CMakeLists.txt index bd63a53061f36..bdbb32d81e1c1 100644 --- a/libc/src/threads/CMakeLists.txt +++ b/libc/src/threads/CMakeLists.txt @@ -35,6 +35,17 @@ add_entrypoint_object( libc.src.__support.threads.thread ) +add_entrypoint_object( + thrd_detach + SRCS + thrd_detach.cpp + HDRS + thrd_detach.h + DEPENDS + libc.include.threads + libc.src.__support.threads.thread +) + add_entrypoint_object( mtx_init SRCS diff --git a/libc/src/threads/thrd_detach.cpp b/libc/src/threads/thrd_detach.cpp new file mode 100644 index 0000000000000..e3c4301709cdf --- /dev/null +++ b/libc/src/threads/thrd_detach.cpp @@ -0,0 +1,26 @@ +//===-- Linux implementation of the thrd_detach 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_detach.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(int, thrd_detach, (thrd_t th)) { + auto *thread = reinterpret_cast *>(&th); + thread->detach(); + return 0; +} + +} // namespace __llvm_libc diff --git a/libc/src/threads/thrd_detach.h b/libc/src/threads/thrd_detach.h new file mode 100644 index 0000000000000..176770d59986e --- /dev/null +++ b/libc/src/threads/thrd_detach.h @@ -0,0 +1,20 @@ +//===-- Implementation header for thrd_detach 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. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_LIBC_SRC_THREADS_THRD_DETACH_H +#define LLVM_LIBC_SRC_THREADS_THRD_DETACH_H + +#include "include/threads.h" + +namespace __llvm_libc { + +int thrd_detach(thrd_t thread); + +} // namespace __llvm_libc + +#endif // LLVM_LIBC_SRC_THREADS_THRD_DETACH_H diff --git a/libc/test/src/__support/CMakeLists.txt b/libc/test/src/__support/CMakeLists.txt index 64c014c6c4d58..6f0750cd5d404 100644 --- a/libc/test/src/__support/CMakeLists.txt +++ b/libc/test/src/__support/CMakeLists.txt @@ -77,3 +77,4 @@ add_custom_command(TARGET libc_str_to_float_comparison_test add_subdirectory(CPP) add_subdirectory(File) add_subdirectory(OSUtil) +add_subdirectory(threads) diff --git a/libc/test/src/__support/threads/CMakeLists.txt b/libc/test/src/__support/threads/CMakeLists.txt new file mode 100644 index 0000000000000..c713e8677378b --- /dev/null +++ b/libc/test/src/__support/threads/CMakeLists.txt @@ -0,0 +1,18 @@ +if(NOT (TARGET libc.src.__support.threads.thread AND + TARGET libc.src.__support.threads.mutex)) + return() +endif() + +add_libc_unittest( + thread_detach_test + SUITE + SRCS + thread_detach_test.cpp + DEPENDS + libc.src.__support.threads.mutex + libc.src.__support.threads.thread + libc.src.__support.threads.thread_attrib + COMPILE_OPTIONS + -O3 + -fno-omit-frame-pointer +) diff --git a/libc/test/src/__support/threads/thread_detach_test.cpp b/libc/test/src/__support/threads/thread_detach_test.cpp new file mode 100644 index 0000000000000..060ee08533796 --- /dev/null +++ b/libc/test/src/__support/threads/thread_detach_test.cpp @@ -0,0 +1,52 @@ +//===-- Unittests for thrd_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 +// +//===----------------------------------------------------------------------===// + +#include "src/__support/threads/mutex.h" +#include "src/__support/threads/thread.h" +#include "src/__support/threads/thread_attrib.h" +#include "utils/UnitTest/Test.h" + +__llvm_libc::Mutex mutex(false, false, false); + +int func(void *) { + mutex.lock(); + mutex.unlock(); + return 0; +} + +TEST(LlvmLibcTestThreadTest, DetachSimpleTest) { + mutex.lock(); + __llvm_libc::Thread th; + th.run(func, nullptr, nullptr, 0); + + // Since |mutex| is held by the current thread, we guarantee that + // th is running and hence it is safe to detach. Since the thread is + // still running, it should be simple detach. + ASSERT_EQ(th.detach(), int(__llvm_libc::DetachType::SIMPLE)); + + // We will release |mutex| now to let the thread finish an cleanup itself. + mutex.unlock(); +} + +TEST(LlvmLibcTestThreadTest, DetachCleanupTest) { + mutex.lock(); + __llvm_libc::Thread th; + ASSERT_EQ(0, th.run(func, nullptr, nullptr, 0)); + + // Since |mutex| is held by the current thread, we will release it + // to let |th| run. + mutex.unlock(); + + // We will wait for |th| to finish. Since it is a joinable thread, + // we can wait on it safely. + th.wait(); + + // Since |th| is now finished, detaching should cleanup the thread + // resources. + ASSERT_EQ(th.detach(), int(__llvm_libc::DetachType::CLEANUP)); +}