Eliminate cyclic locks in runtime

This change introduces a new deadlock detector for Cosmo's POSIX threads
implementation. Error check mutexes will now track a DAG of nested locks
and report EDEADLK when a deadlock is theoretically possible. These will
occur rarely, but it's important for production hardening your code. You
don't even need to change your mutexes to use the POSIX error check mode
because `cosmocc -mdbg` will enable error checking on mutexes by default
globally. When cycles are found, an error message showing your demangled
symbols describing the strongly connected component are printed and then
the SIGTRAP is raised, which means you'll also get a backtrace if you're
using ShowCrashReports() too. This new error checker is so low-level and
so pure that it's able to verify the relationships of every libc runtime
lock, including those locks upon which the mutex implementation depends.
This commit is contained in:
Justine Tunney 2024-12-16 20:51:27 -08:00
parent 26c051c297
commit af7bd80430
No known key found for this signature in database
GPG key ID: BE714B4575D6E328
141 changed files with 2094 additions and 1601 deletions

View file

@ -34,13 +34,16 @@
#include "libc/sysv/consts/sig.h"
#include "libc/sysv/errfuns.h"
#include "libc/thread/itimer.internal.h"
#include "libc/thread/thread2.h"
#include "libc/thread/tls.h"
#include "third_party/nsync/mu.h"
#ifdef __x86_64__
#define STACK_SIZE 65536
struct IntervalTimer __itimer;
struct IntervalTimer __itimer = {
.lock = PTHREAD_MUTEX_INITIALIZER,
.cond = PTHREAD_COND_INITIALIZER,
};
static textwindows dontinstrument uint32_t __itimer_worker(void *arg) {
struct CosmoTib tls;
@ -52,7 +55,7 @@ static textwindows dontinstrument uint32_t __itimer_worker(void *arg) {
for (;;) {
bool dosignal = false;
struct timeval now, waituntil;
nsync_mu_lock(&__itimer.lock);
pthread_mutex_lock(&__itimer.lock);
now = timeval_real();
if (timeval_iszero(__itimer.it.it_value)) {
waituntil = timeval_max;
@ -73,13 +76,13 @@ static textwindows dontinstrument uint32_t __itimer_worker(void *arg) {
dosignal = true;
}
}
nsync_mu_unlock(&__itimer.lock);
pthread_mutex_unlock(&__itimer.lock);
if (dosignal)
__sig_generate(SIGALRM, SI_TIMER);
nsync_mu_lock(&__itimer.lock);
nsync_cv_wait_with_deadline(&__itimer.cond, &__itimer.lock, CLOCK_REALTIME,
timeval_totimespec(waituntil), 0);
nsync_mu_unlock(&__itimer.lock);
pthread_mutex_lock(&__itimer.lock);
struct timespec deadline = timeval_totimespec(waituntil);
pthread_cond_timedwait(&__itimer.cond, &__itimer.lock, &deadline);
pthread_mutex_unlock(&__itimer.lock);
}
return 0;
}
@ -109,7 +112,7 @@ textwindows int sys_setitimer_nt(int which, const struct itimerval *neu,
config = *neu;
}
BLOCK_SIGNALS;
nsync_mu_lock(&__itimer.lock);
pthread_mutex_lock(&__itimer.lock);
if (old) {
old->it_interval = __itimer.it.it_interval;
old->it_value = timeval_subz(__itimer.it.it_value, timeval_real());
@ -119,9 +122,9 @@ textwindows int sys_setitimer_nt(int which, const struct itimerval *neu,
config.it_value = timeval_add(config.it_value, timeval_real());
}
__itimer.it = config;
nsync_cv_signal(&__itimer.cond);
pthread_cond_signal(&__itimer.cond);
}
nsync_mu_unlock(&__itimer.lock);
pthread_mutex_unlock(&__itimer.lock);
ALLOW_SIGNALS;
return 0;
}

View file

@ -2,15 +2,14 @@
#define COSMOPOLITAN_LIBC_ITIMER_H_
#include "libc/atomic.h"
#include "libc/calls/struct/itimerval.h"
#include "third_party/nsync/cv.h"
#include "third_party/nsync/mu.h"
#include "libc/thread/thread.h"
COSMOPOLITAN_C_START_
struct IntervalTimer {
atomic_uint once;
intptr_t thread;
nsync_mu lock;
nsync_cv cond;
pthread_mutex_t lock;
pthread_cond_t cond;
struct itimerval it;
};

View file

@ -3,18 +3,25 @@
COSMOPOLITAN_C_START_
//
// ┌depth
// │
// COSMOPOLITAN MUTEXES │ ┌waited
// │ │
// │ │┌locked
// │ ││
// │ ││┌pshared
// owner │ │││
// tid │ │││┌type
// │ │ ││││
// ┌──────────────┴───────────────┐ ┌─┴──┐│││├┐
// ┌undead
// │
// │┌dead
// ││
// ││┌robust
// │││
// │││ ┌depth
// │││ │
// COSMOPOLITAN MUTEXES │││ │ ┌waited
// │││ │ │
// │││ │ │┌locked
// │││ │ ││
// │││ │ ││┌pshared
// owner │││ │ │││
// tid │││ │ │││┌type
// │ │││ │ ││││
// ┌──────────────┴───────────────┐ │││┌─┴──┐│││├┐
// 0b0000000000000000000000000000000000000000000000000000000000000000
//
#define MUTEX_DEPTH_MIN 0x00000020ull
#define MUTEX_DEPTH_MAX 0x000007e0ull

View file

@ -98,7 +98,6 @@ extern struct Dll *_pthread_list;
extern struct PosixThread _pthread_static;
extern _Atomic(pthread_key_dtor) _pthread_key_dtor[PTHREAD_KEYS_MAX];
int _pthread_atfork(atfork_f, atfork_f, atfork_f) libcesque;
int _pthread_reschedule(struct PosixThread *) libcesque;
int _pthread_setschedparam_freebsd(int, int, const struct sched_param *);
int _pthread_tid(struct PosixThread *) libcesque;

View file

@ -0,0 +1,179 @@
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi
Copyright 2022 Justine Alexandra Roberts Tunney
Permission to use, copy, modify, and/or distribute this software for
any purpose with or without fee is hereby granted, provided that the
above copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/errno.h"
#include "libc/intrin/strace.h"
#include "libc/mem/mem.h"
#include "libc/thread/posixthread.internal.h"
#include "libc/thread/thread.h"
struct AtFork {
struct AtFork *p[2];
atfork_f f[3];
};
struct AtForks {
pthread_once_t once;
pthread_mutex_t lock;
struct AtFork *list;
};
static struct AtForks _atforks = {
.once = PTHREAD_ONCE_INIT,
.lock = PTHREAD_MUTEX_INITIALIZER,
};
static void pthread_atfork_clear(void) {
struct AtFork *a, *b;
for (a = _atforks.list; a; a = b) {
b = a->p[0];
free(a);
}
}
static void pthread_atfork_init(void) {
atexit(pthread_atfork_clear);
}
static void _pthread_onfork(int i, const char *op) {
struct AtFork *a;
for (a = _atforks.list; a; a = a->p[!i]) {
if (a->f[i]) {
STRACE("pthread_atfork(%s, %t)", op, a->f[i]);
a->f[i]();
}
_atforks.list = a;
}
}
void _pthread_onfork_prepare(void) {
_pthread_onfork(0, "prepare");
}
void _pthread_onfork_parent(void) {
_pthread_onfork(1, "parent");
}
void _pthread_onfork_child(void) {
pthread_mutex_wipe_np(&_atforks.lock);
_pthread_onfork(2, "child");
}
/**
* Registers fork callbacks.
*
* When fork happens, your prepare functions will be called in the
* reverse order they were registered. Then, in the parent and child
* processes, their callbacks will be called in the same order they were
* registered.
*
* One big caveat with fork() is that it hard kills all threads except
* the calling thread. So let's say one of those threads was printing to
* stdout while it was killed. In that case, the stdout lock will still
* be held when your child process comes alive, which means that the
* child will deadlock next time it tries to print.
*
* The solution for that is simple. Every lock in your process should be
* registered with this interface. However there's one highly important
* thing you need to know. Locks must follow a consistent hierarchy. So
* the order in which you register locks matters. If nested locks aren't
* acquired in the same order globally, then rarely occurring deadlocks
* will happen. So what we recommend is that you hunt down all the locks
* that exist in your app and its dependencies, and register them all at
* once from your main() function at startup. This ensures a clear order
* and if you aren't sure what that order should be, cosmo libc has got
* you covered. Simply link your program with the `cosmocc -mdbg` flag
* and cosmo will detect locking violations with your `pthread_mutex_t`
* objects and report them by printing the strongly connected component.
* This will include the demangled symbol name of each mutex, assuming
* the `pthread_mutex_t` objects are stored in static memory. cosmo.h
* also exposes a deadlock API that lets you incorporate your own lock
* object types into this error checking system, which we also use to
* verify the entire libc runtime itself. See libc/intrin/deadlock.c.
*
* Special care should be taken when using this interface in libraries.
* While it may seem tempting to use something like a `__constructor__`
* attribute to register your mutexes in a clean and abstracted way, it
* is only appropriate if your mutex is guarding pure memory operations
* and poses zero risk of nesting with locks outside your library. For
* example, calling open() or printf() while holding your lock will do
* just that, since the C runtime functions you may consider pure will
* actually use mutexes under the hood, which are also validated under
* `cosmocc -mdbg` builds. So if your locks can't be made unnestable
* pure memory operations, then you should consider revealing their
* existence to users of your library.
*
* Here's an example of how pthread_atfork() can be used:
*
* static struct {
* pthread_once_t once;
* pthread_mutex_t lock;
* // data structures...
* } g_lib;
*
* static void lib_lock(void) {
* pthread_mutex_lock(&g_lib.lock);
* }
*
* static void lib_unlock(void) {
* pthread_mutex_unlock(&g_lib.lock);
* }
*
* static void lib_wipe(void) {
* pthread_mutex_wipe_np(&g_lib.lock);
* }
*
* static void lib_setup(void) {
* pthread_mutex_init(&g_lib.lock, 0);
* pthread_atfork(lib_lock, lib_unlock, lib_wipe);
* }
*
* static void lib_init(void) {
* pthread_once(&g_lib.once, lib_setup);
* }
*
* void lib(void) {
* lib_init();
* lib_lock();
* // do stuff...
* lib_unlock();
* }
*
* @param prepare is run by fork() before forking happens
* @param parent is run by fork() after forking happens in parent process
* @param child is run by fork() after forking happens in childe process
* @return 0 on success, or errno on error
* @raise ENOMEM if we require more vespene gas
*/
int pthread_atfork(atfork_f prepare, atfork_f parent, atfork_f child) {
pthread_once(&_atforks.once, pthread_atfork_init);
struct AtFork *a;
if (!(a = calloc(1, sizeof(struct AtFork))))
return ENOMEM;
a->f[0] = prepare;
a->f[1] = parent;
a->f[2] = child;
pthread_mutex_lock(&_atforks.lock);
a->p[0] = 0;
a->p[1] = _atforks.list;
if (_atforks.list)
_atforks.list->p[0] = a;
_atforks.list = a;
pthread_mutex_unlock(&_atforks.lock);
return 0;
}

View file

@ -55,7 +55,7 @@ errno_t pthread_cond_broadcast(pthread_cond_t *cond) {
// favor *NSYNC if this is a process private condition variable
// if using Mike Burrows' code isn't possible, use a naive impl
if (!cond->_footek) {
nsync_cv_broadcast((nsync_cv *)cond);
nsync_cv_broadcast((nsync_cv *)cond->_nsync);
return 0;
}
#endif

View file

@ -33,7 +33,7 @@ errno_t pthread_cond_destroy(pthread_cond_t *cond) {
// check if there's active waiters
#if PTHREAD_USE_NSYNC
if (!cond->_pshared) {
if (((nsync_cv *)cond)->waiters)
if (((nsync_cv *)cond->_nsync)->waiters)
return EINVAL;
} else {
if (atomic_load_explicit(&cond->_waiters, memory_order_relaxed))

View file

@ -54,7 +54,7 @@ errno_t pthread_cond_signal(pthread_cond_t *cond) {
// favor *NSYNC if this is a process private condition variable
// if using Mike Burrows' code isn't possible, use a naive impl
if (!cond->_footek) {
nsync_cv_signal((nsync_cv *)cond);
nsync_cv_signal((nsync_cv *)cond->_nsync);
return 0;
}
#endif

View file

@ -43,7 +43,7 @@ struct PthreadWait {
static bool can_use_nsync(uint64_t muword) {
return !IsXnuSilicon() && //
MUTEX_TYPE(muword) == PTHREAD_MUTEX_NORMAL &&
MUTEX_TYPE(muword) != PTHREAD_MUTEX_RECURSIVE &&
MUTEX_PSHARED(muword) == PTHREAD_PROCESS_PRIVATE;
}
@ -124,9 +124,9 @@ errno_t pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex,
uint64_t muword = atomic_load_explicit(&mutex->_word, memory_order_relaxed);
// check that mutex is held by caller
if (MUTEX_TYPE(muword) == PTHREAD_MUTEX_ERRORCHECK &&
MUTEX_OWNER(muword) != gettid())
return EPERM;
if (IsModeDbg() || MUTEX_TYPE(muword) == PTHREAD_MUTEX_ERRORCHECK)
if (__deadlock_tracked(mutex) == 0)
return EPERM;
// if the cond is process shared then the mutex needs to be too
if ((cond->_pshared == PTHREAD_PROCESS_SHARED) ^
@ -154,7 +154,7 @@ errno_t pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex,
// if using Mike Burrows' code isn't possible, use a naive impl
if (!cond->_footek) {
err = nsync_cv_wait_with_deadline(
(nsync_cv *)cond, (nsync_mu *)mutex, cond->_clock,
(nsync_cv *)cond->_nsync, (nsync_mu *)mutex->_nsync, cond->_clock,
abstime ? *abstime : nsync_time_no_deadline, 0);
} else {
err = pthread_cond_timedwait_impl(cond, mutex, abstime);

View file

@ -61,7 +61,6 @@ __static_yoink("nsync_mu_unlock");
__static_yoink("nsync_mu_trylock");
__static_yoink("nsync_mu_rlock");
__static_yoink("nsync_mu_runlock");
__static_yoink("_pthread_atfork");
__static_yoink("_pthread_onfork_prepare");
__static_yoink("_pthread_onfork_parent");
__static_yoink("_pthread_onfork_child");

View file

@ -8,11 +8,13 @@
#define PTHREAD_BARRIER_SERIAL_THREAD 31337
#define PTHREAD_MUTEX_NORMAL 0
#define PTHREAD_MUTEX_RECURSIVE 1
#define PTHREAD_MUTEX_ERRORCHECK 2
#define PTHREAD_MUTEX_STALLED 0
#define PTHREAD_MUTEX_ROBUST 1
#define PTHREAD_MUTEX_DEFAULT 0
#define PTHREAD_MUTEX_NORMAL 1
#define PTHREAD_MUTEX_RECURSIVE 2
#define PTHREAD_MUTEX_ERRORCHECK 3
#define PTHREAD_MUTEX_STALLED 0
#define PTHREAD_MUTEX_ROBUST 2048
#define PTHREAD_PROCESS_PRIVATE 0
#define PTHREAD_PROCESS_SHARED 4
@ -43,12 +45,14 @@ COSMOPOLITAN_C_START_
#define PTHREAD_ONCE_INIT {0}
#define PTHREAD_COND_INITIALIZER {0}
#define PTHREAD_RWLOCK_INITIALIZER {0}
#define PTHREAD_MUTEX_INITIALIZER {0}
#define PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP {0, {}, PTHREAD_MUTEX_RECURSIVE}
#define PTHREAD_MUTEX_INITIALIZER {0, PTHREAD_MUTEX_DEFAULT}
#define PTHREAD_NORMAL_MUTEX_INITIALIZER_NP {0, PTHREAD_MUTEX_NORMAL}
#define PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP {0, PTHREAD_MUTEX_RECURSIVE}
#define PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP {0, PTHREAD_MUTEX_ERRORCHECK}
#define PTHREAD_SIGNAL_SAFE_MUTEX_INITIALIZER_NP \
{0, {}, PTHREAD_MUTEX_RECURSIVE | PTHREAD_PROCESS_SHARED}
{0, PTHREAD_MUTEX_RECURSIVE | PTHREAD_PROCESS_SHARED}
#ifndef __cplusplus
#define _PTHREAD_ATOMIC(x) _Atomic(x)
@ -72,14 +76,11 @@ typedef struct pthread_spinlock_s {
} pthread_spinlock_t;
typedef struct pthread_mutex_s {
uint32_t _nsync;
union {
int32_t _pid;
_PTHREAD_ATOMIC(int32_t) _futex;
};
/* this cleverly overlaps with NSYNC struct Dll *waiters; */
void *_edges;
_PTHREAD_ATOMIC(uint64_t) _word;
long _nsyncx[2];
_PTHREAD_ATOMIC(int) _futex;
int _pid;
void *_nsync[2];
} pthread_mutex_t;
typedef struct pthread_mutexattr_s {
@ -92,18 +93,13 @@ typedef struct pthread_condattr_s {
} pthread_condattr_t;
typedef struct pthread_cond_s {
union {
void *_align;
struct {
uint32_t _nsync;
char _pshared;
char _clock;
char _footek;
_PTHREAD_ATOMIC(char) _waited;
};
};
char _pshared;
char _clock;
char _footek;
_PTHREAD_ATOMIC(char) _waited;
_PTHREAD_ATOMIC(uint32_t) _sequence;
_PTHREAD_ATOMIC(uint32_t) _waiters;
void *_nsync[2];
} pthread_cond_t;
typedef struct pthread_rwlock_s {
@ -156,20 +152,20 @@ int pthread_attr_getguardsize(const pthread_attr_t *, size_t *) libcesque params
int pthread_attr_getinheritsched(const pthread_attr_t *, int *) libcesque paramsnonnull();
int pthread_attr_getschedpolicy(const pthread_attr_t *, int *) libcesque paramsnonnull();
int pthread_attr_getscope(const pthread_attr_t *, int *) libcesque paramsnonnull();
int pthread_attr_getstack(const pthread_attr_t *, void **, size_t *) libcesque paramsnonnull();
int pthread_attr_getstacksize(const pthread_attr_t *, size_t *) libcesque paramsnonnull();
int pthread_attr_getsigaltstack_np(const pthread_attr_t *, void **, size_t *) libcesque paramsnonnull();
int pthread_attr_getsigaltstacksize_np(const pthread_attr_t *, size_t *) libcesque paramsnonnull();
int pthread_attr_getstack(const pthread_attr_t *, void **, size_t *) libcesque paramsnonnull();
int pthread_attr_getstacksize(const pthread_attr_t *, size_t *) libcesque paramsnonnull();
int pthread_attr_init(pthread_attr_t *) libcesque paramsnonnull();
int pthread_attr_setdetachstate(pthread_attr_t *, int) libcesque paramsnonnull();
int pthread_attr_setguardsize(pthread_attr_t *, size_t) libcesque paramsnonnull();
int pthread_attr_setinheritsched(pthread_attr_t *, int) libcesque paramsnonnull();
int pthread_attr_setschedpolicy(pthread_attr_t *, int) libcesque paramsnonnull();
int pthread_attr_setscope(pthread_attr_t *, int) libcesque paramsnonnull();
int pthread_attr_setstack(pthread_attr_t *, void *, size_t) libcesque paramsnonnull((1));
int pthread_attr_setstacksize(pthread_attr_t *, size_t) libcesque paramsnonnull();
int pthread_attr_setsigaltstack_np(pthread_attr_t *, void *, size_t) libcesque paramsnonnull((1));
int pthread_attr_setsigaltstacksize_np(pthread_attr_t *, size_t);
int pthread_attr_setstack(pthread_attr_t *, void *, size_t) libcesque paramsnonnull((1));
int pthread_attr_setstacksize(pthread_attr_t *, size_t) libcesque paramsnonnull();
int pthread_barrier_destroy(pthread_barrier_t *) libcesque paramsnonnull();
int pthread_barrier_init(pthread_barrier_t *, const pthread_barrierattr_t *, unsigned) libcesque paramsnonnull((1));
int pthread_barrier_wait(pthread_barrier_t *) libcesque paramsnonnull();
@ -183,13 +179,15 @@ int pthread_cond_destroy(pthread_cond_t *) libcesque paramsnonnull();
int pthread_cond_init(pthread_cond_t *, const pthread_condattr_t *) libcesque paramsnonnull((1));
int pthread_cond_signal(pthread_cond_t *) libcesque paramsnonnull();
int pthread_cond_wait(pthread_cond_t *, pthread_mutex_t *) libcesque paramsnonnull();
int pthread_condattr_init(pthread_condattr_t *) libcesque paramsnonnull();
int pthread_condattr_destroy(pthread_condattr_t *) libcesque paramsnonnull();
int pthread_condattr_setpshared(pthread_condattr_t *, int) libcesque paramsnonnull();
int pthread_condattr_getpshared(const pthread_condattr_t *, int *) libcesque paramsnonnull();
int pthread_condattr_setclock(pthread_condattr_t *, int) libcesque paramsnonnull();
int pthread_condattr_getclock(const pthread_condattr_t *, int *) libcesque paramsnonnull();
int pthread_condattr_getpshared(const pthread_condattr_t *, int *) libcesque paramsnonnull();
int pthread_condattr_init(pthread_condattr_t *) libcesque paramsnonnull();
int pthread_condattr_setclock(pthread_condattr_t *, int) libcesque paramsnonnull();
int pthread_condattr_setpshared(pthread_condattr_t *, int) libcesque paramsnonnull();
int pthread_create(pthread_t *, const pthread_attr_t *, void *(*)(void *), void *) dontthrow paramsnonnull((1));
int pthread_decimate_np(void) libcesque;
int pthread_delay_np(const void *, int) libcesque;
int pthread_detach(pthread_t) libcesque;
int pthread_equal(pthread_t, pthread_t) libcesque;
int pthread_getattr_np(pthread_t, pthread_attr_t *) libcesque paramsnonnull();
@ -205,15 +203,17 @@ int pthread_mutex_init(pthread_mutex_t *, const pthread_mutexattr_t *) libcesque
int pthread_mutex_lock(pthread_mutex_t *) libcesque paramsnonnull();
int pthread_mutex_trylock(pthread_mutex_t *) libcesque paramsnonnull();
int pthread_mutex_unlock(pthread_mutex_t *) libcesque paramsnonnull();
int pthread_mutex_wipe_np(pthread_mutex_t *) libcesque paramsnonnull();
int pthread_mutexattr_destroy(pthread_mutexattr_t *) libcesque paramsnonnull();
int pthread_mutexattr_getpshared(const pthread_mutexattr_t *, int *) libcesque paramsnonnull();
int pthread_mutexattr_getrobust(const pthread_mutexattr_t *, int *) libcesque paramsnonnull();
int pthread_mutexattr_gettype(const pthread_mutexattr_t *, int *) libcesque paramsnonnull();
int pthread_mutexattr_init(pthread_mutexattr_t *) libcesque paramsnonnull();
int pthread_mutexattr_setpshared(pthread_mutexattr_t *, int) libcesque paramsnonnull();
int pthread_mutexattr_setrobust(const pthread_mutexattr_t *, int) libcesque paramsnonnull();
int pthread_mutexattr_settype(pthread_mutexattr_t *, int) libcesque paramsnonnull();
int pthread_once(pthread_once_t *, void (*)(void)) paramsnonnull();
int pthread_orphan_np(void) libcesque;
int pthread_decimate_np(void) libcesque;
int pthread_rwlock_destroy(pthread_rwlock_t *) libcesque paramsnonnull();
int pthread_rwlock_init(pthread_rwlock_t *, const pthread_rwlockattr_t *) libcesque paramsnonnull((1));
int pthread_rwlock_rdlock(pthread_rwlock_t *) libcesque paramsnonnull();
@ -237,17 +237,16 @@ int pthread_spin_trylock(pthread_spinlock_t *) libcesque paramsnonnull();
int pthread_spin_unlock(pthread_spinlock_t *) libcesque paramsnonnull();
int pthread_testcancel_np(void) libcesque;
int pthread_tryjoin_np(pthread_t, void **) libcesque;
int pthread_delay_np(const void *, int) libcesque;
int pthread_yield_np(void) libcesque;
int pthread_yield(void) libcesque;
int pthread_yield_np(void) libcesque;
pthread_id_np_t pthread_getthreadid_np(void) libcesque;
pthread_t pthread_self(void) libcesque pureconst;
void *pthread_getspecific(pthread_key_t) libcesque;
void pthread_cleanup_pop(struct _pthread_cleanup_buffer *, int) libcesque paramsnonnull();
void pthread_cleanup_push(struct _pthread_cleanup_buffer *, void (*)(void *), void *) libcesque paramsnonnull((1));
void pthread_exit(void *) libcesque wontreturn;
void pthread_testcancel(void) libcesque;
void pthread_pause_np(void) libcesque;
void pthread_testcancel(void) libcesque;
/* clang-format on */

View file

@ -15,7 +15,7 @@ struct CosmoFtrace { /* 16 */
int64_t ft_lastaddr; /* 8 */
};
/* cosmopolitan thread information block (512 bytes) */
/* cosmopolitan thread information block (1024 bytes) */
/* NOTE: update aarch64 libc/errno.h if sizeof changes */
/* NOTE: update aarch64 libc/proc/vfork.S if sizeof changes */
/* NOTE: update aarch64 libc/nexgen32e/gc.S if sizeof changes */
@ -40,6 +40,7 @@ struct CosmoTib {
void *tib_nsync;
void *tib_atexit;
_Atomic(void *) tib_keys[46];
void *tib_locks[64];
} __attribute__((__aligned__(64)));
extern char __tls_morphed;
@ -78,6 +79,10 @@ forceinline pureconst struct CosmoTib *__get_tls(void) {
#endif
}
struct CosmoTib *__get_tls_privileged(void) dontthrow pureconst;
struct CosmoTib *__get_tls_win32(void) dontthrow;
void __set_tls_win32(void *) libcesque;
#ifdef __x86_64__
#define __adj_tls(tib) (tib)
#elif defined(__aarch64__)

View file

@ -1,43 +0,0 @@
#ifndef COSMOPOLITAN_LIBC_THREAD_TLS2_H_
#define COSMOPOLITAN_LIBC_THREAD_TLS2_H_
#include "libc/dce.h"
#include "libc/thread/tls.h"
COSMOPOLITAN_C_START_
#if defined(__GNUC__) && defined(__x86_64__)
/**
* Returns location of thread information block.
*
* This should be favored over __get_tls() for .privileged code that
* can't be self-modified by __enable_tls().
*/
forceinline struct CosmoTib *__get_tls_privileged(void) {
char *tib, *lin = (char *)0x30;
if (IsNetbsd() || IsOpenbsd()) {
__asm__("mov\t%%fs:(%1),%0" : "=a"(tib) : "r"(lin) : "memory");
} else {
__asm__("mov\t%%gs:(%1),%0" : "=a"(tib) : "r"(lin) : "memory");
if (IsWindows())
tib = *(char **)(tib + 0x1480 + __tls_index * 8);
}
return (struct CosmoTib *)tib;
}
forceinline struct CosmoTib *__get_tls_win32(void) {
char *tib, *lin = (char *)0x30;
__asm__("mov\t%%gs:(%1),%0" : "=a"(tib) : "r"(lin) : "memory");
tib = *(char **)(tib + 0x1480 + __tls_index * 8);
return (struct CosmoTib *)tib;
}
forceinline void __set_tls_win32(void *tls) {
__asm__("mov\t%1,%%gs:%0" : "=m"(*((long *)0x1480 + __tls_index)) : "r"(tls));
}
#elif defined(__aarch64__)
#define __get_tls_privileged() __get_tls()
#define __get_tls_win32() ((struct CosmoTib *)0)
#define __set_tls_win32(tls) (void)0
#endif /* GNU x86-64 */
COSMOPOLITAN_C_END_
#endif /* COSMOPOLITAN_LIBC_THREAD_TLS2_H_ */