diff --git a/CMakeLists.txt b/CMakeLists.txt index c48f41b15..5e261d183 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -87,6 +87,9 @@ endif() if(TARGET_TRIPLE MATCHES "-threads$") set(THREADS ON) add_compile_options(-mthread-model posix -pthread -ftls-model=local-exec -matomics) +elseif(TARGET_TRIPLE MATCHES "-wasip3$") + set(THREADS OFF) + add_compile_options(-mthread-model posix -pthread -ftls-model=local-exec) else() set(THREADS OFF) add_compile_options(-mthread-model single) @@ -139,6 +142,7 @@ elseif(WASI STREQUAL "p2") set(__wasip2__ ON) elseif(WASI STREQUAL "p3") set(__wasip3__ ON) + set(__wasi_cooperative_threads__ ON) else() message(FATAL_ERROR "Unknown WASI version: ${WASI}") endif() diff --git a/cmake/wasm-component-ld.cmake b/cmake/wasm-component-ld.cmake index 2e7976602..d0b8b417e 100644 --- a/cmake/wasm-component-ld.cmake +++ b/cmake/wasm-component-ld.cmake @@ -10,7 +10,7 @@ if (NOT WASM_COMPONENT_LD_EXECUTABLE) ba_download( wasm-component-ld "https://github.com/bytecodealliance/wasm-component-ld" - "v0.5.19" + "v0.5.21" ) ExternalProject_Get_Property(wasm-component-ld SOURCE_DIR) set(WASM_COMPONENT_LD_EXECUTABLE "${SOURCE_DIR}/wasm-component-ld") diff --git a/expected/wasm32-wasip1-threads/defined-symbols.txt b/expected/wasm32-wasip1-threads/defined-symbols.txt index 4fb5d53fc..1d2f3c63c 100644 --- a/expected/wasm32-wasip1-threads/defined-symbols.txt +++ b/expected/wasm32-wasip1-threads/defined-symbols.txt @@ -81,6 +81,7 @@ __ftello __ftello_unlocked __funcs_on_exit __funcs_on_quick_exit +__futexwait __futimesat __fwritable __fwritex @@ -308,6 +309,7 @@ __unlockfile __uselocale __utc __wait +__wake __wasi_args_get __wasi_args_sizes_get __wasi_clock_res_get diff --git a/expected/wasm32-wasip3/defined-symbols.txt b/expected/wasm32-wasip3/defined-symbols.txt index 34149271a..b75189dbd 100644 --- a/expected/wasm32-wasip3/defined-symbols.txt +++ b/expected/wasm32-wasip3/defined-symbols.txt @@ -12,8 +12,11 @@ __ENOMEM __SIG_ERR __SIG_IGN __acquire_ptc +__aio_close __asctime_r __assert_fail +__at_quick_exit_lockptr +__atexit_lockptr __c_dot_utf8 __c_dot_utf8_locale __c_locale @@ -22,6 +25,7 @@ __clock_gettime __clock_nanosleep __component_type_object_force_link_wasip3 __component_type_object_force_link_wasip3_public_use_in_this_compilation_unit +__copy_tls __cos __cosdf __cosl @@ -143,7 +147,11 @@ __libc_calloc __libc_free __libc_malloc __loc_is_allocated +__locale_lock +__locale_lockptr __localtime_r +__lock +__lockfile __log2_data __log2f_data __log_data @@ -208,6 +216,7 @@ __pthread_tsd_size __putenv __qsort_r __rand48_step +__random_lockptr __reallocarray __release_ptc __rem_pio2 @@ -235,6 +244,7 @@ __stdin_used __stdio_close __stdio_exit __stdio_exit_needed +__stdio_ofl_lockptr __stdio_read __stdio_seek __stdio_write @@ -266,6 +276,7 @@ __tan __tandf __tanl __testcancel +__thread_list_lock __tl_lock __tl_unlock __tm_to_secs @@ -285,8 +296,13 @@ __tre_mem_new_impl __tsearch_balance __uflow __unlist_locked_file +__unlock +__unlockfile __uselocale __utc +__waitlist_wait_on +__waitlist_wake_all +__waitlist_wake_one __wasi_init_tp __wasi_sockets_services_db __wasilibc_access @@ -294,6 +310,8 @@ __wasilibc_add_file __wasilibc_add_tcp_socket __wasilibc_add_udp_socket __wasilibc_cwd +__wasilibc_cwd_lock +__wasilibc_cwd_unlock __wasilibc_deinitialize_environ __wasilibc_dttoif __wasilibc_ensure_environ diff --git a/expected/wasm32-wasip3/predefined-macros.txt b/expected/wasm32-wasip3/predefined-macros.txt index 8e2f62089..f4176082a 100644 --- a/expected/wasm32-wasip3/predefined-macros.txt +++ b/expected/wasm32-wasip3/predefined-macros.txt @@ -1734,6 +1734,8 @@ #define SEEK_SET 0 #define SEGSIZE 512 #define SEM_FAILED ((sem_t *)0) +#define SEM_NSEMS_MAX 256 +#define SEM_VALUE_MAX 0x7fffffff #define SERVFAIL ns_r_servfail #define SHORTBITS (sizeof(short) * 8) #define SHRT_MAX 0x7fff @@ -2429,6 +2431,7 @@ #define _POSIX_VERSION 200809L #define _PTHREAD_H #define _PTRDIFF_T +#define _REENTRANT 1 #define _REGEX_H #define _SCHED_H #define _SC_2_CHAR_TERM 95 @@ -3133,6 +3136,7 @@ #define __va_copy(d,s) __builtin_va_copy(d, s) #define __wasi__ 1 #define __wasi_api_h +#define __wasi_cooperative_threads__ #define __wasi_libc_busywait_h #define __wasi_libc_environ_h #define __wasi_libc_find_relpath_h @@ -3206,6 +3210,7 @@ #define __wasm32 1 #define __wasm32__ 1 #define __wasm__ 1 +#define __wasm_atomics__ 1 #define _tolower(a) ((a)|0x20) #define _toupper(a) ((a)&0x5f) #define acos(x) __tg_real_complex(acos, (x)) diff --git a/expected/wasm32-wasip3/undefined-symbols.txt b/expected/wasm32-wasip3/undefined-symbols.txt index d785794c4..2d644ca97 100644 --- a/expected/wasm32-wasip3/undefined-symbols.txt +++ b/expected/wasm32-wasip3/undefined-symbols.txt @@ -41,6 +41,7 @@ __thread_yield __thread_yield_cancellable __thread_yield_to_suspended __thread_yield_to_suspended_cancellable +__tls_align __trunctfdf2 __trunctfsf2 __unordtf2 @@ -143,6 +144,7 @@ __wasm_import_terminal_output_terminal_output_drop __wasm_import_terminal_stderr_get_terminal_stderr __wasm_import_terminal_stdin_get_terminal_stdin __wasm_import_terminal_stdout_get_terminal_stdout +__wasm_init_tls filesystem_future_result_void_error_code__cancel_read filesystem_future_result_void_error_code__cancel_write filesystem_future_result_void_error_code__drop_readable diff --git a/libc-bottom-half/cloudlibc/src/libc/sched/sched_yield.c b/libc-bottom-half/cloudlibc/src/libc/sched/sched_yield.c index fc13322b6..ceb0c9d43 100644 --- a/libc-bottom-half/cloudlibc/src/libc/sched/sched_yield.c +++ b/libc-bottom-half/cloudlibc/src/libc/sched/sched_yield.c @@ -7,7 +7,14 @@ #include int sched_yield(void) { -#ifdef __wasip1__ +#ifdef __wasi_cooperative_threads__ + #ifdef __wasip3__ + wasip3_thread_yield(); + return 0; + #else + #error "Unknown WASI version" + #endif +#elif defined(__wasip1__) __wasi_errno_t error = __wasi_sched_yield(); if (error != 0) { errno = error; diff --git a/libc-bottom-half/headers/public/wasi/version.h.in b/libc-bottom-half/headers/public/wasi/version.h.in index 2cca21efc..8da5d9991 100644 --- a/libc-bottom-half/headers/public/wasi/version.h.in +++ b/libc-bottom-half/headers/public/wasi/version.h.in @@ -10,6 +10,7 @@ #cmakedefine __wasip1__ #cmakedefine __wasip2__ #cmakedefine __wasip3__ +#cmakedefine __wasi_cooperative_threads__ #cmakedefine __wasi_sdk_major__ @__wasi_sdk_major__@ #cmakedefine __wasi_sdk_version__ "@__wasi_sdk_version__@" diff --git a/libc-bottom-half/sources/getcwd.c b/libc-bottom-half/sources/getcwd.c index 8b0b0b746..1c7276645 100644 --- a/libc-bottom-half/sources/getcwd.c +++ b/libc-bottom-half/sources/getcwd.c @@ -7,9 +7,10 @@ char *__wasilibc_cwd = "/"; #ifdef _REENTRANT -static volatile int lock[1]; -void __wasilibc_cwd_lock(void) { LOCK(lock); } -void __wasilibc_cwd_unlock(void) { UNLOCK(lock); } +static __lock_t lock[1]; +// Critical section contains no yield points, so we can use weak locks. +void __wasilibc_cwd_lock(void) { WEAK_LOCK(lock); } +void __wasilibc_cwd_unlock(void) { WEAK_UNLOCK(lock); } #else #define __wasilibc_cwd_lock() (void)0 #define __wasilibc_cwd_unlock() (void)0 diff --git a/libc-bottom-half/sources/preopens.c b/libc-bottom-half/sources/preopens.c index cf91ac016..077c12318 100644 --- a/libc-bottom-half/sources/preopens.c +++ b/libc-bottom-half/sources/preopens.c @@ -21,6 +21,12 @@ #include #endif +/// Access to the the above preopen must be protected in the presence of +/// threads. +#ifdef _REENTRANT +static __lock_t lock[1]; +#endif + #if defined(__wasip1__) typedef struct { __wasi_fd_t fd; @@ -96,12 +102,6 @@ static preopen *preopens; static size_t num_preopens; static size_t preopen_capacity; -/// Access to the the above preopen must be protected in the presence of -/// threads. -#ifdef _REENTRANT -static volatile int lock[1]; -#endif - #ifdef NDEBUG #define assert_invariants() // assertions disabled #else diff --git a/libc-top-half/CMakeLists.txt b/libc-top-half/CMakeLists.txt index 69f758eb6..4777ba5d6 100644 --- a/libc-top-half/CMakeLists.txt +++ b/libc-top-half/CMakeLists.txt @@ -473,6 +473,15 @@ else() ) endif() +# Cooperative threading lock/waiting primitives +if (WASI STREQUAL "p3") + list(APPEND top_half_sources + musl/src/stdio/__lockfile.c + musl/src/thread/coop-threads/__lock.c + musl/src/thread/coop-threads/__wait.c + ) +endif() + add_object_library(top-half ${top_half_sources}) foreach(obj top-half-shared top-half-static) target_link_libraries(${obj} PUBLIC musl-top-half-interface) diff --git a/libc-top-half/musl/src/env/__init_tls.c b/libc-top-half/musl/src/env/__init_tls.c index 7f0d92931..c37507749 100644 --- a/libc-top-half/musl/src/env/__init_tls.c +++ b/libc-top-half/musl/src/env/__init_tls.c @@ -12,6 +12,7 @@ #include "libc.h" #include "atomic.h" #include "syscall.h" +#include #if defined(__wasilibc_unmodified_upstream) || defined(_REENTRANT) volatile int __thread_list_lock; @@ -97,7 +98,14 @@ int __init_tp(void *p) td->stack = bounds.base; td->stack_size = bounds.size; td->guard_size = 0; -#ifdef _REENTRANT +#ifdef __wasi_cooperative_threads__ + td->detach_state = DT_JOINABLE; + #ifdef __wasip3__ + td->tid = wasip3_thread_index(); + #else + #error "Unknown WASI version" + #endif +#elif defined(_REENTRANT) td->detach_state = DT_JOINABLE; /* * Initialize the TID to a value which doesn't conflict with diff --git a/libc-top-half/musl/src/exit/at_quick_exit.c b/libc-top-half/musl/src/exit/at_quick_exit.c index 429d0b033..8cfffc426 100644 --- a/libc-top-half/musl/src/exit/at_quick_exit.c +++ b/libc-top-half/musl/src/exit/at_quick_exit.c @@ -8,28 +8,30 @@ static void (*funcs[COUNT])(void); static int count; #if defined(__wasilibc_unmodified_upstream) || defined(_REENTRANT) -static volatile int lock[1]; -volatile int *const __at_quick_exit_lockptr = lock; +// All locks here can be weak, because the locking is only needed to protect against +// concurrent manipulation of the handler table, which hits no context switch points. +static __lock_t lock[1]; +__lock_t *const __at_quick_exit_lockptr = lock; #endif void __funcs_on_quick_exit() { void (*func)(void); - LOCK(lock); + WEAK_LOCK(lock); while (count > 0) { func = funcs[--count]; - UNLOCK(lock); + WEAK_UNLOCK(lock); func(); - LOCK(lock); + WEAK_LOCK(lock); } } int at_quick_exit(void (*func)(void)) { int r = 0; - LOCK(lock); + WEAK_LOCK(lock); if (count == 32) r = -1; else funcs[count++] = func; - UNLOCK(lock); + WEAK_UNLOCK(lock); return r; } diff --git a/libc-top-half/musl/src/exit/atexit.c b/libc-top-half/musl/src/exit/atexit.c index 155292d0e..f13d540a0 100644 --- a/libc-top-half/musl/src/exit/atexit.c +++ b/libc-top-half/musl/src/exit/atexit.c @@ -22,20 +22,24 @@ static struct fl static int slot; #if defined(__wasilibc_unmodified_upstream) || defined(_REENTRANT) -static volatile int lock[1]; -volatile int *const __atexit_lockptr = lock; +#include "lock.h" + +// All locks here can be weak, because the locking is only needed to protect against +// concurrent manipulation of the handler table, which hits no context switch points. +static __lock_t lock[1]; +__lock_t *const __atexit_lockptr = lock; #endif void __funcs_on_exit() { void (*func)(void *), *arg; - LOCK(lock); + WEAK_LOCK(lock); for (; head; head=head->next, slot=COUNT) while(slot-->0) { func = head->f[slot]; arg = head->a[slot]; - UNLOCK(lock); + WEAK_UNLOCK(lock); func(arg); - LOCK(lock); + WEAK_LOCK(lock); } } @@ -45,7 +49,7 @@ void __cxa_finalize(void *dso) int __cxa_atexit(void (*func)(void *), void *arg, void *dso) { - LOCK(lock); + WEAK_LOCK(lock); /* Defer initialization of head so it can be in BSS */ if (!head) head = &builtin; @@ -54,7 +58,7 @@ int __cxa_atexit(void (*func)(void *), void *arg, void *dso) if (slot==COUNT) { struct fl *new_fl = calloc(sizeof(struct fl), 1); if (!new_fl) { - UNLOCK(lock); + WEAK_UNLOCK(lock); return -1; } new_fl->next = head; @@ -67,7 +71,7 @@ int __cxa_atexit(void (*func)(void *), void *arg, void *dso) head->a[slot] = arg; slot++; - UNLOCK(lock); + WEAK_UNLOCK(lock); return 0; } diff --git a/libc-top-half/musl/src/internal/locale_impl.h b/libc-top-half/musl/src/internal/locale_impl.h index 88f5dcf8f..b792f8d0e 100644 --- a/libc-top-half/musl/src/internal/locale_impl.h +++ b/libc-top-half/musl/src/internal/locale_impl.h @@ -18,7 +18,8 @@ struct __locale_map { }; #if defined(__wasilibc_unmodified_upstream) || defined(_REENTRANT) -extern hidden volatile int __locale_lock[1]; +#include "lock.h" +extern hidden __lock_t __locale_lock[1]; #endif extern hidden const struct __locale_map __c_dot_utf8; diff --git a/libc-top-half/musl/src/internal/lock.h b/libc-top-half/musl/src/internal/lock.h index 29787fb1f..feacd7ce8 100644 --- a/libc-top-half/musl/src/internal/lock.h +++ b/libc-top-half/musl/src/internal/lock.h @@ -1,15 +1,56 @@ #ifndef LOCK_H #define LOCK_H -#if defined(__wasilibc_unmodified_upstream) || defined(_REENTRANT) -hidden void __lock(volatile int *); -hidden void __unlock(volatile int *); -#define LOCK(x) __lock(x) -#define UNLOCK(x) __unlock(x) +#include + +// Defines internal locking primitives. The main elements defined here are: +// - __lock_t: the type of a lock variable +// - __lock_ptr_t: the type of a pointer to a lock variable +// - __LOCK_INIT: initializer for a lock variable +// - LOCK/UNLOCK: functions to acquire/release a lock for a critical section that contains +// potential context switch points, and thus must be executed even in cooperative threading mode. +// - WEAK_LOCK/WEAK_UNLOCK: functions to acquire/release a lock for a critical section that does not contain any context switch points, +// and thus can be no-ops in cooperative threading mode. + + +#ifdef __wasi_cooperative_threads__ + struct __waitlist_node; + struct __coop_lock { + int owner; // tid of owning thread, or 0 if unlocked + struct __waitlist_node *waiters; + }; + + typedef struct __coop_lock __lock_t; + typedef struct __coop_lock *__lock_ptr_t; + #define __COOP_LOCK_INIT {0, NULL} + #define __LOCK_INIT __COOP_LOCK_INIT + + hidden void __lock(struct __coop_lock *lock); + hidden void __unlock(struct __coop_lock *lock); + + #define LOCK(x) __lock(x) + #define UNLOCK(x) __unlock(x) + #define WEAK_LOCK(x) ((void)0) + #define WEAK_UNLOCK(x) ((void)0) +#elif defined(__wasilibc_unmodified_upstream) || defined(_REENTRANT) + typedef volatile int __lock_t; + typedef volatile int *__lock_ptr_t; + + #define __LOCK_INIT {0} + + hidden void __lock(volatile int *); + hidden void __unlock(volatile int *); + + #define LOCK(x) __lock(x) + #define UNLOCK(x) __unlock(x) + #define WEAK_LOCK(x) __lock(x) + #define WEAK_UNLOCK(x) __unlock(x) #else -// No locking needed. -#define LOCK(x) ((void)0) -#define UNLOCK(x) ((void)0) + // No locking needed. + #define LOCK(x) ((void)0) + #define UNLOCK(x) ((void)0) + #define WEAK_LOCK(x) ((void)0) + #define WEAK_UNLOCK(x) ((void)0) #endif -#endif +#endif \ No newline at end of file diff --git a/libc-top-half/musl/src/internal/pthread_impl.h b/libc-top-half/musl/src/internal/pthread_impl.h index 0106ac385..7a056d69d 100644 --- a/libc-top-half/musl/src/internal/pthread_impl.h +++ b/libc-top-half/musl/src/internal/pthread_impl.h @@ -18,6 +18,8 @@ #include "futex.h" #include "pthread_arch.h" +#include "lock.h" +#include #define pthread __pthread @@ -172,35 +174,24 @@ hidden int __libc_sigaction(int, const struct sigaction *, struct sigaction *); #endif hidden void __unmapself(void *, size_t); +#ifdef __wasi_cooperative_threads__ +struct __waitlist_node { + uint32_t tid; + struct __waitlist_node *next; +}; +hidden void __waitlist_wait_on(struct __waitlist_node **list); +hidden void __waitlist_wake_one(struct __waitlist_node **list); +hidden void __waitlist_wake_all(struct __waitlist_node **list); +#else #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); -static inline void __wake(volatile void *addr, int cnt, int priv) -{ - if (priv) priv = FUTEX_PRIVATE; - if (cnt<0) cnt = INT_MAX; -#ifdef __wasilibc_unmodified_upstream - __syscall(SYS_futex, addr, FUTEX_WAKE|priv, cnt) != -ENOSYS || - __syscall(SYS_futex, addr, FUTEX_WAKE, cnt); -#else -#ifdef _REENTRANT - __builtin_wasm_memory_atomic_notify((int*)addr, cnt); -#endif -#endif -} -static inline void __futexwait(volatile void *addr, int val, int priv) -{ -#ifdef __wasilibc_unmodified_upstream - if (priv) priv = FUTEX_PRIVATE; - __syscall(SYS_futex, addr, FUTEX_WAIT|priv, val, 0) != -ENOSYS || - __syscall(SYS_futex, addr, FUTEX_WAIT, val, 0); -#else - __wait(addr, NULL, val, priv); +hidden void __wake(volatile void *addr, int cnt, int priv); +hidden void __futexwait(volatile void *addr, int val, int priv); #endif -} hidden void __acquire_ptc(void); hidden void __release_ptc(void); diff --git a/libc-top-half/musl/src/internal/stdio_impl.h b/libc-top-half/musl/src/internal/stdio_impl.h index 7f19dd951..cfd476715 100644 --- a/libc-top-half/musl/src/internal/stdio_impl.h +++ b/libc-top-half/musl/src/internal/stdio_impl.h @@ -8,10 +8,21 @@ #define UNGET 8 -#if defined(__wasilibc_unmodified_upstream) || defined(_REENTRANT) +#include "lock.h" +#include + +#ifdef __wasi_cooperative_threads__ +#define FFINALLOCK(f) __lockfile((f)) +#define FLOCK(f) int __need_unlock = __lockfile((f)) +#define FUNLOCK(f) do { if (__need_unlock) __unlockfile((f)); } while (0) +#define __STDIO_LOCK_INIT {0, 0} +#define __STDIO_LOCK_RESET(lock) do { (lock)->owner = 0; (lock)->waiters = NULL; } while (0) +#elif defined(__wasilibc_unmodified_upstream) || defined(_REENTRANT) #define FFINALLOCK(f) ((f)->lock>=0 ? __lockfile((f)) : 0) #define FLOCK(f) int __need_unlock = ((f)->lock>=0 ? __lockfile((f)) : 0) #define FUNLOCK(f) do { if (__need_unlock) __unlockfile((f)); } while (0) +#define __STDIO_LOCK_INIT -1 +#define __STDIO_LOCK_RESET(lock) do { (*(lock)) = -1; } while (0) #else // No locking needed. #define FFINALLOCK(f) ((void)(f)) @@ -51,7 +62,7 @@ struct _IO_FILE { #endif int mode; #if defined(__wasilibc_unmodified_upstream) || defined(_REENTRANT) - volatile int lock; + __lock_t lock; #endif int lbf; void *cookie; @@ -73,8 +84,9 @@ extern hidden FILE *volatile __stdout_used; extern hidden FILE *volatile __stderr_used; #if defined(__wasilibc_unmodified_upstream) || defined(_REENTRANT) -hidden int __lockfile(FILE *); -hidden void __unlockfile(FILE *); +// Needs to be unhidden because they're used by the emulated signal implementation +int __lockfile(FILE *); +void __unlockfile(FILE *); #endif hidden size_t __stdio_read(FILE *, unsigned char *, size_t); diff --git a/libc-top-half/musl/src/locale/locale_map.c b/libc-top-half/musl/src/locale/locale_map.c index ebfb158f4..ca25ac827 100644 --- a/libc-top-half/musl/src/locale/locale_map.c +++ b/libc-top-half/musl/src/locale/locale_map.c @@ -31,8 +31,8 @@ static const char envvars[][12] = { }; #if defined(__wasilibc_unmodified_upstream) || defined(_REENTRANT) -volatile int __locale_lock[1]; -volatile int *const __locale_lockptr = __locale_lock; +__lock_t __locale_lock[1]; +__lock_t *const __locale_lockptr = __locale_lock; #endif const struct __locale_map *__get_locale(int cat, const char *val) diff --git a/libc-top-half/musl/src/malloc/oldmalloc/malloc.c b/libc-top-half/musl/src/malloc/oldmalloc/malloc.c index 25d00d44d..f4caed87c 100644 --- a/libc-top-half/musl/src/malloc/oldmalloc/malloc.c +++ b/libc-top-half/musl/src/malloc/oldmalloc/malloc.c @@ -27,6 +27,16 @@ static struct { /* Synchronization tools */ +#ifdef __wasi_cooperative_threads__ +/* The implementation has no yield points, so locks can be no-ops. */ +static inline void lock(volatile int *lk) +{ +} + +static inline void unlock(volatile int *lk) +{ +} +#else static inline void lock(volatile int *lk) { int need_locks = libc.need_locks; @@ -43,6 +53,7 @@ static inline void unlock(volatile int *lk) if (lk[1]) __wake(lk, 1, 1); } } +#endif static inline void lock_bin(int i) { diff --git a/libc-top-half/musl/src/prng/random.c b/libc-top-half/musl/src/prng/random.c index daac028d8..957f2f955 100644 --- a/libc-top-half/musl/src/prng/random.c +++ b/libc-top-half/musl/src/prng/random.c @@ -24,8 +24,8 @@ static int i = 3; static int j = 0; static uint32_t *x = init+1; #if defined(__wasilibc_unmodified_upstream) || defined(_REENTRANT) -static volatile int lock[1]; -volatile int *const __random_lockptr = lock; +static __lock_t lock[1]; +__lock_t *const __random_lockptr = lock; #endif static uint32_t lcg31(uint32_t x) { @@ -67,9 +67,11 @@ static void __srandom(unsigned seed) { } void srandom(unsigned seed) { - LOCK(lock); + // The random number generator does not hit any context switch points, + // so we can use weak locks here. + WEAK_LOCK(lock); __srandom(seed); - UNLOCK(lock); + WEAK_UNLOCK(lock); } char *initstate(unsigned seed, char *state, size_t size) { @@ -77,7 +79,7 @@ char *initstate(unsigned seed, char *state, size_t size) { if (size < 8) return 0; - LOCK(lock); + WEAK_LOCK(lock); old = savestate(); if (size < 32) n = 0; @@ -92,24 +94,24 @@ char *initstate(unsigned seed, char *state, size_t size) { x = (uint32_t*)state + 1; __srandom(seed); savestate(); - UNLOCK(lock); + WEAK_UNLOCK(lock); return old; } char *setstate(char *state) { void *old; - LOCK(lock); + WEAK_LOCK(lock); old = savestate(); loadstate((uint32_t*)state); - UNLOCK(lock); + WEAK_UNLOCK(lock); return old; } long random(void) { long k; - LOCK(lock); + WEAK_LOCK(lock); if (n == 0) { k = x[0] = lcg31(x[0]); goto end; @@ -121,6 +123,6 @@ long random(void) { if (++j == n) j = 0; end: - UNLOCK(lock); + WEAK_UNLOCK(lock); return k; } diff --git a/libc-top-half/musl/src/stdio/__fdopen.c b/libc-top-half/musl/src/stdio/__fdopen.c index 5c8df495e..205338c2f 100644 --- a/libc-top-half/musl/src/stdio/__fdopen.c +++ b/libc-top-half/musl/src/stdio/__fdopen.c @@ -75,7 +75,7 @@ FILE *__fdopen(int fd, const char *mode) f->close = __stdio_close; #if defined(__wasilibc_unmodified_upstream) || defined(_REENTRANT) - if (!libc.threaded) f->lock = -1; + if (!libc.threaded) __STDIO_LOCK_RESET(&f->lock); #endif /* Add new FILE to open file list */ diff --git a/libc-top-half/musl/src/stdio/__fopen_rb_ca.c b/libc-top-half/musl/src/stdio/__fopen_rb_ca.c index 192050b00..27c2544fe 100644 --- a/libc-top-half/musl/src/stdio/__fopen_rb_ca.c +++ b/libc-top-half/musl/src/stdio/__fopen_rb_ca.c @@ -25,7 +25,7 @@ FILE *__fopen_rb_ca(const char *filename, FILE *f, unsigned char *buf, size_t le f->seek = __stdio_seek; f->close = __stdio_close; #if defined(__wasilibc_unmodified_upstream) || defined(_REENTRANT) - f->lock = -1; + __STDIO_LOCK_RESET(&f->lock); #endif return f; diff --git a/libc-top-half/musl/src/stdio/__lockfile.c b/libc-top-half/musl/src/stdio/__lockfile.c index 0f60a1499..25b19e60c 100644 --- a/libc-top-half/musl/src/stdio/__lockfile.c +++ b/libc-top-half/musl/src/stdio/__lockfile.c @@ -1,6 +1,24 @@ #include "stdio_impl.h" #include "pthread_impl.h" +#include +#ifdef __wasi_cooperative_threads__ +#include "lock.h" +int __lockfile(FILE *f) +{ + // Allow recursive locking + if (f->lock.owner == __pthread_self()->tid) + return 0; + + LOCK(&f->lock); + return 1; +} + +void __unlockfile(FILE *f) +{ + UNLOCK(&f->lock); +} +#else int __lockfile(FILE *f) { int owner = f->lock, tid = __pthread_self()->tid; @@ -21,3 +39,4 @@ void __unlockfile(FILE *f) if (a_swap(&f->lock, 0) & MAYBE_WAITERS) __wake(&f->lock, 1, 1); } +#endif diff --git a/libc-top-half/musl/src/stdio/fmemopen.c b/libc-top-half/musl/src/stdio/fmemopen.c index 3ee57b9ea..22628e56f 100644 --- a/libc-top-half/musl/src/stdio/fmemopen.c +++ b/libc-top-half/musl/src/stdio/fmemopen.c @@ -130,7 +130,7 @@ FILE *fmemopen(void *restrict buf, size_t size, const char *restrict mode) f->f.close = mclose; #if defined(__wasilibc_unmodified_upstream) || defined(_REENTRANT) - if (!libc.threaded) f->f.lock = -1; + if (!libc.threaded) __STDIO_LOCK_RESET(&f->f.lock); #endif return __ofl_add(&f->f); diff --git a/libc-top-half/musl/src/stdio/ftrylockfile.c b/libc-top-half/musl/src/stdio/ftrylockfile.c index 50650585b..489d46f88 100644 --- a/libc-top-half/musl/src/stdio/ftrylockfile.c +++ b/libc-top-half/musl/src/stdio/ftrylockfile.c @@ -4,9 +4,15 @@ void __do_orphaned_stdio_locks() { - FILE *f; - for (f=__pthread_self()->stdio_locks; f; f=f->next_locked) - a_store(&f->lock, 0x40000000); + FILE *f; + for (f=__pthread_self()->stdio_locks; f; f=f->next_locked) { + #ifdef __wasi_cooperative_threads__ + f->lock.owner = 0; + __waitlist_wake_all(&f->lock.waiters); + #else + a_store(&f->lock, 0x40000000); + #endif + } } void __unlist_locked_file(FILE *f) @@ -27,6 +33,26 @@ void __register_locked_file(FILE *f, pthread_t self) self->stdio_locks = f; } +#ifdef __wasi_cooperative_threads__ +int ftrylockfile(FILE *f) +{ + int self_tid = __pthread_self()->tid; + if (f->lock.owner == self_tid) { + if (f->lockcount == LONG_MAX) + return -1; + f->lockcount++; + return 0; + } + + // Try to acquire the lock + if (f->lock.owner != 0) + return -1; + + f->lock.owner = self_tid; + f->lockcount = 1; + return 0; +} +#else int ftrylockfile(FILE *f) { pthread_t self = __pthread_self(); @@ -44,3 +70,4 @@ int ftrylockfile(FILE *f) __register_locked_file(f, self); return 0; } +#endif diff --git a/libc-top-half/musl/src/stdio/getc.h b/libc-top-half/musl/src/stdio/getc.h index e62e3f0da..61074d607 100644 --- a/libc-top-half/musl/src/stdio/getc.h +++ b/libc-top-half/musl/src/stdio/getc.h @@ -1,23 +1,35 @@ #include "stdio_impl.h" #if defined(__wasilibc_unmodified_upstream) || defined(_REENTRANT) #include "pthread_impl.h" +#include #ifdef __GNUC__ __attribute__((__noinline__)) #endif static int locking_getc(FILE *f) { +#ifdef __wasi_cooperative_threads__ + __lockfile(f); + int c = getc_unlocked(f); + __unlockfile(f); + return c; +#else if (a_cas(&f->lock, 0, MAYBE_WAITERS-1)) __lockfile(f); int c = getc_unlocked(f); if (a_swap(&f->lock, 0) & MAYBE_WAITERS) __wake(&f->lock, 1, 1); return c; +#endif } #endif static inline int do_getc(FILE *f) { -#if defined(__wasilibc_unmodified_upstream) || defined(_REENTRANT) +#ifdef __wasi_cooperative_threads__ + if (f->lock.owner == __pthread_self()->tid) + return getc_unlocked(f); + return locking_getc(f); +#elif defined(__wasilibc_unmodified_upstream) || defined(_REENTRANT) int l = f->lock; if (l < 0 || l && (l & ~MAYBE_WAITERS) == __pthread_self()->tid) return getc_unlocked(f); diff --git a/libc-top-half/musl/src/stdio/ofl.c b/libc-top-half/musl/src/stdio/ofl.c index 33a8aa50b..d69ee2805 100644 --- a/libc-top-half/musl/src/stdio/ofl.c +++ b/libc-top-half/musl/src/stdio/ofl.c @@ -4,8 +4,8 @@ static FILE *ofl_head; #if defined(__wasilibc_unmodified_upstream) || defined(_REENTRANT) -static volatile int ofl_lock[1]; -volatile int *const __stdio_ofl_lockptr = ofl_lock; +static __lock_t ofl_lock[1]; +__lock_t *const __stdio_ofl_lockptr = ofl_lock; #endif FILE **__ofl_lock() diff --git a/libc-top-half/musl/src/stdio/open_memstream.c b/libc-top-half/musl/src/stdio/open_memstream.c index aa5022065..9690fe8b3 100644 --- a/libc-top-half/musl/src/stdio/open_memstream.c +++ b/libc-top-half/musl/src/stdio/open_memstream.c @@ -102,7 +102,7 @@ FILE *open_memstream(char **bufp, size_t *sizep) f->f.mode = -1; #if defined(__wasilibc_unmodified_upstream) || defined(_REENTRANT) - if (!libc.threaded) f->f.lock = -1; + if (!libc.threaded) __STDIO_LOCK_RESET(&f->f.lock); #endif return __ofl_add(&f->f); diff --git a/libc-top-half/musl/src/stdio/open_wmemstream.c b/libc-top-half/musl/src/stdio/open_wmemstream.c index 198d5d439..d06c24db8 100644 --- a/libc-top-half/musl/src/stdio/open_wmemstream.c +++ b/libc-top-half/musl/src/stdio/open_wmemstream.c @@ -107,7 +107,7 @@ FILE *open_wmemstream(wchar_t **bufp, size_t *sizep) f->f.close = wms_close; #if defined(__wasilibc_unmodified_upstream) || defined(_REENTRANT) - if (!libc.threaded) f->f.lock = -1; + if (!libc.threaded) __STDIO_LOCK_RESET(&f->f.lock); #endif fwide(&f->f, 1); diff --git a/libc-top-half/musl/src/stdio/putc.h b/libc-top-half/musl/src/stdio/putc.h index 2cc63d2db..7e1d49d6b 100644 --- a/libc-top-half/musl/src/stdio/putc.h +++ b/libc-top-half/musl/src/stdio/putc.h @@ -1,23 +1,35 @@ #include "stdio_impl.h" #if defined(__wasilibc_unmodified_upstream) || defined(_REENTRANT) #include "pthread_impl.h" +#include #ifdef __GNUC__ __attribute__((__noinline__)) #endif static int locking_putc(int c, FILE *f) { +#ifdef __wasi_cooperative_threads__ + __lockfile(f); + c = putc_unlocked(c, f); + __unlockfile(f); + return c; +#else if (a_cas(&f->lock, 0, MAYBE_WAITERS-1)) __lockfile(f); c = putc_unlocked(c, f); if (a_swap(&f->lock, 0) & MAYBE_WAITERS) __wake(&f->lock, 1, 1); return c; +#endif } #endif static inline int do_putc(int c, FILE *f) { -#if defined(__wasilibc_unmodified_upstream) || defined(_REENTRANT) +#ifdef __wasi_cooperative_threads__ + if (f->lock.owner == __pthread_self()->tid) + return putc_unlocked(c, f); + return locking_putc(c, f); +#elif defined(__wasilibc_unmodified_upstream) || defined(_REENTRANT) int l = f->lock; if (l < 0 || l && (l & ~MAYBE_WAITERS) == __pthread_self()->tid) return putc_unlocked(c, f); diff --git a/libc-top-half/musl/src/stdio/stderr.c b/libc-top-half/musl/src/stdio/stderr.c index 5f24549f8..a14ba5637 100644 --- a/libc-top-half/musl/src/stdio/stderr.c +++ b/libc-top-half/musl/src/stdio/stderr.c @@ -13,7 +13,7 @@ hidden FILE __stderr_FILE = { .seek = __stdio_seek, .close = __stdio_close, #if defined(__wasilibc_unmodified_upstream) || defined(_REENTRANT) - .lock = -1, + .lock = __STDIO_LOCK_INIT, #endif }; FILE *const stderr = &__stderr_FILE; diff --git a/libc-top-half/musl/src/stdio/stdin.c b/libc-top-half/musl/src/stdio/stdin.c index 68e1c3f64..ddc1a892a 100644 --- a/libc-top-half/musl/src/stdio/stdin.c +++ b/libc-top-half/musl/src/stdio/stdin.c @@ -12,7 +12,7 @@ hidden FILE __stdin_FILE = { .seek = __stdio_seek, .close = __stdio_close, #if defined(__wasilibc_unmodified_upstream) || defined(_REENTRANT) - .lock = -1, + .lock = __STDIO_LOCK_INIT, #endif }; FILE *const stdin = &__stdin_FILE; diff --git a/libc-top-half/musl/src/stdio/stdout.c b/libc-top-half/musl/src/stdio/stdout.c index e0e2bced9..914f5a456 100644 --- a/libc-top-half/musl/src/stdio/stdout.c +++ b/libc-top-half/musl/src/stdio/stdout.c @@ -13,7 +13,7 @@ hidden FILE __stdout_FILE = { .seek = __stdio_seek, .close = __stdio_close, #if defined(__wasilibc_unmodified_upstream) || defined(_REENTRANT) - .lock = -1, + .lock = __STDIO_LOCK_INIT, #endif }; FILE *const stdout = &__stdout_FILE; diff --git a/libc-top-half/musl/src/stdio/vdprintf.c b/libc-top-half/musl/src/stdio/vdprintf.c index cef0a1af2..4ab32308a 100644 --- a/libc-top-half/musl/src/stdio/vdprintf.c +++ b/libc-top-half/musl/src/stdio/vdprintf.c @@ -6,7 +6,7 @@ int vdprintf(int fd, const char *restrict fmt, va_list ap) .fd = fd, .lbf = EOF, .write = __stdio_write, .buf = (void *)fmt, .buf_size = 0, #if defined(__wasilibc_unmodified_upstream) || defined(_REENTRANT) - .lock = -1 + .lock = __STDIO_LOCK_INIT, #endif }; return vfprintf(&f, fmt, ap); diff --git a/libc-top-half/musl/src/stdio/vsnprintf.c b/libc-top-half/musl/src/stdio/vsnprintf.c index 48c3aef7e..4d8366baf 100644 --- a/libc-top-half/musl/src/stdio/vsnprintf.c +++ b/libc-top-half/musl/src/stdio/vsnprintf.c @@ -41,7 +41,7 @@ int vsnprintf(char *restrict s, size_t n, const char *restrict fmt, va_list ap) .lbf = EOF, .write = sn_write, #if defined(__wasilibc_unmodified_upstream) || defined(_REENTRANT) - .lock = -1, + .lock = __STDIO_LOCK_INIT, #endif .buf = buf, .cookie = &c, diff --git a/libc-top-half/musl/src/stdio/vsscanf.c b/libc-top-half/musl/src/stdio/vsscanf.c index 0e5b48265..883e1d8c4 100644 --- a/libc-top-half/musl/src/stdio/vsscanf.c +++ b/libc-top-half/musl/src/stdio/vsscanf.c @@ -19,10 +19,9 @@ int vsscanf(const char *restrict s, const char *restrict fmt, va_list ap) { FILE f = { .buf = (void *)s, .cookie = (void *)s, + .read = string_read, #if defined(__wasilibc_unmodified_upstream) || defined(_REENTRANT) - .read = string_read, .lock = -1 -#else - .read = string_read + .lock = __STDIO_LOCK_INIT #endif }; return vfscanf(&f, fmt, ap); diff --git a/libc-top-half/musl/src/stdio/vswprintf.c b/libc-top-half/musl/src/stdio/vswprintf.c index cab94cba7..d17da02ba 100644 --- a/libc-top-half/musl/src/stdio/vswprintf.c +++ b/libc-top-half/musl/src/stdio/vswprintf.c @@ -44,7 +44,7 @@ int vswprintf(wchar_t *restrict s, size_t n, const wchar_t *restrict fmt, va_lis .lbf = EOF, .write = sw_write, #if defined(__wasilibc_unmodified_upstream) || defined(_REENTRANT) - .lock = -1, + .lock = __STDIO_LOCK_INIT, #endif .buf = buf, .buf_size = sizeof buf, diff --git a/libc-top-half/musl/src/stdio/vswscanf.c b/libc-top-half/musl/src/stdio/vswscanf.c index ea827102d..feef4e451 100644 --- a/libc-top-half/musl/src/stdio/vswscanf.c +++ b/libc-top-half/musl/src/stdio/vswscanf.c @@ -30,10 +30,9 @@ int vswscanf(const wchar_t *restrict s, const wchar_t *restrict fmt, va_list ap) FILE f = { .buf = buf, .buf_size = sizeof buf, .cookie = (void *)s, + .read = wstring_read, #if defined(__wasilibc_unmodified_upstream) || defined(_REENTRANT) - .read = wstring_read, .lock = -1 -#else - .read = wstring_read + .lock = __STDIO_LOCK_INIT #endif }; return vfwscanf(&f, fmt, ap); diff --git a/libc-top-half/musl/src/stdlib/wcstod.c b/libc-top-half/musl/src/stdlib/wcstod.c index 97b894eb2..c198f08c5 100644 --- a/libc-top-half/musl/src/stdlib/wcstod.c +++ b/libc-top-half/musl/src/stdlib/wcstod.c @@ -44,7 +44,7 @@ static long double wcstox(const wchar_t *s, wchar_t **p, int prec) f.rpos = f.rend = f.buf = buf + 4; f.buf_size = sizeof buf - 4; #if defined(__wasilibc_unmodified_upstream) || defined(_REENTRANT) - f.lock = -1; + __STDIO_LOCK_RESET(&f.lock); #endif f.read = do_read; while (iswspace(*t)) t++; diff --git a/libc-top-half/musl/src/stdlib/wcstol.c b/libc-top-half/musl/src/stdlib/wcstol.c index 3aefd06f4..43439c0d0 100644 --- a/libc-top-half/musl/src/stdlib/wcstol.c +++ b/libc-top-half/musl/src/stdlib/wcstol.c @@ -38,7 +38,7 @@ static unsigned long long wcstox(const wchar_t *s, wchar_t **p, int base, unsign f.rpos = f.rend = f.buf = buf + 4; f.buf_size = sizeof buf - 4; #if defined(__wasilibc_unmodified_upstream) || defined(_REENTRANT) - f.lock = -1; + __STDIO_LOCK_RESET(&f.lock); #endif f.read = do_read; while (iswspace(*t)) t++; diff --git a/libc-top-half/musl/src/thread/coop-threads/__lock.c b/libc-top-half/musl/src/thread/coop-threads/__lock.c new file mode 100644 index 000000000..939d87ad9 --- /dev/null +++ b/libc-top-half/musl/src/thread/coop-threads/__lock.c @@ -0,0 +1,37 @@ +#include "pthread_impl.h" +#include "lock.h" +#include + +#ifndef __wasip3__ +#error "Unknown WASI version" +#endif + +void __lock(struct __coop_lock *lock) +{ + if (lock->owner == __pthread_self()->tid) { + /* Trap on recursive locking. */ + __builtin_trap(); + } + + /* Loop until we acquire the lock. */ + while (lock->owner != 0) { + __waitlist_wait_on(&lock->waiters); + /* After waking, the lock might still be held by another + * thread that was scheduled before us, so loop back. */ + } + + lock->owner = __pthread_self()->tid; +} + +void __unlock(struct __coop_lock *lock) +{ + int tid = __pthread_self()->tid; + if (lock->owner != tid) { + /* We're trying to unlock a lock we don't own. */ + __builtin_trap(); + } + + lock->owner = 0; + /* Awake one waiter; the others will be resumed on future unlocks. */ + __waitlist_wake_one(&lock->waiters); +} \ No newline at end of file diff --git a/libc-top-half/musl/src/thread/coop-threads/__wait.c b/libc-top-half/musl/src/thread/coop-threads/__wait.c new file mode 100644 index 000000000..db8646dcc --- /dev/null +++ b/libc-top-half/musl/src/thread/coop-threads/__wait.c @@ -0,0 +1,47 @@ +#include "pthread_impl.h" +#include + +#ifndef __wasip3__ +#error "Unknown WASI version" +#endif + +void __waitlist_wait_on(struct __waitlist_node **list) +{ + struct __waitlist_node node = { + .tid = __pthread_self()->tid, + .next = *list, + }; + *list = &node; + + wasip3_thread_suspend(); +} + +void __waitlist_wake_one(struct __waitlist_node **list) +{ + if (*list == NULL) { + return; + } + struct __waitlist_node *node = *list; + *list = node->next; + wasip3_thread_yield_to_suspended(node->tid); +} + +void __waitlist_wake_all(struct __waitlist_node **list) +{ + struct __waitlist_node **prev = list; + struct __waitlist_node *curr = *list; + + while (curr) { + uint32_t tid = curr->tid; + *prev = curr->next; + // As a scheduling optimization, we always yield directly to the last + // suspended thread instead of just scheduling it to run at some point. + if (curr->next == NULL) { + wasip3_thread_yield_to_suspended(tid); + } + else { + wasip3_thread_unsuspend(tid); + } + curr = *prev; + } +} \ No newline at end of file diff --git a/libc-top-half/musl/src/thread/wasi-threads/__wait.c b/libc-top-half/musl/src/thread/wasi-threads/__wait.c index 9484c0f3d..8248571f9 100644 --- a/libc-top-half/musl/src/thread/wasi-threads/__wait.c +++ b/libc-top-half/musl/src/thread/wasi-threads/__wait.c @@ -64,3 +64,28 @@ void __wait(volatile int *addr, volatile int *waiters, int val, int priv) } if (waiters) a_dec(waiters); } + +void __wake(volatile void *addr, int cnt, int priv) +{ + if (priv) priv = FUTEX_PRIVATE; + if (cnt<0) cnt = INT_MAX; +#ifdef __wasilibc_unmodified_upstream + __syscall(SYS_futex, addr, FUTEX_WAKE|priv, cnt) != -ENOSYS || + __syscall(SYS_futex, addr, FUTEX_WAKE, cnt); +#else +#ifdef _REENTRANT + __builtin_wasm_memory_atomic_notify((int*)addr, cnt); +#endif +#endif +} + +void __futexwait(volatile void *addr, int val, int priv) +{ +#ifdef __wasilibc_unmodified_upstream + if (priv) priv = FUTEX_PRIVATE; + __syscall(SYS_futex, addr, FUTEX_WAIT|priv, val, 0) != -ENOSYS || + __syscall(SYS_futex, addr, FUTEX_WAIT, val, 0); +#else + __wait(addr, NULL, val, priv); +#endif +} \ No newline at end of file diff --git a/libc-top-half/musl/src/thread/wasi-threads/pthread_create.c b/libc-top-half/musl/src/thread/wasi-threads/pthread_create.c index 879b48c1f..4d1c02f16 100644 --- a/libc-top-half/musl/src/thread/wasi-threads/pthread_create.c +++ b/libc-top-half/musl/src/thread/wasi-threads/pthread_create.c @@ -349,7 +349,9 @@ weak_alias(dummy_file, __stderr_used); static void init_file_lock(FILE *f) { +#ifndef __wasi_cooperative_threads__ if (f && f->lock<0) f->lock = 0; +#endif } int __pthread_create(pthread_t *restrict res, const pthread_attr_t *restrict attrp, void *(*entry)(void *), void *restrict arg) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index bb51b2c05..d24aeec96 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -126,6 +126,7 @@ function(register_test test_name executable_name) endif() if (WASI STREQUAL "p3") list(APPEND wasmtime_args --wasm component-model-async) + list(APPEND wasmtime_args --wasm component-model-threading) list(APPEND wasmtime_args --wasi p3) endif()