Path: blob/main/system/lib/pthread/pthread_create.c
6171 views
/*1* Copyright 2021 The Emscripten Authors. All rights reserved.2* Emscripten is available under two separate licenses, the MIT license and the3* University of Illinois/NCSA Open Source License. Both these licenses can be4* found in the LICENSE file.5*/67#define _GNU_SOURCE8#include "pthread_impl.h"9#include "stdio_impl.h"10#include "assert.h"11#include <pthread.h>12#include <stdbool.h>13#include <string.h>14#include <threads.h>15#include <unistd.h>16#include <emscripten/heap.h>17#include <emscripten/threading.h>1819#define STACK_ALIGN __BIGGEST_ALIGNMENT__20#define TSD_ALIGN (sizeof(void*))2122// Comment this line to enable tracing of thread creation and destruction:23// #define PTHREAD_DEBUG24#ifdef PTHREAD_DEBUG25#define dbg(fmt, ...) emscripten_dbgf(fmt, ##__VA_ARGS__)26#else27#define dbg(fmt, ...)28#endif2930// See musl's pthread_create.c3132static void dummy_0() {}33weak_alias(dummy_0, __pthread_tsd_run_dtors);3435static void __run_cleanup_handlers() {36pthread_t self = __pthread_self();37while (self->cancelbuf) {38void (*f)(void *) = self->cancelbuf->__f;39void *x = self->cancelbuf->__x;40self->cancelbuf = self->cancelbuf->__next;41f(x);42}43}4445void __do_cleanup_push(struct __ptcb *cb) {46struct pthread *self = __pthread_self();47cb->__next = self->cancelbuf;48self->cancelbuf = cb;49}5051void __do_cleanup_pop(struct __ptcb *cb) {52__pthread_self()->cancelbuf = cb->__next;53}5455static FILE *volatile dummy_file = 0;56weak_alias(dummy_file, __stdin_used);57weak_alias(dummy_file, __stdout_used);58weak_alias(dummy_file, __stderr_used);5960static void init_file_lock(FILE *f) {61if (f && f->lock<0) f->lock = 0;62}6364static pid_t next_tid = 0;6566// In case the stub syscall is not linked it67static int dummy_getpid(void) {68return 42;69}70weak_alias(dummy_getpid, __syscall_getpid);7172static int tl_lock_count;73static int tl_lock_waiters;7475volatile int __thread_list_lock;7677void __tl_lock(void) {78int tid = __pthread_self()->tid;79int val = __thread_list_lock;80if (val == tid) {81tl_lock_count++;82return;83}84while ((val = a_cas(&__thread_list_lock, 0, tid)))85__wait(&__thread_list_lock, &tl_lock_waiters, val, 0);86}8788void __tl_unlock(void) {89if (tl_lock_count) {90tl_lock_count--;91return;92}93a_store(&__thread_list_lock, 0);94if (tl_lock_waiters) __wake(&__thread_list_lock, 1, 0);95}9697void __tl_sync(pthread_t td)98{99a_barrier();100int val = __thread_list_lock;101if (!val) return;102__wait(&__thread_list_lock, &tl_lock_waiters, val, 0);103if (tl_lock_waiters) __wake(&__thread_list_lock, 1, 0);104}105106/* pthread_key_create.c overrides this */107static volatile size_t dummy = 0;108weak_alias(dummy, __pthread_tsd_size);109110#define ROUND_UP(x, ALIGNMENT) (((x)+ALIGNMENT-1)&-ALIGNMENT)111112int __pthread_create(pthread_t* restrict res,113const pthread_attr_t* restrict attrp,114void* (*entry)(void*),115void* restrict arg) {116// Note on LSAN: lsan intercepts/wraps calls to pthread_create so any117// allocation we do here should be considered leaks.118// See: lsan_interceptors.cpp.119if (!res) {120return EINVAL;121}122123// Create threads with monotonically increasing TID starting with the main124// thread which has TID == PID.125if (!next_tid) {126next_tid = getpid() + 1;127}128129if (!libc.threaded) {130for (FILE *f=*__ofl_lock(); f; f=f->next)131init_file_lock(f);132__ofl_unlock();133init_file_lock(__stdin_used);134init_file_lock(__stdout_used);135init_file_lock(__stderr_used);136libc.threaded = 1;137}138139pthread_attr_t attr = { 0 };140if (attrp && attrp != __ATTRP_C11_THREAD) attr = *attrp;141if (!attr._a_stacksize) {142attr._a_stacksize = __default_stacksize;143}144145// Allocate memory for new thread. The layout of the thread block is146// as follows. From low to high address:147//148// 1. pthread struct (sizeof struct pthread)149// 2. tls data (__builtin_wasm_tls_size())150// 3. tsd pointers (__pthread_tsd_size)151// 4. stack (__default_stacksize AKA -sDEFAULT_PTHREAD_STACK_SIZE)152size_t size = sizeof(struct pthread);153if (__builtin_wasm_tls_size()) {154size += __builtin_wasm_tls_size() + __builtin_wasm_tls_align() - 1;155}156size += __pthread_tsd_size + TSD_ALIGN - 1;157size_t zero_size = size;158if (!attr._a_stackaddr) {159size += attr._a_stacksize + STACK_ALIGN - 1;160}161162// Allocate all the data for the new thread and zero-initialize all parts163// except for the stack.164unsigned char* block = emscripten_builtin_malloc(size);165memset(block, 0, zero_size);166167uintptr_t offset = (uintptr_t)block;168169// 1. pthread struct170struct pthread *new = (struct pthread*)offset;171offset += sizeof(struct pthread);172173new->map_base = block;174new->map_size = size;175176// The pthread struct has a field that points to itself - this is used as a177// magic ID to detect whether the pthread_t structure is 'alive'.178new->self = new;179new->tid = next_tid++;180181// pthread struct robust_list head should point to itself.182new->robust_list.head = &new->robust_list.head;183184new->locale = &libc.global_locale;185if (attr._a_detach) {186new->detach_state = DT_DETACHED;187} else {188new->detach_state = DT_JOINABLE;189}190new->stack_size = attr._a_stacksize;191192// 2. tls data193if (__builtin_wasm_tls_size()) {194offset = ROUND_UP(offset, __builtin_wasm_tls_align());195new->tls_base = (void*)offset;196offset += __builtin_wasm_tls_size();197}198199// 3. tsd slots200if (__pthread_tsd_size) {201offset = ROUND_UP(offset, TSD_ALIGN);202new->tsd = (void*)offset;203offset += __pthread_tsd_size;204}205206// 4. stack data207// musl stores top of the stack in pthread_t->stack (i.e. the high208// end from which it grows down).209if (attr._a_stackaddr) {210new->stack = (void*)attr._a_stackaddr;211} else {212offset = ROUND_UP(offset + new->stack_size, STACK_ALIGN);213new->stack = (void*)offset;214}215216// Check that we didn't use more data than we allocated.217assert(offset < (uintptr_t)block + size);218219#ifndef NDEBUG220_emscripten_thread_profiler_init(new);221#endif222223_emscripten_thread_mailbox_init(new);224225struct pthread *self = __pthread_self();226dbg("start __pthread_create: new=%p new_end=%p stack=%p->%p "227"stack_size=%zu tls_base=%p",228new,229new + 1,230(char*)new->stack - new->stack_size,231new->stack,232new->stack_size,233new->tls_base);234235// thread may already be running/exited after the _pthread_create_js call below236__tl_lock();237238new->next = self->next;239new->prev = self;240new->next->prev = new;241new->prev->next = new;242243__tl_unlock();244245// Set libc.need_locks before calling __pthread_create_js since246// by the time it returns the thread could be running and we247// want libc.need_locks to be set from the moment it starts.248if (!libc.threads_minus_1++) libc.need_locks = 1;249250// Assign the pthread_t object over immediately, so that by the time pthread_create_js()251// is dispatched to a pthread and the pthread main runs, the value will be visible to252// the thread to examine.253// Use __atomic_store_n() instead of a_store() to avoid splicing the pointer.254__atomic_store_n(res, new, __ATOMIC_SEQ_CST);255256int rtn = __pthread_create_js(new, &attr, entry, arg);257if (rtn != 0) {258// Reset the pthread_t return value to zero (we assigned to it above,259// so by clearing it here we won't litter bits to caller)260__atomic_store_n(res, 0, __ATOMIC_SEQ_CST);261262if (!--libc.threads_minus_1) libc.need_locks = 0;263264// undo previous addition to the thread list265__tl_lock();266267new->next->prev = new->prev;268new->prev->next = new->next;269new->next = new->prev = new;270271__tl_unlock();272273return rtn;274}275276dbg("done __pthread_create next=%p prev=%p new=%p",277self->next,278self->prev,279new);280281return 0;282}283284/*285* Called from JS main thread to free data associated with a thread286* that is no longer running.287*/288void _emscripten_thread_free_data(pthread_t t) {289// A thread can never free its own thread data.290assert(t != pthread_self());291#ifndef NDEBUG292if (t->profilerBlock) {293emscripten_builtin_free(t->profilerBlock);294}295#endif296297// Free all the entire thread block (called map_base because298// musl normally allocates this using mmap). This region299// includes the pthread structure itself.300unsigned char* block = t->map_base;301dbg("_emscripten_thread_free_data thread=%p map_base=%p", t, block);302// To aid in debugging, set the entire region to zero.303memset(block, 0, sizeof(struct pthread));304emscripten_builtin_free(block);305}306307void _emscripten_thread_exit(void* result) {308struct pthread *self = __pthread_self();309assert(self);310311self->canceldisable = PTHREAD_CANCEL_DISABLE;312self->cancelasync = PTHREAD_CANCEL_DEFERRED;313self->result = result;314315_emscripten_thread_mailbox_shutdown(self);316317// Run any handlers registered with pthread_cleanup_push318__run_cleanup_handlers();319320// Call into the musl function that runs destructors of all thread-specific data.321__pthread_tsd_run_dtors();322323__tl_lock();324325/* Process robust list in userspace to handle non-pshared mutexes326* and the detached thread case where the robust list head will327* be invalid when the kernel would process it. */328__vm_lock();329volatile void *volatile *rp;330while ((rp=self->robust_list.head) && rp != &self->robust_list.head) {331pthread_mutex_t *m = (void *)((char *)rp332- offsetof(pthread_mutex_t, _m_next));333int waiters = m->_m_waiters;334int priv = (m->_m_type & 128) ^ 128;335self->robust_list.pending = rp;336self->robust_list.head = *rp;337int cont = a_swap(&m->_m_lock, 0x40000000);338self->robust_list.pending = 0;339if (cont < 0 || waiters)340__wake(&m->_m_lock, 1, priv);341}342__vm_unlock();343344if (!--libc.threads_minus_1) libc.need_locks = 0;345346self->next->prev = self->prev;347self->prev->next = self->next;348self->prev = self->next = self;349350__tl_unlock();351352if (emscripten_is_main_runtime_thread()) {353exit(0);354return;355}356357// Not hosting a pthread anymore in this worker set __pthread_self to NULL358__set_thread_state(NULL, 0, 0, 1);359360/* This atomic potentially competes with a concurrent pthread_detach361* call; the loser is responsible for freeing thread resources. */362int state = a_cas(&self->detach_state, DT_JOINABLE, DT_EXITING);363364if (state == DT_DETACHED) {365_emscripten_thread_cleanup(self);366} else {367// Mark the thread as no longer running, so it can be joined.368// Once we publish this, any threads that are waiting to join with us can369// proceed and this worker can be recycled and used on another thread.370#ifdef EMSCRIPTEN_DYNAMIC_LINKING371// When dynamic linking is enabled we need to keep track of zombie threads372_emscripten_thread_exit_joinable(self);373#endif374a_store(&self->detach_state, DT_EXITED);375__wake(&self->detach_state, 1, 1); // Wake any joiner.376}377}378379// Mark as `no_sanitize("address"` since emscripten_pthread_exit destroys380// the current thread and runs its exit handlers. Without this asan injects381// a call to __asan_handle_no_return before emscripten_unwind_to_js_event_loop382// which seem to cause a crash later down the line.383__attribute__((no_sanitize("address")))384_Noreturn void __pthread_exit(void* retval) {385_emscripten_thread_exit(retval);386emscripten_unwind_to_js_event_loop();387}388389weak_alias(__pthread_create, emscripten_builtin_pthread_create);390weak_alias(__pthread_create, pthread_create);391weak_alias(__pthread_exit, emscripten_builtin_pthread_exit);392weak_alias(__pthread_exit, pthread_exit);393394395