Skip to content

Commit 33c3753

Browse files
authored
threads: implement support for pthread mutexes (#315)
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.
1 parent dcd28cf commit 33c3753

14 files changed

Lines changed: 137 additions & 4 deletions

Makefile

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,29 @@ LIBC_TOP_HALF_MUSL_SOURCES = \
187187
$(wildcard $(LIBC_TOP_HALF_MUSL_SRC_DIR)/complex/*.c)) \
188188
$(wildcard $(LIBC_TOP_HALF_MUSL_SRC_DIR)/crypt/*.c)
189189

190+
ifeq ($(THREAD_MODEL), posix)
191+
LIBC_TOP_HALF_MUSL_SOURCES += \
192+
$(addprefix $(LIBC_TOP_HALF_MUSL_SRC_DIR)/, \
193+
thread/__wait.c \
194+
thread/__timedwait.c \
195+
thread/pthread_mutex_consistent.c \
196+
thread/pthread_mutex_destroy.c \
197+
thread/pthread_mutex_init.c \
198+
thread/pthread_mutex_getprioceiling.c \
199+
thread/pthread_mutex_lock.c \
200+
thread/pthread_mutex_timedlock.c \
201+
thread/pthread_mutex_trylock.c \
202+
thread/pthread_mutex_unlock.c \
203+
thread/pthread_mutexattr_destroy.c \
204+
thread/pthread_mutexattr_init.c \
205+
thread/pthread_mutexattr_setprotocol.c \
206+
thread/pthread_mutexattr_setpshared.c \
207+
thread/pthread_mutexattr_setrobust.c \
208+
thread/pthread_mutexattr_settype.c \
209+
thread/pthread_setcancelstate.c \
210+
)
211+
endif
212+
190213
MUSL_PRINTSCAN_SOURCES = \
191214
$(LIBC_TOP_HALF_MUSL_SRC_DIR)/internal/floatscan.c \
192215
$(LIBC_TOP_HALF_MUSL_SRC_DIR)/stdio/vfprintf.c \

expected/wasm32-wasi/posix/defined-symbols.txt

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,12 @@ __pow_log_data
179179
__powf_log2_data
180180
__progname
181181
__progname_full
182+
__pthread_mutex_lock
183+
__pthread_mutex_timedlock
184+
__pthread_mutex_trylock
185+
__pthread_mutex_trylock_owner
186+
__pthread_mutex_unlock
187+
__pthread_setcancelstate
182188
__putenv
183189
__qsort_r
184190
__rand48_step
@@ -237,6 +243,8 @@ __sysv_signal
237243
__tan
238244
__tandf
239245
__tanl
246+
__timedwait
247+
__timedwait_cp
240248
__tm_to_secs
241249
__tm_to_tzname
242250
__tolower_l
@@ -256,6 +264,7 @@ __uflow
256264
__unlist_locked_file
257265
__uselocale
258266
__utc
267+
__wait
259268
__wasi_args_get
260269
__wasi_args_sizes_get
261270
__wasi_clock_res_get
@@ -313,6 +322,7 @@ __wasilibc_fd_renumber
313322
__wasilibc_find_abspath
314323
__wasilibc_find_relpath
315324
__wasilibc_find_relpath_alloc
325+
__wasilibc_futex_wait
316326
__wasilibc_get_environ
317327
__wasilibc_iftodt
318328
__wasilibc_initialize_environ
@@ -914,6 +924,21 @@ program_invocation_name
914924
program_invocation_short_name
915925
pselect
916926
psignal
927+
pthread_mutex_consistent
928+
pthread_mutex_destroy
929+
pthread_mutex_getprioceiling
930+
pthread_mutex_init
931+
pthread_mutex_lock
932+
pthread_mutex_timedlock
933+
pthread_mutex_trylock
934+
pthread_mutex_unlock
935+
pthread_mutexattr_destroy
936+
pthread_mutexattr_init
937+
pthread_mutexattr_setprotocol
938+
pthread_mutexattr_setpshared
939+
pthread_mutexattr_setrobust
940+
pthread_mutexattr_settype
941+
pthread_setcancelstate
917942
putc
918943
putc_unlocked
919944
putchar

expected/wasm32-wasi/posix/undefined-symbols.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,4 +72,3 @@ __unlockfile
7272
__unordtf2
7373
__wasilibc_pthread_self
7474
__wasm_call_ctors
75-
pthread_setcancelstate

libc-top-half/musl/src/internal/pthread_impl.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,9 @@ hidden int __libc_sigaction(int, const struct sigaction *, struct sigaction *);
170170
#endif
171171
hidden void __unmapself(void *, size_t);
172172

173+
#ifndef __wasilibc_unmodified_upstream
174+
hidden int __wasilibc_futex_wait(volatile void *, int, int, int64_t);
175+
#endif
173176
hidden int __timedwait(volatile int *, int, clockid_t, const struct timespec *, int);
174177
hidden int __timedwait_cp(volatile int *, int, clockid_t, const struct timespec *, int);
175178
hidden void __wait(volatile int *, volatile int *, int, int);

libc-top-half/musl/src/internal/syscall.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
#ifdef __wasilibc_unmodified_upstream
12
#ifndef _INTERNAL_SYSCALL_H
23
#define _INTERNAL_SYSCALL_H
34

@@ -396,3 +397,4 @@ hidden void __procfdname(char __buf[static 15+3*sizeof(int)], unsigned);
396397
hidden void *__vdsosym(const char *, const char *);
397398

398399
#endif
400+
#endif

libc-top-half/musl/src/thread/__timedwait.c

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include "syscall.h"
66
#include "pthread_impl.h"
77

8+
#ifdef __wasilibc_unmodified_upstream
89
#define IS32BIT(x) !((x)+0x80000000ULL>>32)
910
#define CLAMP(x) (int)(IS32BIT(x) ? (x) : 0x7fffffffU+((0ULL+(x))>>63))
1011

@@ -28,6 +29,16 @@ static int __futex4_cp(volatile void *addr, int op, int val, const struct timesp
2829

2930
static volatile int dummy = 0;
3031
weak_alias(dummy, __eintr_valid_flag);
32+
#else
33+
static int __futex4_cp(volatile void *addr, int op, int val, const struct timespec *to)
34+
{
35+
int64_t max_wait_ns = -1;
36+
if (to) {
37+
max_wait_ns = (int64_t)(to->tv_sec * 1000000000 + to->tv_nsec);
38+
}
39+
return __wasilibc_futex_wait(addr, op, val, max_wait_ns);
40+
}
41+
#endif
3142

3243
int __timedwait_cp(volatile int *addr, int val,
3344
clockid_t clk, const struct timespec *at, int priv)
@@ -51,11 +62,13 @@ int __timedwait_cp(volatile int *addr, int val,
5162

5263
r = -__futex4_cp(addr, FUTEX_WAIT|priv, val, top);
5364
if (r != EINTR && r != ETIMEDOUT && r != ECANCELED) r = 0;
65+
#ifdef __wasilibc_unmodified_upstream
5466
/* Mitigate bug in old kernels wrongly reporting EINTR for non-
5567
* interrupting (SA_RESTART) signal handlers. This is only practical
5668
* when NO interrupting signal handlers have been installed, and
5769
* works by sigaction tracking whether that's the case. */
5870
if (r == EINTR && !__eintr_valid_flag) r = 0;
71+
#endif
5972

6073
return r;
6174
}

libc-top-half/musl/src/thread/__wait.c

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,38 @@
11
#include "pthread_impl.h"
2+
#ifndef __wasilibc_unmodified_upstream
3+
#include "assert.h"
4+
#endif
5+
6+
#ifndef __wasilibc_unmodified_upstream
7+
// Use WebAssembly's `wait` instruction to implement a futex. Note that `op` is
8+
// unused but retained as a parameter to match the original signature of the
9+
// syscall and that, for `max_wait_ns`, -1 (or any negative number) means wait
10+
// indefinitely.
11+
//
12+
// Adapted from Emscripten: see
13+
// https://github.com/emscripten-core/emscripten/blob/058a9fff/system/lib/pthread/emscripten_futex_wait.c#L111-L150.
14+
int __wasilibc_futex_wait(volatile void *addr, int op, int val, int64_t max_wait_ns)
15+
{
16+
if ((((intptr_t)addr) & 3) != 0) {
17+
return -EINVAL;
18+
}
19+
20+
int ret = __builtin_wasm_memory_atomic_wait32((int *)addr, val, max_wait_ns);
21+
22+
// memory.atomic.wait32 returns:
23+
// 0 => "ok", woken by another agent.
24+
// 1 => "not-equal", loaded value != expected value
25+
// 2 => "timed-out", the timeout expired
26+
if (ret == 1) {
27+
return -EWOULDBLOCK;
28+
}
29+
if (ret == 2) {
30+
return -ETIMEDOUT;
31+
}
32+
assert(ret == 0);
33+
return 0;
34+
}
35+
#endif
236

337
void __wait(volatile int *addr, volatile int *waiters, int val, int priv)
438
{
@@ -10,8 +44,12 @@ void __wait(volatile int *addr, volatile int *waiters, int val, int priv)
1044
}
1145
if (waiters) a_inc(waiters);
1246
while (*addr==val) {
47+
#ifdef __wasilibc_unmodified_upstream
1348
__syscall(SYS_futex, addr, FUTEX_WAIT|priv, val, 0) != -ENOSYS
1449
|| __syscall(SYS_futex, addr, FUTEX_WAIT, val, 0);
50+
#else
51+
__wasilibc_futex_wait(addr, FUTEX_WAIT, val, 0);
52+
#endif
1553
}
1654
if (waiters) a_dec(waiters);
1755
}

libc-top-half/musl/src/thread/pthread_mutex_destroy.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,17 @@
22

33
int pthread_mutex_destroy(pthread_mutex_t *mutex)
44
{
5+
#ifdef __wasilibc_unmodified_upstream
56
/* If the mutex being destroyed is process-shared and has nontrivial
67
* type (tracking ownership), it might be in the pending slot of a
78
* robust_list; wait for quiescence. */
89
if (mutex->_m_type > 128) __vm_wait();
10+
#else
11+
/* For now, wasi-libc chooses to avoid implementing robust mutex support
12+
* though this could be added later. The error code indicates that the
13+
* mutex was an invalid type, but it would be more accurate as
14+
* "unimplemented". */
15+
if (mutex->_m_type > 128) return EINVAL;
16+
#endif
917
return 0;
1018
}

libc-top-half/musl/src/thread/pthread_mutex_timedlock.c

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#include "pthread_impl.h"
22

3+
#ifdef __wasilibc_unmodified_upstream
34
#define IS32BIT(x) !((x)+0x80000000ULL>>32)
45
#define CLAMP(x) (int)(IS32BIT(x) ? (x) : 0x7fffffffU+((0ULL+(x))>>63))
56

@@ -52,6 +53,7 @@ static int pthread_mutex_timedlock_pi(pthread_mutex_t *restrict m, const struct
5253
while (e != ETIMEDOUT);
5354
return e;
5455
}
56+
#endif
5557

5658
int __pthread_mutex_timedlock(pthread_mutex_t *restrict m, const struct timespec *restrict at)
5759
{
@@ -65,8 +67,10 @@ int __pthread_mutex_timedlock(pthread_mutex_t *restrict m, const struct timespec
6567
r = __pthread_mutex_trylock(m);
6668
if (r != EBUSY) return r;
6769

70+
#ifdef __wasilibc_unmodified_upstream
6871
if (type&8) return pthread_mutex_timedlock_pi(m, at);
69-
72+
#endif
73+
7074
int spins = 100;
7175
while (spins-- && m->_m_lock && !m->_m_waiters) a_spin();
7276

libc-top-half/musl/src/thread/pthread_mutex_trylock.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,9 @@ int __pthread_mutex_trylock_owner(pthread_mutex_t *m)
2727
if (type & 128) {
2828
if (!self->robust_list.off) {
2929
self->robust_list.off = (char*)&m->_m_lock-(char *)&m->_m_next;
30+
#ifdef __wasilibc_unmodified_upstream
3031
__syscall(SYS_set_robust_list, &self->robust_list, 3*sizeof(long));
32+
#endif
3133
}
3234
if (m->_m_waiters) tid |= 0x80000000;
3335
self->robust_list.pending = &m->_m_next;
@@ -43,7 +45,9 @@ int __pthread_mutex_trylock_owner(pthread_mutex_t *m)
4345
success:
4446
if ((type&8) && m->_m_waiters) {
4547
int priv = (type & 128) ^ 128;
48+
#ifdef __wasilibc_unmodified_upstream
4649
__syscall(SYS_futex, &m->_m_lock, FUTEX_UNLOCK_PI|priv);
50+
#endif
4751
self->robust_list.pending = 0;
4852
return (type&4) ? ENOTRECOVERABLE : EBUSY;
4953
}

0 commit comments

Comments
 (0)