From fe8017476c7788806859a0fbac0c82be38874b47 Mon Sep 17 00:00:00 2001 From: Siva Chandra Reddy Date: Sat, 25 Jun 2022 08:28:57 +0000 Subject: [PATCH] [libc][NFC] Make the support thread library an object library. It was previously a header library. Making it an object library will allow us to declare thread local variables which can used to setup a thread's self object. --- libc/src/__support/threads/CMakeLists.txt | 10 +- libc/src/__support/threads/linux/thread.cpp | 260 +++++++++++++++++++++++ libc/src/__support/threads/linux/thread.h | 319 ---------------------------- libc/src/__support/threads/thread.h | 168 ++++++++++++--- libc/src/__support/threads/thread_attrib.h | 79 ------- 5 files changed, 401 insertions(+), 435 deletions(-) create mode 100644 libc/src/__support/threads/linux/thread.cpp delete mode 100644 libc/src/__support/threads/linux/thread.h delete mode 100644 libc/src/__support/threads/thread_attrib.h diff --git a/libc/src/__support/threads/CMakeLists.txt b/libc/src/__support/threads/CMakeLists.txt index 5dce96c..04f0287 100644 --- a/libc/src/__support/threads/CMakeLists.txt +++ b/libc/src/__support/threads/CMakeLists.txt @@ -28,11 +28,17 @@ if(TARGET libc.src.__support.threads.${LIBC_TARGET_OS}.mutex) endif() if(TARGET libc.src.__support.threads.${LIBC_TARGET_OS}.thread) - add_header_library( + add_object_library( thread HDRS thread.h + SRCS + ${LIBC_TARGET_OS}/thread.cpp DEPENDS - .${LIBC_TARGET_OS}.thread + .thread_attrib + COMPILE_OPTIONS + -O3 + -fno-omit-frame-pointer # This allows us to sniff out the thread args from + # the new thread's stack reliably. ) endif() diff --git a/libc/src/__support/threads/linux/thread.cpp b/libc/src/__support/threads/linux/thread.cpp new file mode 100644 index 0000000..fd0a3f6 --- /dev/null +++ b/libc/src/__support/threads/linux/thread.cpp @@ -0,0 +1,260 @@ +//===--- Implementation of a Linux thread class -----------------*- 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 "src/__support/threads/thread.h" +#include "src/__support/CPP/atomic.h" +#include "src/__support/CPP/error.h" +#include "src/__support/OSUtil/syscall.h" // For syscall functions. +#include "src/__support/threads/linux/futex_word.h" // For FutexWordType + +#ifdef LLVM_LIBC_ARCH_AARCH64 +#include +#endif + +#include +#include // For CLONE_* flags. +#include +#include // For PROT_* and MAP_* definitions. +#include // For syscall numbers. + +namespace __llvm_libc { + +#ifdef SYS_mmap2 +static constexpr long MMAP_SYSCALL_NUMBER = SYS_mmap2; +#elif SYS_mmap +static constexpr long MMAP_SYSCALL_NUMBER = SYS_mmap; +#else +#error "SYS_mmap or SYS_mmap2 not available on the target platform" +#endif + +static constexpr size_t DEFAULT_STACK_SIZE = (1 << 16); // 64KB +static constexpr uint32_t CLEAR_TID_VALUE = 0xABCD1234; +static constexpr unsigned CLONE_SYSCALL_FLAGS = + CLONE_VM // Share the memory space with the parent. + | CLONE_FS // Share the file system with the parent. + | CLONE_FILES // Share the files with the parent. + | CLONE_SIGHAND // Share the signal handlers with the parent. + | CLONE_THREAD // Same thread group as the parent. + | CLONE_SYSVSEM // Share a single list of System V semaphore adjustment + // values + | CLONE_PARENT_SETTID // Set child thread ID in |ptid| of the parent. + | CLONE_CHILD_CLEARTID; // Let the kernel clear the tid address + // wake the joining thread. +// TODO: Add the CLONE_SETTLS flag and setup the TLS area correctly +// when making the clone syscall. + +static inline cpp::ErrorOr alloc_stack(size_t size) { + long mmap_result = + __llvm_libc::syscall(MMAP_SYSCALL_NUMBER, + 0, // No special address + size, + PROT_READ | PROT_WRITE, // Read and write stack + MAP_ANONYMOUS | MAP_PRIVATE, // Process private + -1, // Not backed by any file + 0 // No offset + ); + if (mmap_result < 0 && (uintptr_t(mmap_result) >= UINTPTR_MAX - size)) + return cpp::Error{int(-mmap_result)}; + return reinterpret_cast(mmap_result); +} + +static inline void free_stack(void *stack, size_t size) { + __llvm_libc::syscall(SYS_munmap, stack, size); +} + +struct Thread; + +// We align the start args to 16-byte boundary as we adjust the allocated +// stack memory with its size. We want the adjusted address to be at a +// 16-byte boundary to satisfy the x86_64 and aarch64 ABI requirements. +// If different architecture in future requires higher alignment, then we +// can add a platform specific alignment spec. +struct alignas(STACK_ALIGNMENT) StartArgs { + Thread *thread; + ThreadRunner runner; + void *arg; +}; + +__attribute__((always_inline)) inline uintptr_t get_start_args_addr() { +// NOTE: For __builtin_frame_address to work reliably across compilers, +// architectures and various optimization levels, the TU including this file +// should be compiled with -fno-omit-frame-pointer. +#ifdef LLVM_LIBC_ARCH_X86_64 + return reinterpret_cast(__builtin_frame_address(0)) + // The x86_64 call instruction pushes resume address on to the stack. + // Next, The x86_64 SysV ABI requires that the frame pointer be pushed + // on to the stack. So, we have to step past two 64-bit values to get + // to the start args. + + sizeof(uintptr_t) * 2; +#elif defined(LLVM_LIBC_ARCH_AARCH64) + // The frame pointer after cloning the new thread in the Thread::run method + // is set to the stack pointer where start args are stored. So, we fetch + // from there. + return reinterpret_cast(__builtin_frame_address(1)); +#endif +} + +static void start_thread() __attribute__((noinline)) { + auto *start_args = reinterpret_cast(get_start_args_addr()); + auto *thread = start_args->thread; + auto *attrib = thread->attrib; + 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); + } else { + attrib->retval.stdc_retval = + start_args->runner.stdc_runner(start_args->arg); + retval = long(attrib->retval.stdc_retval); + } + + 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, retval); +} + +int Thread::run(ThreadStyle style, ThreadRunner runner, void *arg, void *stack, + size_t size, bool detached) { + bool owned_stack = false; + if (stack == nullptr) { + if (size == 0) + size = DEFAULT_STACK_SIZE; + auto alloc = alloc_stack(size); + if (!alloc) + return alloc.error_code(); + else + stack = alloc.value(); + owned_stack = true; + } + + // When the new thread is spawned by the kernel, the new thread gets the + // stack we pass to the clone syscall. However, this stack is empty and does + // not have any local vars present in this function. Hence, one cannot + // pass arguments to the thread start function, or use any local vars from + // here. So, we pack them into the new stack from where the thread can sniff + // them out. + // + // Likewise, the actual thread state information is also stored on the + // stack memory. + uintptr_t adjusted_stack = reinterpret_cast(stack) + size - + sizeof(StartArgs) - sizeof(ThreadAttributes) - + sizeof(cpp::Atomic); + adjusted_stack &= ~(uintptr_t(STACK_ALIGNMENT) - 1); + + auto *start_args = reinterpret_cast(adjusted_stack); + start_args->thread = this; + start_args->runner = runner; + start_args->arg = arg; + + attrib = + reinterpret_cast(adjusted_stack + sizeof(StartArgs)); + attrib->style = style; + attrib->detach_state = + uint32_t(detached ? DetachState::DETACHED : DetachState::JOINABLE); + attrib->stack = stack; + attrib->stack_size = size; + attrib->owned_stack = owned_stack; + + auto clear_tid = reinterpret_cast *>( + adjusted_stack + sizeof(StartArgs) + sizeof(ThreadAttributes)); + clear_tid->val = CLEAR_TID_VALUE; + platform_data = clear_tid; + + // The clone syscall takes arguments in an architecture specific order. + // Also, we want the result of the syscall to be in a register as the child + // thread gets a completely different stack after it is created. The stack + // variables from this function will not be availalbe to the child thread. +#ifdef LLVM_LIBC_ARCH_X86_64 + long register clone_result asm("rax"); + clone_result = __llvm_libc::syscall( + SYS_clone, CLONE_SYSCALL_FLAGS, adjusted_stack, + &attrib->tid, // The address where the child tid is written + &clear_tid->val, // The futex where the child thread status is signalled + 0 // Set TLS to null for now. + ); +#elif defined(LLVM_LIBC_ARCH_AARCH64) + long register clone_result asm("x0"); + clone_result = __llvm_libc::syscall( + SYS_clone, CLONE_SYSCALL_FLAGS, adjusted_stack, + &attrib->tid, // The address where the child tid is written + 0, // Set TLS to null for now. + &clear_tid->val // The futex where the child thread status is signalled + ); +#else +#error "Unsupported architecture for the clone syscall." +#endif + + if (clone_result == 0) { +#ifdef LLVM_LIBC_ARCH_AARCH64 + // We set the frame pointer to be the same as the "sp" so that start args + // can be sniffed out from start_thread. + __arm_wsr64("x29", __arm_rsr64("sp")); +#endif + start_thread(); + } else if (clone_result < 0) { + if (attrib->owned_stack) + free_stack(attrib->stack, attrib->stack_size); + return -clone_result; + } + + return 0; +} + +int Thread::join(ThreadReturnValue &retval) { + wait(); + + if (attrib->style == ThreadStyle::POSIX) + retval.posix_retval = attrib->retval.posix_retval; + else + retval.stdc_retval = attrib->retval.stdc_retval; + + if (attrib->owned_stack) + free_stack(attrib->stack, attrib->stack_size); + + return 0; +} + +int Thread::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); +} + +void Thread::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. + auto *clear_tid = + reinterpret_cast *>(platform_data); + while (clear_tid->load() != 0) { + // We cannot do a FUTEX_WAIT_PRIVATE here as the kernel does a + // FUTEX_WAKE and not a FUTEX_WAKE_PRIVATE. + __llvm_libc::syscall(SYS_futex, &clear_tid->val, FUTEX_WAIT, + CLEAR_TID_VALUE, nullptr); + } +} + +} // namespace __llvm_libc diff --git a/libc/src/__support/threads/linux/thread.h b/libc/src/__support/threads/linux/thread.h deleted file mode 100644 index 365f0fc..0000000 --- a/libc/src/__support/threads/linux/thread.h +++ /dev/null @@ -1,319 +0,0 @@ -//===--- Implementation of a Linux thread class -----------------*- 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_SUPPORT_THREADS_LINUX_THREAD_H -#define LLVM_LIBC_SRC_SUPPORT_THREADS_LINUX_THREAD_H - -#include "src/__support/CPP/atomic.h" -#include "src/__support/CPP/error.h" -#include "src/__support/OSUtil/syscall.h" // For syscall functions. -#include "src/__support/threads/linux/futex_word.h" // For FutexWordType -#include "src/__support/threads/thread_attrib.h" - -#ifdef LLVM_LIBC_ARCH_AARCH64 -#include -#endif - -#include -#include // For CLONE_* flags. -#include -#include // For PROT_* and MAP_* definitions. -#include // For syscall numbers. - -namespace __llvm_libc { - -#ifdef SYS_mmap2 -static constexpr long MMAP_SYSCALL_NUMBER = SYS_mmap2; -#elif SYS_mmap -static constexpr long MMAP_SYSCALL_NUMBER = SYS_mmap; -#else -#error "SYS_mmap or SYS_mmap2 not available on the target platform" -#endif - -static constexpr size_t DEFAULT_STACK_SIZE = (1 << 16); // 64KB -static constexpr uint32_t CLEAR_TID_VALUE = 0xABCD1234; -static constexpr unsigned CLONE_SYSCALL_FLAGS = - CLONE_VM // Share the memory space with the parent. - | CLONE_FS // Share the file system with the parent. - | CLONE_FILES // Share the files with the parent. - | CLONE_SIGHAND // Share the signal handlers with the parent. - | CLONE_THREAD // Same thread group as the parent. - | CLONE_SYSVSEM // Share a single list of System V semaphore adjustment - // values - | CLONE_PARENT_SETTID // Set child thread ID in |ptid| of the parent. - | CLONE_CHILD_CLEARTID; // Let the kernel clear the tid address - // wake the joining thread. -// TODO: Add the CLONE_SETTLS flag and setup the TLS area correctly -// when making the clone syscall. - -static inline cpp::ErrorOr alloc_stack(size_t size) { - long mmap_result = - __llvm_libc::syscall(MMAP_SYSCALL_NUMBER, - 0, // No special address - size, - PROT_READ | PROT_WRITE, // Read and write stack - MAP_ANONYMOUS | MAP_PRIVATE, // Process private - -1, // Not backed by any file - 0 // No offset - ); - if (mmap_result < 0 && (uintptr_t(mmap_result) >= UINTPTR_MAX - size)) - return cpp::Error{int(-mmap_result)}; - return reinterpret_cast(mmap_result); -} - -static inline void free_stack(void *stack, size_t size) { - __llvm_libc::syscall(SYS_munmap, stack, size); -} - -struct Thread; - -// We align the start args to 16-byte boundary as we adjust the allocated -// stack memory with its size. We want the adjusted address to be at a -// 16-byte boundary to satisfy the x86_64 and aarch64 ABI requirements. -// If different architecture in future requires higher alignment, then we -// can add a platform specific alignment spec. -struct alignas(STACK_ALIGNMENT) StartArgs { - Thread *thread; - ThreadRunner runner; - void *arg; -}; - -__attribute__((always_inline)) inline uintptr_t get_start_args_addr() { - // NOTE: For __builtin_frame_address to work reliably across compilers, - // architectures and various optimization levels, the TU including this file - // should be compiled with -fno-omit-frame-pointer. -#ifdef LLVM_LIBC_ARCH_X86_64 - return reinterpret_cast(__builtin_frame_address(0)) - // The x86_64 call instruction pushes resume address on to the stack. - // Next, The x86_64 SysV ABI requires that the frame pointer be pushed - // on to the stack. So, we have to step past two 64-bit values to get - // to the start args. - + sizeof(uintptr_t) * 2; -#elif defined(LLVM_LIBC_ARCH_AARCH64) - // The frame pointer after cloning the new thread in the Thread::run method - // is set to the stack pointer where start args are stored. So, we fetch - // from there. - return reinterpret_cast(__builtin_frame_address(1)); -#endif -} - -struct Thread { -private: - ThreadAttributes *attrib; - cpp::Atomic *clear_tid; - -public: - Thread() = default; - - static void start_thread() __attribute__((noinline)) { - auto *start_args = reinterpret_cast(get_start_args_addr()); - auto *thread = start_args->thread; - auto *attrib = thread->attrib; - 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); - } else { - attrib->retval.stdc_retval = - start_args->runner.stdc_runner(start_args->arg); - retval = long(attrib->retval.stdc_retval); - } - - 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, retval); - } - - int run(ThreadRunnerPosix *func, void *arg, void *stack, size_t size, - bool detached = false) { - ThreadRunner runner; - runner.posix_runner = func; - return run(ThreadStyle::POSIX, runner, arg, stack, size, detached); - } - - int run(ThreadRunnerStdc *func, void *arg, void *stack, size_t size, - bool detached = false) { - ThreadRunner runner; - runner.stdc_runner = func; - return run(ThreadStyle::STDC, runner, arg, stack, size, detached); - } - - // Return 0 on success or an error value on failure. - int run(ThreadStyle style, ThreadRunner runner, void *arg, void *stack, - size_t size, bool detached) { - bool owned_stack = false; - if (stack == nullptr) { - if (size == 0) - size = DEFAULT_STACK_SIZE; - auto alloc = alloc_stack(size); - if (!alloc) - return alloc.error_code(); - else - stack = alloc.value(); - owned_stack = true; - } - - // When the new thread is spawned by the kernel, the new thread gets the - // stack we pass to the clone syscall. However, this stack is empty and does - // not have any local vars present in this function. Hence, one cannot - // pass arguments to the thread start function, or use any local vars from - // here. So, we pack them into the new stack from where the thread can sniff - // them out. - // - // Likewise, the actual thread state information is also stored on the - // stack memory. - uintptr_t adjusted_stack = reinterpret_cast(stack) + size - - sizeof(StartArgs) - sizeof(ThreadAttributes) - - sizeof(cpp::Atomic); - adjusted_stack &= ~(uintptr_t(STACK_ALIGNMENT) - 1); - - auto *start_args = reinterpret_cast(adjusted_stack); - start_args->thread = this; - start_args->runner = runner; - start_args->arg = arg; - - attrib = reinterpret_cast(adjusted_stack + - sizeof(StartArgs)); - attrib->style = style; - attrib->detach_state = - uint32_t(detached ? DetachState::DETACHED : DetachState::JOINABLE); - attrib->stack = stack; - attrib->stack_size = size; - attrib->owned_stack = owned_stack; - - clear_tid = reinterpret_cast *>( - adjusted_stack + sizeof(StartArgs) + sizeof(ThreadAttributes)); - clear_tid->val = CLEAR_TID_VALUE; - - // The clone syscall takes arguments in an architecture specific order. - // Also, we want the result of the syscall to be in a register as the child - // thread gets a completely different stack after it is created. The stack - // variables from this function will not be availalbe to the child thread. -#ifdef LLVM_LIBC_ARCH_X86_64 - long register clone_result asm("rax"); - clone_result = __llvm_libc::syscall( - SYS_clone, CLONE_SYSCALL_FLAGS, adjusted_stack, - &attrib->tid, // The address where the child tid is written - &clear_tid->val, // The futex where the child thread status is signalled - 0 // Set TLS to null for now. - ); -#elif defined(LLVM_LIBC_ARCH_AARCH64) - long register clone_result asm("x0"); - clone_result = __llvm_libc::syscall( - SYS_clone, CLONE_SYSCALL_FLAGS, adjusted_stack, - &attrib->tid, // The address where the child tid is written - 0, // Set TLS to null for now. - &clear_tid->val // The futex where the child thread status is signalled - ); -#else -#error "Unsupported architecture for the clone syscall." -#endif - - if (clone_result == 0) { -#ifdef LLVM_LIBC_ARCH_AARCH64 - // We set the frame pointer to be the same as the "sp" so that start args - // can be sniffed out from start_thread. - __arm_wsr64("x29", __arm_rsr64("sp")); -#endif - start_thread(); - } else if (clone_result < 0) { - if (attrib->owned_stack) - free_stack(attrib->stack, attrib->stack_size); - return -clone_result; - } - - return 0; - } - - int join(int *val) { - ThreadReturnValue retval; - int status = join(retval); - if (status != 0) - return status; - *val = retval.stdc_retval; - return 0; - } - - int join(void **val) { - ThreadReturnValue retval; - int status = join(retval); - if (status != 0) - return status; - *val = retval.posix_retval; - return 0; - } - - int join(ThreadReturnValue &retval) { - wait(); - - if (attrib->style == ThreadStyle::POSIX) - retval.posix_retval = attrib->retval.posix_retval; - else - retval.stdc_retval = attrib->retval.stdc_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. - while (clear_tid->load() != 0) { - // We cannot do a FUTEX_WAIT_PRIVATE here as the kernel does a - // FUTEX_WAKE and not a FUTEX_WAKE_PRIVATE. - __llvm_libc::syscall(SYS_futex, &clear_tid->val, FUTEX_WAIT, - CLEAR_TID_VALUE, nullptr); - } - } -}; - -} // namespace __llvm_libc - -#endif // LLVM_LIBC_SRC_SUPPORT_THREADS_LINUX_THREAD_H diff --git a/libc/src/__support/threads/thread.h b/libc/src/__support/threads/thread.h index 368df2b..36a191c 100644 --- a/libc/src/__support/threads/thread.h +++ b/libc/src/__support/threads/thread.h @@ -9,7 +9,13 @@ #ifndef LLVM_LIBC_SRC_SUPPORT_THREADS_THREAD_H #define LLVM_LIBC_SRC_SUPPORT_THREADS_THREAD_H -#include +#include "src/__support/CPP/atomic.h" +#include "src/__support/architectures.h" + +#include // For size_t +#include + +namespace __llvm_libc { using ThreadRunnerPosix = void *(void *); using ThreadRunnerStdc = int(void *); @@ -24,40 +30,132 @@ union ThreadReturnValue { int stdc_retval; }; -// The platform specific implemnetations are pulled via the following include. -// The idea is for the platform implementation to implement a class named Thread -// in the namespace __llvm_libc with the following properties: -// -// 1. Has a defaulted default constructor (not a default constructor). -// -// 2. Has a "run" method with the following signature: -// -// int run(ThreadRunner runner, void *arg, void *stack, size_t size, -// bool detached); -// -// Returns: -// 0 on success and an error value on failure. -// Args: -// runner - The function to execute in the new thread. -// arg - The argument to be passed to the thread runner after the thread -// is created. -// stack - The stack to use for the thread. -// size - The stack size. -// detached - The detached state of the thread at startup. -// -// If callers pass a non-null |stack| value, then it will be assumed that -// 1. The clean up the stack memory is their responsibility -// 2. The guard area is setup appropriately by the caller. -// -// 3. Has a "join" method with the following signature: -// int join(ThreadReturnValue &retval); -// The "join" method should return 0 on success and set retcode to the -// threads return value. On failure, an appropriate errno value should be -// returned. +#if (defined(LLVM_LIBC_ARCH_AARCH64) || defined(LLVM_LIBC_ARCH_X86_64)) +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 +}; + +enum class ThreadStyle : uint8_t { POSIX = 0x1, STDC = 0x2 }; + +// 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 +// part of a thread's state. For example, the stack guard size. // -// 4. Has an operator== for comparison between two threads. -#ifdef __unix__ -#include "linux/thread.h" -#endif // __unix__ +// Thread attributes are typically stored on the stack. So, we align as required +// for the target architecture. +struct alignas(STACK_ALIGNMENT) ThreadAttributes { + // 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 + void *tls; + unsigned long long stack_size; // Size of the stack + unsigned char owned_stack; // Indicates if the thread owns this stack memory + int tid; + ThreadStyle style; + ThreadReturnValue retval; +}; + +struct Thread { + ThreadAttributes *attrib; + void *platform_data; + + Thread() = default; + + int run(ThreadRunnerPosix *func, void *arg, void *stack, size_t size, + bool detached = false) { + ThreadRunner runner; + runner.posix_runner = func; + return run(ThreadStyle::POSIX, runner, arg, stack, size, detached); + } + + int run(ThreadRunnerStdc *func, void *arg, void *stack, size_t size, + bool detached = false) { + ThreadRunner runner; + runner.stdc_runner = func; + return run(ThreadStyle::STDC, runner, arg, stack, size, detached); + } + + int join(int *val) { + ThreadReturnValue retval; + int status = join(retval); + if (status != 0) + return status; + *val = retval.stdc_retval; + return 0; + } + + int join(void **val) { + ThreadReturnValue retval; + int status = join(retval); + if (status != 0) + return status; + *val = retval.posix_retval; + return 0; + } + + // Platform should implement the functions below. + + // Return 0 on success or an error value on failure. + int run(ThreadStyle style, ThreadRunner runner, void *arg, void *stack, + size_t stack_size, bool detached); + + // Return 0 on success or an error value on failure. + int join(ThreadReturnValue &retval); + + // 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(); + + // 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(); +}; + +} // namespace __llvm_libc #endif // LLVM_LIBC_SRC_SUPPORT_THREADS_THREAD_H diff --git a/libc/src/__support/threads/thread_attrib.h b/libc/src/__support/threads/thread_attrib.h deleted file mode 100644 index c4deabd..0000000 --- a/libc/src/__support/threads/thread_attrib.h +++ /dev/null @@ -1,79 +0,0 @@ -//===--- A data type for thread attributes ----------------------*- 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_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)) -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 -}; - -enum class ThreadStyle : uint8_t { POSIX = 0x1, STDC = 0x2 }; - -// 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 -// part of a thread's state. For example, the stack guard size. -// -// Thread attributes are typically stored on the stack. So, we align as required -// for the target architecture. -struct alignas(STACK_ALIGNMENT) ThreadAttributes { - // 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 - void *tls; - unsigned long long stack_size; // Size of the stack - unsigned char owned_stack; // Indicates if the thread owns this stack memory - int tid; - ThreadStyle style; - ThreadReturnValue retval; -}; -} // namespace __llvm_libc - -#endif // LLVM_LIBC_SRC_SUPPORT_THREADS_THREAD_ATTRIB_H -- 2.7.4