forked from OSchip/llvm-project
[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.
This commit is contained in:
parent
b06614e2e8
commit
fe8017476c
|
@ -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()
|
||||
|
|
|
@ -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 <arm_acle.h>
|
||||
#endif
|
||||
|
||||
#include <linux/futex.h>
|
||||
#include <linux/sched.h> // For CLONE_* flags.
|
||||
#include <stdint.h>
|
||||
#include <sys/mman.h> // For PROT_* and MAP_* definitions.
|
||||
#include <sys/syscall.h> // 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<void *> 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<void *>(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<uintptr_t>(__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<uintptr_t>(__builtin_frame_address(1));
|
||||
#endif
|
||||
}
|
||||
|
||||
static void start_thread() __attribute__((noinline)) {
|
||||
auto *start_args = reinterpret_cast<StartArgs *>(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<uintptr_t>(stack) + size -
|
||||
sizeof(StartArgs) - sizeof(ThreadAttributes) -
|
||||
sizeof(cpp::Atomic<FutexWordType>);
|
||||
adjusted_stack &= ~(uintptr_t(STACK_ALIGNMENT) - 1);
|
||||
|
||||
auto *start_args = reinterpret_cast<StartArgs *>(adjusted_stack);
|
||||
start_args->thread = this;
|
||||
start_args->runner = runner;
|
||||
start_args->arg = arg;
|
||||
|
||||
attrib =
|
||||
reinterpret_cast<ThreadAttributes *>(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<cpp::Atomic<FutexWordType> *>(
|
||||
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<cpp::Atomic<FutexWordType> *>(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
|
|
@ -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 <arm_acle.h>
|
||||
#endif
|
||||
|
||||
#include <linux/futex.h>
|
||||
#include <linux/sched.h> // For CLONE_* flags.
|
||||
#include <stdint.h>
|
||||
#include <sys/mman.h> // For PROT_* and MAP_* definitions.
|
||||
#include <sys/syscall.h> // 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<void *> 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<void *>(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<uintptr_t>(__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<uintptr_t>(__builtin_frame_address(1));
|
||||
#endif
|
||||
}
|
||||
|
||||
struct Thread {
|
||||
private:
|
||||
ThreadAttributes *attrib;
|
||||
cpp::Atomic<FutexWordType> *clear_tid;
|
||||
|
||||
public:
|
||||
Thread() = default;
|
||||
|
||||
static void start_thread() __attribute__((noinline)) {
|
||||
auto *start_args = reinterpret_cast<StartArgs *>(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<uintptr_t>(stack) + size -
|
||||
sizeof(StartArgs) - sizeof(ThreadAttributes) -
|
||||
sizeof(cpp::Atomic<FutexWordType>);
|
||||
adjusted_stack &= ~(uintptr_t(STACK_ALIGNMENT) - 1);
|
||||
|
||||
auto *start_args = reinterpret_cast<StartArgs *>(adjusted_stack);
|
||||
start_args->thread = this;
|
||||
start_args->runner = runner;
|
||||
start_args->arg = arg;
|
||||
|
||||
attrib = reinterpret_cast<ThreadAttributes *>(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<cpp::Atomic<FutexWordType> *>(
|
||||
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
|
|
@ -9,7 +9,13 @@
|
|||
#ifndef LLVM_LIBC_SRC_SUPPORT_THREADS_THREAD_H
|
||||
#define LLVM_LIBC_SRC_SUPPORT_THREADS_THREAD_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include "src/__support/CPP/atomic.h"
|
||||
#include "src/__support/architectures.h"
|
||||
|
||||
#include <stddef.h> // For size_t
|
||||
#include <stdint.h>
|
||||
|
||||
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:
|
||||
#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.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// 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<uint32_t> 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
|
||||
|
|
|
@ -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 <stdint.h>
|
||||
|
||||
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<uint32_t> 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
|
Loading…
Reference in New Issue