diff --git a/include/alltypes.h.in b/include/alltypes.h.in index 8478fb4..4cd2cb1 100644 --- a/include/alltypes.h.in +++ b/include/alltypes.h.in @@ -58,6 +58,10 @@ TYPEDEF struct { unsigned __attr; } pthread_mutexattr_t; TYPEDEF struct { unsigned __attr; } pthread_condattr_t; TYPEDEF struct { unsigned __attr; } pthread_barrierattr_t; TYPEDEF struct { unsigned __attr[2]; } pthread_rwlockattr_t; +TYPEDEF pthread_cond_t cnd_t; +TYPEDEF pthread_mutex_t mtx_t; + + TYPEDEF struct _IO_FILE FILE; diff --git a/include/threads.h b/include/threads.h new file mode 100644 index 0000000..e131781 --- /dev/null +++ b/include/threads.h @@ -0,0 +1,169 @@ +#ifndef _THREADS_H +#define _THREADS_H + +/* This one is explicitly allowed to be included. */ +#include + + +#ifdef __cplusplus +extern "C" { +#endif + +#define __NEED_cnd_t +#define __NEED_mtx_t +/* Until we come up with a better naming scheme, we need to expose + some pthread types. */ +#define __NEED_pthread_cond_t +#define __NEED_pthread_mutex_t +#define __NEED_pthread_once_t +#define __NEED_pthread_t +#define __NEED_pthread_key_t + +#include +#include + +typedef pthread_once_t once_flag; +typedef pthread_t thrd_t; +typedef pthread_key_t tss_t; +typedef int (*thrd_start_t)(void*); +typedef void (*tss_dtor_t)(void*); + + +#ifndef __THRD_EXPERIMENTAL +# define __THRD_EXPERIMENTAL 1 +#endif + + /* The following list of 9 integer constants makes up for the binary + compatibility of this C thread implementation. You must never + link code against versions of the C library that do not agree + upon these ABI parameters. + + Additionally this implementation assumes that the 5 types have + the same size across C libraries and that these types can be + initialized by the default initializer. + + The values for the 9 parameters are not fixed for now. Depending + on the choices of other implementations and the evolution of the + C standard they may still change. This should happen rarely, but + you have to consider the C thread feature to be experimental + until then, and be prepared to recompile your binary when linking + against a different implementation or a new version. + + The macro __THRD_EXPERIMENTAL will be defined as long as we + consider this ABI to be unstable. This comes with some link time + checks and an overhead of some bytes. At your own risk you may + switch this check off by defining __THRD_EXPERIMENTAL macro to + 0. */ + +#define __THRD_SUCCESS 0 +#define __THRD_BUSY EBUSY +#define __THRD_ERROR EINVAL +#define __THRD_NOMEM ENOMEM +#define __THRD_TIMEDOUT ETIMEDOUT +#define __MTX_PLAIN 0 +#define __MTX_RECURSIVE 1 +#define __MTX_TIMED 2 +#define TSS_DTOR_ITERATIONS 4 + +#define __THRD_CONCAT10_(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9) \ +_0 ## _ ## _1 ## _ ## _2 ## _ ## _3 ## _ ## _4 ## _ ## _5 ## _ ## _6 ## _ ## _7 ## _ ## _8 ## _ ## _9 + +#define __THRD_CONCAT10(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9) \ + __THRD_CONCAT10_(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9) + + +#define __THRD_ABI \ +__THRD_CONCAT10(__thrd_abi, \ + __THRD_SUCCESS, \ + __THRD_BUSY, \ + __THRD_ERROR, \ + __THRD_NOMEM, \ + __THRD_TIMEDOUT, \ + __MTX_PLAIN, \ + __MTX_RECURSIVE, \ + __MTX_TIMED, \ + TSS_DTOR_ITERATIONS \ + ) + +#define __THRD_SHFT(X, Y) (((X) << 3) ^ (Y)) + +enum { + __thrd_abi = + __THRD_SHFT(sizeof(cnd_t), + __THRD_SHFT(sizeof(mtx_t), + __THRD_SHFT(sizeof(once_flag), + __THRD_SHFT(sizeof(thrd_t), + sizeof(tss_t))))), +}; + +extern unsigned const __THRD_ABI; + +#define __THRD_ABI_CHECK (1/(__THRD_ABI == __thrd_abi)) + +#if __THRD_EXPERIMENTAL +# define __THRD_ABI_MARK __attribute__((used)) static unsigned const*const __thrd_abi_mark = &__THRD_ABI +#else +# define __THRD_ABI_MARK typedef unsigned __thrd_abi_dummy_type +#endif + +enum { + thrd_success = __THRD_SUCCESS, + thrd_busy = __THRD_BUSY, + thrd_error = __THRD_ERROR, + thrd_nomem = __THRD_NOMEM, + thrd_timedout = __THRD_TIMEDOUT, +}; + +enum { + mtx_plain = __MTX_PLAIN, + mtx_recursive = __MTX_RECURSIVE, + // all mutexes are timed, here. so this is a no-op + mtx_timed = __MTX_TIMED, +}; + +#define ONCE_FLAG_INIT { 0 } +#define thread_local _Thread_local + +int thrd_create(thrd_t *, thrd_start_t, void *); +_Noreturn void thrd_exit(int); + +int thrd_detach(thrd_t); +int thrd_join(thrd_t, int *); + +int thrd_sleep(const struct timespec *, struct timespec *); +void thrd_yield(void); + +thrd_t thrd_current(void); +int thrd_equal(thrd_t, thrd_t); +#define thrd_equal(A, B) ((A) == (B)) + +void call_once(once_flag *, void (*)(void)); + +int mtx_init(mtx_t *, int); +void mtx_destroy(mtx_t *); + +int mtx_lock(mtx_t *); +int mtx_timedlock(mtx_t *restrict, const struct timespec *restrict); +int mtx_trylock(mtx_t *); +int mtx_unlock(mtx_t *); + +int cnd_init(cnd_t *); +void cnd_destroy(cnd_t *); + +int cnd_broadcast(cnd_t *); +int cnd_signal(cnd_t *); + +int cnd_timedwait(cnd_t *restrict, mtx_t *restrict, const struct timespec *restrict); +int cnd_wait(cnd_t *, mtx_t *); + +int tss_create(tss_t *, tss_dtor_t); +void tss_delete(tss_t key); + +int tss_set(tss_t, void *); +void *tss_get(tss_t); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/sched/thrd_yield.c b/src/sched/thrd_yield.c new file mode 100644 index 0000000..301e953 --- /dev/null +++ b/src/sched/thrd_yield.c @@ -0,0 +1,7 @@ +#include +#include "syscall.h" + +void thrd_yield() +{ + (void)syscall(SYS_sched_yield); +} diff --git a/src/thread/__thrd_abi.c b/src/thread/__thrd_abi.c new file mode 100644 index 0000000..e5674e6 --- /dev/null +++ b/src/thread/__thrd_abi.c @@ -0,0 +1,3 @@ +#include + +unsigned const __THRD_ABI = __thrd_abi; diff --git a/src/thread/__timedwait.c b/src/thread/__timedwait.c index 302273a..168007a 100644 --- a/src/thread/__timedwait.c +++ b/src/thread/__timedwait.c @@ -4,6 +4,8 @@ #include "futex.h" #include "syscall.h" +int __pthread_setcancelstate(int new, int *old); + static int do_wait(volatile int *addr, int val, clockid_t clk, const struct timespec *at, int priv) { @@ -33,13 +35,13 @@ int __timedwait(volatile int *addr, int val, { int r, cs; - if (!cleanup) pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cs); + if (!cleanup) __pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cs); pthread_cleanup_push(cleanup, arg); r = do_wait(addr, val, clk, at, priv); pthread_cleanup_pop(0); - if (!cleanup) pthread_setcancelstate(cs, 0); + if (!cleanup) __pthread_setcancelstate(cs, 0); return r; } diff --git a/src/thread/call_once.c b/src/thread/call_once.c new file mode 100644 index 0000000..742a707 --- /dev/null +++ b/src/thread/call_once.c @@ -0,0 +1,9 @@ +#include "pthread_impl.h" +#include + +int __pthread_once(once_flag *, void (*)(void)); + +void (call_once)(once_flag *flag, void (*func)(void)) { + __THRD_ABI_MARK; + (void)__pthread_once(flag, func); +} diff --git a/src/thread/cnd_broadcast.c b/src/thread/cnd_broadcast.c new file mode 100644 index 0000000..945018f --- /dev/null +++ b/src/thread/cnd_broadcast.c @@ -0,0 +1,12 @@ +#include "pthread_impl.h" +#include + +int __pthread_cond_broadcast(pthread_cond_t *); + +int cnd_broadcast(cnd_t * cnd) { + /* In the best of all worlds this is a tail call. */ + int ret = __pthread_cond_broadcast(cnd); + if (thrd_success) + return ret ? thrd_error : thrd_success; + return ret; +} diff --git a/src/thread/cnd_destroy.c b/src/thread/cnd_destroy.c new file mode 100644 index 0000000..11cfc19 --- /dev/null +++ b/src/thread/cnd_destroy.c @@ -0,0 +1,17 @@ +#include "pthread_impl.h" +#include + +int __pthread_cond_destroy(pthread_cond_t *); + +/* The behavior of cnd_destroy is undefined if cnd is still in + use. The choice for pthread_cond_destroy in that situation is to + wake up all users before destroying. I am not sure that we should + do it like that here, too. Alternatives would be: + - complain by using perror or equivalent + - assert that there is no waiter + - abort when there is a waiter + - do nothing + */ +void (cnd_destroy)(cnd_t *cnd) { + (void)__pthread_cond_destroy(cnd); +} diff --git a/src/thread/cnd_init.c b/src/thread/cnd_init.c new file mode 100644 index 0000000..c7f1edf --- /dev/null +++ b/src/thread/cnd_init.c @@ -0,0 +1,10 @@ +#include "pthread_impl.h" +#include + +int cnd_init(cnd_t * c) +{ + *c = (cnd_t) { + 0 + }; + return thrd_success; +} diff --git a/src/thread/cnd_signal.c b/src/thread/cnd_signal.c new file mode 100644 index 0000000..10241c5 --- /dev/null +++ b/src/thread/cnd_signal.c @@ -0,0 +1,12 @@ +#include "pthread_impl.h" +#include + +int __pthread_cond_signal(pthread_cond_t *); + +int cnd_signal(cnd_t * cnd) { + /* In the best of all worlds this is a tail call. */ + int ret = __pthread_cond_signal(cnd); + if (thrd_success) + return ret ? thrd_error : thrd_success; + return ret; +} diff --git a/src/thread/cnd_timedwait.c b/src/thread/cnd_timedwait.c new file mode 100644 index 0000000..9397706 --- /dev/null +++ b/src/thread/cnd_timedwait.c @@ -0,0 +1,22 @@ +#include "pthread_impl.h" +#include + +int __pthread_cond_timedwait(pthread_cond_t *restrict c, pthread_mutex_t *restrict m, const struct timespec *restrict ts); + +int cnd_timedwait(cnd_t *restrict cond, mtx_t *restrict mutex, const struct timespec *restrict ts) { + /* In the best of all worlds this is a tail call. */ + int ret = __pthread_cond_timedwait(cond, mutex, ts); + switch (ret) { + case 0: + if (thrd_success == 0) break; + else return thrd_success; + case ETIMEDOUT: + if (thrd_timedout == ETIMEDOUT) break; + else return thrd_timedout; + case EINVAL: + if (thrd_error == EINVAL) break; + else return thrd_error; + } + /* In case of UB may also return EPERM. */ + return ret; +} diff --git a/src/thread/cnd_wait.c b/src/thread/cnd_wait.c new file mode 100644 index 0000000..39d0a0d --- /dev/null +++ b/src/thread/cnd_wait.c @@ -0,0 +1,11 @@ +#include + +int cnd_wait(cnd_t *cond, mtx_t *mutex) +{ + __THRD_ABI_MARK; + /* Calling cnd_timedwait with a null pointer is an + extension. Such a call is convenient, here since it avoids to + repeat the case analysis that is already done for + cnd_timedwait. */ + return cnd_timedwait(cond, mutex, 0); +} diff --git a/src/thread/mtx_destroy.c b/src/thread/mtx_destroy.c new file mode 100644 index 0000000..482b781 --- /dev/null +++ b/src/thread/mtx_destroy.c @@ -0,0 +1,5 @@ +#include + +void (mtx_destroy)(mtx_t *mtx) { + /* empty */ +} diff --git a/src/thread/mtx_init.c b/src/thread/mtx_init.c new file mode 100644 index 0000000..dcee8c8 --- /dev/null +++ b/src/thread/mtx_init.c @@ -0,0 +1,10 @@ +#include "pthread_impl.h" +#include + +int mtx_init(mtx_t * m, int type) +{ + *m = (pthread_mutex_t) { + ._m_type = ((type&mtx_recursive) ? PTHREAD_MUTEX_RECURSIVE : 0), + }; + return 0; +} diff --git a/src/thread/mtx_lock.c b/src/thread/mtx_lock.c new file mode 100644 index 0000000..89730f1 --- /dev/null +++ b/src/thread/mtx_lock.c @@ -0,0 +1,14 @@ +#include "pthread_impl.h" +#include + +int mtx_lock(mtx_t *m) +{ + __THRD_ABI_MARK; + if (m->_m_type == PTHREAD_MUTEX_NORMAL && !a_cas(&m->_m_lock, 0, EBUSY)) + return thrd_success; + /* Calling mtx_timedlock with a null pointer is an + extension. Such a call is convenient, here, since it avoids + to repeat the case analysis that is already done for + mtx_timedlock. */ + return mtx_timedlock(m, 0); +} diff --git a/src/thread/mtx_timedlock.c b/src/thread/mtx_timedlock.c new file mode 100644 index 0000000..1e3fd37 --- /dev/null +++ b/src/thread/mtx_timedlock.c @@ -0,0 +1,26 @@ +#include "pthread_impl.h" +#include + +int __pthread_mutex_timedlock(pthread_mutex_t *restrict m, const struct timespec *restrict ts); + +int mtx_timedlock(mtx_t *restrict mutex, const struct timespec *restrict ts) { + /* In the best of all worlds this is a tail call. */ + int ret = __pthread_mutex_timedlock(mutex, ts); + switch (ret) { + case 0: + if (thrd_success == 0) break; + else return thrd_success; + case ETIMEDOUT: + if (thrd_timedout == ETIMEDOUT) break; + else return thrd_timedout; + case EINVAL: + if (thrd_error == EINVAL) break; + else return thrd_error; + } + /* In case of UB may also return EPERM, EDEADLK or EAGAIN. EAGAIN is + specially tricky since C11 doesn't define how many recursive + calls can be done. (this *isn't* the maximum amount of nested + calls!) This implementation here deals this with a counter and + detects overflow, so this is definitively UB. */ + return ret; +} diff --git a/src/thread/mtx_trylock.c b/src/thread/mtx_trylock.c new file mode 100644 index 0000000..4b01bf7 --- /dev/null +++ b/src/thread/mtx_trylock.c @@ -0,0 +1,29 @@ +#include "pthread_impl.h" +#include + +int __pthread_mutex_trylock(pthread_mutex_t *restrict m); + +int mtx_trylock(mtx_t *restrict m) { + if (m->_m_type == PTHREAD_MUTEX_NORMAL) + return a_cas(&m->_m_lock, 0, EBUSY) & EBUSY; + + /* In the best of all worlds this is a tail call. */ + int ret = __pthread_mutex_trylock(m); + switch (ret) { + case 0: + if (thrd_success == 0) break; + else return thrd_success; + case EBUSY: + if (thrd_busy == EBUSY) break; + else return thrd_busy; + case EINVAL: + if (thrd_error == EINVAL) break; + else return thrd_error; + } + /* In case of UB may also return EPERM, EDEADLK or EAGAIN. EAGAIN is + specially tricky since C11 doesn't define how many recursive + calls can be done. (this *isn't* the maximum amount of nested + calls!) This implementation here deals this with a counter and + detects overflow, so this is definitively UB. */ + return ret; +} diff --git a/src/thread/mtx_unlock.c b/src/thread/mtx_unlock.c new file mode 100644 index 0000000..d774b81 --- /dev/null +++ b/src/thread/mtx_unlock.c @@ -0,0 +1,13 @@ +#include "pthread_impl.h" +#include + +int __pthread_mutex_unlock(pthread_mutex_t *); + +int (mtx_unlock)(mtx_t *mtx) { + /* In the best of all worlds this is a tail call. */ + int ret = __pthread_mutex_unlock(mtx); + if (thrd_success) + return ret ? thrd_error : thrd_success; + /* In case of UB may also return EPERM. */ + return ret; +} diff --git a/src/thread/pthread_cond_broadcast.c b/src/thread/pthread_cond_broadcast.c index 0901daf..7a9cfd9 100644 --- a/src/thread/pthread_cond_broadcast.c +++ b/src/thread/pthread_cond_broadcast.c @@ -1,6 +1,6 @@ #include "pthread_impl.h" -int pthread_cond_broadcast(pthread_cond_t *c) +int __pthread_cond_broadcast(pthread_cond_t *c) { pthread_mutex_t *m; @@ -37,3 +37,5 @@ out: return 0; } + +weak_alias(__pthread_cond_broadcast, pthread_cond_broadcast); diff --git a/src/thread/pthread_cond_destroy.c b/src/thread/pthread_cond_destroy.c index a096c55..5d99e88 100644 --- a/src/thread/pthread_cond_destroy.c +++ b/src/thread/pthread_cond_destroy.c @@ -1,6 +1,6 @@ #include "pthread_impl.h" -int pthread_cond_destroy(pthread_cond_t *c) +int __pthread_cond_destroy(pthread_cond_t *c) { int priv = c->_c_mutex != (void *)-1; int cnt; @@ -11,3 +11,5 @@ int pthread_cond_destroy(pthread_cond_t *c) __wait(&c->_c_waiters, 0, cnt, priv); return 0; } + +weak_alias(__pthread_cond_destroy, pthread_cond_destroy); diff --git a/src/thread/pthread_cond_signal.c b/src/thread/pthread_cond_signal.c index 71bcdcd..38754f2 100644 --- a/src/thread/pthread_cond_signal.c +++ b/src/thread/pthread_cond_signal.c @@ -1,9 +1,11 @@ #include "pthread_impl.h" -int pthread_cond_signal(pthread_cond_t *c) +int __pthread_cond_signal(pthread_cond_t *c) { if (!c->_c_waiters) return 0; a_inc(&c->_c_seq); if (c->_c_waiters) __wake(&c->_c_seq, 1, 0); return 0; } + +weak_alias(__pthread_cond_signal, pthread_cond_signal); diff --git a/src/thread/pthread_cond_timedwait.c b/src/thread/pthread_cond_timedwait.c index 99d62cc..819a7af 100644 --- a/src/thread/pthread_cond_timedwait.c +++ b/src/thread/pthread_cond_timedwait.c @@ -1,5 +1,9 @@ #include "pthread_impl.h" +void __pthread_testcancel(void); +int __pthread_mutex_lock(pthread_mutex_t *); +int __pthread_mutex_unlock(pthread_mutex_t *m); + struct cm { pthread_cond_t *c; pthread_mutex_t *m; @@ -33,10 +37,10 @@ static void cleanup(void *p) { struct cm *cm = p; unwait(cm->c, cm->m); - pthread_mutex_lock(cm->m); + __pthread_mutex_lock(cm->m); } -int pthread_cond_timedwait(pthread_cond_t *restrict c, pthread_mutex_t *restrict m, const struct timespec *restrict ts) +int __pthread_cond_timedwait(pthread_cond_t *restrict c, pthread_mutex_t *restrict m, const struct timespec *restrict ts) { struct cm cm = { .c=c, .m=m }; int r, e=0, seq; @@ -47,7 +51,7 @@ int pthread_cond_timedwait(pthread_cond_t *restrict c, pthread_mutex_t *restrict if (ts && ts->tv_nsec >= 1000000000UL) return EINVAL; - pthread_testcancel(); + __pthread_testcancel(); a_inc(&c->_c_waiters); @@ -62,7 +66,7 @@ int pthread_cond_timedwait(pthread_cond_t *restrict c, pthread_mutex_t *restrict seq = c->_c_seq; - pthread_mutex_unlock(m); + __pthread_mutex_unlock(m); do e = __timedwait(&c->_c_seq, seq, c->_c_clock, ts, cleanup, &cm, 0); while (c->_c_seq == seq && (!e || e==EINTR)); @@ -70,7 +74,9 @@ int pthread_cond_timedwait(pthread_cond_t *restrict c, pthread_mutex_t *restrict unwait(c, m); - if ((r=pthread_mutex_lock(m))) return r; + if ((r=__pthread_mutex_lock(m))) return r; return e; } + +weak_alias(__pthread_cond_timedwait, pthread_cond_timedwait); diff --git a/src/thread/pthread_cond_wait.c b/src/thread/pthread_cond_wait.c index 8735bf1..58656f7 100644 --- a/src/thread/pthread_cond_wait.c +++ b/src/thread/pthread_cond_wait.c @@ -1,6 +1,8 @@ #include "pthread_impl.h" +int __pthread_cond_timedwait(pthread_cond_t *restrict c, pthread_mutex_t *restrict m, const struct timespec *restrict ts); + int pthread_cond_wait(pthread_cond_t *restrict c, pthread_mutex_t *restrict m) { - return pthread_cond_timedwait(c, m, 0); + return __pthread_cond_timedwait(c, m, 0); } diff --git a/src/thread/pthread_create.c b/src/thread/pthread_create.c index e77e54a..0b24a1c 100644 --- a/src/thread/pthread_create.c +++ b/src/thread/pthread_create.c @@ -3,6 +3,9 @@ #include "stdio_impl.h" #include "libc.h" #include +#include + +#define __THRD_C11 ((void*)(uintptr_t)-1) static void dummy_0() { @@ -11,7 +14,7 @@ weak_alias(dummy_0, __acquire_ptc); weak_alias(dummy_0, __release_ptc); weak_alias(dummy_0, __pthread_tsd_run_dtors); -_Noreturn void pthread_exit(void *result) +_Noreturn void __pthread_exit(void *result) { pthread_t self = __pthread_self(); sigset_t set; @@ -113,6 +116,19 @@ static int start(void *p) return 0; } +static _Noreturn void __thrd_exit(int result) { + __pthread_exit((void*)(intptr_t)result); +} + + +static int start_c11(void *p) +{ + thrd_t self = p; + int (*start)(void*) = (int(*)(void*)) self->start; + __thrd_exit(start(self->start_arg)); + return 0; +} + #define ROUND(x) (((x)+PAGE_SIZE-1)&-PAGE_SIZE) /* pthread_key_create.c overrides this */ @@ -133,10 +149,10 @@ static void init_file_lock(FILE *f) void *__copy_tls(unsigned char *); -int pthread_create(pthread_t *restrict res, const pthread_attr_t *restrict attrp, void *(*entry)(void *), void *restrict arg) +static int __pthread_create(pthread_t *restrict res, const pthread_attr_t *restrict attrp, void *(*entry)(void *), void *restrict arg) { - int ret; - size_t size, guard; + int ret, c11 = (attrp == __THRD_C11); + size_t size, guard = 0; struct pthread *self, *new; unsigned char *map = 0, *stack = 0, *tsd = 0, *stack_limit; unsigned flags = CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND @@ -157,6 +173,9 @@ int pthread_create(pthread_t *restrict res, const pthread_attr_t *restrict attrp self->tsd = (void **)__pthread_tsd_main; libc.threaded = 1; } + if (c11) { + attrp = 0; + } if (attrp) attr = *attrp; __acquire_ptc(); @@ -223,7 +242,10 @@ int pthread_create(pthread_t *restrict res, const pthread_attr_t *restrict attrp new->canary = self->canary; a_inc(&libc.threads_minus_1); - ret = __clone(start, stack, flags, new, &new->tid, TP_ADJ(new), &new->tid); + if (c11) + ret = __clone(start_c11, stack, flags, new, &new->tid, TP_ADJ(new), &new->tid); + else + ret = __clone(start, stack, flags, new, &new->tid, TP_ADJ(new), &new->tid); __release_ptc(); @@ -251,3 +273,19 @@ fail: __release_ptc(); return EAGAIN; } + +static int __thrd_create(thrd_t *thr, + thrd_start_t func, + void *arg) { + /* In the best of all worlds this is a tail call. */ + int ret = __pthread_create(thr, __THRD_C11, (void * (*)(void *))func, arg); + if (thrd_success) + return ret ? thrd_error : thrd_success; + /* In case of UB may also return ENOSYS or EAGAIN. */ + return ret; +} + +weak_alias(__pthread_exit, pthread_exit); +weak_alias(__pthread_create, pthread_create); +weak_alias(__thrd_create, thrd_create); +weak_alias(__thrd_exit, thrd_exit); diff --git a/src/thread/pthread_detach.c b/src/thread/pthread_detach.c index 651c38e..5ca7855 100644 --- a/src/thread/pthread_detach.c +++ b/src/thread/pthread_detach.c @@ -1,11 +1,15 @@ #include "pthread_impl.h" -int pthread_detach(pthread_t t) +int __pthread_join(pthread_t t, void **res); + +int __pthread_detach(pthread_t t) { /* Cannot detach a thread that's already exiting */ if (a_swap(t->exitlock, 1)) - return pthread_join(t, 0); + return __pthread_join(t, 0); t->detached = 2; __unlock(t->exitlock); return 0; } + +weak_alias(__pthread_detach, pthread_detach); diff --git a/src/thread/pthread_equal.c b/src/thread/pthread_equal.c index 3e3df4f..38fb45e 100644 --- a/src/thread/pthread_equal.c +++ b/src/thread/pthread_equal.c @@ -1,4 +1,4 @@ -#include +#include "pthread_impl.h" int (pthread_equal)(pthread_t a, pthread_t b) { diff --git a/src/thread/pthread_getspecific.c b/src/thread/pthread_getspecific.c index b2a282c..bfc4294 100644 --- a/src/thread/pthread_getspecific.c +++ b/src/thread/pthread_getspecific.c @@ -1,7 +1,10 @@ #include "pthread_impl.h" -void *pthread_getspecific(pthread_key_t k) +static void *__pthread_getspecific(pthread_key_t k) { struct pthread *self = __pthread_self(); return self->tsd[k]; } + +weak_alias(__pthread_getspecific, pthread_getspecific); +weak_alias(__pthread_getspecific, tss_get); diff --git a/src/thread/pthread_join.c b/src/thread/pthread_join.c index 719c91c..1b59489 100644 --- a/src/thread/pthread_join.c +++ b/src/thread/pthread_join.c @@ -5,7 +5,7 @@ static void dummy(void *p) { } -int pthread_join(pthread_t t, void **res) +int __pthread_join(pthread_t t, void **res) { int tmp; while ((tmp = t->tid)) __timedwait(&t->tid, tmp, 0, 0, dummy, 0, 0); @@ -13,3 +13,5 @@ int pthread_join(pthread_t t, void **res) if (t->map_base) munmap(t->map_base, t->map_size); return 0; } + +weak_alias(__pthread_join, pthread_join); diff --git a/src/thread/pthread_key_create.c b/src/thread/pthread_key_create.c index a9187f7..bfcd597 100644 --- a/src/thread/pthread_key_create.c +++ b/src/thread/pthread_key_create.c @@ -9,7 +9,7 @@ static void nodtor(void *dummy) { } -int pthread_key_create(pthread_key_t *k, void (*dtor)(void *)) +int __pthread_key_create(pthread_key_t *k, void (*dtor)(void *)) { unsigned i = (uintptr_t)&k / 16 % PTHREAD_KEYS_MAX; unsigned j = i; @@ -31,7 +31,7 @@ int pthread_key_create(pthread_key_t *k, void (*dtor)(void *)) return EAGAIN; } -int pthread_key_delete(pthread_key_t k) +int __pthread_key_delete(pthread_key_t k) { keys[k] = 0; return 0; @@ -53,3 +53,6 @@ void __pthread_tsd_run_dtors() } } } + +weak_alias(__pthread_key_delete, pthread_key_delete); +weak_alias(__pthread_key_create, pthread_key_create); diff --git a/src/thread/pthread_mutex_lock.c b/src/thread/pthread_mutex_lock.c index 42b5af6..9ea4a97 100644 --- a/src/thread/pthread_mutex_lock.c +++ b/src/thread/pthread_mutex_lock.c @@ -1,9 +1,13 @@ #include "pthread_impl.h" -int pthread_mutex_lock(pthread_mutex_t *m) +int __pthread_mutex_timedlock(pthread_mutex_t *restrict m, const struct timespec *restrict at); + +int __pthread_mutex_lock(pthread_mutex_t *m) { if (m->_m_type == PTHREAD_MUTEX_NORMAL && !a_cas(&m->_m_lock, 0, EBUSY)) return 0; - return pthread_mutex_timedlock(m, 0); + return __pthread_mutex_timedlock(m, 0); } + +weak_alias(__pthread_mutex_lock, pthread_mutex_lock); diff --git a/src/thread/pthread_mutex_timedlock.c b/src/thread/pthread_mutex_timedlock.c index 7b1afc0..8238e70 100644 --- a/src/thread/pthread_mutex_timedlock.c +++ b/src/thread/pthread_mutex_timedlock.c @@ -1,6 +1,6 @@ #include "pthread_impl.h" -int pthread_mutex_timedlock(pthread_mutex_t *restrict m, const struct timespec *restrict at) +int __pthread_mutex_timedlock(pthread_mutex_t *restrict m, const struct timespec *restrict at) { int r, t; @@ -22,3 +22,5 @@ int pthread_mutex_timedlock(pthread_mutex_t *restrict m, const struct timespec * } return r; } + +weak_alias(__pthread_mutex_timedlock, pthread_mutex_timedlock); diff --git a/src/thread/pthread_mutex_trylock.c b/src/thread/pthread_mutex_trylock.c index 00ad65d..8085bc6 100644 --- a/src/thread/pthread_mutex_trylock.c +++ b/src/thread/pthread_mutex_trylock.c @@ -1,6 +1,6 @@ #include "pthread_impl.h" -int pthread_mutex_trylock(pthread_mutex_t *m) +int __pthread_mutex_trylock(pthread_mutex_t *m) { int tid, old, own; pthread_t self; @@ -50,3 +50,5 @@ int pthread_mutex_trylock(pthread_mutex_t *m) return 0; } + +weak_alias(__pthread_mutex_trylock, pthread_mutex_trylock); diff --git a/src/thread/pthread_mutex_unlock.c b/src/thread/pthread_mutex_unlock.c index b4bd74b..3f6ce4a 100644 --- a/src/thread/pthread_mutex_unlock.c +++ b/src/thread/pthread_mutex_unlock.c @@ -3,7 +3,7 @@ void __vm_lock_impl(int); void __vm_unlock_impl(void); -int pthread_mutex_unlock(pthread_mutex_t *m) +int __pthread_mutex_unlock(pthread_mutex_t *m) { pthread_t self; int waiters = m->_m_waiters; @@ -35,3 +35,5 @@ int pthread_mutex_unlock(pthread_mutex_t *m) __wake(&m->_m_lock, 1, 0); return 0; } + +weak_alias(__pthread_mutex_unlock, pthread_mutex_unlock); diff --git a/src/thread/pthread_once.c b/src/thread/pthread_once.c index e01f6d4..a678597 100644 --- a/src/thread/pthread_once.c +++ b/src/thread/pthread_once.c @@ -6,7 +6,7 @@ static void undo(void *control) __wake(control, 1, 0); } -int pthread_once(pthread_once_t *control, void (*init)(void)) +int __pthread_once(pthread_once_t *control, void (*init)(void)) { static int waiters; @@ -34,3 +34,5 @@ int pthread_once(pthread_once_t *control, void (*init)(void)) return 0; } } + +weak_alias(__pthread_once, pthread_once); diff --git a/src/thread/pthread_self.c b/src/thread/pthread_self.c index 5f9e651..04f3e87 100644 --- a/src/thread/pthread_self.c +++ b/src/thread/pthread_self.c @@ -1,5 +1,7 @@ #include "pthread_impl.h" +/* This is needed because not all arch have __pthread_self as a static + symbol. */ pthread_t pthread_self() { return __pthread_self(); diff --git a/src/thread/pthread_setcancelstate.c b/src/thread/pthread_setcancelstate.c index 060bcdc..2268217 100644 --- a/src/thread/pthread_setcancelstate.c +++ b/src/thread/pthread_setcancelstate.c @@ -1,6 +1,6 @@ #include "pthread_impl.h" -int pthread_setcancelstate(int new, int *old) +int __pthread_setcancelstate(int new, int *old) { if (new > 1U) return EINVAL; if (!libc.has_thread_pointer) return ENOSYS; @@ -9,3 +9,5 @@ int pthread_setcancelstate(int new, int *old) self->canceldisable = new; return 0; } + +weak_alias(__pthread_setcancelstate, pthread_setcancelstate); diff --git a/src/thread/pthread_setcanceltype.c b/src/thread/pthread_setcanceltype.c index bf0a3f3..d493c1b 100644 --- a/src/thread/pthread_setcanceltype.c +++ b/src/thread/pthread_setcanceltype.c @@ -1,11 +1,13 @@ #include "pthread_impl.h" +void __pthread_testcancel(void); + int pthread_setcanceltype(int new, int *old) { struct pthread *self = __pthread_self(); if (new > 1U) return EINVAL; if (old) *old = self->cancelasync; self->cancelasync = new; - if (new) pthread_testcancel(); + if (new) __pthread_testcancel(); return 0; } diff --git a/src/thread/pthread_setspecific.c b/src/thread/pthread_setspecific.c index 55e46a8..dd18b96 100644 --- a/src/thread/pthread_setspecific.c +++ b/src/thread/pthread_setspecific.c @@ -1,6 +1,6 @@ #include "pthread_impl.h" -int pthread_setspecific(pthread_key_t k, const void *x) +int __pthread_setspecific(pthread_key_t k, const void *x) { struct pthread *self = __pthread_self(); /* Avoid unnecessary COW */ @@ -10,3 +10,5 @@ int pthread_setspecific(pthread_key_t k, const void *x) } return 0; } + +weak_alias(__pthread_setspecific, pthread_setspecific); diff --git a/src/thread/pthread_testcancel.c b/src/thread/pthread_testcancel.c index ba5f7c6..ee48e6d 100644 --- a/src/thread/pthread_testcancel.c +++ b/src/thread/pthread_testcancel.c @@ -7,7 +7,9 @@ static void dummy() weak_alias(dummy, __testcancel); -void pthread_testcancel() +void __pthread_testcancel() { __testcancel(); } + +weak_alias(__pthread_testcancel, pthread_testcancel); diff --git a/src/thread/thrd_current.c b/src/thread/thrd_current.c new file mode 100644 index 0000000..39f8b89 --- /dev/null +++ b/src/thread/thrd_current.c @@ -0,0 +1,9 @@ +#include "pthread_impl.h" +#include + +/* This is needed because not all arch have __pthread_self as a static + symbol. */ +pthread_t thrd_current() +{ + return __pthread_self(); +} diff --git a/src/thread/thrd_detach.c b/src/thread/thrd_detach.c new file mode 100644 index 0000000..12dff14 --- /dev/null +++ b/src/thread/thrd_detach.c @@ -0,0 +1,13 @@ +#include "pthread_impl.h" +#include + +int __pthread_detach(pthread_t t); + +int thrd_detach(thrd_t t) +{ + /* In the best of all worlds this is a tail call. */ + int ret = __pthread_detach(t); + if (thrd_success) + return ret ? thrd_error : thrd_success; + return ret; +} diff --git a/src/thread/thrd_equal.c b/src/thread/thrd_equal.c new file mode 100644 index 0000000..ac49a44 --- /dev/null +++ b/src/thread/thrd_equal.c @@ -0,0 +1,6 @@ +#include + +int (thrd_equal)(thrd_t a, thrd_t b) +{ + return a==b; +} diff --git a/src/thread/thrd_join.c b/src/thread/thrd_join.c new file mode 100644 index 0000000..8d69839 --- /dev/null +++ b/src/thread/thrd_join.c @@ -0,0 +1,14 @@ +#include "pthread_impl.h" +#include +#include + +/* C11 threads cannot be canceled, so there is no need for a + cancelation function pointer, here. */ +int thrd_join(thrd_t t, int *res) +{ + int tmp; + while ((tmp = t->tid)) __timedwait(&t->tid, tmp, 0, 0, 0, 0, 0); + if (res) *res = (int)(intptr_t)t->result; + if (t->map_base) munmap(t->map_base, t->map_size); + return 0; +} diff --git a/src/thread/tss_create.c b/src/thread/tss_create.c new file mode 100644 index 0000000..0cbadc8 --- /dev/null +++ b/src/thread/tss_create.c @@ -0,0 +1,13 @@ +#include "pthread_impl.h" +#include + +int __pthread_key_create(pthread_key_t *k, void (*dtor)(void *)); + +int tss_create(tss_t *tss, tss_dtor_t dtor) +{ + __THRD_ABI_MARK; + /* Different error returns are possible. C glues them together into + just failure notification. Can't be optimized to a tail call, + unless thrd_error equals EAGAIN. */ + return __pthread_key_create(tss, dtor) ? thrd_error : thrd_success; +} diff --git a/src/thread/tss_delete.c b/src/thread/tss_delete.c new file mode 100644 index 0000000..d50731f --- /dev/null +++ b/src/thread/tss_delete.c @@ -0,0 +1,8 @@ +#include "pthread_impl.h" +#include + +int __pthread_key_delete(pthread_key_t k); + +void (tss_delete)(tss_t key) { + (void)__pthread_key_delete(key); +} diff --git a/src/thread/tss_set.c b/src/thread/tss_set.c new file mode 100644 index 0000000..7e4659c --- /dev/null +++ b/src/thread/tss_set.c @@ -0,0 +1,13 @@ +#include "pthread_impl.h" +#include + +int tss_set(tss_t k, void *x) +{ + struct pthread *self = __pthread_self(); + /* Avoid unnecessary COW */ + if (self->tsd[k] != x) { + self->tsd[k] = x; + self->tsd_used = 1; + } + return 0; +} diff --git a/src/time/thrd_sleep.c b/src/time/thrd_sleep.c new file mode 100644 index 0000000..c595032 --- /dev/null +++ b/src/time/thrd_sleep.c @@ -0,0 +1,23 @@ +#include +#include +#include "syscall.h" +#include "libc.h" +#include "threads.h" + +/* Roughly __syscall already returns the right thing: 0 if all went + well or a negative error indication, otherwise. Unfortunately the C + standard foresees the special case of an interrupted call and fixes + that error return to -1 (instead of introducing EINTR). */ +int thrd_sleep(const struct timespec *req, struct timespec *rem) +{ + int ret = __syscall(SYS_nanosleep, req, rem); + // compile time comparison, constant propagated + if (EINTR != 1) { + switch (ret) { + case -EINTR: return -1; + case -1: return -EINTR; + } + } + // potentially a tail call + return ret; +}