#include "src/__support/threads/thread.h"
#include "config/linux/app.h"
#include "src/__support/CPP/atomic.h"
#include "src/__support/CPP/error.h"
#include "src/__support/OSUtil/syscall.h"
#include "src/__support/threads/linux/futex_word.h"
#ifdef LLVM_LIBC_ARCH_AARCH64
#include <arm_acle.h>
#endif
#include <linux/futex.h>
#include <linux/sched.h>
#include <stdint.h>
#include <sys/mman.h>
#include <sys/syscall.h>
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);
static constexpr uint32_t CLEAR_TID_VALUE = 0xABCD1234;
static constexpr unsigned CLONE_SYSCALL_FLAGS =
CLONE_VM
| CLONE_FS
| CLONE_FILES
| CLONE_SIGHAND
| CLONE_THREAD
| CLONE_SYSVSEM
| CLONE_PARENT_SETTID
| CLONE_CHILD_CLEARTID
| CLONE_SETTLS;
static inline cpp::ErrorOr<void *> alloc_stack(size_t size) {
long mmap_result =
__llvm_libc::syscall(MMAP_SYSCALL_NUMBER,
0,
size,
PROT_READ | PROT_WRITE,
MAP_ANONYMOUS | MAP_PRIVATE,
-1,
0
);
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;
struct alignas(STACK_ALIGNMENT) StartArgs {
ThreadAttributes *thread_attrib;
ThreadRunner runner;
void *arg;
};
static void cleanup_thread_resources(ThreadAttributes *attrib) {
cleanup_tls(attrib->tls, attrib->tls_size);
if (attrib->owned_stack)
free_stack(attrib->stack, attrib->stack_size);
}
__attribute__((always_inline)) inline uintptr_t get_start_args_addr() {
#ifdef LLVM_LIBC_ARCH_X86_64
return reinterpret_cast<uintptr_t>(__builtin_frame_address(0))
+ sizeof(uintptr_t) * 2;
#elif defined(LLVM_LIBC_ARCH_AARCH64)
return reinterpret_cast<uintptr_t>(__builtin_frame_address(1));
#endif
}
__attribute__((noinline))
static void start_thread() {
auto *start_args = reinterpret_cast<StartArgs *>(get_start_args_addr());
auto *attrib = start_args->thread_attrib;
self.attrib = 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 (!attrib->detach_state.compare_exchange_strong(
joinable_state, uint32_t(DetachState::EXITING))) {
cleanup_thread_resources(attrib);
__llvm_libc::syscall(SYS_set_tid_address, 0);
}
__llvm_libc::syscall(SYS_exit, retval);
}
int Thread::run(ThreadStyle style, ThreadRunner runner, void *arg, void *stack,
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;
}
TLSDescriptor tls;
init_tls(tls);
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);
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;
attrib->tls = tls.addr;
attrib->tls_size = tls.size;
start_args->thread_attrib = attrib;
start_args->runner = runner;
start_args->arg = arg;
auto clear_tid = reinterpret_cast<cpp::Atomic<FutexWordType> *>(
adjusted_stack + sizeof(StartArgs) + sizeof(ThreadAttributes));
clear_tid->val = CLEAR_TID_VALUE;
attrib->platform_data = clear_tid;
#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,
&clear_tid->val,
tls.tp
);
#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,
tls.tp,
&clear_tid->val
);
#else
#error "Unsupported architecture for the clone syscall."
#endif
if (clone_result == 0) {
#ifdef LLVM_LIBC_ARCH_AARCH64
__arm_wsr64("x29", __arm_rsr64("sp"));
#endif
start_thread();
} else if (clone_result < 0) {
cleanup_thread_resources(attrib);
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;
cleanup_thread_resources(attrib);
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);
}
wait();
cleanup_thread_resources(attrib);
return int(DetachType::CLEANUP);
}
void Thread::wait() {
auto *clear_tid =
reinterpret_cast<cpp::Atomic<FutexWordType> *>(attrib->platform_data);
while (clear_tid->load() != 0) {
__llvm_libc::syscall(SYS_futex, &clear_tid->val, FUTEX_WAIT,
CLEAR_TID_VALUE, nullptr);
}
}
bool Thread::operator==(const Thread &thread) const {
return attrib->tid == thread.attrib->tid;
}
}