| |
| |
| |
| |
| |
| |
|
|
| #define _GNU_SOURCE |
| #include "pthread_impl.h" |
| #include "stdio_impl.h" |
| #include "assert.h" |
| #include <pthread.h> |
| #include <stdbool.h> |
| #include <string.h> |
| #include <threads.h> |
| #include <unistd.h> |
|
|
| #include <emscripten/heap.h> |
| #include <emscripten/threading.h> |
|
|
| #define STACK_ALIGN __BIGGEST_ALIGNMENT__ |
| #define TSD_ALIGN (sizeof(void*)) |
|
|
| |
| |
| #ifdef PTHREAD_DEBUG |
| #define dbg(fmt, ...) emscripten_dbgf(fmt, ##__VA_ARGS__) |
| #else |
| #define dbg(fmt, ...) |
| #endif |
|
|
| |
|
|
| static void dummy_0() {} |
| weak_alias(dummy_0, __pthread_tsd_run_dtors); |
|
|
| static void __run_cleanup_handlers() { |
| pthread_t self = __pthread_self(); |
| while (self->cancelbuf) { |
| void (*f)(void *) = self->cancelbuf->__f; |
| void *x = self->cancelbuf->__x; |
| self->cancelbuf = self->cancelbuf->__next; |
| f(x); |
| } |
| } |
|
|
| void __do_cleanup_push(struct __ptcb *cb) { |
| struct pthread *self = __pthread_self(); |
| cb->__next = self->cancelbuf; |
| self->cancelbuf = cb; |
| } |
|
|
| void __do_cleanup_pop(struct __ptcb *cb) { |
| __pthread_self()->cancelbuf = cb->__next; |
| } |
|
|
| static FILE *volatile dummy_file = 0; |
| weak_alias(dummy_file, __stdin_used); |
| weak_alias(dummy_file, __stdout_used); |
| weak_alias(dummy_file, __stderr_used); |
|
|
| static void init_file_lock(FILE *f) { |
| if (f && f->lock<0) f->lock = 0; |
| } |
|
|
| static int tl_lock_count; |
| static int tl_lock_waiters; |
|
|
| volatile int __thread_list_lock; |
|
|
| void __tl_lock(void) { |
| int tid = __pthread_self()->tid; |
| int val = __thread_list_lock; |
| if (val == tid) { |
| tl_lock_count++; |
| return; |
| } |
| while ((val = a_cas(&__thread_list_lock, 0, tid))) |
| __wait(&__thread_list_lock, &tl_lock_waiters, val, 0); |
| } |
|
|
| void __tl_unlock(void) { |
| if (tl_lock_count) { |
| tl_lock_count--; |
| return; |
| } |
| a_store(&__thread_list_lock, 0); |
| if (tl_lock_waiters) __wake(&__thread_list_lock, 1, 0); |
| } |
|
|
| void __tl_sync(pthread_t td) |
| { |
| a_barrier(); |
| int val = __thread_list_lock; |
| if (!val) return; |
| __wait(&__thread_list_lock, &tl_lock_waiters, val, 0); |
| if (tl_lock_waiters) __wake(&__thread_list_lock, 1, 0); |
| } |
|
|
| |
| static volatile size_t dummy = 0; |
| weak_alias(dummy, __pthread_tsd_size); |
|
|
| #define ROUND_UP(x, ALIGNMENT) (((x)+ALIGNMENT-1)&-ALIGNMENT) |
|
|
| int __pthread_create(pthread_t* restrict res, |
| const pthread_attr_t* restrict attrp, |
| void* (*entry)(void*), |
| void* restrict arg) { |
| |
| |
| |
| if (!res) { |
| return EINVAL; |
| } |
|
|
| if (!libc.threaded) { |
| for (FILE *f=*__ofl_lock(); f; f=f->next) |
| init_file_lock(f); |
| __ofl_unlock(); |
| init_file_lock(__stdin_used); |
| init_file_lock(__stdout_used); |
| init_file_lock(__stderr_used); |
| libc.threaded = 1; |
| } |
|
|
| pthread_attr_t attr = { 0 }; |
| if (attrp && attrp != __ATTRP_C11_THREAD) attr = *attrp; |
| if (!attr._a_stacksize) { |
| attr._a_stacksize = __default_stacksize; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| size_t size = sizeof(struct pthread); |
| if (__builtin_wasm_tls_size()) { |
| size += __builtin_wasm_tls_size() + __builtin_wasm_tls_align() - 1; |
| } |
| size += __pthread_tsd_size + TSD_ALIGN - 1; |
| size_t zero_size = size; |
| if (!attr._a_stackaddr) { |
| size += attr._a_stacksize + STACK_ALIGN - 1; |
| } |
|
|
| |
| |
| unsigned char* block = emscripten_builtin_malloc(size); |
| memset(block, 0, zero_size); |
|
|
| uintptr_t offset = (uintptr_t)block; |
|
|
| |
| struct pthread *new = (struct pthread*)offset; |
| offset += sizeof(struct pthread); |
|
|
| new->map_base = block; |
| new->map_size = size; |
|
|
| |
| |
| new->self = new; |
| new->tid = _emscripten_get_next_tid(); |
|
|
| |
| new->robust_list.head = &new->robust_list.head; |
|
|
| if (attr._a_detach) { |
| new->detach_state = DT_DETACHED; |
| } else { |
| new->detach_state = DT_JOINABLE; |
| } |
| new->stack_size = attr._a_stacksize; |
|
|
| |
| if (__builtin_wasm_tls_size()) { |
| offset = ROUND_UP(offset, __builtin_wasm_tls_align()); |
| new->tls_base = (void*)offset; |
| offset += __builtin_wasm_tls_size(); |
| } |
|
|
| |
| if (__pthread_tsd_size) { |
| offset = ROUND_UP(offset, TSD_ALIGN); |
| new->tsd = (void*)offset; |
| offset += __pthread_tsd_size; |
| } |
|
|
| |
| |
| |
| if (attr._a_stackaddr) { |
| new->stack = (void*)attr._a_stackaddr; |
| } else { |
| offset = ROUND_UP(offset + new->stack_size, STACK_ALIGN); |
| new->stack = (void*)offset; |
| } |
|
|
| |
| assert(offset < (uintptr_t)block + size); |
|
|
| #ifndef NDEBUG |
| _emscripten_thread_profiler_init(new); |
| #endif |
|
|
| _emscripten_thread_mailbox_init(new); |
|
|
| struct pthread *self = __pthread_self(); |
| dbg("start __pthread_create: new=%p new_end=%p stack=%p->%p " |
| "stack_size=%zu tls_base=%p", |
| new, |
| new + 1, |
| (char*)new->stack - new->stack_size, |
| new->stack, |
| new->stack_size, |
| new->tls_base); |
|
|
| |
| __tl_lock(); |
|
|
| new->next = self->next; |
| new->prev = self; |
| new->next->prev = new; |
| new->prev->next = new; |
|
|
| __tl_unlock(); |
|
|
| |
| |
| |
| if (!libc.threads_minus_1++) libc.need_locks = 1; |
|
|
| |
| |
| |
| |
| __atomic_store_n(res, new, __ATOMIC_SEQ_CST); |
|
|
| int rtn = __pthread_create_js(new, &attr, entry, arg); |
| if (rtn != 0) { |
| |
| |
| __atomic_store_n(res, 0, __ATOMIC_SEQ_CST); |
|
|
| if (!--libc.threads_minus_1) libc.need_locks = 0; |
|
|
| |
| __tl_lock(); |
|
|
| new->next->prev = new->prev; |
| new->prev->next = new->next; |
| new->next = new->prev = new; |
|
|
| __tl_unlock(); |
|
|
| return rtn; |
| } |
|
|
| dbg("done __pthread_create next=%p prev=%p new=%p", |
| self->next, |
| self->prev, |
| new); |
|
|
| return 0; |
| } |
|
|
| |
| |
| |
| |
| void _emscripten_thread_free_data(pthread_t t) { |
| |
| assert(t != pthread_self()); |
| #ifndef NDEBUG |
| if (t->profilerBlock) { |
| emscripten_builtin_free(t->profilerBlock); |
| } |
| #endif |
|
|
| |
| |
| |
| unsigned char* block = t->map_base; |
| dbg("_emscripten_thread_free_data thread=%p map_base=%p", t, block); |
| |
| memset(block, 0, sizeof(struct pthread)); |
| emscripten_builtin_free(block); |
| } |
|
|
| void _emscripten_thread_exit(void* result) { |
| struct pthread *self = __pthread_self(); |
| assert(self); |
|
|
| self->canceldisable = PTHREAD_CANCEL_DISABLE; |
| self->cancelasync = 0; |
| self->result = result; |
|
|
| _emscripten_thread_mailbox_shutdown(self); |
|
|
| |
| __run_cleanup_handlers(); |
|
|
| |
| __pthread_tsd_run_dtors(); |
|
|
| __tl_lock(); |
|
|
| |
| |
| |
| __vm_lock(); |
| volatile void *volatile *rp; |
| while ((rp=self->robust_list.head) && rp != &self->robust_list.head) { |
| pthread_mutex_t *m = (void *)((char *)rp |
| - offsetof(pthread_mutex_t, _m_next)); |
| int waiters = m->_m_waiters; |
| int priv = (m->_m_type & 128) ^ 128; |
| self->robust_list.pending = rp; |
| self->robust_list.head = *rp; |
| int cont = a_swap(&m->_m_lock, 0x40000000); |
| self->robust_list.pending = 0; |
| if (cont < 0 || waiters) |
| __wake(&m->_m_lock, 1, priv); |
| } |
| __vm_unlock(); |
|
|
| if (!--libc.threads_minus_1) libc.need_locks = 0; |
|
|
| self->next->prev = self->prev; |
| self->prev->next = self->next; |
| self->prev = self->next = self; |
|
|
| __tl_unlock(); |
|
|
| if (emscripten_is_main_runtime_thread()) { |
| exit(0); |
| return; |
| } |
|
|
| |
| __set_thread_state(NULL, 0, 0, 1); |
|
|
| |
| |
| int state = a_cas(&self->detach_state, DT_JOINABLE, DT_EXITING); |
|
|
| if (state == DT_DETACHED) { |
| _emscripten_thread_cleanup(self); |
| } else { |
| |
| |
| |
| #ifdef EMSCRIPTEN_DYNAMIC_LINKING |
| |
| _emscripten_thread_exit_joinable(self); |
| #endif |
| a_store(&self->detach_state, DT_EXITED); |
| __wake(&self->detach_state, 1, 1); |
| } |
| } |
|
|
| |
| |
| |
| |
| __attribute__((no_sanitize("address"))) |
| _Noreturn void __pthread_exit(void* retval) { |
| _emscripten_thread_exit(retval); |
| emscripten_unwind_to_js_event_loop(); |
| } |
|
|
| weak_alias(__pthread_create, emscripten_builtin_pthread_create); |
| weak_alias(__pthread_create, pthread_create); |
| weak_alias(__pthread_exit, emscripten_builtin_pthread_exit); |
| weak_alias(__pthread_exit, pthread_exit); |
|
|