Files
kaguya 9a9b91c940 user: implement mlibc as the libc, finally.
It's finally done..

Signed-off-by: kaguya <vpshinomiya@protonmail.com>
2026-05-02 03:31:49 -04:00

1429 lines
38 KiB
C++

#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>
#include <errno.h>
#include <inttypes.h>
#include <bits/ensure.h>
#include <frg/allocation.hpp>
#include <frg/array.hpp>
#include <mlibc/allocator.hpp>
#include <mlibc/debug.hpp>
#include <mlibc/posix-sysdeps.hpp>
#include <mlibc/thread.hpp>
#include <mlibc/tcb.hpp>
#include <mlibc/tid.hpp>
#include <mlibc/threads.hpp>
static bool enableTrace = false;
struct ScopeTrace {
ScopeTrace(const char *file, int line, const char *function)
: _file(file), _line(line), _function(function) {
if(!enableTrace)
return;
mlibc::infoLogger() << "trace: Enter scope "
<< _file << ":" << _line << " (in function "
<< _function << ")" << frg::endlog;
}
~ScopeTrace() {
if(!enableTrace)
return;
mlibc::infoLogger() << "trace: Exit scope" << frg::endlog;
}
private:
const char *_file;
int _line;
const char *_function;
};
#define SCOPE_TRACE() ScopeTrace(__FILE__, __LINE__, __FUNCTION__)
static constexpr unsigned int mutexRecursive = 1;
// TODO: either use uint32_t or determine the bit based on sizeof(int).
static constexpr unsigned int mutex_owner_mask = (static_cast<uint32_t>(1) << 30) - 1;
static constexpr unsigned int mutex_waiters_bit = static_cast<uint32_t>(1) << 31;
// Only valid for the internal __mlibc_m mutex of wrlocks.
static constexpr unsigned int mutex_excl_bit = static_cast<uint32_t>(1) << 30;
static constexpr unsigned int rc_count_mask = (static_cast<uint32_t>(1) << 31) - 1;
static constexpr unsigned int rc_waiters_bit = static_cast<uint32_t>(1) << 31;
static constexpr size_t default_stacksize = 0x200000;
static constexpr size_t default_guardsize = 4096;
// ----------------------------------------------------------------------------
// pthread_attr and pthread functions.
// ----------------------------------------------------------------------------
// pthread_attr functions.
int pthread_attr_init(pthread_attr_t *attr) {
*attr = pthread_attr_t{};
attr->__mlibc_stacksize = default_stacksize;
attr->__mlibc_guardsize = default_guardsize;
attr->__mlibc_detachstate = PTHREAD_CREATE_JOINABLE;
return 0;
}
int pthread_attr_destroy(pthread_attr_t *) {
return 0;
}
int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate) {
*detachstate = attr->__mlibc_detachstate;
return 0;
}
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate) {
if (detachstate != PTHREAD_CREATE_DETACHED &&
detachstate != PTHREAD_CREATE_JOINABLE)
return EINVAL;
attr->__mlibc_detachstate = detachstate;
return 0;
}
int pthread_attr_getstacksize(const pthread_attr_t *__restrict attr, size_t *__restrict stacksize) {
*stacksize = attr->__mlibc_stacksize;
return 0;
}
int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize) {
if (stacksize < PTHREAD_STACK_MIN)
return EINVAL;
attr->__mlibc_stacksize = stacksize;
return 0;
}
int pthread_attr_getstackaddr(const pthread_attr_t *attr, void **stackaddr) {
*stackaddr = attr->__mlibc_stackaddr;
return 0;
}
int pthread_attr_setstackaddr(pthread_attr_t *attr, void *stackaddr) {
attr->__mlibc_stackaddr = stackaddr;
return 0;
}
int pthread_attr_getstack(const pthread_attr_t *attr, void **stackaddr, size_t *stacksize) {
*stackaddr = attr->__mlibc_stackaddr;
*stacksize = attr->__mlibc_stacksize;
return 0;
}
int pthread_attr_setstack(pthread_attr_t *attr, void *stackaddr, size_t stacksize) {
if (stacksize < PTHREAD_STACK_MIN)
return EINVAL;
attr->__mlibc_stacksize = stacksize;
attr->__mlibc_stackaddr = stackaddr;
return 0;
}
int pthread_attr_getguardsize(const pthread_attr_t *__restrict attr, size_t *__restrict guardsize) {
*guardsize = attr->__mlibc_guardsize;
return 0;
}
int pthread_attr_setguardsize(pthread_attr_t *attr, size_t guardsize) {
attr->__mlibc_guardsize = guardsize;
return 0;
}
int pthread_attr_getscope(const pthread_attr_t *attr, int *scope) {
*scope = attr->__mlibc_scope;
return 0;
}
int pthread_attr_setscope(pthread_attr_t *attr, int scope) {
if (scope != PTHREAD_SCOPE_SYSTEM &&
scope != PTHREAD_SCOPE_PROCESS)
return EINVAL;
if (scope == PTHREAD_SCOPE_PROCESS)
return ENOTSUP;
attr->__mlibc_scope = scope;
return 0;
}
int pthread_attr_getinheritsched(const pthread_attr_t *attr, int *inheritsched) {
*inheritsched = attr->__mlibc_inheritsched;
return 0;
}
int pthread_attr_setinheritsched(pthread_attr_t *attr, int inheritsched) {
if (inheritsched != PTHREAD_INHERIT_SCHED &&
inheritsched != PTHREAD_EXPLICIT_SCHED)
return EINVAL;
attr->__mlibc_inheritsched = inheritsched;
return 0;
}
int pthread_attr_getschedparam(const pthread_attr_t *__restrict attr, struct sched_param *__restrict schedparam) {
*schedparam = attr->__mlibc_schedparam;
return 0;
}
int pthread_attr_setschedparam(pthread_attr_t *__restrict attr, const struct sched_param *__restrict schedparam) {
// TODO: this is supposed to return EINVAL for when the schedparam doesn't make sense
// for the given schedpolicy.
attr->__mlibc_schedparam = *schedparam;
return 0;
}
int pthread_attr_getschedpolicy(const pthread_attr_t *__restrict attr, int *__restrict policy) {
*policy = attr->__mlibc_schedpolicy;
return 0;
}
int pthread_attr_setschedpolicy(pthread_attr_t *__restrict attr, int policy) {
if (policy != SCHED_FIFO && policy != SCHED_RR &&
policy != SCHED_OTHER)
return EINVAL;
attr->__mlibc_schedpolicy = policy;
return 0;
}
#if __MLIBC_LINUX_OPTION
int pthread_attr_getaffinity_np(const pthread_attr_t *__restrict attr,
size_t cpusetsize, cpu_set_t *__restrict cpusetp) {
if (!attr)
return EINVAL;
if (!attr->__mlibc_cpuset) {
memset(cpusetp, -1, cpusetsize);
return 0;
}
for (size_t cnt = cpusetsize; cnt < attr->__mlibc_cpusetsize; cnt++)
if (reinterpret_cast<char*>(attr->__mlibc_cpuset)[cnt] != '\0')
return ERANGE;
auto p = memcpy(cpusetp, attr->__mlibc_cpuset,
std::min(cpusetsize, attr->__mlibc_cpusetsize));
if (cpusetsize > attr->__mlibc_cpusetsize)
memset(p, '\0', cpusetsize - attr->__mlibc_cpusetsize);
return 0;
}
int pthread_attr_setaffinity_np(pthread_attr_t *__restrict attr,
size_t cpusetsize, const cpu_set_t *__restrict cpusetp) {
if (!attr)
return EINVAL;
if (!cpusetp || !cpusetsize) {
attr->__mlibc_cpuset = nullptr;
attr->__mlibc_cpusetsize = 0;
return 0;
}
if (attr->__mlibc_cpusetsize != cpusetsize) {
auto newp = realloc(attr->__mlibc_cpuset, cpusetsize);
if (!newp)
return ENOMEM;
attr->__mlibc_cpuset = static_cast<cpu_set_t*>(newp);
attr->__mlibc_cpusetsize = cpusetsize;
}
memcpy(attr->__mlibc_cpuset, cpusetp, cpusetsize);
return 0;
}
int pthread_attr_getsigmask_np(const pthread_attr_t *__restrict attr,
sigset_t *__restrict sigmask) {
if (!attr)
return EINVAL;
if (!attr->__mlibc_sigmaskset) {
sigemptyset(sigmask);
return PTHREAD_ATTR_NO_SIGMASK_NP;
}
*sigmask = attr->__mlibc_sigmask;
return 0;
}
int pthread_attr_setsigmask_np(pthread_attr_t *__restrict attr,
const sigset_t *__restrict sigmask) {
if (!attr)
return EINVAL;
if (!sigmask) {
attr->__mlibc_sigmaskset = 0;
return 0;
}
attr->__mlibc_sigmask = *sigmask;
attr->__mlibc_sigmaskset = 1;
// Filter out internally used signals.
sigdelset(&attr->__mlibc_sigmask, SIGCANCEL);
return 0;
}
namespace {
void get_own_stackinfo(void **stack_addr, size_t *stack_size) {
auto fp = fopen("/proc/self/maps", "r");
if (!fp) {
mlibc::infoLogger() << "mlibc pthreads: /proc/self/maps does not exist! Producing incorrect"
" stack results!" << frg::endlog;
return;
}
char line[256];
auto sp = mlibc::get_sp();
while (fgets(line, 256, fp)) {
uintptr_t from, to;
if(sscanf(line, "%" SCNxPTR "-%" SCNxPTR, &from, &to) != 2)
continue;
if (sp < to && sp > from) {
// We need to return the lowest byte of the stack.
*stack_addr = reinterpret_cast<void*>(from);
*stack_size = to - from;
fclose(fp);
return;
}
}
fclose(fp);
}
} // namespace
int pthread_getattr_np(pthread_t thread, pthread_attr_t *attr) {
auto tcb = reinterpret_cast<Tcb*>(thread);
*attr = pthread_attr_t{};
if (!tcb->stackAddr || !tcb->stackSize) {
get_own_stackinfo(&attr->__mlibc_stackaddr, &attr->__mlibc_stacksize);
} else {
attr->__mlibc_stacksize = tcb->stackSize;
attr->__mlibc_stackaddr = tcb->stackAddr;
}
attr->__mlibc_guardsize = tcb->guardSize;
attr->__mlibc_detachstate = tcb->isJoinable ? PTHREAD_CREATE_JOINABLE : PTHREAD_CREATE_DETACHED;
mlibc::infoLogger() << "pthread_getattr_np(): Implementation is incomplete!" << frg::endlog;
return 0;
}
int pthread_getaffinity_np(pthread_t thread, size_t cpusetsize, cpu_set_t *mask) {
MLIBC_CHECK_OR_ENOSYS(mlibc::sys_getthreadaffinity, ENOSYS);
return mlibc::sys_getthreadaffinity(reinterpret_cast<Tcb*>(thread)->tid, cpusetsize, mask);
}
int pthread_setaffinity_np(pthread_t thread, size_t cpusetsize, const cpu_set_t *mask) {
MLIBC_CHECK_OR_ENOSYS(mlibc::sys_setthreadaffinity, ENOSYS);
return mlibc::sys_setthreadaffinity(reinterpret_cast<Tcb*>(thread)->tid, cpusetsize, mask);
}
#endif // __MLIBC_LINUX_OPTION
extern "C" Tcb *__rtld_allocateTcb();
// pthread functions.
int pthread_create(pthread_t *__restrict thread, const pthread_attr_t *__restrict attrp,
void *(*entry) (void *), void *__restrict user_arg) {
return mlibc::thread_create(thread, attrp, reinterpret_cast<void *>(entry), user_arg, false);
}
pthread_t pthread_self(void) {
return reinterpret_cast<pthread_t>(mlibc::get_current_tcb());
}
int pthread_equal(pthread_t t1, pthread_t t2) {
if(t1 == t2)
return 1;
return 0;
}
namespace {
struct key_global_info {
bool in_use;
void (*dtor)(void *);
uint64_t generation;
};
constinit frg::array<
key_global_info,
PTHREAD_KEYS_MAX
> key_globals_{};
FutexLock key_mutex_;
} // namespace
namespace mlibc {
__attribute__ ((__noreturn__)) void do_exit() {
sys_thread_exit();
__builtin_unreachable();
}
} // namespace mlibc
__attribute__ ((__noreturn__)) void pthread_exit(void *ret_val) {
auto self = mlibc::get_current_tcb();
if (__atomic_load_n(&self->cancelBits, __ATOMIC_RELAXED) & tcbExitingBit)
mlibc::do_exit();
__atomic_fetch_or(&self->cancelBits, tcbExitingBit, __ATOMIC_RELAXED);
auto hand = self->cleanupEnd;
while (hand) {
auto old = hand;
hand->func(hand->arg);
hand = hand->prev;
frg::destruct(getAllocator(), old);
}
for (size_t j = 0; j < PTHREAD_DESTRUCTOR_ITERATIONS; j++) {
for (size_t i = 0; i < PTHREAD_KEYS_MAX; i++) {
if (auto v = pthread_getspecific(i)) {
key_mutex_.lock();
auto dtor = key_globals_[i].dtor;
key_mutex_.unlock();
if (dtor) {
dtor(v);
(*self->localKeys)[i].value = nullptr;
}
}
}
}
self->returnValue.voidPtr = ret_val;
__atomic_store_n(&self->didExit, 1, __ATOMIC_RELEASE);
mlibc::sys_futex_wake(&self->didExit);
// TODO: clean up thread resources when we are detached.
// TODO: do exit(0) when we're the only thread instead
mlibc::do_exit();
}
int pthread_join(pthread_t thread, void **ret) {
return mlibc::thread_join(thread, ret);
}
int pthread_detach(pthread_t thread) {
auto tcb = reinterpret_cast<Tcb*>(thread);
if (!__atomic_load_n(&tcb->isJoinable, __ATOMIC_RELAXED))
return EINVAL;
int expected = 1;
if(!__atomic_compare_exchange_n(&tcb->isJoinable, &expected, 0, false, __ATOMIC_RELEASE,
__ATOMIC_RELAXED))
return EINVAL;
return 0;
}
void pthread_cleanup_push(void (*func) (void *), void *arg) {
auto self = mlibc::get_current_tcb();
auto hand = frg::construct<Tcb::CleanupHandler>(getAllocator());
hand->func = func;
hand->arg = arg;
hand->next = nullptr;
hand->prev = self->cleanupEnd;
if (self->cleanupEnd)
self->cleanupEnd->next = hand;
self->cleanupEnd = hand;
if (!self->cleanupBegin)
self->cleanupBegin = self->cleanupEnd;
}
void pthread_cleanup_pop(int execute) {
auto self = mlibc::get_current_tcb();
auto hand = self->cleanupEnd;
if (self->cleanupEnd)
self->cleanupEnd = self->cleanupEnd->prev;
if (self->cleanupEnd)
self->cleanupEnd->next = nullptr;
if (execute)
hand->func(hand->arg);
frg::destruct(getAllocator(), hand);
}
int pthread_setname_np(pthread_t thread, const char *name) {
auto tcb = reinterpret_cast<Tcb*>(thread);
auto sysdep = MLIBC_CHECK_OR_ENOSYS(mlibc::sys_thread_setname, ENOSYS);
if(int e = sysdep(tcb, name); e) {
return e;
}
return 0;
}
int pthread_getname_np(pthread_t thread, char *name, size_t size) {
auto tcb = reinterpret_cast<Tcb*>(thread);
auto sysdep = MLIBC_CHECK_OR_ENOSYS(mlibc::sys_thread_getname, ENOSYS);
if(int e = sysdep(tcb, name, size); e) {
return e;
}
return 0;
}
int pthread_setschedparam(pthread_t thread, int policy, const struct sched_param *param) {
auto tcb = reinterpret_cast<Tcb*>(thread);
MLIBC_CHECK_OR_ENOSYS(mlibc::sys_setschedparam, ENOSYS);
if(int e = mlibc::sys_setschedparam(tcb, policy, param); e) {
return e;
}
return 0;
}
int pthread_getschedparam(pthread_t thread, int *policy, struct sched_param *param) {
auto tcb = reinterpret_cast<Tcb*>(thread);
MLIBC_CHECK_OR_ENOSYS(mlibc::sys_getschedparam, ENOSYS);
if(int e = mlibc::sys_getschedparam(tcb, policy, param); e) {
return e;
}
return 0;
}
//pthread cancel functions
extern "C" void __mlibc_do_cancel() {
//TODO(geert): for now the same as pthread_exit()
pthread_exit(PTHREAD_CANCELED);
}
namespace {
void sigcancel_handler(int signal, siginfo_t *info, void *ucontext) {
ucontext_t *uctx = static_cast<ucontext_t*>(ucontext);
// The function could be called from other signals, or from another
// process, in which case we should do nothing.
if (signal != SIGCANCEL || info->si_pid != getpid() ||
info->si_code != SI_TKILL)
return;
auto tcb = reinterpret_cast<Tcb*>(mlibc::get_current_tcb());
int old_value = tcb->cancelBits;
/*
* When a thread is marked with deferred cancellation and performs a blocking syscall,
* the spec mandates that the syscall can get interrupted before it has caused any side
* effects (e.g. before a read() has read any bytes from disk). If the syscall has
* already caused side effects it should return its partial work, and set the program
* counter just after the syscall. If the syscall hasn't caused any side effects, it
* should fail with EINTR and set the program counter to the syscall instruction.
*
* cancellable_syscall:
* test whether_a_cancel_is_queued
* je cancel
* syscall
* end_cancellable_syscall
*
* The mlibc::sys_before_cancellable_syscall sysdep should return 1 when the
* program counter is between the 'canellable_syscall' and 'end_cancellable_syscall' label.
*/
if (!(old_value & tcbCancelAsyncBit) &&
mlibc::sys_before_cancellable_syscall && !mlibc::sys_before_cancellable_syscall(uctx))
return;
int bitmask = tcbCancelTriggerBit | tcbCancelingBit;
while (1) {
int new_value = old_value | bitmask;
// Check if we are already cancelled or exiting
if (old_value == new_value || old_value & tcbExitingBit)
return;
int current_value = old_value;
if (__atomic_compare_exchange_n(&tcb->cancelBits, &current_value,
new_value, true,__ATOMIC_RELAXED, __ATOMIC_RELAXED)) {
tcb->returnValue.voidPtr = PTHREAD_CANCELED;
// Perform cancellation
__mlibc_do_cancel();
break;
}
old_value = current_value;
}
}
} // namespace
namespace mlibc {
namespace {
struct PthreadSignalInstaller {
PthreadSignalInstaller() {
struct sigaction sa;
sa.sa_sigaction = sigcancel_handler;
sa.sa_flags = SA_SIGINFO;
auto e = ENOSYS;
if(sys_sigaction)
e = sys_sigaction(SIGCANCEL, &sa, nullptr);
// Opt-out of cancellation support.
if(e == ENOSYS)
return;
__ensure(!e);
}
};
PthreadSignalInstaller pthread_signal_installer;
} // anonymous namespace
} // namespace mlibc
int pthread_setcanceltype(int type, int *oldtype) {
if (type != PTHREAD_CANCEL_DEFERRED && type != PTHREAD_CANCEL_ASYNCHRONOUS)
return EINVAL;
auto self = reinterpret_cast<Tcb *>(mlibc::get_current_tcb());
int old_value = self->cancelBits;
while (1) {
int new_value = old_value & ~tcbCancelAsyncBit;
if (type == PTHREAD_CANCEL_ASYNCHRONOUS)
new_value |= tcbCancelAsyncBit;
if (oldtype)
*oldtype = ((old_value & tcbCancelAsyncBit)
? PTHREAD_CANCEL_ASYNCHRONOUS
: PTHREAD_CANCEL_DEFERRED);
// Avoid unecessary atomic op.
if (old_value == new_value)
break;
int current_value = old_value;
if (__atomic_compare_exchange_n(&self->cancelBits, &current_value,
new_value, true, __ATOMIC_RELAXED, __ATOMIC_RELAXED)) {
if (mlibc::tcb_async_cancelled(new_value))
__mlibc_do_cancel();
break;
}
old_value = current_value;
}
return 0;
}
int pthread_setcancelstate(int state, int *oldstate) {
if (state != PTHREAD_CANCEL_ENABLE && state != PTHREAD_CANCEL_DISABLE)
return EINVAL;
auto self = reinterpret_cast<Tcb *>(mlibc::get_current_tcb());
int old_value = self->cancelBits;
while (1) {
int new_value = old_value & ~tcbCancelEnableBit;
if (state == PTHREAD_CANCEL_ENABLE)
new_value |= tcbCancelEnableBit;
if (oldstate)
*oldstate = ((old_value & tcbCancelEnableBit)
? PTHREAD_CANCEL_ENABLE
: PTHREAD_CANCEL_DISABLE);
// Avoid unecessary atomic op.
if (old_value == new_value)
break;
int current_value = old_value;
if (__atomic_compare_exchange_n(&self->cancelBits, &current_value,
new_value, true, __ATOMIC_RELAXED, __ATOMIC_RELAXED)) {
if (mlibc::tcb_async_cancelled(new_value))
__mlibc_do_cancel();
sigset_t set = {};
sigaddset(&set, SIGCANCEL);
if (new_value & PTHREAD_CANCEL_ENABLE)
sigprocmask(SIG_UNBLOCK, &set, nullptr);
else
sigprocmask(SIG_BLOCK, &set, nullptr);
break;
}
old_value = current_value;
}
return 0;
}
void pthread_testcancel(void) {
auto self = reinterpret_cast<Tcb *>(mlibc::get_current_tcb());
int value = self->cancelBits;
if ((value & tcbCancelEnableBit) && (value & tcbCancelTriggerBit)) {
__mlibc_do_cancel();
__builtin_unreachable();
}
}
int pthread_cancel(pthread_t thread) {
if (!mlibc::sys_tgkill) {
MLIBC_MISSING_SYSDEP();
return ENOSYS;
}
auto tcb = reinterpret_cast<Tcb *>(thread);
// Check if the TCB is valid, somewhat..
if (tcb->selfPointer != tcb)
return ESRCH;
int old_value = __atomic_load_n(&tcb->cancelBits, __ATOMIC_RELAXED);
while (1) {
int bitmask = tcbCancelTriggerBit;
int new_value = old_value | bitmask;
if (old_value == new_value)
break;
int current_value = old_value;
if (__atomic_compare_exchange_n(&tcb->cancelBits, &current_value,
new_value, true, __ATOMIC_RELAXED, __ATOMIC_RELAXED)) {
if (mlibc::tcb_cancel_enabled(new_value)) {
pid_t pid = getpid();
int res = mlibc::sys_tgkill(pid, tcb->tid, SIGCANCEL);
current_value = __atomic_load_n(&tcb->cancelBits, __ATOMIC_RELAXED);
// If we can't find the thread anymore, it's possible that it exited between
// us setting the cancel trigger bit, and us sending the signal. Check the
// cancelBits for tcbExitingBit to confirm that.
// XXX(qookie): This will be an use-after-free once we start freeing TCBs on
// exit. Perhaps the TCB should be refcounted.
if (!(res == ESRCH && (current_value & tcbExitingBit)))
return res;
}
break;
}
old_value = current_value;
}
return 0;
}
int pthread_atfork(void (*prepare) (void), void (*parent) (void), void (*child) (void)) {
auto self = mlibc::get_current_tcb();
auto hand = frg::construct<Tcb::AtforkHandler>(getAllocator());
if (!hand)
return -1;
hand->prepare = prepare;
hand->parent = parent;
hand->child = child;
hand->next = nullptr;
hand->prev = self->atforkEnd;
if (self->atforkEnd)
self->atforkEnd->next = hand;
self->atforkEnd = hand;
if (!self->atforkBegin)
self->atforkBegin = self->atforkEnd;
return 0;
}
// ----------------------------------------------------------------------------
// pthread_key functions.
// ----------------------------------------------------------------------------
int pthread_key_create(pthread_key_t *out, void (*destructor)(void *)) {
SCOPE_TRACE();
auto g = frg::guard(&key_mutex_);
pthread_key_t key = PTHREAD_KEYS_MAX;
for (size_t i = 0; i < PTHREAD_KEYS_MAX; i++) {
if (!key_globals_[i].in_use) {
key = i;
break;
}
}
if (key == PTHREAD_KEYS_MAX)
return EAGAIN;
key_globals_[key].in_use = true;
key_globals_[key].dtor = destructor;
*out = key;
return 0;
}
int pthread_key_delete(pthread_key_t key) {
SCOPE_TRACE();
auto g = frg::guard(&key_mutex_);
if (key >= PTHREAD_KEYS_MAX || !key_globals_[key].in_use)
return EINVAL;
key_globals_[key].in_use = false;
key_globals_[key].dtor = nullptr;
key_globals_[key].generation++;
return 0;
}
void *pthread_getspecific(pthread_key_t key) {
SCOPE_TRACE();
auto self = mlibc::get_current_tcb();
auto g = frg::guard(&key_mutex_);
if (key >= PTHREAD_KEYS_MAX || !key_globals_[key].in_use)
return nullptr;
if (key_globals_[key].generation > (*self->localKeys)[key].generation) {
(*self->localKeys)[key].value = nullptr;
(*self->localKeys)[key].generation = key_globals_[key].generation;
}
return (*self->localKeys)[key].value;
}
int pthread_setspecific(pthread_key_t key, const void *value) {
SCOPE_TRACE();
auto self = mlibc::get_current_tcb();
auto g = frg::guard(&key_mutex_);
if (key >= PTHREAD_KEYS_MAX || !key_globals_[key].in_use)
return EINVAL;
(*self->localKeys)[key].value = const_cast<void *>(value);
(*self->localKeys)[key].generation = key_globals_[key].generation;
return 0;
}
// ----------------------------------------------------------------------------
// pthread_once functions.
// ----------------------------------------------------------------------------
static constexpr unsigned int onceComplete = 1;
static constexpr unsigned int onceLocked = 2;
int pthread_once(pthread_once_t *once, void (*function) (void)) {
SCOPE_TRACE();
auto expected = __atomic_load_n(&once->__mlibc_done, __ATOMIC_ACQUIRE);
// fast path: the function was already run.
while(!(expected & onceComplete)) {
if(!expected) {
// try to acquire the mutex.
if(!__atomic_compare_exchange_n(&once->__mlibc_done,
&expected, onceLocked, false, __ATOMIC_ACQUIRE, __ATOMIC_ACQUIRE))
continue;
function();
// unlock the mutex.
__atomic_exchange_n(&once->__mlibc_done, onceComplete, __ATOMIC_RELEASE);
if(int e = mlibc::sys_futex_wake((int *)&once->__mlibc_done); e)
__ensure(!"sys_futex_wake() failed");
return 0;
}else{
// a different thread is currently running the initializer.
__ensure(expected == onceLocked);
// if the wait gets interrupted by a signal, check again.
// EAGAIN will also be a retry, as it means the other thread completed
// and changed the __mlibc_done variable to signal it before we actually went to sleep.
if(int e = mlibc::sys_futex_wait((int *)&once->__mlibc_done, onceLocked, nullptr); e && e != EINTR && e != EAGAIN)
__ensure(!"sys_futex_wait() failed");
expected = __atomic_load_n(&once->__mlibc_done, __ATOMIC_ACQUIRE);
}
}
return 0;
}
// ----------------------------------------------------------------------------
// pthread_mutexattr and pthread_mutex functions.
// ----------------------------------------------------------------------------
// pthread_mutexattr functions
int pthread_mutexattr_init(pthread_mutexattr_t *attr) {
SCOPE_TRACE();
return mlibc::thread_mutexattr_init(attr);
}
int pthread_mutexattr_destroy(pthread_mutexattr_t *attr) {
SCOPE_TRACE();
return mlibc::thread_mutexattr_destroy(attr);
}
int pthread_mutexattr_gettype(const pthread_mutexattr_t *__restrict attr, int *__restrict type) {
return mlibc::thread_mutexattr_gettype(attr, type);
}
int pthread_mutexattr_settype(pthread_mutexattr_t *attr, int type) {
return mlibc::thread_mutexattr_settype(attr, type);
}
int pthread_mutexattr_getrobust(const pthread_mutexattr_t *__restrict attr,
int *__restrict robust) {
*robust = attr->__mlibc_robust;
return 0;
}
int pthread_mutexattr_setrobust(pthread_mutexattr_t *attr, int robust) {
if (robust != PTHREAD_MUTEX_STALLED && robust != PTHREAD_MUTEX_ROBUST)
return EINVAL;
attr->__mlibc_robust = robust;
return 0;
}
int pthread_mutexattr_getpshared(const pthread_mutexattr_t *attr, int *pshared) {
*pshared = attr->__mlibc_pshared;
return 0;
}
int pthread_mutexattr_setpshared(pthread_mutexattr_t *attr, int pshared) {
if (pshared != PTHREAD_PROCESS_PRIVATE && pshared != PTHREAD_PROCESS_SHARED)
return EINVAL;
attr->__mlibc_pshared = pshared;
return 0;
}
int pthread_mutexattr_getprotocol(const pthread_mutexattr_t *__restrict attr,
int *__restrict protocol) {
*protocol = attr->__mlibc_protocol;
return 0;
}
int pthread_mutexattr_setprotocol(pthread_mutexattr_t *attr, int protocol) {
if (protocol != PTHREAD_PRIO_NONE && protocol != PTHREAD_PRIO_INHERIT
&& protocol != PTHREAD_PRIO_PROTECT)
return EINVAL;
attr->__mlibc_protocol = protocol;
return 0;
}
int pthread_mutexattr_getprioceiling(const pthread_mutexattr_t *__restrict attr,
int *__restrict prioceiling) {
(void)attr;
(void)prioceiling;
return EINVAL;
}
int pthread_mutexattr_setprioceiling(pthread_mutexattr_t *attr, int prioceiling) {
(void)attr;
(void)prioceiling;
return EINVAL;
}
// pthread_mutex functions
int pthread_mutex_init(pthread_mutex_t *__restrict mutex,
const pthread_mutexattr_t *__restrict attr) {
SCOPE_TRACE();
return mlibc::thread_mutex_init(mutex, attr);
}
int pthread_mutex_destroy(pthread_mutex_t *mutex) {
return mlibc::thread_mutex_destroy(mutex);
}
int pthread_mutex_lock(pthread_mutex_t *mutex) {
SCOPE_TRACE();
return mlibc::thread_mutex_lock(mutex);
}
int pthread_mutex_trylock(pthread_mutex_t *mutex) {
SCOPE_TRACE();
unsigned int this_tid = mlibc::this_tid();
unsigned int expected = __atomic_load_n(&mutex->__mlibc_state, __ATOMIC_RELAXED);
if(!expected) {
// Try to take the mutex here.
if(__atomic_compare_exchange_n(&mutex->__mlibc_state,
&expected, this_tid, false, __ATOMIC_ACQUIRE, __ATOMIC_ACQUIRE)) {
__ensure(!mutex->__mlibc_recursion);
mutex->__mlibc_recursion = 1;
return 0;
}
} else {
// If this (recursive) mutex is already owned by us, increment the recursion level.
if((expected & mutex_owner_mask) == this_tid) {
if(!(mutex->__mlibc_flags & mutexRecursive)) {
return EBUSY;
}
++mutex->__mlibc_recursion;
return 0;
}
}
return EBUSY;
}
int pthread_mutex_timedlock(pthread_mutex_t *__restrict,
const struct timespec *__restrict) {
__ensure(!"Not implemented");
__builtin_unreachable();
}
int pthread_mutex_unlock(pthread_mutex_t *mutex) {
SCOPE_TRACE();
return mlibc::thread_mutex_unlock(mutex);
}
int pthread_mutex_consistent(pthread_mutex_t *) {
__ensure(!"Not implemented");
__builtin_unreachable();
}
// ----------------------------------------------------------------------------
// pthread_condattr and pthread_cond functions.
// ----------------------------------------------------------------------------
int pthread_condattr_init(pthread_condattr_t *attr) {
attr->__mlibc_pshared = PTHREAD_PROCESS_PRIVATE;
attr->__mlibc_clock = CLOCK_REALTIME;
return 0;
}
int pthread_condattr_destroy(pthread_condattr_t *attr) {
memset(attr, 0, sizeof(*attr));
return 0;
}
int pthread_condattr_getclock(const pthread_condattr_t *__restrict attr,
clockid_t *__restrict clock) {
*clock = attr->__mlibc_clock;
return 0;
}
int pthread_condattr_setclock(pthread_condattr_t *attr, clockid_t clock) {
if (clock != CLOCK_REALTIME && clock != CLOCK_MONOTONIC
&& clock != CLOCK_MONOTONIC_RAW && clock != CLOCK_REALTIME_COARSE
&& clock != CLOCK_MONOTONIC_COARSE && clock != CLOCK_BOOTTIME)
return EINVAL;
attr->__mlibc_clock = clock;
return 0;
}
int pthread_condattr_getpshared(const pthread_condattr_t *__restrict attr,
int *__restrict pshared) {
*pshared = attr->__mlibc_pshared;
return 0;
}
int pthread_condattr_setpshared(pthread_condattr_t *attr, int pshared) {
if (pshared != PTHREAD_PROCESS_PRIVATE && pshared != PTHREAD_PROCESS_SHARED)
return EINVAL;
attr->__mlibc_pshared = pshared;
return 0;
}
int pthread_cond_init(pthread_cond_t *__restrict cond, const pthread_condattr_t *__restrict attr) {
SCOPE_TRACE();
return mlibc::thread_cond_init(cond, attr);
}
int pthread_cond_destroy(pthread_cond_t *cond) {
SCOPE_TRACE();
return mlibc::thread_cond_destroy(cond);
}
int pthread_cond_wait(pthread_cond_t *__restrict cond, pthread_mutex_t *__restrict mutex) {
return pthread_cond_timedwait(cond, mutex, nullptr);
}
int pthread_cond_timedwait(pthread_cond_t *__restrict cond, pthread_mutex_t *__restrict mutex,
const struct timespec *__restrict abstime) {
return mlibc::thread_cond_timedwait(cond, mutex, abstime);
}
int pthread_cond_signal(pthread_cond_t *cond) {
SCOPE_TRACE();
return pthread_cond_broadcast(cond);
}
int pthread_cond_broadcast(pthread_cond_t *cond) {
SCOPE_TRACE();
return mlibc::thread_cond_broadcast(cond);
}
// ----------------------------------------------------------------------------
// pthread_barrierattr and pthread_barrier functions.
// ----------------------------------------------------------------------------
int pthread_barrierattr_init(pthread_barrierattr_t *attr) {
attr->__mlibc_pshared = PTHREAD_PROCESS_PRIVATE;
return 0;
}
int pthread_barrierattr_getpshared(const pthread_barrierattr_t *__restrict attr,
int *__restrict pshared) {
*pshared = attr->__mlibc_pshared;
return 0;
}
int pthread_barrierattr_setpshared(pthread_barrierattr_t *attr, int pshared) {
if (pshared != PTHREAD_PROCESS_SHARED && pshared != PTHREAD_PROCESS_PRIVATE)
return EINVAL;
attr->__mlibc_pshared = pshared;
return 0;
}
int pthread_barrierattr_destroy(pthread_barrierattr_t *) {
return 0;
}
int pthread_barrier_init(pthread_barrier_t *__restrict barrier,
const pthread_barrierattr_t *__restrict attr, unsigned count) {
if (count == 0)
return EINVAL;
barrier->__mlibc_waiting = 0;
barrier->__mlibc_inside = 0;
barrier->__mlibc_seq = 0;
barrier->__mlibc_count = count;
// Since we don't implement these yet, set a flag to error later.
auto pshared = attr ? attr->__mlibc_pshared : PTHREAD_PROCESS_PRIVATE;
barrier->__mlibc_flags = pshared;
return 0;
}
int pthread_barrier_destroy(pthread_barrier_t *barrier) {
// Wait until there are no threads still using the barrier.
unsigned inside = 0;
do {
unsigned expected = __atomic_load_n(&barrier->__mlibc_inside, __ATOMIC_RELAXED);
if (expected == 0)
break;
int e = mlibc::sys_futex_wait((int *)&barrier->__mlibc_inside, expected, nullptr);
if (e != 0 && e != EAGAIN && e != EINTR)
mlibc::panicLogger() << "mlibc: sys_futex_wait() returned error " << e << frg::endlog;
} while (inside > 0);
memset(barrier, 0, sizeof *barrier);
return 0;
}
int pthread_barrier_wait(pthread_barrier_t *barrier) {
if (barrier->__mlibc_flags != 0) {
mlibc::panicLogger() << "mlibc: pthread_barrier_t flags were non-zero"
<< frg::endlog;
}
// inside is incremented on entry and decremented on exit.
// This is used to synchronise with pthread_barrier_destroy, to ensure that a thread doesn't pass
// the barrier and immediately destroy its state while other threads still rely on it.
__atomic_fetch_add(&barrier->__mlibc_inside, 1, __ATOMIC_ACQUIRE);
auto leave = [&](){
unsigned inside = __atomic_sub_fetch(&barrier->__mlibc_inside, 1, __ATOMIC_RELEASE);
if (inside == 0)
mlibc::sys_futex_wake((int *)&barrier->__mlibc_inside);
};
unsigned seq = __atomic_load_n(&barrier->__mlibc_seq, __ATOMIC_ACQUIRE);
while (true) {
unsigned expected = __atomic_load_n(&barrier->__mlibc_waiting, __ATOMIC_RELAXED);
bool swapped = __atomic_compare_exchange_n(&barrier->__mlibc_waiting, &expected, expected + 1, false, __ATOMIC_ACQUIRE, __ATOMIC_ACQUIRE);
if (swapped) {
if (expected + 1 == barrier->__mlibc_count) {
// We were the last thread to hit the barrier. Reset waiters and wake the others.
__atomic_fetch_add(&barrier->__mlibc_seq, 1, __ATOMIC_ACQUIRE);
__atomic_store_n(&barrier->__mlibc_waiting, 0, __ATOMIC_RELEASE);
mlibc::sys_futex_wake((int *)&barrier->__mlibc_seq);
leave();
return PTHREAD_BARRIER_SERIAL_THREAD;
}
while (true) {
int e = mlibc::sys_futex_wait((int *)&barrier->__mlibc_seq, seq, nullptr);
if (e != 0 && e != EAGAIN && e != EINTR)
mlibc::panicLogger() << "mlibc: sys_futex_wait() returned error " << e << frg::endlog;
unsigned newSeq = __atomic_load_n(&barrier->__mlibc_seq, __ATOMIC_ACQUIRE);
if (newSeq > seq) {
leave();
return 0;
}
}
}
}
}
// ----------------------------------------------------------------------------
// pthread_rwlock functions.
// ----------------------------------------------------------------------------
namespace {
void rwlock_m_lock(pthread_rwlock_t *rw, bool excl) {
unsigned int m_expected = __atomic_load_n(&rw->__mlibc_m, __ATOMIC_RELAXED);
while(true) {
if(m_expected) {
__ensure(m_expected & mutex_owner_mask);
// Try to set the waiters bit.
if(!(m_expected & mutex_waiters_bit)) {
unsigned int desired = m_expected | mutex_waiters_bit;
if(!__atomic_compare_exchange_n(&rw->__mlibc_m,
&m_expected, desired, false, __ATOMIC_RELAXED, __ATOMIC_RELAXED))
continue;
}
// Wait on the futex.
mlibc::sys_futex_wait((int *)&rw->__mlibc_m, m_expected | mutex_waiters_bit, nullptr);
// Opportunistically try to take the lock after we wake up.
m_expected = 0;
}else{
// Try to lock the mutex.
unsigned int desired = 1;
if(excl)
desired |= mutex_excl_bit;
if(__atomic_compare_exchange_n(&rw->__mlibc_m,
&m_expected, desired, false, __ATOMIC_ACQUIRE, __ATOMIC_RELAXED))
break;
}
}
}
int rwlock_m_trylock(pthread_rwlock_t *rw, bool excl) {
unsigned int m_expected = __atomic_load_n(&rw->__mlibc_m, __ATOMIC_RELAXED);
if(!m_expected) {
// Try to lock the mutex.
unsigned int desired = 1;
if(excl)
desired |= mutex_excl_bit;
if(__atomic_compare_exchange_n(&rw->__mlibc_m,
&m_expected, desired, false, __ATOMIC_ACQUIRE, __ATOMIC_RELAXED))
return 0;
}
__ensure(m_expected & mutex_owner_mask);
// POSIX says that this function should never block but also that
// readers should not be blocked by readers. We implement this by returning EAGAIN
// (and not EBUSY) if a reader would block a reader.
if(!excl && !(m_expected & mutex_excl_bit))
return EAGAIN;
return EBUSY;
}
void rwlock_m_unlock(pthread_rwlock_t *rw) {
auto m = __atomic_exchange_n(&rw->__mlibc_m, 0, __ATOMIC_RELEASE);
if(m & mutex_waiters_bit)
mlibc::sys_futex_wake((int *)&rw->__mlibc_m);
}
} // namespace
int pthread_rwlockattr_init(pthread_rwlockattr_t *attr) {
attr->__mlibc_pshared = PTHREAD_PROCESS_PRIVATE;
return 0;
}
int pthread_rwlockattr_getpshared(const pthread_rwlockattr_t *__restrict attr,
int *__restrict pshared) {
*pshared = attr->__mlibc_pshared;
return 0;
}
int pthread_rwlockattr_setpshared(pthread_rwlockattr_t *attr, int pshared) {
if (pshared != PTHREAD_PROCESS_SHARED && pshared != PTHREAD_PROCESS_PRIVATE)
return EINVAL;
attr->__mlibc_pshared = pshared;
return 0;
}
int pthread_rwlockattr_destroy(pthread_rwlockattr_t *) {
return 0;
}
int pthread_rwlock_init(pthread_rwlock_t *__restrict rw, const pthread_rwlockattr_t *__restrict attr) {
SCOPE_TRACE();
rw->__mlibc_m = 0;
rw->__mlibc_rc = 0;
// Since we don't implement this yet, set a flag to error later.
auto pshared = attr ? attr->__mlibc_pshared : PTHREAD_PROCESS_PRIVATE;
rw->__mlibc_flags = pshared;
return 0;
}
int pthread_rwlock_destroy(pthread_rwlock_t *rw) {
__ensure(!rw->__mlibc_m);
__ensure(!rw->__mlibc_rc);
return 0;
}
int pthread_rwlock_trywrlock(pthread_rwlock_t *rw) {
SCOPE_TRACE();
if (rw->__mlibc_flags != 0) {
mlibc::panicLogger() << "mlibc: pthread_rwlock_t flags were non-zero"
<< frg::endlog;
}
// Take the __mlibc_m mutex.
// Will be released in pthread_rwlock_unlock().
if(int e = rwlock_m_trylock(rw, true))
return e;
// Check that there are no readers.
unsigned int rc_expected = __atomic_load_n(&rw->__mlibc_rc, __ATOMIC_ACQUIRE);
if(rc_expected) {
rwlock_m_unlock(rw);
return EBUSY;
}
return 0;
}
int pthread_rwlock_wrlock(pthread_rwlock_t *rw) {
SCOPE_TRACE();
if (rw->__mlibc_flags != 0) {
mlibc::panicLogger() << "mlibc: pthread_rwlock_t flags were non-zero"
<< frg::endlog;
}
// Take the __mlibc_m mutex.
// Will be released in pthread_rwlock_unlock().
rwlock_m_lock(rw, true);
// Now wait until there are no more readers.
unsigned int rc_expected = __atomic_load_n(&rw->__mlibc_rc, __ATOMIC_ACQUIRE);
while(true) {
if(!rc_expected)
break;
__ensure(rc_expected & rc_count_mask);
// Try to set the waiters bit.
if(!(rc_expected & rc_waiters_bit)) {
unsigned int desired = rc_expected | rc_count_mask;
if(!__atomic_compare_exchange_n(&rw->__mlibc_rc,
&rc_expected, desired, false, __ATOMIC_ACQUIRE, __ATOMIC_ACQUIRE))
continue;
}
// Wait on the futex.
mlibc::sys_futex_wait((int *)&rw->__mlibc_rc, rc_expected | rc_waiters_bit, nullptr);
// Re-check the reader counter.
rc_expected = __atomic_load_n(&rw->__mlibc_rc, __ATOMIC_ACQUIRE);
}
return 0;
}
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rw) {
SCOPE_TRACE();
if (rw->__mlibc_flags != 0) {
mlibc::panicLogger() << "mlibc: pthread_rwlock_t flags were non-zero"
<< frg::endlog;
}
// Increment the reader count while holding the __mlibc_m mutex.
if(int e = rwlock_m_trylock(rw, false); e)
return e;
__atomic_fetch_add(&rw->__mlibc_rc, 1, __ATOMIC_ACQUIRE);
rwlock_m_unlock(rw);
return 0;
}
int pthread_rwlock_rdlock(pthread_rwlock_t *rw) {
SCOPE_TRACE();
if (rw->__mlibc_flags != 0) {
mlibc::panicLogger() << "mlibc: pthread_rwlock_t flags were non-zero"
<< frg::endlog;
}
// Increment the reader count while holding the __mlibc_m mutex.
rwlock_m_lock(rw, false);
__atomic_fetch_add(&rw->__mlibc_rc, 1, __ATOMIC_ACQUIRE);
rwlock_m_unlock(rw);
return 0;
}
int pthread_rwlock_unlock(pthread_rwlock_t *rw) {
SCOPE_TRACE();
unsigned int rc_expected = __atomic_load_n(&rw->__mlibc_rc, __ATOMIC_RELAXED);
if(!rc_expected) {
// We are doing a write-unlock.
rwlock_m_unlock(rw);
return 0;
}else{
// We are doing a read-unlock.
while(true) {
unsigned int count = rc_expected & rc_count_mask;
__ensure(count);
// Try to decrement the count.
if(count == 1 && (rc_expected & rc_waiters_bit)) {
unsigned int desired = 0;
if(!__atomic_compare_exchange_n(&rw->__mlibc_rc,
&rc_expected, desired, false, __ATOMIC_RELEASE, __ATOMIC_RELAXED))
continue;
// Wake the futex.
mlibc::sys_futex_wake((int *)&rw->__mlibc_rc);
break;
}else{
unsigned int desired = (rc_expected & ~rc_count_mask) | (count - 1);
if(!__atomic_compare_exchange_n(&rw->__mlibc_rc,
&rc_expected, desired, false, __ATOMIC_RELEASE, __ATOMIC_RELAXED))
continue;
break;
}
}
return 0;
}
}
int pthread_getcpuclockid(pthread_t, clockid_t *) {
mlibc::infoLogger() << "mlibc: pthread_getcpuclockid() always returns ENOENT"
<< frg::endlog;
return ENOENT;
}