Make improvements

- Every unit test now passes on Apple Silicon. The final piece of this
  puzzle was porting our POSIX threads cancelation support, since that
  works differently on ARM64 XNU vs. AMD64. Our semaphore support on
  Apple Silicon is also superior now compared to AMD64, thanks to the
  grand central dispatch library which lets *NSYNC locks go faster.

- The Cosmopolitan runtime is now more stable, particularly on Windows.
  To do this, thread local storage is mandatory at all runtime levels,
  and the innermost packages of the C library is no longer being built
  using ASAN. TLS is being bootstrapped with a 128-byte TIB during the
  process startup phase, and then later on the runtime re-allocates it
  either statically or dynamically to support code using _Thread_local.
  fork() and execve() now do a better job cooperating with threads. We
  can now check how much stack memory is left in the process or thread
  when functions like kprintf() / execve() etc. call alloca(), so that
  ENOMEM can be raised, reduce a buffer size, or just print a warning.

- POSIX signal emulation is now implemented the same way kernels do it
  with pthread_kill() and raise(). Any thread can interrupt any other
  thread, regardless of what it's doing. If it's blocked on read/write
  then the killer thread will cancel its i/o operation so that EINTR can
  be returned in the mark thread immediately. If it's doing a tight CPU
  bound operation, then that's also interrupted by the signal delivery.
  Signal delivery works now by suspending a thread and pushing context
  data structures onto its stack, and redirecting its execution to a
  trampoline function, which calls SetThreadContext(GetCurrentThread())
  when it's done.

- We're now doing a better job managing locks and handles. On NetBSD we
  now close semaphore file descriptors in forked children. Semaphores on
  Windows can now be canceled immediately, which means mutexes/condition
  variables will now go faster. Apple Silicon semaphores can be canceled
  too. We're now using Apple's pthread_yield() funciton. Apple _nocancel
  syscalls are now used on XNU when appropriate to ensure pthread_cancel
  requests aren't lost. The MbedTLS library has been updated to support
  POSIX thread cancelations. See tool/build/runitd.c for an example of
  how it can be used for production multi-threaded tls servers. Handles
  on Windows now leak less often across processes. All i/o operations on
  Windows are now overlapped, which means file pointers can no longer be
  inherited across dup() and fork() for the time being.

- We now spawn a thread on Windows to deliver SIGCHLD and wakeup wait4()
  which means, for example, that posix_spawn() now goes 3x faster. POSIX
  spawn is also now more correct. Like Musl, it's now able to report the
  failure code of execve() via a pipe although our approach favors using
  shared memory to do that on systems that have a true vfork() function.

- We now spawn a thread to deliver SIGALRM to threads when setitimer()
  is used. This enables the most precise wakeups the OS makes possible.

- The Cosmopolitan runtime now uses less memory. On NetBSD for example,
  it turned out the kernel would actually commit the PT_GNU_STACK size
  which caused RSS to be 6mb for every process. Now it's down to ~4kb.
  On Apple Silicon, we reduce the mandatory upstream thread size to the
  smallest possible size to reduce the memory overhead of Cosmo threads.
  The examples directory has a program called greenbean which can spawn
  a web server on Linux with 10,000 worker threads and have the memory
  usage of the process be ~77mb. The 1024 byte overhead of POSIX-style
  thread-local storage is now optional; it won't be allocated until the
  pthread_setspecific/getspecific functions are called. On Windows, the
  threads that get spawned which are internal to the libc implementation
  use reserve rather than commit memory, which shaves a few hundred kb.

- sigaltstack() is now supported on Windows, however it's currently not
  able to be used to handle stack overflows, since crash signals are
  still generated by WIN32. However the crash handler will still switch
  to the alt stack, which is helpful in environments with tiny threads.

- Test binaries are now smaller. Many of the mandatory dependencies of
  the test runner have been removed. This ensures many programs can do a
  better job only linking the the thing they're testing. This caused the
  test binaries for LIBC_FMT for example, to decrease from 200kb to 50kb

- long double is no longer used in the implementation details of libc,
  except in the APIs that define it. The old code that used long double
  for time (instead of struct timespec) has now been thoroughly removed.

- ShowCrashReports() is now much tinier in MODE=tiny. Instead of doing
  backtraces itself, it'll just print a command you can run on the shell
  using our new `cosmoaddr2line` program to view the backtrace.

- Crash report signal handling now works in a much better way. Instead
  of terminating the process, it now relies on SA_RESETHAND so that the
  default SIG_IGN behavior can terminate the process if necessary.

- Our pledge() functionality has now been fully ported to AARCH64 Linux.
This commit is contained in:
Justine Tunney 2023-09-18 20:44:45 -07:00
parent c4eb838516
commit ec480f5aa0
No known key found for this signature in database
GPG key ID: BE714B4575D6E328
638 changed files with 7925 additions and 8282 deletions

41
libc/thread/alarm.c Normal file
View file

@ -0,0 +1,41 @@
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi
Copyright 2020 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/assert.h"
#include "libc/calls/calls.h"
#include "libc/calls/struct/itimerval.h"
#include "libc/calls/struct/timeval.h"
#include "libc/sysv/consts/itimer.h"
/**
* Asks for single-shot SIGALRM to be raise()'d after interval.
*
* @param seconds is how long to wait before raising SIGALRM (which will
* only happen once) or zero to clear any previously scheduled alarm
* @return seconds that were remaining on the previously scheduled
* alarm, or zero if there wasn't one (failure isn't possible)
* @see setitimer() for a more powerful api
* @asyncsignalsafe
*/
unsigned alarm(unsigned seconds) {
struct itimerval it, old;
it.it_value = timeval_fromseconds(seconds);
it.it_interval = timeval_zero;
npassert(!setitimer(ITIMER_REAL, &it, &old));
return timeval_toseconds(old.it_value);
}

48
libc/thread/getitimer.c Normal file
View file

@ -0,0 +1,48 @@
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi
Copyright 2020 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/calls/calls.h"
#include "libc/calls/struct/itimerval.h"
#include "libc/calls/struct/itimerval.internal.h"
#include "libc/dce.h"
#include "libc/intrin/asan.internal.h"
#include "libc/intrin/describeflags.internal.h"
#include "libc/intrin/strace.internal.h"
#include "libc/sysv/errfuns.h"
/**
* Retrieves last setitimer() value, correcting for remaining time.
*
* @param which can be ITIMER_REAL, ITIMER_VIRTUAL, etc.
* @return 0 on success or -1 w/ errno
*/
int getitimer(int which, struct itimerval *curvalue) {
int rc;
if (IsAsan() && !__asan_is_valid(curvalue, sizeof(*curvalue))) {
rc = efault();
} else if (!IsWindows()) {
rc = sys_getitimer(which, curvalue);
} else if (!curvalue) {
rc = efault();
} else {
rc = sys_setitimer_nt(which, 0, curvalue);
}
STRACE("getitimer(%s, [%s]) → %d% m", DescribeItimer(which),
DescribeItimerval(rc, curvalue), rc);
return rc;
}

118
libc/thread/itimer.c Normal file
View file

@ -0,0 +1,118 @@
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi
Copyright 2020 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/sysv/consts/itimer.h"
#include "libc/calls/sig.internal.h"
#include "libc/calls/struct/itimerval.h"
#include "libc/calls/struct/itimerval.internal.h"
#include "libc/calls/struct/timeval.h"
#include "libc/cosmo.h"
#include "libc/nt/enum/processcreationflags.h"
#include "libc/nt/thread.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/sicode.h"
#include "libc/sysv/consts/sig.h"
#include "libc/sysv/errfuns.h"
#include "libc/thread/itimer.internal.h"
#include "libc/thread/tls.h"
#include "third_party/nsync/mu.h"
#ifdef __x86_64__
struct IntervalTimer __itimer;
static textwindows dontinstrument uint32_t __itimer_worker(void *arg) {
struct CosmoTib tls;
__bootstrap_tls(&tls, __builtin_frame_address(0));
for (;;) {
bool dosignal = false;
struct timeval now, waituntil;
nsync_mu_lock(&__itimer.lock);
now = timeval_real();
if (timeval_iszero(__itimer.it.it_value)) {
waituntil = timeval_max;
} else {
if (timeval_cmp(now, __itimer.it.it_value) < 0) {
waituntil = __itimer.it.it_value;
} else {
if (timeval_iszero(__itimer.it.it_interval)) {
__itimer.it.it_value = timeval_zero;
waituntil = timeval_max;
} else {
do {
__itimer.it.it_value =
timeval_add(__itimer.it.it_value, __itimer.it.it_interval);
} while (timeval_cmp(now, __itimer.it.it_value) > 0);
waituntil = __itimer.it.it_value;
}
dosignal = true;
}
}
nsync_mu_unlock(&__itimer.lock);
if (dosignal) {
__sig_generate(SIGALRM, SI_TIMER);
}
nsync_mu_lock(&__itimer.lock);
nsync_cv_wait_with_deadline(&__itimer.cond, &__itimer.lock,
timeval_totimespec(waituntil), 0);
nsync_mu_unlock(&__itimer.lock);
}
return 0;
}
static textwindows void __itimer_setup(void) {
__itimer.thread = CreateThread(0, 65536, __itimer_worker, 0,
kNtStackSizeParamIsAReservation, 0);
}
textwindows void __itimer_reset(void) {
// this function is called by fork(), because
// timers aren't inherited by forked subprocesses
bzero(&__itimer, sizeof(__itimer));
}
textwindows int sys_setitimer_nt(int which, const struct itimerval *neu,
struct itimerval *old) {
struct itimerval config;
cosmo_once(&__itimer.once, __itimer_setup);
if (which != ITIMER_REAL || (neu && (!timeval_isvalid(neu->it_value) ||
!timeval_isvalid(neu->it_interval)))) {
return einval();
}
if (neu) {
// POSIX defines setitimer() with the restrict keyword but let's
// accommodate the usage setitimer(ITIMER_REAL, &it, &it) anyway
config = *neu;
}
nsync_mu_lock(&__itimer.lock);
if (old) {
old->it_interval = __itimer.it.it_interval;
old->it_value = timeval_subz(__itimer.it.it_value, timeval_real());
}
if (neu) {
if (!timeval_iszero(config.it_value)) {
config.it_value = timeval_add(config.it_value, timeval_real());
}
__itimer.it = config;
nsync_cv_signal(&__itimer.cond);
}
nsync_mu_unlock(&__itimer.lock);
return 0;
}
#endif /* __x86_64__ */

View file

@ -0,0 +1,24 @@
#ifndef COSMOPOLITAN_LIBC_ITIMER_H_
#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"
#if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_
struct IntervalTimer {
atomic_uint once;
intptr_t thread;
nsync_mu lock;
nsync_cv cond;
struct itimerval it;
};
extern struct IntervalTimer __itimer;
void __itimer_reset(void);
COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
#endif /* COSMOPOLITAN_LIBC_ITIMER_H_ */

View file

@ -27,7 +27,6 @@
#include "libc/runtime/runtime.h"
#include "libc/str/locale.h"
#include "libc/str/str.h"
#include "libc/thread/spawn.h"
#include "libc/thread/tls.h"
#define I(x) ((uintptr_t)x)
@ -132,7 +131,6 @@ static char *_mktls_above(struct CosmoTib **out_tib) {
* @return buffer that must be released with free()
*/
char *_mktls(struct CosmoTib **out_tib) {
__require_tls();
#ifdef __x86_64__
return _mktls_below(out_tib);
#else

View file

@ -2,7 +2,6 @@
#define COSMOPOLITAN_LIBC_THREAD_POSIXTHREAD_INTERNAL_H_
#include "libc/calls/struct/sched_param.h"
#include "libc/calls/struct/sigaltstack.h"
#include "libc/calls/struct/sigset.h"
#include "libc/intrin/dll.h"
#include "libc/runtime/runtime.h"
#include "libc/thread/thread.h"
@ -14,9 +13,9 @@
#define PT_NOCANCEL 8
#define PT_MASKED 16
#define PT_INCANCEL 32
#define PT_BLOCKED 64
#define PT_EXITING 128
#define PT_OPENBSD_KLUDGE 256
#define PT_POLLING 64 // windows only
#define PT_INSEMAPHORE 128 // windows only
#define PT_OPENBSD_KLUDGE 128 // openbsd only
#if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_
@ -36,6 +35,9 @@ enum PosixThreadStatus {
//
// - kPosixThreadJoinable -> kPosixThreadDetached if pthread_detach()
// is called on this thread.
//
// - kPosixThreadJoinable -> kPosixThreadZombie if another thread
// calls fork().
kPosixThreadJoinable,
// this is a managed thread that'll be cleaned up by the library.
@ -45,6 +47,9 @@ enum PosixThreadStatus {
// - kPosixThreadDetached -> kPosixThreadZombie if start_routine()
// returns, or is longjmp'd out of by pthread_exit(), and the thread
// is waiting to be joined.
//
// - kPosixThreadDetached -> kPosixThreadZombie if another thread
// calls fork().
kPosixThreadDetached,
// this is a joinable thread that terminated.
@ -55,6 +60,8 @@ enum PosixThreadStatus {
// pthread_join() is called by the user.
// - kPosixThreadTerminated -> kPosixThreadZombie will happen when
// pthread_detach() is called by the user.
// - kPosixThreadTerminated -> kPosixThreadZombie if another thread
// calls fork().
kPosixThreadTerminated,
// this is a detached thread that terminated.
@ -69,7 +76,7 @@ enum PosixThreadStatus {
#define POSIXTHREAD_CONTAINER(e) DLL_CONTAINER(struct PosixThread, list, e)
struct PosixThread {
int flags; // 0x00: see PT_* constants
int pt_flags; // 0x00: see PT_* constants
_Atomic(int) cancelled; // 0x04: thread has bad beliefs
_Atomic(enum PosixThreadStatus) status;
_Atomic(int) ptid; // transitions 0 → tid
@ -79,9 +86,13 @@ struct PosixThread {
char *tls; // bottom of tls allocation
struct CosmoTib *tib; // middle of tls allocation
struct Dll list; // list of threads
pthread_t next; // for xnu silicon
jmp_buf exiter; // for pthread_exit
_Atomic(_Atomic(int) *) pt_futex;
intptr_t semaphore;
intptr_t iohandle;
void *ioverlap;
jmp_buf exiter;
pthread_attr_t attr;
int abort_errno;
struct _pthread_cleanup_buffer *cleanup;
};
@ -89,20 +100,29 @@ typedef void (*atfork_f)(void);
extern struct Dll *_pthread_list;
extern pthread_spinlock_t _pthread_lock;
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);
void _pthread_decimate(void);
int _pthread_tid(struct PosixThread *);
void _pthread_unkey(struct CosmoTib *);
void _pthread_unwind(struct PosixThread *);
int _pthread_reschedule(struct PosixThread *);
intptr_t _pthread_syshand(struct PosixThread *);
int _pthread_atfork(atfork_f, atfork_f, atfork_f);
int _pthread_setschedparam_freebsd(int, int, const struct sched_param *);
int _pthread_signal(struct PosixThread *, int, int);
void _pthread_free(struct PosixThread *, bool);
void _pthread_zombify(struct PosixThread *);
void _pthread_free(struct PosixThread *);
void _pthread_onfork_prepare(void);
void _pthread_onfork_parent(void);
void _pthread_onfork_child(void);
long _pthread_cancel_sys(void);
long _pthread_cancel_ack(void);
void _pthread_ungarbage(void);
__funline pureconst struct PosixThread *_pthread_self(void) {
return (struct PosixThread *)__get_tls()->tib_pthread;
}
COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
#endif /* COSMOPOLITAN_LIBC_THREAD_POSIXTHREAD_INTERNAL_H_ */

View file

@ -26,7 +26,9 @@
#include "libc/intrin/dll.h"
#include "libc/intrin/handlock.internal.h"
#include "libc/intrin/leaky.internal.h"
#include "libc/macros.internal.h"
#include "libc/mem/mem.h"
#include "libc/proc/proc.internal.h"
#include "libc/runtime/memtrack.internal.h"
#include "libc/runtime/runtime.h"
#include "libc/str/str.h"
@ -34,22 +36,18 @@
#include "libc/thread/thread.h"
#include "libc/thread/tls.h"
struct AtFork {
struct AtFork *p[2];
atfork_f f[3];
};
static struct AtForks {
pthread_spinlock_t lock;
struct AtFork {
struct AtFork *p[2];
atfork_f f[3];
} * list;
struct AtFork *list;
struct AtFork pool[8];
atomic_int allocated;
} _atforks;
static void _pthread_purge(void) {
struct Dll *e;
while ((e = dll_first(_pthread_list))) {
dll_remove(&_pthread_list, e);
_pthread_free(POSIXTHREAD_CONTAINER(e));
}
}
static void _pthread_onfork(int i) {
struct AtFork *a;
unassert(0 <= i && i <= 2);
@ -65,52 +63,48 @@ void _pthread_onfork_prepare(void) {
_pthread_onfork(0);
pthread_spin_lock(&_pthread_lock);
__fds_lock();
__hand_lock();
if (IsWindows()) {
__hand_lock();
}
__mmi_lock();
}
void _pthread_onfork_parent(void) {
__mmi_unlock();
__hand_unlock();
if (IsWindows()) {
__hand_unlock();
}
__fds_unlock();
pthread_spin_unlock(&_pthread_lock);
_pthread_onfork(1);
}
void _pthread_onfork_child(void) {
struct CosmoTib *tib;
struct PosixThread *pt;
if (IsWindows()) __hand_wipe();
pthread_mutexattr_t attr;
extern pthread_mutex_t __mmi_lock_obj;
tib = __get_tls();
pt = (struct PosixThread *)tib->tib_pthread;
// let's choose to let the new process live.
// even though it's unclear what to do with this kind of race.
atomic_store_explicit(&pt->cancelled, false, memory_order_relaxed);
// wipe core runtime locks
__hand_init();
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
extern pthread_mutex_t __mmi_lock_obj;
pthread_mutex_init(&__mmi_lock_obj, &attr);
pthread_mutex_init(&__fds_lock_obj, &attr);
(void)pthread_spin_init(&_pthread_lock, 0);
// call user-supplied forked child callbacks
_pthread_onfork(2);
}
// delete other threads that existed before forking
// this must come after onfork, since it calls free
dll_remove(&_pthread_list, &pt->list);
_pthread_purge();
dll_make_first(&_pthread_list, &pt->list);
static struct AtFork *_pthread_atfork_alloc(void) {
int i, n = ARRAYLEN(_atforks.pool);
if (atomic_load_explicit(&_atforks.allocated, memory_order_relaxed) < n &&
(i = atomic_fetch_add(&_atforks.allocated, 1)) < n) {
return _atforks.pool + i;
} else {
return malloc(sizeof(struct AtFork));
}
}
int _pthread_atfork(atfork_f prepare, atfork_f parent, atfork_f child) {
int rc;
struct AtFork *a;
if (!(a = malloc(sizeof(struct AtFork)))) return ENOMEM;
if (!(a = _pthread_atfork_alloc())) return ENOMEM;
a->f[0] = prepare;
a->f[1] = parent;
a->f[2] = child;

View file

@ -16,15 +16,27 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/errno.h"
#include "libc/thread/thread.h"
/**
* Sets size of unmapped pages at bottom of stack.
*
* This value will be rounded up to the host microprocessor page size,
* which is usually 4096 or 16384. It's important to write code in such
* a way that that code can't skip over the guard area. GCC has warnings
* like `-Wframe-larger-than=4096 -Walloca-larger-than=4096` which help
* guarantee your code is safe in this regard. It should be assumed the
* guard pages exist beneath the stack pointer, rather than the bottom
* of the stack, since guard pages are also used to grow down commit,
* which can be poked using CheckLargeStackAllocation().
*
* @param guardsize contains guard size in bytes
* @return 0 on success, or errno on error
* @raise EINVAL if `guardsize` is zero
*/
errno_t pthread_attr_setguardsize(pthread_attr_t *attr, size_t guardsize) {
if (!guardsize) return EINVAL;
attr->__guardsize = guardsize;
return 0;
}

View file

@ -20,6 +20,8 @@
#include "libc/errno.h"
#include "libc/thread/thread.h"
__static_yoink("_pthread_reschedule");
/**
* Sets thread scheduler inheritance attribute, e.g.
*

View file

@ -18,19 +18,29 @@
*/
#include "libc/assert.h"
#include "libc/calls/calls.h"
#include "libc/calls/sig.internal.h"
#include "libc/calls/struct/sigaction.h"
#include "libc/calls/struct/siginfo.h"
#include "libc/calls/struct/sigset.h"
#include "libc/calls/struct/ucontext.internal.h"
#include "libc/calls/syscall-sysv.internal.h"
#include "libc/calls/syscall_support-sysv.internal.h"
#include "libc/calls/ucontext.h"
#include "libc/dce.h"
#include "libc/errno.h"
#include "libc/intrin/atomic.h"
#include "libc/intrin/kprintf.h"
#include "libc/intrin/describeflags.internal.h"
#include "libc/intrin/strace.internal.h"
#include "libc/nt/enum/context.h"
#include "libc/nt/enum/threadaccess.h"
#include "libc/nt/runtime.h"
#include "libc/nt/struct/context.h"
#include "libc/nt/thread.h"
#include "libc/runtime/runtime.h"
#include "libc/runtime/syslib.internal.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/sa.h"
#include "libc/sysv/consts/sicode.h"
#include "libc/sysv/consts/sig.h"
#include "libc/sysv/errfuns.h"
#include "libc/thread/posixthread.internal.h"
@ -42,42 +52,135 @@ int systemfive_cancel(void);
extern const char systemfive_cancellable[];
extern const char systemfive_cancellable_end[];
long _pthread_cancel_sys(void) {
struct PosixThread *pt;
pt = (struct PosixThread *)__get_tls()->tib_pthread;
if (!(pt->flags & (PT_NOCANCEL | PT_MASKED)) || (pt->flags & PT_ASYNC)) {
long _pthread_cancel_ack(void) {
struct PosixThread *pt = _pthread_self();
if (!(pt->pt_flags & (PT_NOCANCEL | PT_MASKED)) ||
(pt->pt_flags & PT_ASYNC)) {
pthread_exit(PTHREAD_CANCELED);
}
pt->flags |= PT_NOCANCEL | PT_OPENBSD_KLUDGE;
pt->pt_flags |= PT_NOCANCEL | PT_OPENBSD_KLUDGE;
return ecanceled();
}
static void OnSigThr(int sig, siginfo_t *si, void *ctx) {
ucontext_t *uc = ctx;
struct CosmoTib *t;
static void _pthread_cancel_sig(int sig, siginfo_t *si, void *arg) {
ucontext_t *ctx = arg;
// check thread runtime state is initialized and cancelled
struct PosixThread *pt;
if ((t = __get_tls()) && // TODO: why can it be null on freebsd?
(pt = (struct PosixThread *)t->tib_pthread) &&
!(pt->flags & PT_NOCANCEL) &&
atomic_load_explicit(&pt->cancelled, memory_order_acquire)) {
sigaddset(&uc->uc_sigmask, sig);
if (systemfive_cancellable <= (char *)uc->uc_mcontext.PC &&
(char *)uc->uc_mcontext.PC < systemfive_cancellable_end) {
uc->uc_mcontext.PC = (intptr_t)systemfive_cancel;
} else if (pt->flags & PT_ASYNC) {
pthread_exit(PTHREAD_CANCELED);
} else {
__tkill(atomic_load_explicit(&t->tib_tid, memory_order_relaxed), sig, t);
if (!__tls_enabled) return;
if (!(pt = _pthread_self())) return;
if (pt->pt_flags & PT_NOCANCEL) return;
if (!atomic_load_explicit(&pt->cancelled, memory_order_acquire)) return;
// in asynchronous mode we'll just the exit asynchronously
if (pt->pt_flags & PT_ASYNC) {
sigaddset(&ctx->uc_sigmask, SIGTHR);
pthread_sigmask(SIG_SETMASK, &ctx->uc_sigmask, 0);
pthread_exit(PTHREAD_CANCELED);
}
// prevent this handler from being called again by thread
sigaddset(&ctx->uc_sigmask, SIGTHR);
// check for race condition between pre-check and syscall
// rewrite the thread's execution state to acknowledge it
if (systemfive_cancellable <= (char *)ctx->uc_mcontext.PC &&
(char *)ctx->uc_mcontext.PC < systemfive_cancellable_end) {
ctx->uc_mcontext.PC = (intptr_t)systemfive_cancel;
return;
}
// punts cancellation to start of next cancellation point
// we ensure sigthr is a pending signal in case unblocked
if (IsXnuSilicon()) {
__syslib->__pthread_kill(_pthread_syshand(pt), sig);
} else {
sys_tkill(_pthread_tid(pt), sig, __get_tls());
}
}
static void _pthread_cancel_listen(void) {
struct sigaction sa;
if (!IsWindows()) {
sa.sa_sigaction = _pthread_cancel_sig;
sa.sa_flags = SA_SIGINFO | SA_RESTART;
memset(&sa.sa_mask, -1, sizeof(sa.sa_mask));
npassert(!sigaction(SIGTHR, &sa, 0));
}
}
static void pthread_cancel_nt(struct PosixThread *pt, intptr_t hThread) {
uint32_t old_suspend_count;
if ((pt->pt_flags & PT_ASYNC) && !(pt->pt_flags & PT_NOCANCEL)) {
if ((old_suspend_count = SuspendThread(hThread)) != -1u) {
if (!old_suspend_count) {
struct NtContext cpu;
cpu.ContextFlags = kNtContextControl | kNtContextInteger;
if (GetThreadContext(hThread, &cpu)) {
pt->pt_flags |= PT_NOCANCEL;
cpu.Rip = (uintptr_t)pthread_exit;
cpu.Rdi = (uintptr_t)PTHREAD_CANCELED;
cpu.Rsp &= -16;
*(uintptr_t *)(cpu.Rsp -= sizeof(uintptr_t)) = cpu.Rip;
unassert(SetThreadContext(hThread, &cpu));
__sig_cancel(pt, 0);
}
}
ResumeThread(hThread);
}
}
}
static void ListenForSigThr(void) {
struct sigaction sa;
sa.sa_sigaction = OnSigThr;
sa.sa_flags = SA_SIGINFO | SA_RESTART | SA_ONSTACK;
memset(&sa.sa_mask, -1, sizeof(sa.sa_mask));
npassert(!sigaction(SIGTHR, &sa, 0));
static errno_t _pthread_cancel_impl(struct PosixThread *pt) {
// install our special signal handler
static bool once;
if (!once) {
_pthread_cancel_listen();
once = true;
}
// check if thread is already dead
switch (atomic_load_explicit(&pt->status, memory_order_acquire)) {
case kPosixThreadZombie:
case kPosixThreadTerminated:
return ESRCH;
default:
break;
}
// flip the bit indicating that this thread is cancelled
atomic_store_explicit(&pt->cancelled, 1, memory_order_release);
// does this thread want to cancel itself?
if (pt == _pthread_self()) {
unassert(!(pt->pt_flags & PT_NOCANCEL));
if (!(pt->pt_flags & (PT_NOCANCEL | PT_MASKED)) &&
(pt->pt_flags & PT_ASYNC)) {
pthread_exit(PTHREAD_CANCELED);
}
return 0;
}
errno_t err;
if (IsWindows()) {
pthread_cancel_nt(pt, _pthread_syshand(pt));
err = 0;
} else if (IsXnuSilicon()) {
err = __syslib->__pthread_kill(_pthread_syshand(pt), SIGTHR);
} else {
int e = errno;
if (!sys_tkill(_pthread_tid(pt), SIGTHR, pt->tib)) {
err = 0;
} else {
err = errno;
errno = e;
}
}
if (err == ESRCH) {
err = 0; // we already reported this
}
return err;
}
/**
@ -153,6 +256,7 @@ static void ListenForSigThr(void) {
* - `nsync_cv_wait_with_deadline`
* - `nsync_cv_wait`
* - `opendir`
* - `openatemp`, 'mkstemp', etc.
* - `pclose`
* - `popen`
* - `fwrite`, `printf`, `fprintf`, `putc`, etc.
@ -174,7 +278,7 @@ static void ListenForSigThr(void) {
* - `INFOF()`, `WARNF()`, etc.
* - `getentropy`
* - `gmtime_r`
* - `kprintf`
* - `kprintf` (by virtue of asm(syscall) and write_nocancel() on xnu)
* - `localtime_r`
* - `nsync_mu_lock`
* - `nsync_mu_unlock`
@ -185,6 +289,7 @@ static void ListenForSigThr(void) {
* - `pthread_setname_np`
* - `sem_open`
* - `system`
* - `openatemp`, 'mkstemp', etc.
* - `timespec_sleep`
* - `touch`
*
@ -254,53 +359,38 @@ static void ListenForSigThr(void) {
*
* Isn't safe to use in masked mode. That's because if a cancellation
* occurs during the write() operation then cancellations are blocked
* while running read(). Masked mode doesn't have second chances. You
* while running read(). MASKED MODE DOESN'T HAVE SECOND CHANCES. You
* must rigorously check the results of each cancellation point call.
*
* Unit tests should be able to safely ignore the return value, or at
* the very least be programmed to consider ESRCH a successful status
*
* @param thread may be 0 to cancel all threads except self
* @return 0 on success, or errno on error
* @raise ESRCH if system thread wasn't alive or we lost a race
*/
errno_t pthread_cancel(pthread_t thread) {
int e, rc, tid;
static bool once;
struct PosixThread *pt;
__require_tls();
if (!once) {
ListenForSigThr();
once = true;
}
pt = (struct PosixThread *)thread;
switch (atomic_load_explicit(&pt->status, memory_order_acquire)) {
case kPosixThreadZombie:
case kPosixThreadTerminated:
return ESRCH;
default:
break;
}
atomic_store_explicit(&pt->cancelled, 1, memory_order_release);
if (thread == __get_tls()->tib_pthread) {
if (!(pt->flags & (PT_NOCANCEL | PT_MASKED)) && (pt->flags & PT_ASYNC)) {
pthread_exit(PTHREAD_CANCELED);
}
return 0;
}
if (!(rc = pthread_getunique_np(thread, &tid))) {
if (!IsWindows()) {
e = errno;
if (!__tkill(tid, SIGTHR, pt->tib)) {
rc = 0;
} else {
rc = errno;
errno = e;
errno_t err;
struct Dll *e;
struct PosixThread *arg, *other;
if ((arg = (struct PosixThread *)thread)) {
err = _pthread_cancel_impl(arg);
} else {
err = ESRCH;
pthread_spin_lock(&_pthread_lock);
for (e = dll_first(_pthread_list); e; e = dll_next(_pthread_list, e)) {
other = POSIXTHREAD_CONTAINER(e);
if (other != _pthread_self() &&
atomic_load_explicit(&other->status, memory_order_acquire) <
kPosixThreadTerminated) {
_pthread_cancel_impl(other);
err = 0;
}
} else {
rc = 0;
}
pthread_spin_unlock(&_pthread_lock);
}
return rc;
STRACE("pthread_cancel(%d) → %s", _pthread_tid(arg), DescribeErrno(err));
return err;
}
/**
@ -317,9 +407,9 @@ errno_t pthread_cancel(pthread_t thread) {
void pthread_testcancel(void) {
struct PosixThread *pt;
if (!__tls_enabled) return;
if (!(pt = (struct PosixThread *)__get_tls()->tib_pthread)) return;
if (pt->flags & PT_NOCANCEL) return;
if ((!(pt->flags & PT_MASKED) || (pt->flags & PT_ASYNC)) &&
if (!(pt = _pthread_self())) return;
if (pt->pt_flags & PT_NOCANCEL) return;
if ((!(pt->pt_flags & PT_MASKED) || (pt->pt_flags & PT_ASYNC)) &&
atomic_load_explicit(&pt->cancelled, memory_order_acquire)) {
pthread_exit(PTHREAD_CANCELED);
}
@ -344,13 +434,13 @@ void pthread_testcancel(void) {
errno_t pthread_testcancel_np(void) {
struct PosixThread *pt;
if (!__tls_enabled) return 0;
if (!(pt = (struct PosixThread *)__get_tls()->tib_pthread)) return 0;
if (pt->flags & PT_NOCANCEL) return 0;
if (!(pt = _pthread_self())) return 0;
if (pt->pt_flags & PT_NOCANCEL) return 0;
if (!atomic_load_explicit(&pt->cancelled, memory_order_acquire)) return 0;
if (!(pt->flags & PT_MASKED) || (pt->flags & PT_ASYNC)) {
if (!(pt->pt_flags & PT_MASKED) || (pt->pt_flags & PT_ASYNC)) {
pthread_exit(PTHREAD_CANCELED);
} else {
pt->flags |= PT_NOCANCEL;
pt->pt_flags |= PT_NOCANCEL;
return ECANCELED;
}
}

View file

@ -20,20 +20,28 @@
#include "libc/atomic.h"
#include "libc/calls/blocksigs.internal.h"
#include "libc/calls/calls.h"
#include "libc/calls/struct/sigset.h"
#include "libc/calls/syscall-sysv.internal.h"
#include "libc/dce.h"
#include "libc/errno.h"
#include "libc/fmt/itoa.h"
#include "libc/intrin/asan.internal.h"
#include "libc/intrin/atomic.h"
#include "libc/intrin/bits.h"
#include "libc/intrin/bsr.h"
#include "libc/intrin/describeflags.internal.h"
#include "libc/intrin/dll.h"
#include "libc/intrin/kprintf.h"
#include "libc/intrin/strace.internal.h"
#include "libc/intrin/weaken.h"
#include "libc/log/internal.h"
#include "libc/macros.internal.h"
#include "libc/mem/alloca.h"
#include "libc/mem/mem.h"
#include "libc/nexgen32e/crc32.h"
#include "libc/nt/runtime.h"
#include "libc/runtime/runtime.h"
#include "libc/runtime/stack.h"
#include "libc/runtime/syslib.internal.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/auxv.h"
#include "libc/sysv/consts/clone.h"
@ -42,10 +50,8 @@
#include "libc/sysv/consts/sig.h"
#include "libc/sysv/consts/ss.h"
#include "libc/thread/posixthread.internal.h"
#include "libc/thread/spawn.h"
#include "libc/thread/thread.h"
#include "libc/thread/tls.h"
#include "libc/thread/wait0.internal.h"
__static_yoink("nsync_mu_lock");
__static_yoink("nsync_mu_unlock");
@ -56,39 +62,44 @@ __static_yoink("_pthread_atfork");
#define MAP_ANON_OPENBSD 0x1000
#define MAP_STACK_OPENBSD 0x4000
static unsigned long roundup2pow(unsigned long x) {
return x > 1 ? 2ul << _bsrl(x - 1) : x ? 1 : 0;
}
void _pthread_free(struct PosixThread *pt) {
if (pt->flags & PT_STATIC) return;
free(pt->tls);
if ((pt->flags & PT_OWNSTACK) && //
pt->attr.__stackaddr && //
pt->attr.__stackaddr != MAP_FAILED) {
void _pthread_free(struct PosixThread *pt, bool isfork) {
if (pt->pt_flags & PT_STATIC) return;
if (pt->pt_flags & PT_OWNSTACK) {
unassert(!munmap(pt->attr.__stackaddr, pt->attr.__stacksize));
}
if (!isfork) {
if (IsWindows()) {
if (pt->tib->tib_syshand) {
unassert(CloseHandle(pt->tib->tib_syshand));
}
} else if (IsXnuSilicon()) {
if (pt->tib->tib_syshand) {
__syslib->__pthread_join(pt->tib->tib_syshand, 0);
}
}
}
free(pt->tls);
free(pt);
}
static int PosixThread(void *arg, int tid) {
void *rc;
struct PosixThread *pt = arg;
unassert(__get_tls()->tib_tid > 0);
if (pt->attr.__inheritsched == PTHREAD_EXPLICIT_SCHED) {
_pthread_reschedule(pt);
unassert(_weaken(_pthread_reschedule));
_weaken(_pthread_reschedule)(pt); // yoinked by attribute builder
}
// set long jump handler so pthread_exit can bring control back here
if (!setjmp(pt->exiter)) {
pt->next = __get_tls()->tib_pthread;
__get_tls()->tib_pthread = (pthread_t)pt;
unassert(!sigprocmask(SIG_SETMASK, (sigset_t *)pt->attr.__sigmask, 0));
pthread_sigmask(SIG_SETMASK, (sigset_t *)pt->attr.__sigmask, 0);
rc = pt->start(pt->arg);
// ensure pthread_cleanup_pop(), and pthread_exit() popped cleanup
unassert(!pt->cleanup);
// calling pthread_exit() will either jump back here, or call exit
pthread_exit(rc);
}
// avoid signal handler being triggered after we trash our own stack
_sigblockall();
// return to clone polyfill which clears tid, wakes futex, and exits
return 0;
}
@ -160,7 +171,7 @@ static errno_t pthread_create_impl(pthread_t *thread,
// assume they know what they're doing as much as possible
if (IsOpenbsd()) {
if ((rc = FixupCustomStackOnOpenbsd(&pt->attr))) {
_pthread_free(pt);
_pthread_free(pt, false);
return rc;
}
}
@ -168,23 +179,19 @@ static errno_t pthread_create_impl(pthread_t *thread,
// cosmo is managing the stack
// 1. in mono repo optimize for tiniest stack possible
// 2. in public world optimize to *work* regardless of memory
unsigned long default_guardsize;
default_guardsize = getauxval(AT_PAGESZ);
pt->flags = PT_OWNSTACK;
pt->attr.__stacksize = MAX(pt->attr.__stacksize, GetStackSize());
pt->attr.__stacksize = roundup2pow(pt->attr.__stacksize);
pt->attr.__guardsize = ROUNDUP(pt->attr.__guardsize, default_guardsize);
if (pt->attr.__guardsize + default_guardsize >= pt->attr.__stacksize) {
_pthread_free(pt);
int granularity = FRAMESIZE;
int pagesize = getauxval(AT_PAGESZ);
pt->attr.__guardsize = ROUNDUP(pt->attr.__guardsize, pagesize);
pt->attr.__stacksize = ROUNDUP(pt->attr.__stacksize, granularity);
if (pt->attr.__guardsize + pagesize > pt->attr.__stacksize) {
_pthread_free(pt, false);
return EINVAL;
}
if (pt->attr.__guardsize == default_guardsize) {
// user is wisely using smaller stacks with default guard size
if (pt->attr.__guardsize == pagesize) {
pt->attr.__stackaddr =
mmap(0, pt->attr.__stacksize, PROT_READ | PROT_WRITE,
MAP_STACK | MAP_ANONYMOUS, -1, 0);
} else {
// user is tuning things, performance may suffer
pt->attr.__stackaddr =
mmap(0, pt->attr.__stacksize, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
@ -203,9 +210,9 @@ static errno_t pthread_create_impl(pthread_t *thread,
}
}
}
if (pt->attr.__stackaddr == MAP_FAILED) {
if (!pt->attr.__stackaddr || pt->attr.__stackaddr == MAP_FAILED) {
rc = errno;
_pthread_free(pt);
_pthread_free(pt, false);
errno = e;
if (rc == EINVAL || rc == EOVERFLOW) {
return EINVAL;
@ -213,6 +220,7 @@ static errno_t pthread_create_impl(pthread_t *thread,
return EAGAIN;
}
}
pt->pt_flags |= PT_OWNSTACK;
if (IsAsan() && pt->attr.__guardsize) {
__asan_poison(pt->attr.__stackaddr, pt->attr.__guardsize,
kAsanStackOverflow);
@ -220,6 +228,8 @@ static errno_t pthread_create_impl(pthread_t *thread,
}
// set initial status
pt->tib->tib_pthread = (pthread_t)pt;
atomic_store_explicit(&pt->tib->tib_sigmask, -1, memory_order_relaxed);
if (!pt->attr.__havesigmask) {
pt->attr.__havesigmask = true;
memcpy(pt->attr.__sigmask, &oldsigs, sizeof(oldsigs));
@ -234,15 +244,15 @@ static errno_t pthread_create_impl(pthread_t *thread,
memory_order_relaxed);
break;
default:
_pthread_free(pt);
_pthread_free(pt, false);
return EINVAL;
}
// add thread to global list
// we add it to the end since zombies go at the beginning
// we add it to the beginning since zombies go at the end
dll_init(&pt->list);
pthread_spin_lock(&_pthread_lock);
dll_make_last(&_pthread_list, &pt->list);
dll_make_first(&_pthread_list, &pt->list);
pthread_spin_unlock(&_pthread_lock);
// launch PosixThread(pt) in new thread
@ -256,7 +266,7 @@ static errno_t pthread_create_impl(pthread_t *thread,
pthread_spin_lock(&_pthread_lock);
dll_remove(&_pthread_list, &pt->list);
pthread_spin_unlock(&_pthread_lock);
_pthread_free(pt);
_pthread_free(pt, false);
return rc;
}
@ -264,6 +274,13 @@ static errno_t pthread_create_impl(pthread_t *thread,
return 0;
}
static const char *DescribeHandle(char buf[12], errno_t err, pthread_t *th) {
if (err) return "n/a";
if (!th) return "NULL";
FormatInt32(buf, _pthread_tid((struct PosixThread *)*th));
return buf;
}
/**
* Creates thread, e.g.
*
@ -316,11 +333,13 @@ static errno_t pthread_create_impl(pthread_t *thread,
*/
errno_t pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine)(void *), void *arg) {
errno_t rc;
__require_tls();
pthread_decimate_np();
errno_t err;
_pthread_decimate();
BLOCK_SIGNALS;
rc = pthread_create_impl(thread, attr, start_routine, arg, _SigMask);
err = pthread_create_impl(thread, attr, start_routine, arg, _SigMask);
ALLOW_SIGNALS;
return rc;
STRACE("pthread_create([%s], %p, %t, %p) → %s",
DescribeHandle(alloca(12), err, thread), attr, start_routine, arg,
DescribeErrno(err));
return err;
}

View file

@ -27,13 +27,13 @@
/**
* Releases memory of detached threads that have terminated.
*/
void pthread_decimate_np(void) {
void _pthread_decimate(void) {
struct Dll *e;
struct PosixThread *pt;
enum PosixThreadStatus status;
StartOver:
pthread_spin_lock(&_pthread_lock);
for (e = dll_first(_pthread_list); e; e = dll_next(_pthread_list, e)) {
for (e = dll_last(_pthread_list); e; e = dll_prev(_pthread_list, e)) {
pt = POSIXTHREAD_CONTAINER(e);
if (pt->tib == __get_tls()) continue;
status = atomic_load_explicit(&pt->status, memory_order_acquire);
@ -41,7 +41,7 @@ StartOver:
if (!atomic_load_explicit(&pt->tib->tib_tid, memory_order_acquire)) {
dll_remove(&_pthread_list, e);
pthread_spin_unlock(&_pthread_lock);
_pthread_free(pt);
_pthread_free(pt, false);
goto StartOver;
}
}

View file

@ -19,13 +19,36 @@
#include "libc/assert.h"
#include "libc/errno.h"
#include "libc/intrin/atomic.h"
#include "libc/intrin/describeflags.internal.h"
#include "libc/intrin/strace.internal.h"
#include "libc/macros.internal.h"
#include "libc/mem/mem.h"
#include "libc/thread/posixthread.internal.h"
#include "libc/thread/spawn.h"
#include "libc/thread/thread.h"
static errno_t pthread_detach_impl(struct PosixThread *pt) {
enum PosixThreadStatus status, transition;
for (;;) {
status = atomic_load_explicit(&pt->status, memory_order_acquire);
if (status == kPosixThreadJoinable) {
transition = kPosixThreadDetached;
} else if (status == kPosixThreadTerminated) {
transition = kPosixThreadZombie;
} else {
return EINVAL;
}
if (atomic_compare_exchange_weak_explicit(&pt->status, &status, transition,
memory_order_release,
memory_order_relaxed)) {
if (transition == kPosixThreadZombie) {
_pthread_zombify(pt);
}
_pthread_decimate();
return 0;
}
}
}
/**
* Asks POSIX thread to free itself automatically upon termination.
*
@ -37,30 +60,13 @@
* pthread_detach() can't be called twice on the same thread.
*
* @return 0 on success, or errno with error
* @raise EINVAL if `thread` isn't joinable
* @returnserrno
* @threadsafe
*/
errno_t pthread_detach(pthread_t thread) {
struct PosixThread *pt;
enum PosixThreadStatus status, transition;
for (pt = (struct PosixThread *)thread;;) {
status = atomic_load_explicit(&pt->status, memory_order_acquire);
if (status == kPosixThreadJoinable) {
transition = kPosixThreadDetached;
} else if (status == kPosixThreadTerminated) {
transition = kPosixThreadZombie;
} else {
__builtin_unreachable();
}
if (atomic_compare_exchange_weak_explicit(&pt->status, &status, transition,
memory_order_release,
memory_order_relaxed)) {
break;
}
}
if (transition == kPosixThreadZombie) {
_pthread_zombify(pt);
}
pthread_decimate_np();
return 0;
struct PosixThread *pt = (struct PosixThread *)thread;
errno_t err = pthread_detach_impl(pt);
STRACE("pthread_detach(%d) → %s", _pthread_tid(pt), DescribeErrno(err));
return err;
}

View file

@ -24,13 +24,15 @@
#include "libc/intrin/weaken.h"
#include "libc/limits.h"
#include "libc/mem/gc.h"
#include "libc/mem/mem.h"
#include "libc/runtime/internal.h"
#include "libc/runtime/runtime.h"
#include "libc/thread/posixthread.internal.h"
#include "libc/thread/thread.h"
#include "libc/thread/tls.h"
#include "third_party/nsync/futex.internal.h"
static void CleanupThread(struct PosixThread *pt) {
void _pthread_unwind(struct PosixThread *pt) {
struct _pthread_cleanup_buffer *cb;
while ((cb = pt->cleanup)) {
pt->cleanup = cb->__prev;
@ -38,25 +40,27 @@ static void CleanupThread(struct PosixThread *pt) {
}
}
static void DestroyTlsKeys(struct CosmoTib *tib) {
void _pthread_unkey(struct CosmoTib *tib) {
int i, j, gotsome;
void *val, **keys;
pthread_key_dtor dtor;
keys = tib->tib_keys;
for (j = 0; j < PTHREAD_DESTRUCTOR_ITERATIONS; ++j) {
for (gotsome = i = 0; i < PTHREAD_KEYS_MAX; ++i) {
if ((val = keys[i]) &&
(dtor = atomic_load_explicit(_pthread_key_dtor + i,
memory_order_relaxed)) &&
dtor != (pthread_key_dtor)-1) {
gotsome = 1;
keys[i] = 0;
dtor(val);
if ((keys = tib->tib_keys)) {
for (j = 0; j < PTHREAD_DESTRUCTOR_ITERATIONS; ++j) {
for (gotsome = i = 0; i < PTHREAD_KEYS_MAX; ++i) {
if ((val = keys[i]) &&
(dtor = atomic_load_explicit(_pthread_key_dtor + i,
memory_order_relaxed)) &&
dtor != (pthread_key_dtor)-1) {
gotsome = 1;
keys[i] = 0;
dtor(val);
}
}
if (!gotsome) {
break;
}
}
if (!gotsome) {
break;
}
free(keys);
}
}
@ -98,19 +102,25 @@ wontreturn void pthread_exit(void *rc) {
struct PosixThread *pt;
enum PosixThreadStatus status, transition;
STRACE("pthread_exit(%p)", rc);
tib = __get_tls();
pt = (struct PosixThread *)tib->tib_pthread;
unassert(~pt->flags & PT_EXITING);
pt->flags |= PT_EXITING;
pt->pt_flags |= PT_NOCANCEL;
pt->rc = rc;
STRACE("pthread_exit(%p)", rc);
// free resources
CleanupThread(pt);
DestroyTlsKeys(tib);
_pthread_unwind(pt);
_pthread_unkey(tib);
_pthread_ungarbage();
pthread_decimate_np();
_pthread_decimate();
// run atexit handlers if orphaned thread
if (pthread_orphan_np()) {
if (_weaken(__cxa_finalize)) {
_weaken(__cxa_finalize)(NULL);
}
}
// transition the thread to a terminated state
status = atomic_load_explicit(&pt->status, memory_order_acquire);
@ -134,14 +144,17 @@ wontreturn void pthread_exit(void *rc) {
_pthread_zombify(pt);
}
// check if this is the main thread or an orphaned thread
// check if this is the last survivor
if (pthread_orphan_np()) {
exit(0);
for (const uintptr_t *p = __fini_array_end; p > __fini_array_start;) {
((void (*)(void))(*--p))();
}
_Exit(0);
}
// check if the main thread has died whilst children live
// note that the main thread is joinable by child threads
if (pt->flags & PT_STATIC) {
if (pt->pt_flags & PT_STATIC) {
atomic_store_explicit(&tib->tib_tid, 0, memory_order_release);
nsync_futex_wake_(&tib->tib_tid, INT_MAX, !IsWindows());
_Exit1(0);

View file

@ -39,34 +39,33 @@
errno_t pthread_getaffinity_np(pthread_t thread, size_t size,
cpu_set_t *bitset) {
int rc, tid;
tid = _pthread_tid((struct PosixThread *)thread);
if (!(rc = pthread_getunique_np(thread, &tid))) {
if (size != sizeof(cpu_set_t)) {
rc = einval();
} else if (IsWindows() || IsMetal() || IsOpenbsd()) {
rc = enosys();
} else if (IsFreebsd()) {
if (!sys_sched_getaffinity_freebsd(CPU_LEVEL_WHICH, CPU_WHICH_TID, tid,
32, bitset)) {
rc = 32;
} else {
rc = -1;
}
} else if (IsNetbsd()) {
if (!sys_sched_getaffinity_netbsd(tid, 0, 32, bitset)) {
rc = 32;
} else {
rc = -1;
}
if (size != sizeof(cpu_set_t)) {
rc = einval();
} else if (IsWindows() || IsMetal() || IsOpenbsd()) {
rc = enosys();
} else if (IsFreebsd()) {
if (!sys_sched_getaffinity_freebsd(CPU_LEVEL_WHICH, CPU_WHICH_TID, tid, 32,
bitset)) {
rc = 32;
} else {
rc = sys_sched_getaffinity(tid, size, bitset);
rc = -1;
}
if (rc > 0) {
if (rc < size) {
bzero((char *)bitset + rc, size - rc);
}
rc = 0;
} else if (IsNetbsd()) {
if (!sys_sched_getaffinity_netbsd(tid, 0, 32, bitset)) {
rc = 32;
} else {
rc = -1;
}
} else {
rc = sys_sched_getaffinity(tid, size, bitset);
}
if (rc > 0) {
if (rc < size) {
bzero((char *)bitset + rc, size - rc);
}
rc = 0;
}
STRACE("pthread_getaffinity_np(%d, %'zu, %p) → %s", tid, size, bitset,

View file

@ -16,8 +16,18 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/assert.h"
#include "libc/calls/struct/rlimit.h"
#include "libc/dce.h"
#include "libc/intrin/atomic.h"
#include "libc/limits.h"
#include "libc/macros.internal.h"
#include "libc/runtime/runtime.h"
#include "libc/runtime/stack.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/auxv.h"
#include "libc/sysv/consts/rlim.h"
#include "libc/sysv/consts/rlimit.h"
#include "libc/thread/posixthread.internal.h"
#include "libc/thread/thread.h"
@ -37,7 +47,7 @@
* thread. This is useful for knowing where the stack is. It can also
* be useful If you explicitly configured a stack too, since we might
* have needed to slightly tune the address and size to meet platform
* requirements. This function returns information that reflects that
* requirements.
*
* 3. You can view changes pthread_create() may have made to the stack
* guard size by calling pthread_attr_getguardsize() on `attr`
@ -62,5 +72,9 @@ errno_t pthread_getattr_np(pthread_t thread, pthread_attr_t *attr) {
default:
__builtin_unreachable();
}
if (!attr->__stacksize && (pt->pt_flags & PT_STATIC)) {
__get_main_stack(&attr->__stackaddr, &attr->__stacksize,
&attr->__guardsize);
}
return 0;
}

View file

@ -31,10 +31,11 @@
#include "libc/sysv/consts/pr.h"
#include "libc/thread/posixthread.internal.h"
static errno_t pthread_getname_impl(pthread_t thread, char *name, size_t size) {
static errno_t pthread_getname_impl(struct PosixThread *pt, char *name,
size_t size) {
int e, fd, rc, tid, len;
if ((rc = pthread_getunique_np(thread, &tid))) return rc;
tid = _pthread_tid(pt);
if (!size) return 0;
bzero(name, size);
e = errno;
@ -127,8 +128,10 @@ static errno_t pthread_getname_impl(pthread_t thread, char *name, size_t size) {
*/
errno_t pthread_getname_np(pthread_t thread, char *name, size_t size) {
errno_t rc;
struct PosixThread *pt;
pt = (struct PosixThread *)thread;
BLOCK_CANCELLATIONS;
rc = pthread_getname_impl(thread, name, size);
rc = pthread_getname_impl(pt, name, size);
ALLOW_CANCELLATIONS;
return rc;
}

View file

@ -0,0 +1,70 @@
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
vi: set net 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/assert.h"
#include "libc/intrin/atomic.h"
#include "libc/mem/mem.h"
#include "libc/thread/posixthread.internal.h"
#include "libc/thread/thread.h"
#include "libc/thread/tls.h"
// this is a legacy api so we avoid making the tib 1024 bytes larger
static void pthread_key_init(void) {
if (!__get_tls()->tib_keys) {
__get_tls()->tib_keys = calloc(PTHREAD_KEYS_MAX, sizeof(void *));
}
}
/**
* Sets value of TLS slot for current thread.
*
* If `k` wasn't created by pthread_key_create() then the behavior is
* undefined. If `k` was unregistered earlier by pthread_key_delete()
* then the behavior is undefined.
*/
int pthread_setspecific(pthread_key_t k, const void *val) {
// "The effect of calling pthread_getspecific() or
// pthread_setspecific() with a key value not obtained from
// pthread_key_create() or after key has been deleted with
// pthread_key_delete() is undefined."
// ──Quoth POSIX.1-2017
pthread_key_init();
unassert(0 <= k && k < PTHREAD_KEYS_MAX);
unassert(atomic_load_explicit(_pthread_key_dtor + k, memory_order_acquire));
__get_tls()->tib_keys[k] = (void *)val;
return 0;
}
/**
* Gets value of TLS slot for current thread.
*
* If `k` wasn't created by pthread_key_create() then the behavior is
* undefined. If `k` was unregistered earlier by pthread_key_delete()
* then the behavior is undefined.
*/
void *pthread_getspecific(pthread_key_t k) {
// "The effect of calling pthread_getspecific() or
// pthread_setspecific() with a key value not obtained from
// pthread_key_create() or after key has been deleted with
// pthread_key_delete() is undefined."
// ──Quoth POSIX.1-2017
pthread_key_init();
unassert(0 <= k && k < PTHREAD_KEYS_MAX);
unassert(atomic_load_explicit(_pthread_key_dtor + k, memory_order_acquire));
return __get_tls()->tib_keys[k];
}

View file

@ -16,26 +16,14 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/errno.h"
#include "libc/intrin/atomic.h"
#include "libc/thread/posixthread.internal.h"
#include "libc/thread/thread.h"
/**
* Returns system thread id of POSIX thread.
*
* @return 0 on success, or errno on error
*/
errno_t pthread_getunique_np(pthread_t thread, pthread_id_np_t *out_tid) {
int tid;
struct PosixThread *pt;
for (pt = (struct PosixThread *)thread;;) {
tid = atomic_load_explicit(&pt->ptid, memory_order_acquire);
if (!tid) {
pthread_yield();
} else {
*out_tid = tid;
return 0;
}
}
*out_tid = _pthread_tid((struct PosixThread *)thread);
return 0;
}

View file

@ -0,0 +1,60 @@
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
vi: set net 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/atomic.h"
#include "libc/thread/posixthread.internal.h"
#include "libc/thread/thread.h"
/**
* Allocates TLS slot.
*
* This function creates a thread-local storage registration, that will
* apply to all threads. The new identifier is written to `key`, and it
* can be passed to the pthread_setspecific() and pthread_getspecific()
* functions to set and get its associated value. Each thread will have
* its key value initialized to zero upon creation. It is also possible
* to use pthread_key_delete() to unregister a key.
*
* If `dtor` is non-null, then it'll be called upon pthread_exit() when
* the key's value is nonzero. The key's value is set to zero before it
* is called. The ordering of multiple destructor calls is unspecified.
* The same key can be destroyed `PTHREAD_DESTRUCTOR_ITERATIONS` times,
* in cases where it gets set again by a destructor.
*
* @param key is set to the allocated key on success
* @param dtor specifies an optional destructor callback
* @return 0 on success, or errno on error
* @raise EAGAIN if `PTHREAD_KEYS_MAX` keys exist
*/
int pthread_key_create(pthread_key_t *key, pthread_key_dtor dtor) {
int i;
pthread_key_dtor expect;
if (!dtor) dtor = (pthread_key_dtor)-1;
for (i = 0; i < PTHREAD_KEYS_MAX; ++i) {
if (!(expect = atomic_load_explicit(_pthread_key_dtor + i,
memory_order_acquire)) &&
atomic_compare_exchange_strong_explicit(_pthread_key_dtor + i, &expect,
dtor, memory_order_release,
memory_order_relaxed)) {
*key = i;
return 0;
}
}
return EAGAIN;
}

View file

@ -17,51 +17,25 @@
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/assert.h"
#include "libc/calls/cp.internal.h"
#include "libc/calls/struct/timespec.h"
#include "libc/dce.h"
#include "libc/errno.h"
#include "libc/intrin/atomic.h"
#include "libc/thread/posixthread.internal.h"
#include "libc/thread/thread.h"
#include "libc/thread/tls.h"
#include "libc/thread/wait0.internal.h"
#include "third_party/nsync/futex.internal.h"
/**
* Blocks until memory location becomes zero.
* Deletes TLS slot.
*
* This is intended to be used on the child thread id, which is updated
* by the clone() system call when a thread terminates. We need this in
* order to know when it's safe to free a thread's stack. This function
* uses futexes on Linux, FreeBSD, OpenBSD, and Windows. On other
* platforms this uses polling with exponential backoff.
* This function should only be called if all threads have finished
* using the key registration. If a key is used after being deleted
* then the behavior is undefined. If `k` was not registered by the
* pthread_key_create() function then the behavior is undefined.
*
* @param key was created by pthread_key_create()
* @return 0 on success, or errno on error
* @raise ECANCELED if calling thread was cancelled in masked mode
* @raise EBUSY if `abstime` was specified and deadline expired
* @cancellationpoint
*/
errno_t _wait0(atomic_int *ctid, struct timespec *abstime) {
int x, e, rc = 0;
// "The behavior is undefined if the value specified by the thread
// argument to pthread_join() refers to the calling thread."
// ──Quoth POSIX.1-2017
unassert(ctid != &__get_tls()->tib_tid);
// "If the thread calling pthread_join() is canceled, then the target
// thread shall not be detached." ──Quoth POSIX.1-2017
if (!(rc = pthread_testcancel_np())) {
BEGIN_CANCELLATION_POINT;
while ((x = atomic_load_explicit(ctid, memory_order_acquire))) {
e = nsync_futex_wait_(ctid, x, !IsWindows(), abstime);
if (e == -ECANCELED) {
rc = ECANCELED;
break;
} else if (e == -ETIMEDOUT) {
rc = EBUSY;
break;
}
}
END_CANCELLATION_POINT;
}
return rc;
int pthread_key_delete(pthread_key_t k) {
unassert(0 <= k && k < PTHREAD_KEYS_MAX);
unassert(atomic_load_explicit(_pthread_key_dtor + k, memory_order_acquire));
atomic_store_explicit(_pthread_key_dtor + k, 0, memory_order_release);
return 0;
}

View file

@ -0,0 +1,22 @@
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
vi: set net 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/thread/posixthread.internal.h"
#include "libc/thread/thread.h"
_Atomic(pthread_key_dtor) _pthread_key_dtor[PTHREAD_KEYS_MAX];

View file

@ -17,9 +17,15 @@
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/calls/calls.h"
#include "libc/calls/syscall_support-sysv.internal.h"
#include "libc/calls/sig.internal.h"
#include "libc/calls/syscall-sysv.internal.h"
#include "libc/dce.h"
#include "libc/errno.h"
#include "libc/intrin/atomic.h"
#include "libc/intrin/describeflags.internal.h"
#include "libc/intrin/strace.internal.h"
#include "libc/runtime/syslib.internal.h"
#include "libc/sysv/consts/sicode.h"
#include "libc/thread/posixthread.internal.h"
#include "libc/thread/thread.h"
@ -29,22 +35,38 @@
* @return 0 on success, or errno on error
* @raise ESRCH if `tid` was valid but no such thread existed
* @raise EAGAIN if `RLIMIT_SIGPENDING` was exceeded
* @raise EINVAL if `tid` or `sig` was invalid
* @raise EINVAL if `sig` wasn't a legal signal
* @raise EPERM if permission was denied
* @asyncsignalsafe
*/
errno_t pthread_kill(pthread_t thread, int sig) {
int e, rc, tid;
struct PosixThread *p;
if (!(rc = pthread_getunique_np(thread, &tid))) {
e = errno;
p = (struct PosixThread *)thread;
if (!__tkill(tid, sig, p->tib)) {
rc = 0;
int err = 0;
struct PosixThread *pt;
pt = (struct PosixThread *)thread;
if (!(1 <= sig && sig <= 64)) {
err = EINVAL;
} else if (thread == __get_tls()->tib_pthread) {
err = raise(sig); // XNU will EDEADLK it otherwise
} else if (atomic_load_explicit(&pt->status, memory_order_acquire) >=
kPosixThreadTerminated) {
err = ESRCH;
} else if (IsWindows()) {
err = __sig_kill(pt, sig, SI_TKILL);
} else {
if (IsXnuSilicon()) {
err = __syslib->__pthread_kill(_pthread_syshand(pt), sig);
} else {
rc = errno;
errno = e;
int e = errno;
if (sys_tkill(_pthread_tid(pt), sig, pt->tib)) {
err = errno;
errno = e;
}
}
if (err == ESRCH) {
err = 0; // we already reported this
}
}
return rc;
STRACE("pthread_kill(%d, %G) → %s", _pthread_tid(pt), sig,
DescribeErrno(err));
return err;
}

View file

@ -25,24 +25,22 @@
#include "libc/thread/posixthread.internal.h"
errno_t _pthread_reschedule(struct PosixThread *pt) {
int e, rc, tid;
int policy = pt->attr.__schedpolicy;
int e, rc, tid = _pthread_tid(pt);
struct sched_param param = {pt->attr.__schedparam};
if (!(rc = pthread_getunique_np((pthread_t)pt, &tid))) {
e = errno;
if (IsNetbsd()) {
rc = sys_sched_setparam_netbsd(0, tid, policy, &param);
} else if (IsLinux()) {
rc = sys_sched_setscheduler(tid, policy, &param);
} else if (IsFreebsd()) {
rc = _pthread_setschedparam_freebsd(tid, policy, &param);
} else {
rc = enosys();
}
if (rc == -1) {
rc = errno;
errno = e;
}
e = errno;
if (IsNetbsd()) {
rc = sys_sched_setparam_netbsd(0, tid, policy, &param);
} else if (IsLinux()) {
rc = sys_sched_setscheduler(tid, policy, &param);
} else if (IsFreebsd()) {
rc = _pthread_setschedparam_freebsd(tid, policy, &param);
} else {
rc = enosys();
}
if (rc == -1) {
rc = errno;
errno = e;
}
return rc;
}

View file

@ -25,8 +25,5 @@
* @asyncsignalsafe
*/
pthread_t pthread_self(void) {
pthread_t t;
t = __get_tls()->tib_pthread;
unassert(t);
return t;
return __get_tls()->tib_pthread;
}

View file

@ -31,15 +31,12 @@
#include "libc/thread/posixthread.internal.h"
static dontinline textwindows int sys_pthread_setaffinity_nt(
int tid, uint64_t size, const cpu_set_t *bitset) {
int rc;
int64_t h;
h = OpenThread(kNtThreadSetInformation | kNtThreadQueryInformation, false,
tid);
if (!h) return __winerr();
rc = SetThreadAffinityMask(h, bitset->__bits[0]) ? 0 : __winerr();
CloseHandle(h);
return rc;
struct PosixThread *pt, uint64_t size, const cpu_set_t *bitset) {
if (SetThreadAffinityMask(_pthread_syshand(pt), bitset->__bits[0])) {
return 0;
} else {
return __winerr();
}
}
/**
@ -54,24 +51,25 @@ static dontinline textwindows int sys_pthread_setaffinity_nt(
errno_t pthread_setaffinity_np(pthread_t thread, size_t size,
const cpu_set_t *bitset) {
int e, rc, tid;
if (!(rc = pthread_getunique_np(thread, &tid))) {
e = errno;
if (size != sizeof(cpu_set_t)) {
rc = einval();
} else if (IsWindows()) {
rc = sys_pthread_setaffinity_nt(tid, size, bitset);
} else if (IsFreebsd()) {
rc = sys_sched_setaffinity_freebsd(CPU_LEVEL_WHICH, CPU_WHICH_TID, tid,
32, bitset);
} else if (IsNetbsd()) {
rc = sys_sched_setaffinity_netbsd(tid, 0, 32, bitset);
} else {
rc = sys_sched_setaffinity(tid, size, bitset);
}
if (rc == -1) {
rc = errno;
errno = e;
}
struct PosixThread *pt;
e = errno;
pt = (struct PosixThread *)thread;
tid = _pthread_tid(pt);
if (size != sizeof(cpu_set_t)) {
rc = einval();
} else if (IsWindows()) {
rc = sys_pthread_setaffinity_nt(pt, size, bitset);
} else if (IsFreebsd()) {
rc = sys_sched_setaffinity_freebsd(CPU_LEVEL_WHICH, CPU_WHICH_TID, tid, 32,
bitset);
} else if (IsNetbsd()) {
rc = sys_sched_setaffinity_netbsd(tid, 0, 32, bitset);
} else {
rc = sys_sched_setaffinity(tid, size, bitset);
}
if (rc == -1) {
rc = errno;
errno = e;
}
STRACE("pthread_setaffinity_np(%d, %'zu, %p) → %s", tid, size, bitset,
DescribeErrno(rc));

View file

@ -18,10 +18,23 @@
*/
#include "libc/dce.h"
#include "libc/errno.h"
#include "libc/fmt/itoa.h"
#include "libc/intrin/describeflags.internal.h"
#include "libc/intrin/strace.internal.h"
#include "libc/mem/alloca.h"
#include "libc/thread/posixthread.internal.h"
#include "libc/thread/thread.h"
#include "libc/thread/tls.h"
static const char *DescribeCancelType(char buf[12], int err, int *t) {
if (err) return "n/a";
if (!t) return "NULL";
if (*t == PTHREAD_CANCEL_DEFERRED) return "PTHREAD_CANCEL_DEFERRED";
if (*t == PTHREAD_CANCEL_ASYNCHRONOUS) return "PTHREAD_CANCEL_ASYNCHRONOUS";
FormatInt32(buf, *t);
return buf;
}
/**
* Sets cancellation strategy.
*
@ -35,29 +48,32 @@
* @see pthread_cancel() for docs
*/
errno_t pthread_setcanceltype(int type, int *oldtype) {
int err;
struct PosixThread *pt;
switch (type) {
case PTHREAD_CANCEL_ASYNCHRONOUS:
if (IsWindows()) {
return ENOTSUP;
}
// fallthrough
case PTHREAD_CANCEL_DEFERRED:
pt = (struct PosixThread *)__get_tls()->tib_pthread;
pt = _pthread_self();
if (oldtype) {
if (pt->flags & PT_ASYNC) {
if (pt->pt_flags & PT_ASYNC) {
*oldtype = PTHREAD_CANCEL_ASYNCHRONOUS;
} else {
*oldtype = PTHREAD_CANCEL_DEFERRED;
}
}
if (type == PTHREAD_CANCEL_DEFERRED) {
pt->flags &= ~PT_ASYNC;
pt->pt_flags &= ~PT_ASYNC;
} else {
pt->flags |= PT_ASYNC;
pt->pt_flags |= PT_ASYNC;
}
return 0;
err = 0;
break;
default:
return EINVAL;
err = EINVAL;
break;
}
STRACE("pthread_setcanceltype(%s, [%s]) → %s",
DescribeCancelType(alloca(12), 0, &type),
DescribeCancelType(alloca(12), err, oldtype), DescribeErrno(err));
return err;
}

View file

@ -24,20 +24,32 @@
#include "libc/fmt/itoa.h"
#include "libc/intrin/asan.internal.h"
#include "libc/intrin/atomic.h"
#include "libc/intrin/describeflags.internal.h"
#include "libc/intrin/strace.internal.h"
#include "libc/runtime/syslib.internal.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/at.h"
#include "libc/sysv/consts/o.h"
#include "libc/sysv/consts/pr.h"
#include "libc/thread/posixthread.internal.h"
#include "libc/thread/thread.h"
#include "libc/thread/tls.h"
static errno_t pthread_setname_impl(pthread_t thread, const char *name) {
static errno_t pthread_setname_impl(struct PosixThread *pt, const char *name) {
char path[128], *p;
int e, fd, rc, tid, len;
if ((rc = pthread_getunique_np(thread, &tid))) return rc;
len = strlen(name);
tid = _pthread_tid(pt);
if (IsLinux()) {
if (IsXnuSilicon()) {
if (pt == _pthread_self()) {
__syslib->__pthread_setname_np(name);
return 0;
} else {
return EPERM;
}
} else if (IsLinux()) {
e = errno;
if (tid == gettid()) {
if (prctl(PR_SET_NAME, name) == -1) {
@ -111,13 +123,17 @@ static errno_t pthread_setname_impl(pthread_t thread, const char *name) {
* @return 0 on success, or errno on error
* @raise ERANGE if length of `name` exceeded system limit, in which
* case the name may have still been set with os using truncation
* @raise ENOSYS on MacOS, and Windows
* @raise ENOSYS on Windows and AMD64-XNU
* @see pthread_getname_np()
*/
errno_t pthread_setname_np(pthread_t thread, const char *name) {
errno_t rc;
errno_t err;
struct PosixThread *pt;
pt = (struct PosixThread *)thread;
BLOCK_CANCELLATIONS;
rc = pthread_setname_impl(thread, name);
err = pthread_setname_impl(pt, name);
ALLOW_CANCELLATIONS;
return rc;
STRACE("pthread_setname_np(%d, %s) → %s", _pthread_tid(pt), name,
DescribeErrno(err));
return err;
}

View file

@ -17,14 +17,67 @@
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/assert.h"
#include "libc/calls/cp.internal.h"
#include "libc/calls/struct/timespec.h"
#include "libc/calls/struct/timespec.internal.h"
#include "libc/errno.h"
#include "libc/fmt/itoa.h"
#include "libc/intrin/atomic.h"
#include "libc/intrin/describeflags.internal.h"
#include "libc/intrin/dll.h"
#include "libc/intrin/strace.internal.h"
#include "libc/thread/posixthread.internal.h"
#include "libc/thread/thread2.h"
#include "libc/thread/tls.h"
#include "libc/thread/wait0.internal.h"
#include "third_party/nsync/futex.internal.h"
// TODO(jart): Use condition variable for thread waiting.
static const char *DescribeReturnValue(char buf[30], int err, void **value) {
char *p = buf;
if (!value) return "NULL";
if (err) return "[n/a]";
*p++ = '[';
p = FormatHex64(p, (uintptr_t)*value, 1);
*p++ = ']';
return buf;
}
/**
* Blocks until memory location becomes zero.
*
* This is intended to be used on the child thread id, which is updated
* by the clone() system call when a thread terminates. We need this in
* order to know when it's safe to free a thread's stack. This function
* uses futexes on Linux, FreeBSD, OpenBSD, and Windows. On other
* platforms this uses polling with exponential backoff.
*
* @return 0 on success, or errno on error
* @raise ECANCELED if calling thread was cancelled in masked mode
* @raise EBUSY if `abstime` was specified and deadline expired
* @cancellationpoint
*/
static errno_t _pthread_wait(atomic_int *ctid, struct timespec *abstime) {
int x, e, rc = 0;
unassert(ctid != &__get_tls()->tib_tid);
// "If the thread calling pthread_join() is canceled, then the target
// thread shall not be detached." ──Quoth POSIX.1-2017
if (!(rc = pthread_testcancel_np())) {
BEGIN_CANCELLATION_POINT;
while ((x = atomic_load_explicit(ctid, memory_order_acquire))) {
e = nsync_futex_wait_(ctid, x, !IsWindows(), abstime);
if (e == -ECANCELED) {
rc = ECANCELED;
break;
} else if (e == -ETIMEDOUT) {
rc = EBUSY;
break;
}
}
END_CANCELLATION_POINT;
}
return rc;
}
/**
* Waits for thread to terminate.
@ -50,7 +103,7 @@
*/
errno_t pthread_timedjoin_np(pthread_t thread, void **value_ptr,
struct timespec *abstime) {
errno_t rc;
errno_t err;
struct PosixThread *pt;
enum PosixThreadStatus status;
pt = (struct PosixThread *)thread;
@ -59,15 +112,18 @@ errno_t pthread_timedjoin_np(pthread_t thread, void **value_ptr,
// argument to pthread_join() does not refer to a joinable thread."
// ──Quoth POSIX.1-2017
unassert(status == kPosixThreadJoinable || status == kPosixThreadTerminated);
if (!(rc = _wait0(&pt->tib->tib_tid, abstime))) {
if (!(err = _pthread_wait(&pt->tib->tib_tid, abstime))) {
pthread_spin_lock(&_pthread_lock);
dll_remove(&_pthread_list, &pt->list);
pthread_spin_unlock(&_pthread_lock);
if (value_ptr) {
*value_ptr = pt->rc;
}
_pthread_free(pt);
pthread_decimate_np();
_pthread_free(pt, false);
_pthread_decimate();
}
return 0;
STRACE("pthread_timedjoin_np(%d, %s, %s) → %s", _pthread_tid(pt),
DescribeReturnValue(alloca(30), err, value_ptr),
DescribeTimespec(err ? -1 : 0, abstime), DescribeErrno(err));
return err;
}

View file

@ -23,6 +23,6 @@
void _pthread_zombify(struct PosixThread *pt) {
pthread_spin_lock(&_pthread_lock);
dll_remove(&_pthread_list, &pt->list);
dll_make_first(&_pthread_list, &pt->list);
dll_make_last(&_pthread_list, &pt->list);
pthread_spin_unlock(&_pthread_lock);
}

View file

@ -57,13 +57,13 @@ static void sem_open_unlock(void) {
pthread_mutex_unlock(&g_semaphores.lock);
}
static void sem_open_funlock(void) {
static void sem_open_wipe(void) {
pthread_mutex_init(&g_semaphores.lock, 0);
}
static void sem_open_setup(void) {
sem_open_funlock();
pthread_atfork(sem_open_lock, sem_open_unlock, sem_open_funlock);
sem_open_wipe();
pthread_atfork(sem_open_lock, sem_open_unlock, sem_open_wipe);
}
static void sem_open_init(void) {

89
libc/thread/setitimer.c Normal file
View file

@ -0,0 +1,89 @@
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi
Copyright 2020 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/calls/struct/itimerval.h"
#include "libc/calls/struct/itimerval.internal.h"
#include "libc/dce.h"
#include "libc/intrin/asan.internal.h"
#include "libc/intrin/describeflags.internal.h"
#include "libc/intrin/strace.internal.h"
#include "libc/sysv/errfuns.h"
#include "libc/time/time.h"
/**
* Schedules delivery of one-shot or intermittent interrupt signal, e.g.
*
* Raise SIGALRM every 1.5s:
*
* sigaction(SIGALRM,
* &(struct sigaction){.sa_handler = OnSigalrm},
* NULL);
* setitimer(ITIMER_REAL,
* &(const struct itimerval){{1, 500000},
* {1, 500000}},
* NULL);
*
* Single-shot alarm to interrupt connect() after 50ms:
*
* sigaction(SIGALRM,
* &(struct sigaction){.sa_handler = OnSigalrm,
* .sa_flags = SA_RESETHAND},
* NULL);
* setitimer(ITIMER_REAL,
* &(const struct itimerval){{0, 0}, {0, 50000}},
* NULL);
* if (connect(...) == -1 && errno == EINTR) { ... }
*
* Disarm existing timer:
*
* setitimer(ITIMER_REAL, &(const struct itimerval){0}, NULL);
*
* If the goal is to use alarms to interrupt blocking i/o routines, e.g.
* read(), connect(), etc. then it's important to install the signal
* handler using sigaction() rather than signal(), because the latter
* sets the `SA_RESTART` flag.
*
* Timers are not inherited across fork.
*
* @param which can be ITIMER_REAL, ITIMER_VIRTUAL, etc.
* @param newvalue specifies the interval ({0,0} means one-shot) and
* duration ({0,0} means disarm) in microseconds [0,999999] and
* if this parameter is NULL, we'll polyfill getitimer() behavior
* @param out_opt_old may receive remainder of previous op (if any)
* @return 0 on success or -1 w/ errno
*/
int setitimer(int which, const struct itimerval *newvalue,
struct itimerval *oldvalue) {
int rc;
if (IsAsan() &&
((newvalue && !__asan_is_valid(newvalue, sizeof(*newvalue))) ||
(oldvalue && !__asan_is_valid(oldvalue, sizeof(*oldvalue))))) {
rc = efault();
} else if (!IsWindows()) {
if (newvalue) {
rc = sys_setitimer(which, newvalue, oldvalue);
} else {
rc = sys_getitimer(which, oldvalue);
}
} else {
rc = sys_setitimer_nt(which, newvalue, oldvalue);
}
STRACE("setitimer(%s, %s, [%s]) → %d% m", DescribeItimer(which),
DescribeItimerval(0, newvalue), DescribeItimerval(rc, oldvalue), rc);
return rc;
}

View file

@ -1,166 +0,0 @@
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
vi: set net 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/thread/spawn.h"
#include "libc/assert.h"
#include "libc/calls/calls.h"
#include "libc/errno.h"
#include "libc/macros.internal.h"
#include "libc/mem/mem.h"
#include "libc/runtime/internal.h"
#include "libc/runtime/runtime.h"
#include "libc/runtime/stack.h"
#include "libc/stdalign.internal.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/clone.h"
#include "libc/sysv/consts/map.h"
#include "libc/sysv/consts/prot.h"
#include "libc/sysv/errfuns.h"
#include "libc/thread/posixthread.internal.h"
#include "libc/thread/tls.h"
#include "libc/thread/wait0.internal.h"
/**
* @fileoverview Simple threading API
*
* This API is supported on all six operating systems. We have this
* because the POSIX threads API is positively enormous. We currently
* only implement a small subset of POSIX threads, e.g. mutexes. So
* until we can implement all of POSIX threads, this API is great. If we
* consider that the classic forking concurrency library consists of a
* single function, it's a shame POSIX didn't define threads in the past
* to just be this. Since create/join/atomics is really all we need.
*
* Your spawn library abstracts clone() which also works on all
* platforms; however our implementation of clone() is significantly
* complicated so we strongly recommend always favoring this API.
*
* @deprecated
*/
#define _TLSZ ((intptr_t)_tls_size)
#define _TLDZ ((intptr_t)_tdata_size)
#define _TIBZ sizeof(struct CosmoTib)
#define _MEMZ ROUNDUP(_TLSZ + _TIBZ, alignof(struct CosmoTib))
struct spawner {
int (*fun)(void *, int);
void *arg;
};
static int Spawner(void *arg, int tid) {
int rc;
struct spawner *spawner = arg;
rc = spawner->fun(spawner->arg, tid);
_pthread_ungarbage();
free(spawner);
return rc;
}
/**
* Spawns thread, e.g.
*
* int worker(void *arg, int tid) {
* const char *s = arg;
* printf("%s\n", s);
* return 0;
* }
*
* int main() {
* struct spawn th;
* _spawn(worker, "hi", &th);
* _join(&th);
* }
*
* @param fun is thread worker callback, which receives `arg` and `ctid`
* @param arg shall be passed to `fun`
* @param opt_out_thread needn't be initialiized and is always clobbered
* except when it isn't specified, in which case, the thread is kind
* of detached and will (currently) just leak the stack / tls memory
* @return 0 on success, or -1 w/ errno
* @deprecated
*/
int _spawn(int fun(void *, int), void *arg, struct spawn *opt_out_thread) {
errno_t rc;
struct spawn *th, ths;
struct spawner *spawner;
__require_tls();
if (!fun) return einval();
// we need to to clobber the output memory before calling clone, since
// there's no guarantee clone() won't suspend the parent, and focus on
// running the child instead; in that case child might want to read it
if (opt_out_thread) {
th = opt_out_thread;
} else {
th = &ths;
}
// allocate enough TLS memory for all the GNU Linuker (_tls_size)
// organized _Thread_local data, as well as Cosmpolitan Libc (64)
if (!(th->tls = _mktls(&th->tib))) {
return -1;
}
// we must use _mapstack() to allocate the stack because OpenBSD has
// very strict requirements for what's allowed to be used for stacks
if (!(th->stk = NewCosmoStack())) {
free(th->tls);
return -1;
}
spawner = malloc(sizeof(struct spawner));
spawner->fun = fun;
spawner->arg = arg;
rc = clone(Spawner, th->stk, GetStackSize() - 16 /* openbsd:stackbound */,
CLONE_VM | CLONE_THREAD | CLONE_FS | CLONE_FILES | CLONE_SIGHAND |
CLONE_SYSVSEM | CLONE_SETTLS | CLONE_PARENT_SETTID |
CLONE_CHILD_SETTID | CLONE_CHILD_CLEARTID,
spawner, &th->ptid, __adj_tls(th->tib), &th->tib->tib_tid);
if (rc) {
errno = rc;
FreeCosmoStack(th->stk);
free(th->tls);
return -1;
}
return 0;
}
/**
* Waits for thread created by _spawn() to terminate.
*
* This will free your thread's stack and tls memory too.
*
* @deprecated
*/
int _join(struct spawn *th) {
int rc;
if (th->tib) {
// wait for ctid to become zero
npassert(!_wait0(&th->tib->tib_tid, 0));
// free thread memory
free(th->tls);
rc = munmap(th->stk, GetStackSize());
rc = 0;
} else {
rc = 0;
}
bzero(th, sizeof(*th));
return rc;
}

View file

@ -1,20 +0,0 @@
#ifndef COSMOPOLITAN_LIBC_THREAD_SPAWN_H_
#define COSMOPOLITAN_LIBC_THREAD_SPAWN_H_
#include "libc/thread/tls.h"
#if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_
struct spawn {
int ptid;
char *stk;
char *tls;
struct CosmoTib *tib;
};
int _spawn(int (*)(void *, int), void *, struct spawn *);
int _join(struct spawn *);
char *_mktls(struct CosmoTib **);
COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
#endif /* COSMOPOLITAN_LIBC_THREAD_SPAWN_H_ */

View file

@ -2,7 +2,7 @@
#define COSMOPOLITAN_LIBC_THREAD_THREAD_H_
#define PTHREAD_KEYS_MAX 128
#define PTHREAD_STACK_MIN 262144
#define PTHREAD_STACK_MIN 65536
#define PTHREAD_DESTRUCTOR_ITERATIONS 4
#define PTHREAD_BARRIER_SERIAL_THREAD 31337
@ -97,10 +97,10 @@ typedef struct pthread_attr_s {
int __schedparam;
int __schedpolicy;
int __contentionscope;
unsigned __guardsize;
unsigned __stacksize;
int __guardsize;
size_t __stacksize;
uint32_t __sigmask[4];
char *__stackaddr;
void *__stackaddr;
} pthread_attr_t;
struct _pthread_cleanup_buffer {
@ -199,7 +199,6 @@ pthread_t pthread_self(void) pureconst;
void *pthread_getspecific(pthread_key_t);
void pthread_cleanup_pop(struct _pthread_cleanup_buffer *, int) paramsnonnull();
void pthread_cleanup_push(struct _pthread_cleanup_buffer *, void (*)(void *), void *) paramsnonnull((1));
void pthread_decimate_np(void);
void pthread_exit(void *) wontreturn;
void pthread_testcancel(void);

View file

@ -49,6 +49,12 @@ $(LIBC_THREAD_A).pkg: \
$(LIBC_THREAD_A_OBJS) \
$(foreach x,$(LIBC_THREAD_A_DIRECTDEPS),$($(x)_A).pkg)
$(LIBC_THREAD_A_OBJS): private \
COPTS += \
-fno-sanitize=all \
-Wframe-larger-than=4096 \
-Walloca-larger-than=4096
LIBC_THREAD_LIBS = $(foreach x,$(LIBC_THREAD_ARTIFACTS),$($(x)))
LIBC_THREAD_SRCS = $(foreach x,$(LIBC_THREAD_ARTIFACTS),$($(x)_SRCS))
LIBC_THREAD_HDRS = $(foreach x,$(LIBC_THREAD_ARTIFACTS),$($(x)_HDRS))

View file

@ -17,6 +17,9 @@ struct CosmoFtrace { /* 16 */
int64_t ft_lastaddr; /* 8 */
};
/* 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 */
struct CosmoTib {
struct CosmoTib *tib_self; /* 0x00 */
struct CosmoFtrace tib_ftracer; /* 0x08 */
@ -29,54 +32,54 @@ struct CosmoTib {
uint64_t tib_flags; /* 0x40 */
int tib_ftrace; /* inherited */
int tib_strace; /* inherited */
uint64_t tib_sigmask; /* inherited */
uint64_t tib_sigpending;
void *tib_reserved4;
void *tib_reserved5;
void *tib_reserved6;
void *tib_reserved7;
void *tib_keys[128];
_Atomic(uint64_t) tib_sigmask; /* inherited */
_Atomic(uint64_t) tib_sigpending;
_Atomic(uint64_t) tib_syshand; /* win32=kThread, xnusilicon=pthread_t */
char *tib_sigstack_addr;
uint32_t tib_sigstack_size;
uint32_t tib_sigstack_flags;
void **tib_keys;
};
extern int __threaded;
extern unsigned __tls_index;
char *_mktls(struct CosmoTib **);
void __bootstrap_tls(struct CosmoTib *, char *);
#ifdef __x86_64__
extern bool __tls_enabled;
#define __tls_enabled_set(x) __tls_enabled = x
#elif defined(__aarch64__)
#define __tls_enabled \
({ \
register struct CosmoTib *_t asm("x28"); \
!!_t; \
})
#define __tls_enabled true
#define __tls_enabled_set(x) (void)0
#else
#error "unsupported architecture"
#endif
void __require_tls(void);
void __set_tls(struct CosmoTib *);
#ifdef __x86_64__
/**
* Returns location of thread information block.
*
* This can't be used in privileged functions.
*/
#define __get_tls() \
({ \
struct CosmoTib *_t; \
asm("mov\t%%fs:0,%0" : "=r"(_t) : /* no inputs */ : "memory"); \
_t; \
})
__funline pureconst struct CosmoTib *__get_tls(void) {
#ifdef __chibicc__
return 0;
#elif __x86_64__
struct CosmoTib *__tib;
__asm__("mov\t%%fs:0,%0" : "=r"(__tib));
return __tib;
#elif defined(__aarch64__)
register struct CosmoTib *__tls __asm__("x28");
return __tls - 1;
#endif
}
#ifdef __x86_64__
#define __adj_tls(tib) (tib)
#elif defined(__aarch64__)
#define __get_tls() \
({ \
register struct CosmoTib *_t asm("x28"); \
_t - 1; \
})
#define __adj_tls(tib) ((struct CosmoTib *)(tib) + 1)
#endif

View file

@ -25,14 +25,14 @@ __funline struct CosmoTib *__get_tls_privileged(void) {
return (struct CosmoTib *)tib;
}
static dontasan inline struct CosmoTib *__get_tls_win32(void) {
__funline 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;
}
static dontasan inline void __set_tls_win32(void *tls) {
__funline void __set_tls_win32(void *tls) {
asm("mov\t%1,%%gs:%0" : "=m"(*((long *)0x1480 + __tls_index)) : "r"(tls));
}

40
libc/thread/ualarm.c Normal file
View file

@ -0,0 +1,40 @@
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi
Copyright 2020 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/assert.h"
#include "libc/calls/calls.h"
#include "libc/calls/struct/itimerval.h"
#include "libc/calls/struct/timeval.h"
#include "libc/sysv/consts/itimer.h"
/**
* Sets microsecond-precision alarm.
*
* This generates a `SIGALRM` signal after `usecs` microseconds. If
* `reload` is non-zero, then it'll keep generating `SIGALRM` every
* `reload` microseconds after the first signal.
*
* @asyncsignalsafe
*/
unsigned ualarm(unsigned usecs, unsigned reload) {
struct itimerval it, old;
it.it_value = timeval_frommicros(usecs);
it.it_interval = timeval_frommicros(reload);
npassert(!setitimer(ITIMER_REAL, &it, &old));
return timeval_tomicros(old.it_value);
}

View file

@ -1,11 +0,0 @@
#ifndef COSMOPOLITAN_LIBC_INTRIN_WAIT0_H_
#define COSMOPOLITAN_LIBC_INTRIN_WAIT0_H_
#include "libc/calls/struct/timespec.h"
#if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_
errno_t _wait0(_Atomic(int) *, struct timespec *);
COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
#endif /* COSMOPOLITAN_LIBC_INTRIN_WAIT0_H_ */