]> git.proxmox.com Git - wasi-libc.git/commitdiff
threads: implement support for pthread mutexes (#315)
authorAndrew Brown <andrew.brown@intel.com>
Mon, 22 Aug 2022 15:39:44 +0000 (08:39 -0700)
committerGitHub <noreply@github.com>
Mon, 22 Aug 2022 15:39:44 +0000 (08:39 -0700)
This change adds pthread's mutex support to the `THREAD_MODEL=posix`
build of wasi-libc. Some less-common features are unsupported and
documented here:
- mutex robust lists are disabled and their use will return a runtime
  error; currently WASI does not support the concept of multiple
  processes so maintaining robust mutexes across processes does not yet
  make sense in wasi-libc
- timed locks with priority inheritance (PI) are disabled and will act
  as any other mutex; this feature is related to task priorities which
  is not yet relevant in the WASI ecosystem (see [priority-inheritance
  futexes](https://man7.org/linux/man-pages/man2/futex.2.html))
- thread cancellation is ignored; this feature is difficult to support
  and @sunfishcode would likely want to discuss this before adding it at
  some later time.

14 files changed:
Makefile
expected/wasm32-wasi/posix/defined-symbols.txt
expected/wasm32-wasi/posix/undefined-symbols.txt
libc-top-half/musl/src/internal/pthread_impl.h
libc-top-half/musl/src/internal/syscall.h
libc-top-half/musl/src/thread/__timedwait.c
libc-top-half/musl/src/thread/__wait.c
libc-top-half/musl/src/thread/pthread_mutex_destroy.c
libc-top-half/musl/src/thread/pthread_mutex_timedlock.c
libc-top-half/musl/src/thread/pthread_mutex_trylock.c
libc-top-half/musl/src/thread/pthread_mutex_unlock.c
libc-top-half/musl/src/thread/pthread_mutexattr_setprotocol.c
libc-top-half/musl/src/thread/pthread_mutexattr_setrobust.c
libc-top-half/musl/src/thread/thrd_sleep.c

index 343aa3990c0728a5e45b7f4e5b23b63cbf3f05df..f73371a944a3d4f1462e165d9ed6bdfffa919faf 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -187,6 +187,29 @@ LIBC_TOP_HALF_MUSL_SOURCES = \
                  $(wildcard $(LIBC_TOP_HALF_MUSL_SRC_DIR)/complex/*.c)) \
     $(wildcard $(LIBC_TOP_HALF_MUSL_SRC_DIR)/crypt/*.c)
 
+ifeq ($(THREAD_MODEL), posix)
+LIBC_TOP_HALF_MUSL_SOURCES += \
+    $(addprefix $(LIBC_TOP_HALF_MUSL_SRC_DIR)/, \
+        thread/__wait.c \
+        thread/__timedwait.c \
+        thread/pthread_mutex_consistent.c \
+        thread/pthread_mutex_destroy.c \
+        thread/pthread_mutex_init.c \
+        thread/pthread_mutex_getprioceiling.c \
+        thread/pthread_mutex_lock.c \
+        thread/pthread_mutex_timedlock.c \
+        thread/pthread_mutex_trylock.c \
+        thread/pthread_mutex_unlock.c \
+        thread/pthread_mutexattr_destroy.c \
+        thread/pthread_mutexattr_init.c \
+        thread/pthread_mutexattr_setprotocol.c \
+        thread/pthread_mutexattr_setpshared.c \
+        thread/pthread_mutexattr_setrobust.c \
+        thread/pthread_mutexattr_settype.c \
+        thread/pthread_setcancelstate.c \
+    )
+endif
+
 MUSL_PRINTSCAN_SOURCES = \
     $(LIBC_TOP_HALF_MUSL_SRC_DIR)/internal/floatscan.c \
     $(LIBC_TOP_HALF_MUSL_SRC_DIR)/stdio/vfprintf.c \
index 09152cffa38711c31b5354722bfa1ac5c5f06c65..5dfd0f6e3e946934a3fe07a36b99e5f5b093139b 100644 (file)
@@ -179,6 +179,12 @@ __pow_log_data
 __powf_log2_data
 __progname
 __progname_full
+__pthread_mutex_lock
+__pthread_mutex_timedlock
+__pthread_mutex_trylock
+__pthread_mutex_trylock_owner
+__pthread_mutex_unlock
+__pthread_setcancelstate
 __putenv
 __qsort_r
 __rand48_step
@@ -237,6 +243,8 @@ __sysv_signal
 __tan
 __tandf
 __tanl
+__timedwait
+__timedwait_cp
 __tm_to_secs
 __tm_to_tzname
 __tolower_l
@@ -256,6 +264,7 @@ __uflow
 __unlist_locked_file
 __uselocale
 __utc
+__wait
 __wasi_args_get
 __wasi_args_sizes_get
 __wasi_clock_res_get
@@ -313,6 +322,7 @@ __wasilibc_fd_renumber
 __wasilibc_find_abspath
 __wasilibc_find_relpath
 __wasilibc_find_relpath_alloc
+__wasilibc_futex_wait
 __wasilibc_get_environ
 __wasilibc_iftodt
 __wasilibc_initialize_environ
@@ -914,6 +924,21 @@ program_invocation_name
 program_invocation_short_name
 pselect
 psignal
+pthread_mutex_consistent
+pthread_mutex_destroy
+pthread_mutex_getprioceiling
+pthread_mutex_init
+pthread_mutex_lock
+pthread_mutex_timedlock
+pthread_mutex_trylock
+pthread_mutex_unlock
+pthread_mutexattr_destroy
+pthread_mutexattr_init
+pthread_mutexattr_setprotocol
+pthread_mutexattr_setpshared
+pthread_mutexattr_setrobust
+pthread_mutexattr_settype
+pthread_setcancelstate
 putc
 putc_unlocked
 putchar
index 5b5b8d0328f6e6c168c485ec3ee541154d61b96d..f87e6e3dec8bb781b287a10c3974a41b70eb637f 100644 (file)
@@ -72,4 +72,3 @@ __unlockfile
 __unordtf2
 __wasilibc_pthread_self
 __wasm_call_ctors
-pthread_setcancelstate
index 971f56fe89419eaee9abdc633a55fc96c0730b6f..22e557d580095cd1520ede709aacbb86f4ab1c74 100644 (file)
@@ -170,6 +170,9 @@ hidden int __libc_sigaction(int, const struct sigaction *, struct sigaction *);
 #endif
 hidden void __unmapself(void *, size_t);
 
+#ifndef __wasilibc_unmodified_upstream
+hidden int __wasilibc_futex_wait(volatile void *, int, int, int64_t);
+#endif
 hidden int __timedwait(volatile int *, int, clockid_t, const struct timespec *, int);
 hidden int __timedwait_cp(volatile int *, int, clockid_t, const struct timespec *, int);
 hidden void __wait(volatile int *, volatile int *, int, int);
index d5f294d437fb91ccd7870d8ff360b2bdadde668d..32e0e8ac389357a3e3595717f48bfe9eca33c9b8 100644 (file)
@@ -1,3 +1,4 @@
+#ifdef __wasilibc_unmodified_upstream
 #ifndef _INTERNAL_SYSCALL_H
 #define _INTERNAL_SYSCALL_H
 
@@ -396,3 +397,4 @@ hidden void __procfdname(char __buf[static 15+3*sizeof(int)], unsigned);
 hidden void *__vdsosym(const char *, const char *);
 
 #endif
+#endif
index 666093be98516a1c84b2997f075a8dbfcb797b2c..7d6f6be4adf9be7af547957ae2bf2e2e31525182 100644 (file)
@@ -5,6 +5,7 @@
 #include "syscall.h"
 #include "pthread_impl.h"
 
+#ifdef __wasilibc_unmodified_upstream
 #define IS32BIT(x) !((x)+0x80000000ULL>>32)
 #define CLAMP(x) (int)(IS32BIT(x) ? (x) : 0x7fffffffU+((0ULL+(x))>>63))
 
@@ -28,6 +29,16 @@ static int __futex4_cp(volatile void *addr, int op, int val, const struct timesp
 
 static volatile int dummy = 0;
 weak_alias(dummy, __eintr_valid_flag);
+#else
+static int __futex4_cp(volatile void *addr, int op, int val, const struct timespec *to)
+{
+       int64_t max_wait_ns = -1;
+       if (to) {
+               max_wait_ns = (int64_t)(to->tv_sec * 1000000000 + to->tv_nsec);
+       }
+       return __wasilibc_futex_wait(addr, op, val, max_wait_ns);
+}
+#endif
 
 int __timedwait_cp(volatile int *addr, int val,
        clockid_t clk, const struct timespec *at, int priv)
@@ -51,11 +62,13 @@ int __timedwait_cp(volatile int *addr, int val,
 
        r = -__futex4_cp(addr, FUTEX_WAIT|priv, val, top);
        if (r != EINTR && r != ETIMEDOUT && r != ECANCELED) r = 0;
+#ifdef __wasilibc_unmodified_upstream
        /* Mitigate bug in old kernels wrongly reporting EINTR for non-
         * interrupting (SA_RESTART) signal handlers. This is only practical
         * when NO interrupting signal handlers have been installed, and
         * works by sigaction tracking whether that's the case. */
        if (r == EINTR && !__eintr_valid_flag) r = 0;
+#endif
 
        return r;
 }
index dc33c1a30992e6c04e4482cef8a2f8279d18262a..c0e4aac796cfd6cf4d5b2eda977246f01109e097 100644 (file)
@@ -1,4 +1,38 @@
 #include "pthread_impl.h"
+#ifndef __wasilibc_unmodified_upstream
+#include "assert.h"
+#endif
+
+#ifndef __wasilibc_unmodified_upstream
+// Use WebAssembly's `wait` instruction to implement a futex. Note that `op` is
+// unused but retained as a parameter to match the original signature of the
+// syscall and that, for `max_wait_ns`, -1 (or any negative number) means wait
+// indefinitely.
+//
+// Adapted from Emscripten: see
+// https://github.com/emscripten-core/emscripten/blob/058a9fff/system/lib/pthread/emscripten_futex_wait.c#L111-L150.
+int __wasilibc_futex_wait(volatile void *addr, int op, int val, int64_t max_wait_ns)
+{
+    if ((((intptr_t)addr) & 3) != 0) {
+        return -EINVAL;
+    }
+
+    int ret = __builtin_wasm_memory_atomic_wait32((int *)addr, val, max_wait_ns);
+
+    // memory.atomic.wait32 returns:
+    //   0 => "ok", woken by another agent.
+    //   1 => "not-equal", loaded value != expected value
+    //   2 => "timed-out", the timeout expired
+    if (ret == 1) {
+        return -EWOULDBLOCK;
+    }
+    if (ret == 2) {
+        return -ETIMEDOUT;
+    }
+    assert(ret == 0);
+    return 0;
+}
+#endif
 
 void __wait(volatile int *addr, volatile int *waiters, int val, int priv)
 {
@@ -10,8 +44,12 @@ void __wait(volatile int *addr, volatile int *waiters, int val, int priv)
        }
        if (waiters) a_inc(waiters);
        while (*addr==val) {
+#ifdef __wasilibc_unmodified_upstream
                __syscall(SYS_futex, addr, FUTEX_WAIT|priv, val, 0) != -ENOSYS
                || __syscall(SYS_futex, addr, FUTEX_WAIT, val, 0);
+#else
+               __wasilibc_futex_wait(addr, FUTEX_WAIT, val, 0);
+#endif
        }
        if (waiters) a_dec(waiters);
 }
index 8d1bf77b879212256111446c657209dd7a5cd73c..e53c39c684e797c916f55c638ff577f9278ed9ac 100644 (file)
@@ -2,9 +2,17 @@
 
 int pthread_mutex_destroy(pthread_mutex_t *mutex)
 {
+#ifdef __wasilibc_unmodified_upstream
        /* If the mutex being destroyed is process-shared and has nontrivial
         * type (tracking ownership), it might be in the pending slot of a
         * robust_list; wait for quiescence. */
        if (mutex->_m_type > 128) __vm_wait();
+#else
+       /* For now, wasi-libc chooses to avoid implementing robust mutex support
+        * though this could be added later. The error code indicates that the
+        * mutex was an invalid type, but it would be more accurate as 
+        * "unimplemented". */
+       if (mutex->_m_type > 128) return EINVAL;
+#endif
        return 0;
 }
index 9279fc54308ad6d8b1e1b6ada6ffb32f91c1fa15..d22196a55a153bf24f6f2ca6da3f3461379cf94a 100644 (file)
@@ -1,5 +1,6 @@
 #include "pthread_impl.h"
 
+#ifdef __wasilibc_unmodified_upstream
 #define IS32BIT(x) !((x)+0x80000000ULL>>32)
 #define CLAMP(x) (int)(IS32BIT(x) ? (x) : 0x7fffffffU+((0ULL+(x))>>63))
 
@@ -52,6 +53,7 @@ static int pthread_mutex_timedlock_pi(pthread_mutex_t *restrict m, const struct
        while (e != ETIMEDOUT);
        return e;
 }
+#endif
 
 int __pthread_mutex_timedlock(pthread_mutex_t *restrict m, const struct timespec *restrict at)
 {
@@ -65,8 +67,10 @@ int __pthread_mutex_timedlock(pthread_mutex_t *restrict m, const struct timespec
        r = __pthread_mutex_trylock(m);
        if (r != EBUSY) return r;
 
+#ifdef __wasilibc_unmodified_upstream
        if (type&8) return pthread_mutex_timedlock_pi(m, at);
-       
+#endif
+
        int spins = 100;
        while (spins-- && m->_m_lock && !m->_m_waiters) a_spin();
 
index a24e7c58ac390d857bf41d305d6a2e317ab7c38f..c60b45feef9ce9378c7ff9bcd7e41da1d7296867 100644 (file)
@@ -27,7 +27,9 @@ int __pthread_mutex_trylock_owner(pthread_mutex_t *m)
        if (type & 128) {
                if (!self->robust_list.off) {
                        self->robust_list.off = (char*)&m->_m_lock-(char *)&m->_m_next;
+#ifdef __wasilibc_unmodified_upstream
                        __syscall(SYS_set_robust_list, &self->robust_list, 3*sizeof(long));
+#endif
                }
                if (m->_m_waiters) tid |= 0x80000000;
                self->robust_list.pending = &m->_m_next;
@@ -43,7 +45,9 @@ int __pthread_mutex_trylock_owner(pthread_mutex_t *m)
 success:
        if ((type&8) && m->_m_waiters) {
                int priv = (type & 128) ^ 128;
+#ifdef __wasilibc_unmodified_upstream
                __syscall(SYS_futex, &m->_m_lock, FUTEX_UNLOCK_PI|priv);
+#endif
                self->robust_list.pending = 0;
                return (type&4) ? ENOTRECOVERABLE : EBUSY;
        }
index b66423e6c34f7ac39cf81a122dba3d83eea46f26..6beaacbc64a76139e245356f5d3fbe5720228eaf 100644 (file)
@@ -22,7 +22,9 @@ int __pthread_mutex_unlock(pthread_mutex_t *m)
                        new = 0x7fffffff;
                if (!priv) {
                        self->robust_list.pending = &m->_m_next;
+#ifdef __wasilibc_unmodified_upstream
                        __vm_lock();
+#endif
                }
                volatile void *prev = m->_m_prev;
                volatile void *next = m->_m_next;
@@ -30,6 +32,7 @@ int __pthread_mutex_unlock(pthread_mutex_t *m)
                if (next != &self->robust_list.head) *(volatile void *volatile *)
                        ((char *)next - sizeof(void *)) = prev;
        }
+#ifdef __wasilibc_unmodified_upstream
        if (type&8) {
                if (old<0 || a_cas(&m->_m_lock, old, new)!=old) {
                        if (new) a_store(&m->_m_waiters, -1);
@@ -40,9 +43,14 @@ int __pthread_mutex_unlock(pthread_mutex_t *m)
        } else {
                cont = a_swap(&m->_m_lock, new);
        }
+#else
+               cont = a_swap(&m->_m_lock, new);
+#endif
        if (type != PTHREAD_MUTEX_NORMAL && !priv) {
                self->robust_list.pending = 0;
+#ifdef __wasilibc_unmodified_upstream
                __vm_unlock();
+#endif
        }
        if (waiters || cont<0)
                __wake(&m->_m_lock, 1, priv);
index 8b80c1ce9b14c1b039de327c9870dea08a3ca846..84b02ba130ef0d07de7737549a9daa7dcffc36e9 100644 (file)
@@ -11,6 +11,7 @@ int pthread_mutexattr_setprotocol(pthread_mutexattr_t *a, int protocol)
                a->__attr &= ~8;
                return 0;
        case PTHREAD_PRIO_INHERIT:
+#ifdef __wasilibc_unmodified_upstream
                r = check_pi_result;
                if (r < 0) {
                        volatile int lk = 0;
@@ -20,6 +21,9 @@ int pthread_mutexattr_setprotocol(pthread_mutexattr_t *a, int protocol)
                if (r) return r;
                a->__attr |= 8;
                return 0;
+#else
+               return ENOTSUP;
+#endif
        case PTHREAD_PRIO_PROTECT:
                return ENOTSUP;
        default:
index 30a9ac3bea559bb54b762c7df04e0fa035d3ec1e..649a89130c2d534c28eb501f2a51dbc34a8f4072 100644 (file)
@@ -5,6 +5,7 @@ static volatile int check_robust_result = -1;
 
 int pthread_mutexattr_setrobust(pthread_mutexattr_t *a, int robust)
 {
+#ifdef __wasilibc_unmodified_upstream
        if (robust > 1U) return EINVAL;
        if (robust) {
                int r = check_robust_result;
@@ -20,4 +21,7 @@ int pthread_mutexattr_setrobust(pthread_mutexattr_t *a, int robust)
        }
        a->__attr &= ~4;
        return 0;
+#else
+       return EINVAL;
+#endif
 }
index 18055329a1e3695801047605df0f3cac9be9a0a1..97de53455ede450885fcd6bdd6bb6de09b97071a 100644 (file)
@@ -1,9 +1,7 @@
 #include <threads.h>
 #include <time.h>
 #include <errno.h>
-#ifdef __wasilibc_unmodified_upstream
 #include "syscall.h"
-#endif
 
 int thrd_sleep(const struct timespec *req, struct timespec *rem)
 {