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/mman/mprotect.c b/src/mman/mprotect.c index f488486..535787b 100644 --- a/src/mman/mprotect.c +++ b/src/mman/mprotect.c @@ -2,10 +2,12 @@ #include "libc.h" #include "syscall.h" -int mprotect(void *addr, size_t len, int prot) +int __mprotect(void *addr, size_t len, int prot) { size_t start, end; start = (size_t)addr & -PAGE_SIZE; end = (size_t)((char *)addr + len + PAGE_SIZE-1) & -PAGE_SIZE; return syscall(SYS_mprotect, start, end-start, prot); } + +weak_alias(__mprotect, mprotect); 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..3aed2bd 100644 --- a/src/thread/__timedwait.c +++ b/src/thread/__timedwait.c @@ -4,6 +4,9 @@ #include "futex.h" #include "syscall.h" +int __pthread_setcancelstate(int new, int *old); +int __clock_gettime(clockid_t clk, struct timespec *ts); + static int do_wait(volatile int *addr, int val, clockid_t clk, const struct timespec *at, int priv) { @@ -12,7 +15,7 @@ static int do_wait(volatile int *addr, int val, if (at) { if (at->tv_nsec >= 1000000000UL) return EINVAL; - if (clock_gettime(clk, &to)) return EINVAL; + if (__clock_gettime(clk, &to)) return EINVAL; to.tv_sec = at->tv_sec - to.tv_sec; if ((to.tv_nsec = at->tv_nsec - to.tv_nsec) < 0) { to.tv_sec--; @@ -33,13 +36,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..70fb7a7 --- /dev/null +++ b/src/thread/cnd_broadcast.c @@ -0,0 +1,34 @@ +#include "pthread_impl.h" +#include + +int cnd_broadcast(cnd_t *c) +{ + mtx_t *m; + + if (!c->_c_waiters) return thrd_success; + + a_inc(&c->_c_seq); + + /* Block waiters from returning so we can use the mutex. */ + while (a_swap(&c->_c_lock, 1)) + __wait(&c->_c_lock, &c->_c_lockwait, 1, 1); + if (!c->_c_waiters) + goto out; + m = c->_c_mutex; + + /* Move waiter count to the mutex */ + a_fetch_add(&m->_m_waiters, c->_c_waiters2); + c->_c_waiters2 = 0; + + /* Perform the futex requeue, waking one waiter unless we know + * that the calling thread holds the mutex. */ + __syscall(SYS_futex, &c->_c_seq, FUTEX_REQUEUE, + !m->_m_type || (m->_m_lock&INT_MAX)!=__pthread_self()->tid, + INT_MAX, &m->_m_lock); + +out: + a_store(&c->_c_lock, 0); + if (c->_c_lockwait) __wake(&c->_c_lock, 1, 0); + + return thrd_success; +} diff --git a/src/thread/cnd_destroy.c b/src/thread/cnd_destroy.c new file mode 100644 index 0000000..7d6f3a1 --- /dev/null +++ b/src/thread/cnd_destroy.c @@ -0,0 +1,21 @@ +#include "pthread_impl.h" +#include + +/* 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 *c) +{ + int cnt; + c->_c_destroy = 1; + if (c->_c_waiters) + __wake(&c->_c_seq, -1, 0); + while ((cnt = c->_c_waiters)) + __wait(&c->_c_waiters, 0, cnt, 0); +} 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..3967687 --- /dev/null +++ b/src/thread/cnd_signal.c @@ -0,0 +1,10 @@ +#include "pthread_impl.h" +#include + +int cnd_signal(cnd_t * cnd) { + if (cnd->_c_waiters) { + a_inc(&cnd->_c_seq); + if (cnd->_c_waiters) __wake(&cnd->_c_seq, 1, 0); + } + return thrd_success; +} diff --git a/src/thread/cnd_timedwait.c b/src/thread/cnd_timedwait.c new file mode 100644 index 0000000..18db3ef --- /dev/null +++ b/src/thread/cnd_timedwait.c @@ -0,0 +1,81 @@ +#include "pthread_impl.h" +#include + +void __pthread_testcancel(void); + +struct cm { + cnd_t *c; + mtx_t *m; +}; + +static void unwait(cnd_t *c, mtx_t *m) +{ + __THRD_ABI_MARK; + /* Removing a waiter is non-trivial if we could be using requeue + * based broadcast signals, due to mutex access issues, etc. */ + + while (a_swap(&c->_c_lock, 1)) + __wait(&c->_c_lock, &c->_c_lockwait, 1, 1); + + if (c->_c_waiters2) c->_c_waiters2--; + else a_dec(&m->_m_waiters); + + a_store(&c->_c_lock, 0); + if (c->_c_lockwait) __wake(&c->_c_lock, 1, 1); + + a_dec(&c->_c_waiters); + if (c->_c_destroy) __wake(&c->_c_waiters, 1, 1); +} + +static void cleanup(void *p) +{ + struct cm *cm = p; + unwait(cm->c, cm->m); + mtx_lock(cm->m); +} + +int cnd_timedwait(cnd_t *restrict c, mtx_t *restrict m, const struct timespec *restrict ts) +{ + struct cm cm = { .c=c, .m=m }; + int r, e=0, seq; + + if (m->_m_type && (m->_m_lock&INT_MAX) != __pthread_self()->tid) + return thrd_error; + + if (ts && ts->tv_nsec >= 1000000000UL) + return thrd_error; + + __pthread_testcancel(); + + a_inc(&c->_c_waiters); + + c->_c_mutex = m; + while (a_swap(&c->_c_lock, 1)) + __wait(&c->_c_lock, &c->_c_lockwait, 1, 1); + c->_c_waiters2++; + a_store(&c->_c_lock, 0); + if (c->_c_lockwait) __wake(&c->_c_lock, 1, 1); + + seq = c->_c_seq; + + mtx_unlock(m); + + do e = __timedwait(&c->_c_seq, seq, CLOCK_REALTIME, ts, cleanup, &cm, 0); + while (c->_c_seq == seq && (!e || e==EINTR)); + if (e == EINTR) e = 0; + + unwait(c, m); + + if (mtx_lock(m) != thrd_success) + return thrd_error; + + switch (e) { + case 0: + if (thrd_success == 0) break; + else return thrd_success; + case ETIMEDOUT: + if (thrd_timedout == ETIMEDOUT) break; + else return thrd_timedout; + } + return e; +} diff --git a/src/thread/cnd_wait.c b/src/thread/cnd_wait.c new file mode 100644 index 0000000..91e89db --- /dev/null +++ b/src/thread/cnd_wait.c @@ -0,0 +1,10 @@ +#include + +int cnd_wait(cnd_t *cond, mtx_t *mutex) +{ + /* 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..b86afb0 --- /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 = (mtx_t) { + ._m_type = ((type&mtx_recursive) ? PTHREAD_MUTEX_RECURSIVE : 0), + }; + return thrd_success; +} diff --git a/src/thread/mtx_lock.c b/src/thread/mtx_lock.c new file mode 100644 index 0000000..aa3d031 --- /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, thrd_busy)) + 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..2169c4a --- /dev/null +++ b/src/thread/mtx_timedlock.c @@ -0,0 +1,33 @@ +#include "pthread_impl.h" +#include + +int mtx_timedlock(mtx_t *restrict m, const struct timespec *restrict at) +{ + int r, t; + + if (m->_m_type == PTHREAD_MUTEX_NORMAL && !a_cas(&m->_m_lock, 0, thrd_busy)) + return thrd_success; + + for (;;) { + r=mtx_trylock(m); + if (r != thrd_busy) return r; + else { + if (!(r=m->_m_lock) || (r&0x40000000)) continue; + a_inc(&m->_m_waiters); + t = r | 0x80000000; + a_cas(&m->_m_lock, r, t); + r = __timedwait(&m->_m_lock, t, CLOCK_REALTIME, at, 0, 0, 0); + a_dec(&m->_m_waiters); + switch (r) { + case 0: + break; + case EINTR: + break; + case ETIMEDOUT: + return thrd_timedout; + default: + return thrd_error; + } + } + } +} diff --git a/src/thread/mtx_trylock.c b/src/thread/mtx_trylock.c new file mode 100644 index 0000000..9e3e0a5 --- /dev/null +++ b/src/thread/mtx_trylock.c @@ -0,0 +1,25 @@ +#include "pthread_impl.h" +#include + +int mtx_trylock(mtx_t *m) +{ + int tid, old, own, ret = 0; + pthread_t self; + + if (m->_m_type == PTHREAD_MUTEX_NORMAL) { + return a_cas(&m->_m_lock, 0, thrd_busy) & thrd_busy; + } else { + self = __pthread_self(); + tid = self->tid; + old = m->_m_lock; + own = old & 0x7fffffff; + if (own == tid) { + if ((unsigned)m->_m_count >= INT_MAX) return thrd_error; + else m->_m_count++; + } else { + if ((own && !(own & 0x40000000)) || a_cas(&m->_m_lock, old, tid)!=old) + return thrd_busy; + } + } + return thrd_success; +} diff --git a/src/thread/mtx_unlock.c b/src/thread/mtx_unlock.c new file mode 100644 index 0000000..30aa7f3 --- /dev/null +++ b/src/thread/mtx_unlock.c @@ -0,0 +1,16 @@ +#include "pthread_impl.h" +#include + +int mtx_unlock(mtx_t *m) +{ + if ((m->_m_type&3) == PTHREAD_MUTEX_RECURSIVE && m->_m_count) { + if ((m->_m_lock&0x1fffffff) != __pthread_self()->tid) + return thrd_error; + /* _m_count is the count of additional locks, no need to real unlock */ + --m->_m_count; + } else { + if (a_swap(&m->_m_lock, 0)<0 || m->_m_waiters) + __wake(&m->_m_lock, 1, 0); + } + return thrd_success; +} diff --git a/src/thread/pthread_cond_timedwait.c b/src/thread/pthread_cond_timedwait.c index 99d62cc..f43ca9e 100644 --- a/src/thread/pthread_cond_timedwait.c +++ b/src/thread/pthread_cond_timedwait.c @@ -1,5 +1,7 @@ #include "pthread_impl.h" +void __pthread_testcancel(void); + struct cm { pthread_cond_t *c; pthread_mutex_t *m; @@ -47,7 +49,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); diff --git a/src/thread/pthread_create.c b/src/thread/pthread_create.c index e77e54a..fa73826 100644 --- a/src/thread/pthread_create.c +++ b/src/thread/pthread_create.c @@ -3,6 +3,13 @@ #include "stdio_impl.h" #include "libc.h" #include +#include + +void *__mmap(void *, size_t, int, int, int, off_t); +int __munmap(void *, size_t); +int __mprotect(void *, size_t, int); + +#define __THRD_C11 ((void*)(uintptr_t)-1) static void dummy_0() { @@ -11,7 +18,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 +120,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 +153,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 +177,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(); @@ -184,14 +207,14 @@ int pthread_create(pthread_t *restrict res, const pthread_attr_t *restrict attrp if (!tsd) { if (guard) { - map = mmap(0, size, PROT_NONE, MAP_PRIVATE|MAP_ANON, -1, 0); + map = __mmap(0, size, PROT_NONE, MAP_PRIVATE|MAP_ANON, -1, 0); if (map == MAP_FAILED) goto fail; - if (mprotect(map+guard, size-guard, PROT_READ|PROT_WRITE)) { - munmap(map, size); + if (__mprotect(map+guard, size-guard, PROT_READ|PROT_WRITE)) { + __munmap(map, size); goto fail; } } else { - map = mmap(0, size, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANON, -1, 0); + map = __mmap(0, size, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANON, -1, 0); if (map == MAP_FAILED) goto fail; } tsd = map + size - __pthread_tsd_size; @@ -223,7 +246,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(); @@ -233,7 +259,7 @@ int pthread_create(pthread_t *restrict res, const pthread_attr_t *restrict attrp if (ret < 0) { a_dec(&libc.threads_minus_1); - if (map) munmap(map, size); + if (map) __munmap(map, size); return EAGAIN; } @@ -251,3 +277,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_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_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_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..1728535 --- /dev/null +++ b/src/thread/thrd_current.c @@ -0,0 +1,11 @@ +#include "pthread_impl.h" +#include + +/* Not all arch have __pthread_self as a symbol. On some this results + in some magic. So this "call" to __pthread_self is not necessary a + tail call. In particular, other than the appearance, thrd_current + can not be implemented as a weak 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..0446975 --- /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 thrd_success; +} 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..70c4fb7 --- /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 thrd_success; +} diff --git a/src/time/thrd_sleep.c b/src/time/thrd_sleep.c new file mode 100644 index 0000000..6570b0c --- /dev/null +++ b/src/time/thrd_sleep.c @@ -0,0 +1,31 @@ +#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. */ +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 0: return 0; + /* error described by POSIX: */ + /* EINTR The nanosleep() function was interrupted by a signal. */ + /* The C11 wording is: */ + /* -1 if it has been interrupted by a signal */ + case -EINTR: return -1; + /* error described by POSIX */ + case -EINVAL: return -2; + /* described for linux */ + case -EFAULT: return -3; + /* No other error returns are specified. */ + default: return INT_MIN; + } + } + // potentially a tail call + return ret; +}