blob: df294e2e4b4fbfc88fb9d989bcd3dfe1bd6a918b [file]
//===--- 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 "config/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/linux/syscall_wrappers/clone.h"
#include "src/__support/OSUtil/linux/syscall_wrappers/close.h"
#include "src/__support/OSUtil/linux/syscall_wrappers/getpid.h"
#include "src/__support/OSUtil/linux/syscall_wrappers/mmap.h"
#include "src/__support/OSUtil/linux/syscall_wrappers/mprotect.h"
#include "src/__support/OSUtil/linux/syscall_wrappers/munmap.h"
#include "src/__support/OSUtil/linux/syscall_wrappers/open.h"
#include "src/__support/OSUtil/linux/syscall_wrappers/read.h"
#include "src/__support/OSUtil/linux/syscall_wrappers/sched_getparam.h"
#include "src/__support/OSUtil/linux/syscall_wrappers/sched_getscheduler.h"
#include "src/__support/OSUtil/linux/syscall_wrappers/sched_setscheduler.h"
#include "src/__support/OSUtil/linux/syscall_wrappers/tgkill.h"
#include "src/__support/OSUtil/linux/syscall_wrappers/write.h"
#include "src/__support/OSUtil/syscall.h" // For syscall functions.
#include "src/__support/common.h"
#include "src/__support/error_or.h"
#include "src/__support/libc_errno.h" // For error macros
#include "src/__support/macros/config.h"
#include "src/__support/threads/linux/futex_utils.h" // For FutexWordType
#include "src/__support/threads/linux/futex_word.h"
#include "src/__support/threads/thread_attributes.h"
#include "hdr/errno_macros.h"
#include "hdr/fcntl_macros.h"
#include "hdr/sched_macros.h" // For CLONE_* flags.
#include "hdr/signal_macros.h"
#include "hdr/stdint_proxy.h"
#include "hdr/sys_mman_macros.h" // For PROT_* and MAP_* definitions.
#include <linux/param.h> // For EXEC_PAGESIZE.
#include <linux/prctl.h> // For PR_SET_NAME
#include <sys/syscall.h> // For syscall numbers.
namespace LIBC_NAMESPACE_DECL {
static constexpr size_t NAME_SIZE_MAX = 16; // Includes the null terminator
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.
| CLONE_SETTLS; // Setup the thread pointer of the new thread.
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) {
// Guard needs to be mapped with PROT_NONE
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();
// TODO: Maybe add MAP_STACK? Currently unimplemented on linux but helps
// future-proof.
ErrorOr<void *> mmap_result =
linux_syscalls::mmap(nullptr, size, prot,
MAP_ANONYMOUS | MAP_PRIVATE, // Process private.
-1, // Not backed by any file
0 // No offset
);
if (!mmap_result.has_value())
return mmap_result;
char *stack = static_cast<char *>(mmap_result.value()) + guardsize;
if (guardsize) {
// Give read/write permissions to actual stack.
// TODO: We are assuming stack growsdown here.
auto result =
linux_syscalls::mprotect(stack, stacksize, PROT_READ | PROT_WRITE);
if (!result)
return Error{result.error()};
}
return stack;
}
// This must always be inlined as we may be freeing the calling threads stack in
// which case a normal return from the top the stack would cause an invalid
// memory read.
[[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);
linux_syscalls::munmap(stack, stacksize + guardsize);
}
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 {
ThreadAttributes *thread_attrib;
ThreadRunner runner;
void *arg;
};
// This must always be inlined as we may be freeing the calling threads stack in
// which case a normal return from the top the stack would cause an invalid
// memory read.
[[gnu::always_inline]] LIBC_INLINE void
cleanup_thread_resources(ThreadAttributes *attrib) {
// Cleanup the TLS before the stack as the TLS information is stored on
// the stack.
cleanup_tls(attrib->tls, attrib->tls_size);
if (attrib->owned_stack)
free_stack(attrib->stack, attrib->stacksize, attrib->guardsize);
}
static int start_thread(void *arg) {
auto *start_args = reinterpret_cast<StartArgs *>(arg);
auto *attrib = start_args->thread_attrib;
if (attrib->style == ThreadStyle::POSIX) {
ThreadReturnValue retval = start_args->runner.posix_runner(start_args->arg);
thread_exit(retval, ThreadStyle::POSIX);
} else {
ThreadReturnValue retval = start_args->runner.stdc_runner(start_args->arg);
thread_exit(retval, ThreadStyle::STDC);
}
return 0;
}
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) {
// TODO: Should we return EINVAL here? Should we have a generic concept of a
// minimum stacksize (like 16384 for pthread).
if (stacksize == 0)
stacksize = DEFAULT_STACKSIZE;
// Roundup stacksize/guardsize to page size.
// TODO: Should be also add sizeof(ThreadAttribute) and other internal
// meta data?
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;
} else {
// The user is responsible for setting up the stack guard (or not) for the
// provided stack.
guardsize = 0;
}
// Validate that stack/stacksize are validly aligned.
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);
// When the new thread is spawned by the kernel, the new thread gets the
// stack we pass to the clone syscall. Thread initialization structures
// (StartArgs, ThreadAttributes, and the clear_tid Futex) are allocated on
// the new thread's stack memory so their lifetime is managed with the stack
// without requiring heap allocation. A pointer to StartArgs is passed
// directly to start_thread via the clone syscall wrapper's argument.
static constexpr size_t INTERNAL_STACK_DATA_SIZE =
sizeof(StartArgs) + sizeof(ThreadAttributes) + sizeof(Futex);
// This is pretty arbitrary, but at the moment we don't adjust user provided
// stacksize (or default) to account for this data as its assumed minimal. If
// this assert starts failing we probably should. Likewise if we can't bound
// this we may overflow when we subtract it from the top of the stack.
static_assert(INTERNAL_STACK_DATA_SIZE < EXEC_PAGESIZE);
// TODO: We are assuming stack growsdown here.
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;
attrib->joiner = nullptr;
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(1);
attrib->platform_data = clear_tid;
get_tcb(tls.tp)->attrib = attrib;
auto clone_result = linux_syscalls::clone(
start_thread, reinterpret_cast<void *>(adjusted_stack),
CLONE_SYSCALL_FLAGS, start_args, &attrib->tid,
reinterpret_cast<void *>(tls.tp),
reinterpret_cast<pid_t *>(&clear_tid->val));
if (!clone_result.has_value()) {
cleanup_thread_resources(attrib);
return clone_result.error();
}
return 0;
}
int Thread::join(ThreadReturnValue &retval) {
if (current_thread().attrib) {
// Reject self join.
if (current_thread().attrib == attrib)
return EDEADLK;
// Do a best-effort check of concurrent/repeated join.
// This cmpxchg establishes exclusive joiner role by setting the joiner
// field iff there is no previous joiner
ThreadAttributes *expected = nullptr;
if (!attrib->joiner.compare_exchange_strong(
expected, current_thread().attrib, cpp::MemoryOrder::ACQ_REL))
return EINVAL;
// Reject mutual join.
if (current_thread().attrib->joiner.load(cpp::MemoryOrder::ACQUIRE) ==
attrib) {
attrib->joiner.store(nullptr, cpp::MemoryOrder::RELEASE);
return EDEADLK;
}
}
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);
}
// 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();
cleanup_thread_resources(attrib);
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<Futex *>(attrib->platform_data);
// We cannot do a FUTEX_WAIT_PRIVATE here as the kernel does a
// FUTEX_WAKE and not a FUTEX_WAKE_PRIVATE.
FutexWordType clear_tid_value;
while ((clear_tid_value = 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() + // Size of tid
1 + // For '/' character
5; // For the file name "comm" and the nullterminator.
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 == current_thread()) {
// If we are setting the name of the current thread, then we can
// use the syscall to set the name.
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);
ErrorOr<int> fd = linux_syscalls::open(path_name_buffer, O_RDWR, 0);
if (!fd)
return fd.error();
auto write_result =
linux_syscalls::write(fd.value(), name.data(), name.size());
linux_syscalls::close(fd.value());
if (!write_result)
return write_result.error();
else if (write_result.value() != static_cast<ssize_t>(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 == current_thread()) {
// If we are getting the name of the current thread, then we can
// use the syscall to get the name.
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);
ErrorOr<int> fd = linux_syscalls::open(path_name_buffer, O_RDONLY, 0);
if (!fd)
return fd.error();
auto read_result =
linux_syscalls::read(fd.value(), name_buffer, NAME_SIZE_MAX);
linux_syscalls::close(fd.value());
if (!read_result)
return read_result.error();
int retval = static_cast<int>(read_result.value());
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;
}
int Thread::setschedparam(SchedParameters params) {
auto result = linux_syscalls::sched_setscheduler(attrib->tid, params.policy,
&params.param);
if (!result.has_value())
return result.error();
return 0;
}
ErrorOr<SchedParameters> Thread::getschedparam() const {
auto pol_result = linux_syscalls::sched_getscheduler(attrib->tid);
if (!pol_result.has_value())
return Error(pol_result.error());
struct sched_param param;
auto param_result = linux_syscalls::sched_getparam(attrib->tid, &param);
if (!param_result.has_value())
return Error(param_result.error());
return SchedParameters{pol_result.value(), param};
}
ErrorOr<void> Thread::kill(int sig) {
auto state = static_cast<DetachState>(
attrib->detach_state.load(cpp::MemoryOrder::RELAXED));
switch (state) {
case DetachState::EXITING:
// The thread is exiting, or has already exited. POSIX.1-2024 requires that
// pthread_kill does not return ESRCH because the pthread_t (unlike the OS
// TID) is still valid. Calling tgkill would return ESRCH (or target a
// recycled TID), so we return success directly. We only need to "request
// that a signal be delivered", and not actually make sure it has been
// handled. A zombie thread cannot handle signals.
return {};
case DetachState::JOINABLE:
case DetachState::DETACHED:
// A thread in these states can handle a signal. Note that a JOINABLE thread
// can transition to the EXITING state at any moment (and a DETACHED thread
// can disappear), but we're not allowed to take any locks to prevent that
// from happening (this function needs to be async-signal-safe).
break;
}
pid_t pid = linux_syscalls::getpid();
auto result = linux_syscalls::tgkill(pid, attrib->tid, sig);
if (!result.has_value()) {
if (result.error() == ESRCH) {
// Either the thread has exited since we've checked its state, or this
// object is corrupted. The latter is UB, so we're going to assume the
// former.
return {};
}
return Error(result.error());
}
return {};
}
void thread_exit(ThreadReturnValue retval, ThreadStyle style) {
auto attrib = current_thread().attrib;
attrib->retval = retval;
// The very first thing we do is to call the thread's atexit callbacks.
// These callbacks could be the ones registered by the language runtimes,
// for example, the destructors of thread local objects. They can also
// be destructors of the TSS objects set using API like pthread_setspecific.
// NOTE: We cannot call the atexit callbacks as part of the
// cleanup_thread_resources function as that function can be called from a
// different thread. The destructors of thread local and TSS objects should
// be called by the thread which owns them.
internal::call_atexit_callbacks();
uint32_t joinable_state = uint32_t(DetachState::JOINABLE);
if (!attrib->detach_state.compare_exchange_strong(
joinable_state, uint32_t(DetachState::EXITING))) {
// Thread is detached so cleanup the resources.
cleanup_thread_resources(attrib);
// Set the CLEAR_TID address to nullptr to prevent the kernel
// from signalling at a non-existent futex location.
LIBC_NAMESPACE::syscall_impl<long>(SYS_set_tid_address, 0);
// Return value for detached thread should be unused. We need to avoid
// referencing `style` or `retval.*` because they may be stored on the stack
// and we have deallocated our stack!
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();
}
} // namespace LIBC_NAMESPACE_DECL