diff --git a/libc/include/llvm-libc-types/thrd_t.h b/libc/include/llvm-libc-types/thrd_t.h --- a/libc/include/llvm-libc-types/thrd_t.h +++ b/libc/include/llvm-libc-types/thrd_t.h @@ -12,11 +12,14 @@ #include typedef struct { + struct { + void *__stack; + unsigned long long __stack_size; + unsigned char __managed_stack; + int __retval; + int __tid; + } __attrib; __futex_word __clear_tid; - int __tid; - void *__stack; - int __stack_size; - int __retval; } thrd_t; #endif // __LLVM_LIBC_TYPES_THRD_T_H__ diff --git a/libc/src/__support/CPP/CMakeLists.txt b/libc/src/__support/CPP/CMakeLists.txt --- a/libc/src/__support/CPP/CMakeLists.txt +++ b/libc/src/__support/CPP/CMakeLists.txt @@ -73,3 +73,9 @@ HDRS blockstore.h ) + +add_header_library( + error + HDRS + error.h +) diff --git a/libc/src/__support/CPP/error.h b/libc/src/__support/CPP/error.h new file mode 100644 --- /dev/null +++ b/libc/src/__support/CPP/error.h @@ -0,0 +1,51 @@ +//===-- A simple classes to manage error return vals ----------*- 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_CPP_ERROR_H +#define LLVM_LIBC_SRC_SUPPORT_CPP_ERROR_H + +namespace __llvm_libc { +namespace cpp { +// Many C functions return an error val and/or the actual result of the +// evaluation/operation performed by the function. This file defines a simple +// convenience data structure to encapsulate the error and the actual val in +// a single place. + +struct Error { + int error_code; +}; + +// This class is implemented in a simple fashion as the intention is it add +// more generality as required. Currently, it only supports simple copyable +// types for T. +template class ErrorOr { + bool is_error; + + union { + T val; + Error error; + }; + +public: + ErrorOr(const T &value) : is_error(false), val(value) {} + + ErrorOr(const Error &error) : is_error(true), error(error) {} + + operator bool() { return !is_error; } + + operator T &() { return val; } + + T &value() { return val; } + + int error_code() { return is_error ? error.error_code : 0; } +}; + +} // namespace cpp +} // namespace __llvm_libc + +#endif // LLVM_LIBC_SRC_SUPPORT_CPP_ERROR_H 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 @@ -4,6 +4,12 @@ mutex_common.h ) +add_header_library( + thread_attrib + HDRS + thread_attrib.h +) + if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${LIBC_TARGET_OS}) add_subdirectory(${LIBC_TARGET_OS}) endif() @@ -17,3 +23,13 @@ .${LIBC_TARGET_OS}.mutex ) endif() + +if(TARGET libc.src.__support.threads.${LIBC_TARGET_OS}.thread) + add_header_library( + thread + HDRS + thread.h + DEPENDS + .${LIBC_TARGET_OS}.thread + ) +endif() diff --git a/libc/src/__support/threads/linux/CMakeLists.txt b/libc/src/__support/threads/linux/CMakeLists.txt --- a/libc/src/__support/threads/linux/CMakeLists.txt +++ b/libc/src/__support/threads/linux/CMakeLists.txt @@ -1,10 +1,29 @@ +add_header_library( + futex_word_type + HDRS + futex_word.h +) + add_header_library( mutex HDRS mutex.h DEPENDS + .futex_word_type libc.include.sys_syscall libc.src.__support.CPP.atomic libc.src.__support.OSUtil.osutil libc.src.__support.threads.mutex_common ) + +add_header_library( + thread + HDRS + thread.h + DEPENDS + .futex_word_type + libc.include.sys_syscall + libc.src.__support.CPP.atomic + libc.src.__support.CPP.error + libc.src.__support.threads.thread_attrib +) diff --git a/libc/src/__support/threads/linux/futex_word.h b/libc/src/__support/threads/linux/futex_word.h new file mode 100644 --- /dev/null +++ b/libc/src/__support/threads/linux/futex_word.h @@ -0,0 +1,24 @@ +//===--- Definition of a type for a futex word ------------------*- 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_FUTEX_WORD_H +#define LLVM_LIBC_SRC_SUPPORT_THREADS_LINUX_FUTEX_WORD_H + +#include "src/__support/architectures.h" + +namespace __llvm_libc { + +#if defined(LLVM_LIBC_ARCH_X86_64) || defined(LLVM_LIBC_ARCH_AARCH64) +using FutexWordType = unsigned int; +#else +#error "FutexWordType not defined for the target architecture." +#endif + +} // namespace __llvm_libc + +#endif // LLVM_LIBC_SRC_SUPPORT_THREADS_LINUX_FUTEX_WORD_H diff --git a/libc/src/__support/threads/linux/mutex.h b/libc/src/__support/threads/linux/mutex.h --- a/libc/src/__support/threads/linux/mutex.h +++ b/libc/src/__support/threads/linux/mutex.h @@ -11,6 +11,7 @@ #include "src/__support/CPP/atomic.h" #include "src/__support/OSUtil/syscall.h" // For syscall functions. +#include "src/__support/threads/linux/futex_word.h" #include "src/__support/threads/mutex_common.h" #include @@ -27,8 +28,6 @@ void *owner; unsigned long long lock_count; - using FutexWordType = unsigned int; - cpp::Atomic futex_word; enum class LockState : FutexWordType { diff --git a/libc/src/__support/threads/linux/thread.h b/libc/src/__support/threads/linux/thread.h new file mode 100644 --- /dev/null +++ b/libc/src/__support/threads/linux/thread.h @@ -0,0 +1,206 @@ +//===--- 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" + +#include +#include // For CLONE_* flags. +#include +#include // For PROT_* and MAP_* definitions. +#include // For syscall numbers. + +namespace __llvm_libc { + +template struct Thread; + +#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); +} + +template using ThreadRunner = ReturnType(void *); + +// 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. +template struct alignas(16) StartArgs { + Thread *thread; + ThreadRunner *func; + 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. + 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. Similarly on aarch64, previous frame pointer and + // the value of the link register are pushed on to the stack. So, in + // both these cases, we have to step past two 64-bit values to get + // to the start args. + + sizeof(uintptr_t) * 2; +} + +template struct Thread { +private: + ThreadAttributes attrib; + cpp::Atomic clear_tid; + +public: + Thread() = default; + + static void start_thread() __attribute__((noinline)); + + // Return 0 on success or an error value on failure. + int run(ThreadRunner *f, void *arg, void *stack, size_t size) { + 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(); + attrib.owned_stack = true; + } else { + attrib.owned_stack = false; + } + attrib.stack = stack; + attrib.stack_size = size; + clear_tid.val = CLEAR_TID_VALUE; + + // 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. + uintptr_t adjusted_stack = reinterpret_cast(attrib.stack) + + attrib.stack_size - + sizeof(StartArgs); + auto *start_args = + reinterpret_cast *>(adjusted_stack); + start_args->thread = this; + start_args->func = f; + start_args->arg = arg; + + // 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) { + 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() { + // 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); + } + return 0; + } + + // Meaningful only after the thread finishes. + ReturnType return_value() { return attrib.retval; } +}; + +template +__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); + if (thread->attrib.owned_stack) + free_stack(thread->attrib.stack, thread->attrib.stack_size); + __llvm_libc::syscall(SYS_exit, thread->attrib.retval); +} + +} // 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 new file mode 100644 --- /dev/null +++ b/libc/src/__support/threads/thread.h @@ -0,0 +1,47 @@ +//===--- A platform independent indirection for a 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_THREAD_H +#define LLVM_LIBC_SRC_SUPPORT_THREADS_THREAD_H + +#include + +// 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 *f, void *arg, void *stack, size_t size); +// +// Returns: +// 0 on success and an error value on failure. +// Args: +// 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. +// +// If callers pass a non-null |stack| value, then it will 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: +// ErrorOr join(); +// 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. +// +// 4. Has an operator== for comparison between two threads. +#ifdef __unix__ +#include "linux/thread.h" +#endif // __unix__ + +#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 new file mode 100644 --- /dev/null +++ b/libc/src/__support/threads/thread_attrib.h @@ -0,0 +1,29 @@ +//===--- 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 + +namespace __llvm_libc { + +// A data type to hold common thread attributes which have to be stored as +// thread state. A platform thread implementation should store the attrib object +// in its Thread data structure. Note that this is different from public +// attribute types like pthread_attr which contain information which need not +// be saved as part of a thread's state. For example, the stack guard size. +template struct ThreadAttributes { + 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 + ReturnType retval; // The return value of thread runner is saved here + int tid; +}; + +} // namespace __llvm_libc + +#endif // LLVM_LIBC_SRC_SUPPORT_THREADS_THREAD_ATTRIB_H 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 @@ -11,16 +11,28 @@ add_entrypoint_object( thrd_create - ALIAS + SRCS + thrd_create.cpp + HDRS + thrd_create.h DEPENDS - .${LIBC_TARGET_OS}.thrd_create + libc.src.__support.threads.thread + libc.include.threads + COMPILE_OPTIONS + -O3 + -fno-omit-frame-pointer # This allows us to sniff out the thread args from + # the new thread's stack reliably. ) add_entrypoint_object( thrd_join - ALIAS + SRCS + thrd_join.cpp + HDRS + thrd_join.h DEPENDS - .${LIBC_TARGET_OS}.thrd_join + libc.include.threads + libc.src.__support.threads.thread ) add_entrypoint_object( diff --git a/libc/src/threads/linux/CMakeLists.txt b/libc/src/threads/linux/CMakeLists.txt --- a/libc/src/threads/linux/CMakeLists.txt +++ b/libc/src/threads/linux/CMakeLists.txt @@ -17,48 +17,13 @@ HDRS CndVar.h Futex.h - Thread.h DEPENDS libc.include.sys_syscall libc.include.threads libc.src.__support.CPP.atomic libc.src.__support.OSUtil.osutil libc.src.__support.threads.mutex -) - -add_entrypoint_object( - thrd_create - SRCS - thrd_create.cpp - HDRS - ../thrd_create.h - DEPENDS - .threads_utils - libc.include.errno - libc.include.sys_mman - libc.include.sys_syscall - libc.include.threads - libc.src.__support.common - libc.src.__support.OSUtil.osutil - COMPILE_OPTIONS - -O3 - -fno-omit-frame-pointer # This allows us to sniff out the thread args from - # the new thread's stack reliably. -) - -add_entrypoint_object( - thrd_join - SRCS - thrd_join.cpp - HDRS - ../thrd_join.h - DEPENDS - .threads_utils - libc.include.sys_syscall - libc.include.threads - libc.src.__support.CPP.atomic - libc.src.__support.common - libc.src.__support.OSUtil.osutil + libc.src.__support.threads.linux.futex_word_type ) add_entrypoint_object( diff --git a/libc/src/threads/linux/CndVar.h b/libc/src/threads/linux/CndVar.h --- a/libc/src/threads/linux/CndVar.h +++ b/libc/src/threads/linux/CndVar.h @@ -13,6 +13,7 @@ #include "include/threads.h" // For values like thrd_success etc. #include "src/__support/CPP/atomic.h" #include "src/__support/OSUtil/syscall.h" // For syscall functions. +#include "src/__support/threads/linux/futex_word.h" #include "src/__support/threads/mutex.h" #include // For futex operations. @@ -106,7 +107,7 @@ if (waitq_front == nullptr) waitq_back = nullptr; - qmtx.futex_word = Mutex::FutexWordType(Mutex::LockState::Free); + qmtx.futex_word = FutexWordType(Mutex::LockState::Free); __llvm_libc::syscall( SYS_futex, &qmtx.futex_word.val, FUTEX_WAKE_OP, 1, 1, diff --git a/libc/src/threads/linux/Thread.h b/libc/src/threads/linux/Thread.h deleted file mode 100644 --- a/libc/src/threads/linux/Thread.h +++ /dev/null @@ -1,23 +0,0 @@ -//===-- Linux specific definitions for threads implementations. --*- 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_LINUX_THREAD_UTILS_H -#define LLVM_LIBC_SRC_THREADS_LINUX_THREAD_UTILS_H - -#include - -namespace __llvm_libc { - -struct ThreadParams { - static constexpr uintptr_t DEFAULT_STACK_SIZE = 1 << 16; // 64 KB - static constexpr uint32_t CLEAR_TID_VALUE = 0xABCD1234; -}; - -} // namespace __llvm_libc - -#endif // LLVM_LIBC_SRC_THREADS_LINUX_THREAD_UTILS_H diff --git a/libc/src/threads/linux/thrd_create.cpp b/libc/src/threads/linux/thrd_create.cpp deleted file mode 100644 --- a/libc/src/threads/linux/thrd_create.cpp +++ /dev/null @@ -1,153 +0,0 @@ -//===-- Linux implementation of the thrd_create 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 "Futex.h" - -#include "src/__support/OSUtil/syscall.h" // For syscall function. -#include "src/__support/architectures.h" -#include "src/__support/common.h" -#include "src/threads/linux/Thread.h" -#include "src/threads/thrd_create.h" - -#include // For E* error values. -#include // For CLONE_* flags. -#include -#include // For PROT_* and MAP_* definitions. -#include // For syscall numbers. -#include // For thrd_* type definitions. - -#ifdef SYS_mmap2 -constexpr long MMAP_SYSCALL_NUMBER = SYS_mmap2; -#elif SYS_mmap -constexpr long MMAP_SYSCALL_NUMBER = SYS_mmap; -#else -#error "SYS_mmap or SYS_mmap2 not available on the target platform" -#endif - -namespace __llvm_libc { - -// 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(16) StartArgs { - thrd_t *thread; - thrd_start_t func; - 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. - 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. Similarly on aarch64, previous frame pointer and - // the value of the link register are pushed on to the stack. So, in - // both these cases, we have to step past two 64-bit values to get - // to the start args. - + sizeof(uintptr_t) * 2; -} - -static __attribute__((noinline)) void start_thread() { - StartArgs *start_args = reinterpret_cast(get_start_args_addr()); - __llvm_libc::syscall(SYS_exit, start_args->thread->__retval = - start_args->func(start_args->arg)); -} - -LLVM_LIBC_FUNCTION(int, thrd_create, - (thrd_t * thread, thrd_start_t func, void *arg)) { - unsigned clone_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 and futex - // wake the joining thread. - // TODO: Add the CLONE_SETTLS flag and setup the TLS area correctly when - // making the clone syscall. - - // Allocate thread stack. - long mmap_result = - __llvm_libc::syscall(MMAP_SYSCALL_NUMBER, - 0, // No special address - ThreadParams::DEFAULT_STACK_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 - ThreadParams::DEFAULT_STACK_SIZE)) { - return -mmap_result == ENOMEM ? thrd_nomem : thrd_error; - } - void *stack = reinterpret_cast(mmap_result); - - thread->__stack = stack; - thread->__stack_size = ThreadParams::DEFAULT_STACK_SIZE; - thread->__retval = -1; - FutexWordType *clear_tid_address = &thread->__clear_tid.__word; - *clear_tid_address = ThreadParams::CLEAR_TID_VALUE; - - // 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. - uintptr_t adjusted_stack = reinterpret_cast(stack) + - ThreadParams::DEFAULT_STACK_SIZE - - sizeof(StartArgs); - StartArgs *start_args = reinterpret_cast(adjusted_stack); - start_args->thread = thread; - start_args->func = func; - start_args->arg = arg; - - // 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_flags, adjusted_stack, - &thread->__tid, // The address where the child tid is written - clear_tid_address, // 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_flags, adjusted_stack, - &thread->__tid, // The address where the child tid is written - 0, // Set TLS to null for now. - clear_tid_address // The futex where the child thread status is signalled - ); -#else -#error "Unsupported architecture for the clone syscall." -#endif - - if (clone_result == 0) { - start_thread(); - } else if (clone_result < 0) { - __llvm_libc::syscall(SYS_munmap, mmap_result, - ThreadParams::DEFAULT_STACK_SIZE); - int error_val = -clone_result; - return error_val == ENOMEM ? thrd_nomem : thrd_error; - } - - return thrd_success; -} - -} // namespace __llvm_libc diff --git a/libc/src/threads/linux/thrd_join.cpp b/libc/src/threads/linux/thrd_join.cpp deleted file mode 100644 --- a/libc/src/threads/linux/thrd_join.cpp +++ /dev/null @@ -1,46 +0,0 @@ -//===-- Linux implementation of the thrd_join 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 "Futex.h" - -#include "src/__support/CPP/atomic.h" -#include "src/__support/OSUtil/syscall.h" // For syscall function. -#include "src/__support/common.h" -#include "src/threads/linux/Thread.h" -#include "src/threads/thrd_join.h" - -#include // For futex operations. -#include // For syscall numbers. -#include // For thrd_* type definitions. - -namespace __llvm_libc { - -LLVM_LIBC_FUNCTION(int, thrd_join, (thrd_t * thread, int *retval)) { - auto *clear_tid_address = - reinterpret_cast *>(&thread->__clear_tid); - - // 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_address->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_address->val, FUTEX_WAIT, - ThreadParams::CLEAR_TID_VALUE, nullptr); - } - - *retval = thread->__retval; - - if (__llvm_libc::syscall(SYS_munmap, reinterpret_cast(thread->__stack), - thread->__stack_size) == -1) - return thrd_error; - - return thrd_success; -} - -} // namespace __llvm_libc diff --git a/libc/src/threads/thrd_create.h b/libc/src/threads/thrd_create.h --- a/libc/src/threads/thrd_create.h +++ b/libc/src/threads/thrd_create.h @@ -9,7 +9,7 @@ #ifndef LLVM_LIBC_SRC_THREADS_THRD_CREATE_H #define LLVM_LIBC_SRC_THREADS_THRD_CREATE_H -#include "include/threads.h" +#include namespace __llvm_libc { diff --git a/libc/src/threads/thrd_create.cpp b/libc/src/threads/thrd_create.cpp new file mode 100644 --- /dev/null +++ b/libc/src/threads/thrd_create.cpp @@ -0,0 +1,33 @@ +//===-- Linux implementation of the thrd_create 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_create.h" +#include "src/__support/common.h" +#include "src/__support/threads/thread.h" + +#include +#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_create, + (thrd_t * th, thrd_start_t func, void *arg)) { + auto *thread = reinterpret_cast<__llvm_libc::Thread *>(th); + int result = thread->run(func, arg, nullptr, 0); + if (result == 0) + return thrd_success; + else if (result == ENOMEM) + return thrd_nomem; + else + return thrd_error; +} + +} // namespace __llvm_libc diff --git a/libc/src/threads/thrd_join.cpp b/libc/src/threads/thrd_join.cpp new file mode 100644 --- /dev/null +++ b/libc/src/threads/thrd_join.cpp @@ -0,0 +1,30 @@ +//===-- Linux implementation of the thrd_join 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_join.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_join, (thrd_t * th, int *retval)) { + auto *thread = reinterpret_cast *>(th); + int result = thread->join(); + if (result == 0) { + *retval = thread->return_value(); + return thrd_success; + } + return thrd_error; +} + +} // namespace __llvm_libc