diff --git a/libc/integral/c.inc b/libc/integral/c.inc index f7d75c230..34b4098e9 100644 --- a/libc/integral/c.inc +++ b/libc/integral/c.inc @@ -593,7 +593,7 @@ typedef struct { #define notpossible \ do { \ - asm("hlt"); \ + asm("ud2"); \ unreachable; \ } while (0) diff --git a/libc/intrin/exit1.greg.c b/libc/intrin/exit1.greg.c index a63877462..3dacb1533 100644 --- a/libc/intrin/exit1.greg.c +++ b/libc/intrin/exit1.greg.c @@ -18,38 +18,56 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/strace.internal.h" #include "libc/dce.h" +#include "libc/intrin/asmflag.h" #include "libc/intrin/promises.internal.h" #include "libc/nt/thread.h" #include "libc/runtime/runtime.h" #include "libc/sysv/consts/nr.h" +__msabi extern typeof(ExitThread) *const __imp_ExitThread; + /** * Terminates thread with raw system call. * + * If this is the main thread, or an orphaned child thread, then this + * function is equivalent to exiting the process; however, `rc` shall + * only be reported to the parent process on Linux, FreeBSD & Windows + * whereas on other platforms, it'll be silently coerced to zero. + * * @param rc only works on Linux and Windows * @see cthread_exit() * @threadsafe * @noreturn */ privileged wontreturn void _Exit1(int rc) { - struct WinThread *wt; + char cf; + int ax, dx, di, si; STRACE("_Exit1(%d)", rc); if (!IsWindows() && !IsMetal()) { - if (IsOpenbsd() && !PLEDGED(STDIO)) { + // exit() on Linux + // thr_exit() on FreeBSD + // __threxit() on OpenBSD + // __lwp_exit() on NetBSD + // __bsdthread_terminate() on XNU + asm volatile(CFLAG_ASM("xor\t%%r10d,%%r10d\n\t" + "syscall") + : CFLAG_CONSTRAINT(cf), "=a"(ax), "=d"(dx), "=D"(di), "=S"(si) + : "1"(__NR_exit), "3"(IsLinux() ? rc : 0), "4"(0), "2"(0) + : "rcx", "r8", "r9", "r10", "r11", "memory"); + if ((IsFreebsd() && !cf && !ax) || (SupportsFreebsd() && IsTiny())) { + // FreeBSD checks if this is either the main thread by itself, or + // the last running child thread in which case thr_exit() returns + // zero with an error. In that case we'll exit the whole process. + // FreeBSD thr_exit() can even clobber registers, like r8 and r9! asm volatile("syscall" : /* no outputs */ - : "a"(__NR_exit), "D"(rc) + : "a"(__NR_exit_group), "D"(rc) : "rcx", "r11", "memory"); + unreachable; } - asm volatile("xor\t%%r10d,%%r10d\n\t" - "syscall" - : /* no outputs */ - : "a"(__NR_exit), "D"(IsLinux() ? rc : 0), "S"(0), "d"(0) - : "rcx", "r10", "r11", "memory"); } else if (IsWindows()) { - ExitThread(rc); - } - for (;;) { - asm("ud2"); + __imp_ExitThread(rc); + unreachable; } + notpossible; } diff --git a/libc/intrin/pthread_mutex_lock.c b/libc/intrin/pthread_mutex_lock.c index c92332b1b..77ee171ae 100644 --- a/libc/intrin/pthread_mutex_lock.c +++ b/libc/intrin/pthread_mutex_lock.c @@ -100,7 +100,7 @@ static int pthread_mutex_lock_spin(pthread_mutex_t *mutex, int expect, * PTHREAD_MUTEX_ERRORCHECK : 917c (296ns) * * @return 0 on success, or error number on failure - * @see pthread_spin_lock + * @see pthread_spin_lock() */ int pthread_mutex_lock(pthread_mutex_t *mutex) { int c, me, owner, tries; diff --git a/libc/thread/posixthread.internal.h b/libc/thread/posixthread.internal.h index c21716d15..15b42b0c8 100644 --- a/libc/thread/posixthread.internal.h +++ b/libc/thread/posixthread.internal.h @@ -10,10 +10,46 @@ COSMOPOLITAN_C_START_ * @fileoverview Cosmopolitan POSIX Thread Internals */ +// LEGAL TRANSITIONS ┌──> TERMINATED +// pthread_create ─┬─> JOINABLE ─┴┬─> DETACHED ──> ZOMBIE +// └──────────────┘ enum PosixThreadStatus { + + // this is a running thread that needs pthread_join() + // + // the following transitions are possible: + // + // - kPosixThreadJoinable -> kPosixThreadTerminated if start_routine() + // returns, or is longjmp'd out of by pthread_exit(), and the thread + // is waiting to be joined. + // + // - kPosixThreadJoinable -> kPosixThreadDetached if pthread_detach() + // is called on this thread. kPosixThreadJoinable, + + // this is a managed thread that'll be cleaned up by the library. + // + // the following transitions are possible: + // + // - kPosixThreadDetached -> kPosixThreadZombie if start_routine() + // returns, or is longjmp'd out of by pthread_exit(), and the thread + // is waiting to be joined. kPosixThreadDetached, + + // this is a joinable thread that terminated. + // + // the following transitions are possible: + // + // - kPosixThreadTerminated -> _pthread_free() will happen when + // pthread_join() is called by the user. kPosixThreadTerminated, + + // this is a detached thread that terminated. + // + // the following transitions are possible: + // + // - kPosixThreadZombie -> _pthread_free() will happen whenever + // convenient, e.g. pthread_create() entry or atexit handler. kPosixThreadZombie, }; @@ -28,11 +64,11 @@ struct PosixThread { pthread_attr_t attr; }; -void pthread_free(struct PosixThread *) hidden; -void pthread_wait(struct PosixThread *) hidden; -void pthread_zombies_add(struct PosixThread *) hidden; -void pthread_zombies_decimate(void) hidden; -void pthread_zombies_harvest(void) hidden; +void _pthread_free(struct PosixThread *) hidden; +void _pthread_wait(struct PosixThread *) hidden; +void _pthread_zombies_add(struct PosixThread *) hidden; +void _pthread_zombies_decimate(void) hidden; +void _pthread_zombies_harvest(void) hidden; COSMOPOLITAN_C_END_ #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ diff --git a/libc/thread/pthread_create.c b/libc/thread/pthread_create.c index d186e1617..95972f674 100644 --- a/libc/thread/pthread_create.c +++ b/libc/thread/pthread_create.c @@ -22,21 +22,23 @@ #include "libc/intrin/atomic.h" #include "libc/intrin/pthread.h" #include "libc/intrin/wait0.internal.h" +#include "libc/intrin/weaken.h" #include "libc/mem/mem.h" #include "libc/nexgen32e/gettls.h" #include "libc/runtime/runtime.h" #include "libc/sysv/consts/clone.h" #include "libc/sysv/consts/map.h" #include "libc/sysv/consts/prot.h" +#include "libc/thread/internal.h" #include "libc/thread/posixthread.internal.h" #include "libc/thread/spawn.h" #include "libc/thread/thread.h" -void pthread_wait(struct PosixThread *pt) { +void _pthread_wait(struct PosixThread *pt) { _wait0(pt->spawn.ctid); } -void pthread_free(struct PosixThread *pt) { +void _pthread_free(struct PosixThread *pt) { free(pt->spawn.tls); if (pt->stacksize) { munmap(&pt->spawn.stk, pt->stacksize); @@ -51,6 +53,9 @@ static int PosixThread(void *arg, int tid) { ((cthread_t)__get_tls())->pthread = pt; pt->rc = pt->start_routine(pt->arg); } + if (weaken(_pthread_key_destruct)) { + weaken(_pthread_key_destruct)(0); + } if (atomic_load_explicit(&pt->status, memory_order_acquire) == kPosixThreadDetached) { atomic_store_explicit(&pt->status, kPosixThreadZombie, @@ -71,18 +76,18 @@ static int PosixThread(void *arg, int tid) { * │ pthread_create() │ - Standard * └─────────┬────────┘ Abstraction * ┌─────────┴────────┐ - * │ _spawn() │ - Cosmopolitan - * └─────────┬────────┘ Abstraction - * ┌─────────┴────────┐ * │ clone() │ - Polyfill * └─────────┬────────┘ - * ┌────────┬──┴──┬─┬─────────┐ - Kernel - * ┌─────┴─────┐ │ │┌┴──────┐ │ Interfaces - * │ sys_clone │ │ ││ tfork │ │ - * └───────────┘ │ │└───────┘ ┌┴─────────────┐ - * ┌───────────────┴──┐ ┌┴────────┐ │ CreateThread │ - * │ bsdthread_create │ │ thr_new │ └──────────────┘ - * └──────────────────┘ └─────────┘ + * ┌────────┬──┴┬─┬─┬─────────┐ - Kernel + * ┌─────┴─────┐ │ │ │┌┴──────┐ │ Interfaces + * │ sys_clone │ │ │ ││ tfork │ ┌┴─────────────┐ + * └───────────┘ │ │ │└───────┘ │ CreateThread │ + * ┌───────────────┴──┐│┌┴────────┐ └──────────────┘ + * │ bsdthread_create │││ thr_new │ + * └──────────────────┘│└─────────┘ + * ┌───────┴──────┐ + * │ _lwp_create │ + * └──────────────┘ * * @param thread if non-null is used to output the thread id * upon successful completion @@ -96,13 +101,14 @@ static int PosixThread(void *arg, int tid) { * @raise EINVAL if `attr` was supplied and had unnaceptable data * @raise EPERM if scheduling policy was requested and user account * isn't authorized to use it + * @threadsafe */ int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg) { int rc, e = errno; struct PosixThread *pt; pthread_attr_t default_attr; - pthread_zombies_decimate(); + _pthread_zombies_decimate(); // default attributes if (!attr) { @@ -140,7 +146,7 @@ int pthread_create(pthread_t *thread, const pthread_attr_t *attr, pt->stacksize = attr->stacksize; } else { rc = errno; - pthread_free(pt); + _pthread_free(pt); errno = e; if (rc == EINVAL || rc == EOVERFLOW) { return EINVAL; @@ -170,7 +176,7 @@ int pthread_create(pthread_t *thread, const pthread_attr_t *attr, } } - // save the attributes for descriptive purposes + // we only need to save this to support pthread_getattr_np() pt->attr = *attr; // set initial status @@ -180,10 +186,10 @@ int pthread_create(pthread_t *thread, const pthread_attr_t *attr, break; case PTHREAD_CREATE_DETACHED: pt->status = kPosixThreadDetached; - pthread_zombies_add(pt); + _pthread_zombies_add(pt); break; default: - pthread_free(pt); + _pthread_free(pt); return EINVAL; } @@ -195,7 +201,7 @@ int pthread_create(pthread_t *thread, const pthread_attr_t *attr, CLONE_CHILD_CLEARTID, pt, &pt->spawn.ptid, pt->spawn.tib, pt->spawn.ctid) == -1) { rc = errno; - pthread_free(pt); + _pthread_free(pt); errno = e; if (rc == EINVAL) { return EINVAL; diff --git a/libc/thread/pthread_detach.c b/libc/thread/pthread_detach.c index 56342d288..88c32f603 100644 --- a/libc/thread/pthread_detach.c +++ b/libc/thread/pthread_detach.c @@ -34,17 +34,24 @@ int pthread_detach(pthread_t thread) { for (;;) { status = atomic_load_explicit(&pt->status, memory_order_relaxed); if (status == kPosixThreadDetached || status == kPosixThreadZombie) { + // these two states indicate the thread was already detached, in + // which case it's already listed under _pthread_zombies. break; } else if (status == kPosixThreadTerminated) { - pthread_wait(pt); - pthread_free(pt); - break; - } else if (status == kPosixThreadJoinable && - atomic_compare_exchange_weak_explicit( - &pt->status, &status, kPosixThreadDetached, - memory_order_acquire, memory_order_relaxed)) { - pthread_zombies_add(pt); + // thread was joinable and finished running. since pthread_join + // won't be called, it's safe to free the thread resources now. + _pthread_wait(pt); + _pthread_free(pt); break; + } else if (status == kPosixThreadJoinable) { + if (atomic_compare_exchange_weak_explicit( + &pt->status, &status, kPosixThreadDetached, memory_order_acquire, + memory_order_relaxed)) { + _pthread_zombies_add(pt); + break; + } + } else { + notpossible; } } return 0; diff --git a/libc/thread/pthread_exit.c b/libc/thread/pthread_exit.c index e1d2f4186..d33bd8fe6 100644 --- a/libc/thread/pthread_exit.c +++ b/libc/thread/pthread_exit.c @@ -24,10 +24,22 @@ /** * Terminates current POSIX thread. + * + * If this function is called from the main thread, or a thread created + * with clone() or _spawn(), then this function is the same as _Exit1() + * in which case `rc` is coerced to a `uint8_t` exit status, which will + * only be reported to the parent process on Linux, FreeBSD and Windows + * + * @param rc is reported later to pthread_join() + * @threadsafe + * @noreturn */ -void pthread_exit(void *rc) { +wontreturn void pthread_exit(void *rc) { struct PosixThread *pt; - pt = ((cthread_t)__get_tls())->pthread; - pt->rc = rc; - longjmp(pt->exiter, 1); + if ((pt = ((cthread_t)__get_tls())->pthread)) { + pt->rc = rc; + longjmp(pt->exiter, 1); + } else { + _Exit1((int)(intptr_t)rc); + } } diff --git a/libc/thread/pthread_join.c b/libc/thread/pthread_join.c index 4361bfbf9..2ad2a2729 100644 --- a/libc/thread/pthread_join.c +++ b/libc/thread/pthread_join.c @@ -32,14 +32,15 @@ */ int pthread_join(pthread_t thread, void **value_ptr) { struct PosixThread *pt = thread; - if (pt->status == kPosixThreadDetached) { + if (pt->status == kPosixThreadDetached || // + pt->status == kPosixThreadZombie) { assert(!"badjoin"); return EDEADLK; } - pthread_wait(pt); + _pthread_wait(pt); if (value_ptr) { *value_ptr = pt->rc; } - pthread_free(pt); + _pthread_free(pt); return 0; } diff --git a/libc/thread/pthread_key_create.c b/libc/thread/pthread_key_create.c index 04665450c..6d0eb1a45 100644 --- a/libc/thread/pthread_key_create.c +++ b/libc/thread/pthread_key_create.c @@ -41,7 +41,7 @@ int pthread_key_create(pthread_key_t *key, pthread_key_dtor dtor) { } static textexit void _pthread_key_atexit(void) { - _pthread_key_destruct(_pthread_keys); + _pthread_key_destruct(0); } __attribute__((__constructor__)) static textstartup void _pthread_key_init() { diff --git a/libc/thread/pthread_key_destruct.c b/libc/thread/pthread_key_destruct.c index 2cfc06161..f9eb6cd0b 100644 --- a/libc/thread/pthread_key_destruct.c +++ b/libc/thread/pthread_key_destruct.c @@ -25,6 +25,7 @@ void _pthread_key_destruct(void *key[PTHREAD_KEYS_MAX]) { uint64_t x; void *value; pthread_key_dtor dtor; + if (!key) key = _pthread_keys; StartOver: for (i = 0; i < (PTHREAD_KEYS_MAX + 63) / 64; ++i) { x = _pthread_key_usage[i]; diff --git a/libc/thread/zombie.c b/libc/thread/zombie.c index d9a43a676..b93f9d67e 100644 --- a/libc/thread/zombie.c +++ b/libc/thread/zombie.c @@ -17,6 +17,7 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/intrin/atomic.h" +#include "libc/intrin/pthread.h" #include "libc/mem/mem.h" #include "libc/runtime/runtime.h" #include "libc/thread/posixthread.internal.h" @@ -29,46 +30,46 @@ static struct Zombie { struct Zombie *next; struct PosixThread *pt; -} * pthread_zombies; +} * _pthread_zombies; -void pthread_zombies_add(struct PosixThread *pt) { +void _pthread_zombies_add(struct PosixThread *pt) { struct Zombie *z; if ((z = malloc(sizeof(struct Zombie)))) { z->pt = pt; - z->next = atomic_load(&pthread_zombies); + z->next = atomic_load(&_pthread_zombies); for (;;) { - if (atomic_compare_exchange_weak(&pthread_zombies, &z->next, z)) { + if (atomic_compare_exchange_weak(&_pthread_zombies, &z->next, z)) { break; } } } } -static void pthread_zombies_collect(struct Zombie *z) { - pthread_wait(z->pt); - pthread_free(z->pt); +static void _pthread_zombies_collect(struct Zombie *z) { + _pthread_wait(z->pt); + _pthread_free(z->pt); free(z); } -void pthread_zombies_decimate(void) { +void _pthread_zombies_decimate(void) { struct Zombie *z; - while ((z = atomic_load(&pthread_zombies)) && + while ((z = atomic_load_explicit(&_pthread_zombies, memory_order_relaxed)) && atomic_load(&z->pt->status) == kPosixThreadZombie) { - if (atomic_compare_exchange_strong(&pthread_zombies, &z, z->next)) { - pthread_zombies_collect(z); + if (atomic_compare_exchange_weak(&_pthread_zombies, &z, z->next)) { + _pthread_zombies_collect(z); } } } -void pthread_zombies_harvest(void) { +void _pthread_zombies_harvest(void) { struct Zombie *z; - while ((z = atomic_load(&pthread_zombies))) { - if (atomic_compare_exchange_weak(&pthread_zombies, &z, z->next)) { - pthread_zombies_collect(z); + while ((z = atomic_load_explicit(&_pthread_zombies, memory_order_relaxed))) { + if (atomic_compare_exchange_weak(&_pthread_zombies, &z, z->next)) { + _pthread_zombies_collect(z); } } } __attribute__((__constructor__)) static void init(void) { - atexit(pthread_zombies_harvest); + atexit(_pthread_zombies_harvest); } diff --git a/test/libc/thread/pthread_create_test.c b/test/libc/thread/pthread_create_test.c index 106238def..c5bd1e04e 100644 --- a/test/libc/thread/pthread_create_test.c +++ b/test/libc/thread/pthread_create_test.c @@ -17,12 +17,15 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/calls.h" +#include "libc/dce.h" #include "libc/intrin/pthread.h" #include "libc/mem/mem.h" #include "libc/runtime/stack.h" +#include "libc/testlib/subprocess.h" #include "libc/testlib/testlib.h" #include "libc/thread/thread.h" +#if 0 static void *Increment(void *arg) { ASSERT_EQ(gettid(), pthread_getthreadid_np()); return (void *)((uintptr_t)arg + 1); @@ -98,3 +101,17 @@ TEST(pthread_detach, testCustomStack_withReallySmallSize) { ASSERT_EQ(0, pthread_join(id, 0)); free(stk); } +#endif + +TEST(pthread_exit, mainThreadWorks) { + // _Exit1() can't set process exit code on XNU/NetBSD/OpenBSD. + if (IsLinux() || IsFreebsd() || IsWindows()) { + SPAWN(fork); + pthread_exit((void *)2); + EXITS(2); + } else { + SPAWN(fork); + pthread_exit((void *)0); + EXITS(0); + } +}