#include "src/__support/threads/thread.h"
#include "config/linux/app.h"
#include "src/__support/CPP/atomic.h"
#include "src/__support/CPP/string_view.h"
#include "src/__support/CPP/stringstream.h"
#include "src/__support/OSUtil/syscall.h"
#include "src/__support/common.h"
#include "src/__support/error_or.h"
#include "src/__support/macros/config.h"
#include "src/__support/threads/linux/futex_utils.h"
#include "src/errno/libc_errno.h"
#ifdef LIBC_TARGET_ARCH_IS_AARCH64
#include <arm_acle.h>
#endif
#include <fcntl.h>
#include <linux/param.h>
#include <linux/prctl.h>
#include <linux/sched.h>
#include <stdint.h>
#include <sys/mman.h>
#include <sys/syscall.h>
namespace LIBC_NAMESPACE_DECL {
#ifdef SYS_mmap2
static constexpr long MMAP_SYSCALL_NUMBER = SYS_mmap2;
#elif defined(SYS_mmap)
static constexpr long MMAP_SYSCALL_NUMBER = SYS_mmap;
#else
#error "mmap or mmap2 syscalls not available."
#endif
static constexpr size_t NAME_SIZE_MAX = 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;
#ifdef LIBC_TARGET_ARCH_IS_AARCH64
#define CLONE_RESULT_REGISTER "x0"
#elif defined(LIBC_TARGET_ARCH_IS_ANY_RISCV)
#define CLONE_RESULT_REGISTER "t0"
#elif defined(LIBC_TARGET_ARCH_IS_X86_64)
#define CLONE_RESULT_REGISTER "rax"
#else
#error "CLONE_RESULT_REGISTER not defined for your target architecture"
#endif
static constexpr ErrorOr<size_t> add_no_overflow(size_t lhs, size_t rhs) {
if (lhs > SIZE_MAX - rhs)
return Error{EINVAL};
if (rhs > SIZE_MAX - lhs)
return Error{EINVAL};
return lhs + rhs;
}
static constexpr ErrorOr<size_t> round_to_page(size_t v) {
auto vp_or_err = add_no_overflow(v, EXEC_PAGESIZE - 1);
if (!vp_or_err)
return vp_or_err;
return vp_or_err.value() & -EXEC_PAGESIZE;
}
LIBC_INLINE ErrorOr<void *> alloc_stack(size_t stacksize, size_t guardsize) {
int prot = guardsize ? PROT_NONE : PROT_READ | PROT_WRITE;
auto size_or_err = add_no_overflow(stacksize, guardsize);
if (!size_or_err)
return Error{int(size_or_err.error())};
size_t size = size_or_err.value();
long mmap_result = LIBC_NAMESPACE::syscall_impl<long>(
MMAP_SYSCALL_NUMBER,
0,
size, prot,
MAP_ANONYMOUS | MAP_PRIVATE,
-1,
0
);
if (mmap_result < 0 && (uintptr_t(mmap_result) >= UINTPTR_MAX - size))
return Error{int(-mmap_result)};
if (guardsize) {
long result = LIBC_NAMESPACE::syscall_impl<long>(
SYS_mprotect, mmap_result + guardsize, stacksize,
PROT_READ | PROT_WRITE);
if (result != 0)
return Error{int(-result)};
}
mmap_result += guardsize;
return reinterpret_cast<void *>(mmap_result);
}
[[gnu::always_inline]] LIBC_INLINE void
free_stack(void *stack, size_t stacksize, size_t guardsize) {
uintptr_t stackaddr = reinterpret_cast<uintptr_t>(stack);
stackaddr -= guardsize;
stack = reinterpret_cast<void *>(stackaddr);
LIBC_NAMESPACE::syscall_impl<long>(SYS_munmap, stack, stacksize + guardsize);
}
struct Thread;
struct alignas(STACK_ALIGNMENT) StartArgs {
ThreadAttributes *thread_attrib;
ThreadRunner runner;
void *arg;
};
[[gnu::always_inline]] LIBC_INLINE void
cleanup_thread_resources(ThreadAttributes *attrib) {
cleanup_tls(attrib->tls, attrib->tls_size);
if (attrib->owned_stack)
free_stack(attrib->stack, attrib->stacksize, attrib->guardsize);
}
[[gnu::always_inline]] LIBC_INLINE uintptr_t get_start_args_addr() {
#ifdef LIBC_TARGET_ARCH_IS_X86_64
return reinterpret_cast<uintptr_t>(__builtin_frame_address(0))
+ sizeof(uintptr_t) * 2;
#elif defined(LIBC_TARGET_ARCH_IS_AARCH64)
return reinterpret_cast<uintptr_t>(__builtin_frame_address(1));
#elif defined(LIBC_TARGET_ARCH_IS_ANY_RISCV)
return reinterpret_cast<uintptr_t>(__builtin_frame_address(0));
#endif
}
[[gnu::noinline]] void start_thread() {
auto *start_args = reinterpret_cast<StartArgs *>(get_start_args_addr());
auto *attrib = start_args->thread_attrib;
self.attrib = attrib;
self.attrib->atexit_callback_mgr = internal::get_thread_atexit_callback_mgr();
if (attrib->style == ThreadStyle::POSIX) {
attrib->retval.posix_retval =
start_args->runner.posix_runner(start_args->arg);
thread_exit(ThreadReturnValue(attrib->retval.posix_retval),
ThreadStyle::POSIX);
} else {
attrib->retval.stdc_retval =
start_args->runner.stdc_runner(start_args->arg);
thread_exit(ThreadReturnValue(attrib->retval.stdc_retval),
ThreadStyle::STDC);
}
}
int Thread::run(ThreadStyle style, ThreadRunner runner, void *arg, void *stack,
size_t stacksize, size_t guardsize, bool detached) {
bool owned_stack = false;
if (stack == nullptr) {
if (stacksize == 0)
stacksize = DEFAULT_STACKSIZE;
auto round_or_err = round_to_page(guardsize);
if (!round_or_err)
return round_or_err.error();
guardsize = round_or_err.value();
round_or_err = round_to_page(stacksize);
if (!round_or_err)
return round_or_err.error();
stacksize = round_or_err.value();
auto alloc = alloc_stack(stacksize, guardsize);
if (!alloc)
return alloc.error();
else
stack = alloc.value();
owned_stack = true;
}
uintptr_t stackaddr = reinterpret_cast<uintptr_t>(stack);
if ((stackaddr % STACK_ALIGNMENT != 0) ||
((stackaddr + stacksize) % STACK_ALIGNMENT != 0)) {
if (owned_stack)
free_stack(stack, stacksize, guardsize);
return EINVAL;
}
TLSDescriptor tls;
init_tls(tls);
static constexpr size_t INTERNAL_STACK_DATA_SIZE =
sizeof(StartArgs) + sizeof(ThreadAttributes) + sizeof(Futex);
static_assert(INTERNAL_STACK_DATA_SIZE < EXEC_PAGESIZE);
auto adjusted_stack_or_err =
add_no_overflow(reinterpret_cast<uintptr_t>(stack), stacksize);
if (!adjusted_stack_or_err) {
cleanup_tls(tls.addr, tls.size);
if (owned_stack)
free_stack(stack, stacksize, guardsize);
return adjusted_stack_or_err.error();
}
uintptr_t adjusted_stack =
adjusted_stack_or_err.value() - INTERNAL_STACK_DATA_SIZE;
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->stacksize = stacksize;
attrib->guardsize = guardsize;
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<Futex *>(
adjusted_stack + sizeof(StartArgs) + sizeof(ThreadAttributes));
clear_tid->set(CLEAR_TID_VALUE);
attrib->platform_data = clear_tid;
#if defined(LIBC_TARGET_ARCH_IS_X86_64)
long register clone_result asm(CLONE_RESULT_REGISTER);
clone_result = LIBC_NAMESPACE::syscall_impl<long>(
SYS_clone, CLONE_SYSCALL_FLAGS, adjusted_stack,
&attrib->tid,
&clear_tid->val,
tls.tp
);
#elif defined(LIBC_TARGET_ARCH_IS_AARCH64) || \
defined(LIBC_TARGET_ARCH_IS_ANY_RISCV)
long register clone_result asm(CLONE_RESULT_REGISTER);
clone_result = LIBC_NAMESPACE::syscall_impl<long>(
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 LIBC_TARGET_ARCH_IS_AARCH64
#ifdef __clang__
__arm_wsr64("x29", __arm_rsr64("sp"));
#else
asm volatile("mov x29, sp");
#endif
#elif defined(LIBC_TARGET_ARCH_IS_ANY_RISCV)
asm volatile("mv fp, sp");
#endif
start_thread();
} else if (clone_result < 0) {
cleanup_thread_resources(attrib);
return static_cast<int>(-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<Futex *>(attrib->platform_data);
while (clear_tid->load() != 0)
clear_tid->wait(CLEAR_TID_VALUE, cpp::nullopt, true);
}
bool Thread::operator==(const Thread &thread) const {
return attrib->tid == thread.attrib->tid;
}
static constexpr cpp::string_view THREAD_NAME_PATH_PREFIX("/proc/self/task/");
static constexpr size_t THREAD_NAME_PATH_SIZE =
THREAD_NAME_PATH_PREFIX.size() +
IntegerToString<int>::buffer_size() +
1 +
5;
static void construct_thread_name_file_path(cpp::StringStream &stream,
int tid) {
stream << THREAD_NAME_PATH_PREFIX << tid << '/' << cpp::string_view("comm")
<< cpp::StringStream::ENDS;
}
int Thread::set_name(const cpp::string_view &name) {
if (name.size() >= NAME_SIZE_MAX)
return ERANGE;
if (*this == self) {
int retval =
LIBC_NAMESPACE::syscall_impl<int>(SYS_prctl, PR_SET_NAME, name.data());
if (retval < 0)
return -retval;
else
return 0;
}
char path_name_buffer[THREAD_NAME_PATH_SIZE];
cpp::StringStream path_stream(path_name_buffer);
construct_thread_name_file_path(path_stream, attrib->tid);
#ifdef SYS_open
int fd =
LIBC_NAMESPACE::syscall_impl<int>(SYS_open, path_name_buffer, O_RDWR);
#else
int fd = LIBC_NAMESPACE::syscall_impl<int>(SYS_openat, AT_FDCWD,
path_name_buffer, O_RDWR);
#endif
if (fd < 0)
return -fd;
int retval = LIBC_NAMESPACE::syscall_impl<int>(SYS_write, fd, name.data(),
name.size());
LIBC_NAMESPACE::syscall_impl<long>(SYS_close, fd);
if (retval < 0)
return -retval;
else if (retval != int(name.size()))
return EIO;
else
return 0;
}
int Thread::get_name(cpp::StringStream &name) const {
if (name.bufsize() < NAME_SIZE_MAX)
return ERANGE;
char name_buffer[NAME_SIZE_MAX];
if (*this == self) {
int retval =
LIBC_NAMESPACE::syscall_impl<int>(SYS_prctl, PR_GET_NAME, name_buffer);
if (retval < 0)
return -retval;
name << name_buffer << cpp::StringStream::ENDS;
return 0;
}
char path_name_buffer[THREAD_NAME_PATH_SIZE];
cpp::StringStream path_stream(path_name_buffer);
construct_thread_name_file_path(path_stream, attrib->tid);
#ifdef SYS_open
int fd =
LIBC_NAMESPACE::syscall_impl<int>(SYS_open, path_name_buffer, O_RDONLY);
#else
int fd = LIBC_NAMESPACE::syscall_impl<int>(SYS_openat, AT_FDCWD,
path_name_buffer, O_RDONLY);
#endif
if (fd < 0)
return -fd;
int retval = LIBC_NAMESPACE::syscall_impl<int>(SYS_read, fd, name_buffer,
NAME_SIZE_MAX);
LIBC_NAMESPACE::syscall_impl<long>(SYS_close, fd);
if (retval < 0)
return -retval;
if (retval == NAME_SIZE_MAX)
return ERANGE;
if (name_buffer[retval - 1] == '\n')
name_buffer[retval - 1] = '\0';
else
name_buffer[retval] = '\0';
name << name_buffer << cpp::StringStream::ENDS;
return 0;
}
void thread_exit(ThreadReturnValue retval, ThreadStyle style) {
auto attrib = self.attrib;
internal::call_atexit_callbacks(attrib);
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);
LIBC_NAMESPACE::syscall_impl<long>(SYS_set_tid_address, 0);
LIBC_NAMESPACE::syscall_impl<long>(SYS_exit, 0);
__builtin_unreachable();
}
if (style == ThreadStyle::POSIX)
LIBC_NAMESPACE::syscall_impl<long>(SYS_exit, retval.posix_retval);
else
LIBC_NAMESPACE::syscall_impl<long>(SYS_exit, retval.stdc_retval);
__builtin_unreachable();
}
pid_t Thread::get_uncached_tid() { return syscall_impl<pid_t>(SYS_gettid); }
}