Introduce cosmo_futex_wait and cosmo_futex_wake

Cosmopolitan Futexes are now exposed as a public API.
This commit is contained in:
Justine Tunney 2024-11-22 11:08:29 -08:00
parent 729f7045e3
commit 9ddbfd921e
No known key found for this signature in database
GPG key ID: BE714B4575D6E328
66 changed files with 886 additions and 917 deletions

View file

@ -1,717 +0,0 @@
/*-*- 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/sysv/consts/sig.h"
#include "ape/sections.internal.h"
#include "libc/calls/calls.h"
#include "libc/calls/sig.internal.h"
#include "libc/calls/state.internal.h"
#include "libc/calls/struct/sigaction.h"
#include "libc/calls/struct/siginfo.h"
#include "libc/calls/struct/sigset.internal.h"
#include "libc/calls/struct/ucontext.internal.h"
#include "libc/calls/syscall_support-nt.internal.h"
#include "libc/calls/ucontext.h"
#include "libc/dce.h"
#include "libc/errno.h"
#include "libc/intrin/atomic.h"
#include "libc/intrin/bsf.h"
#include "libc/intrin/describebacktrace.h"
#include "libc/intrin/dll.h"
#include "libc/intrin/maps.h"
#include "libc/intrin/strace.h"
#include "libc/intrin/weaken.h"
#include "libc/nt/console.h"
#include "libc/nt/enum/context.h"
#include "libc/nt/enum/exceptionhandleractions.h"
#include "libc/nt/enum/processcreationflags.h"
#include "libc/nt/enum/signal.h"
#include "libc/nt/enum/status.h"
#include "libc/nt/events.h"
#include "libc/nt/runtime.h"
#include "libc/nt/signals.h"
#include "libc/nt/struct/ntexceptionpointers.h"
#include "libc/nt/synchronization.h"
#include "libc/nt/thread.h"
#include "libc/runtime/internal.h"
#include "libc/runtime/symbols.internal.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/sa.h"
#include "libc/sysv/consts/sicode.h"
#include "libc/sysv/consts/ss.h"
#include "libc/thread/posixthread.internal.h"
#ifdef __x86_64__
/**
* @fileoverview Cosmopolitan Signals for Windows.
*/
#define STKSZ 65536
struct SignalFrame {
unsigned rva;
unsigned flags;
siginfo_t si;
ucontext_t ctx;
};
static textwindows bool __sig_ignored_by_default(int sig) {
return sig == SIGURG || //
sig == SIGCONT || //
sig == SIGCHLD || //
sig == SIGWINCH;
}
textwindows bool __sig_ignored(int sig) {
return __sighandrvas[sig] == (intptr_t)SIG_IGN ||
(__sighandrvas[sig] == (intptr_t)SIG_DFL &&
__sig_ignored_by_default(sig));
}
textwindows void __sig_delete(int sig) {
struct Dll *e;
atomic_fetch_and_explicit(__sig.process, ~(1ull << (sig - 1)),
memory_order_relaxed);
_pthread_lock();
for (e = dll_last(_pthread_list); e; e = dll_prev(_pthread_list, e))
atomic_fetch_and_explicit(&POSIXTHREAD_CONTAINER(e)->tib->tib_sigpending,
~(1ull << (sig - 1)), memory_order_relaxed);
_pthread_unlock();
}
static textwindows int __sig_getter(atomic_ulong *sigs, sigset_t masked) {
int sig;
sigset_t bit, pending, deliverable;
for (;;) {
pending = atomic_load_explicit(sigs, memory_order_acquire);
if ((deliverable = pending & ~masked)) {
sig = bsfl(deliverable) + 1;
bit = 1ull << (sig - 1);
if (atomic_fetch_and_explicit(sigs, ~bit, memory_order_acq_rel) & bit)
return sig;
} else {
return 0;
}
}
}
textwindows int __sig_get(sigset_t masked) {
int sig;
if (!(sig = __sig_getter(&__get_tls()->tib_sigpending, masked)))
sig = __sig_getter(__sig.process, masked);
return sig;
}
static textwindows bool __sig_should_use_altstack(unsigned flags,
struct CosmoTib *tib) {
if (!(flags & SA_ONSTACK))
return false; // signal handler didn't enable it
if (!tib->tib_sigstack_size)
return false; // sigaltstack() wasn't installed on this thread
if (tib->tib_sigstack_flags & SS_DISABLE)
return false; // sigaltstack() on this thread was disabled by user
char *bp = __builtin_frame_address(0);
if (tib->tib_sigstack_addr <= bp &&
bp <= tib->tib_sigstack_addr + tib->tib_sigstack_size)
return false; // we're already on the alternate stack
return true;
}
static textwindows wontreturn void __sig_terminate(int sig) {
TerminateThisProcess(sig);
}
textwindows static bool __sig_wake(struct PosixThread *pt, int sig) {
atomic_int *blocker;
blocker = atomic_load_explicit(&pt->pt_blocker, memory_order_acquire);
if (!blocker)
return false;
// threads can create semaphores on an as-needed basis
if (blocker == PT_BLOCKER_EVENT) {
STRACE("%G set %d's event object", sig, _pthread_tid(pt));
SetEvent(pt->pt_event);
return !!atomic_load_explicit(&pt->pt_blocker, memory_order_acquire);
}
// all other blocking ops that aren't overlap should use futexes
// we force restartable futexes to churn by waking w/o releasing
STRACE("%G waking %d's futex", sig, _pthread_tid(pt));
WakeByAddressSingle(blocker);
return !!atomic_load_explicit(&pt->pt_blocker, memory_order_acquire);
}
textwindows static bool __sig_start(struct PosixThread *pt, int sig,
unsigned *rva, unsigned *flags) {
*rva = __sighandrvas[sig];
*flags = __sighandflags[sig];
if (*rva == (intptr_t)SIG_IGN ||
(*rva == (intptr_t)SIG_DFL && __sig_ignored_by_default(sig))) {
STRACE("ignoring %G", sig);
return false;
}
if (atomic_load_explicit(&pt->tib->tib_sigmask, memory_order_acquire) &
(1ull << (sig - 1))) {
STRACE("enqueing %G on %d", sig, _pthread_tid(pt));
atomic_fetch_or_explicit(&pt->tib->tib_sigpending, 1ull << (sig - 1),
memory_order_relaxed);
__sig_wake(pt, sig);
return false;
}
if (*rva == (intptr_t)SIG_DFL) {
STRACE("terminating on %G due to no handler", sig);
__sig_terminate(sig);
}
return true;
}
textwindows static sigaction_f __sig_handler(unsigned rva) {
atomic_fetch_add_explicit(&__sig.count, 1, memory_order_relaxed);
return (sigaction_f)(__executable_start + rva);
}
textwindows int __sig_raise(volatile int sig, int sic) {
// bitset of kinds of handlers called
volatile int handler_was_called = 0;
// loop over pending signals
ucontext_t ctx;
getcontext(&ctx);
if (!sig) {
if ((sig = __sig_get(ctx.uc_sigmask))) {
sic = SI_KERNEL;
} else {
return handler_was_called;
}
}
// process signal(s)
unsigned rva, flags;
struct PosixThread *pt = _pthread_self();
if (__sig_start(pt, sig, &rva, &flags)) {
if (flags & SA_RESETHAND) {
STRACE("resetting %G handler", sig);
__sighandrvas[sig] = (int32_t)(intptr_t)SIG_DFL;
}
// update the signal mask in preparation for signal handller
sigset_t blocksigs = __sighandmask[sig];
if (!(flags & SA_NODEFER))
blocksigs |= 1ull << (sig - 1);
ctx.uc_sigmask = atomic_fetch_or_explicit(&pt->tib->tib_sigmask, blocksigs,
memory_order_acquire);
// call the user's signal handler
char ssbuf[128];
siginfo_t si = {.si_signo = sig, .si_code = sic};
STRACE("__sig_raise(%G, %t) mask %s", sig, __sig_handler(rva),
_DescribeSigset(ssbuf, 0, (sigset_t *)&pt->tib->tib_sigmask));
__sig_handler(rva)(sig, &si, &ctx);
// record this handler
if (flags & SA_RESTART) {
handler_was_called |= SIG_HANDLED_SA_RESTART;
} else {
handler_was_called |= SIG_HANDLED_NO_RESTART;
}
}
// restore sigmask
// loop back to top
// jump where handler says
sig = 0;
return setcontext(&ctx);
}
textwindows int __sig_relay(int sig, int sic, sigset_t waitmask) {
sigset_t m;
int handler_was_called;
m = atomic_exchange_explicit(&__get_tls()->tib_sigmask, waitmask,
memory_order_acquire);
handler_was_called = __sig_raise(sig, SI_KERNEL);
atomic_store_explicit(&__get_tls()->tib_sigmask, m, memory_order_release);
return handler_was_called;
}
// the user's signal handler callback is wrapped with this trampoline
static textwindows wontreturn void __sig_tramp(struct SignalFrame *sf) {
int sig = sf->si.si_signo;
struct CosmoTib *tib = __get_tls();
struct PosixThread *pt = (struct PosixThread *)tib->tib_pthread;
atomic_store_explicit(&pt->pt_intoff, 0, memory_order_release);
for (;;) {
// update the signal mask in preparation for signal handler
sigset_t blocksigs = __sighandmask[sig];
if (!(sf->flags & SA_NODEFER))
blocksigs |= 1ull << (sig - 1);
sf->ctx.uc_sigmask = atomic_fetch_or_explicit(&tib->tib_sigmask, blocksigs,
memory_order_acquire);
// call the user's signal handler
char ssbuf[2][128];
STRACE("__sig_tramp(%G, %t) mask %s → %s", sig, __sig_handler(sf->rva),
_DescribeSigset(ssbuf[0], 0, &sf->ctx.uc_sigmask),
_DescribeSigset(ssbuf[1], 0, (sigset_t *)&tib->tib_sigmask));
__sig_handler(sf->rva)(sig, &sf->si, &sf->ctx);
// restore the signal mask that was used by the interrupted code
// this may have been modified by the signal handler in the callback
atomic_store_explicit(&tib->tib_sigmask, sf->ctx.uc_sigmask,
memory_order_release);
// jump back into original code if there aren't any pending signals
do {
if (!(sig = __sig_get(sf->ctx.uc_sigmask)))
__sig_restore(&sf->ctx);
} while (!__sig_start(pt, sig, &sf->rva, &sf->flags));
// tail recurse into another signal handler
sf->si.si_signo = sig;
sf->si.si_code = SI_KERNEL;
if (sf->flags & SA_RESETHAND) {
STRACE("resetting %G handler", sig);
__sighandrvas[sig] = (int32_t)(intptr_t)SIG_DFL;
}
}
}
// sends signal to another specific thread which is ref'd
static textwindows int __sig_killer(struct PosixThread *pt, int sig, int sic) {
unsigned rva = __sighandrvas[sig];
unsigned flags = __sighandflags[sig];
// do nothing if signal is ignored
if (rva == (intptr_t)SIG_IGN ||
(rva == (intptr_t)SIG_DFL && __sig_ignored_by_default(sig))) {
STRACE("ignoring %G", sig);
return 0;
}
// we can't preempt threads that masked sigs or are blocked on i/o
while ((atomic_load_explicit(&pt->tib->tib_sigmask, memory_order_acquire) &
(1ull << (sig - 1)))) {
if (atomic_fetch_or_explicit(&pt->tib->tib_sigpending, 1ull << (sig - 1),
memory_order_acq_rel) &
(1ull << (sig - 1)))
// we believe signal was already enqueued
return 0;
if (__sig_wake(pt, sig))
// we believe i/o routine will handle signal
return 0;
if (atomic_load_explicit(&pt->tib->tib_sigmask, memory_order_acquire) &
(1ull << (sig - 1)))
// we believe ALLOW_SIGNALS will handle signal
return 0;
if (!(atomic_fetch_and_explicit(&pt->tib->tib_sigpending,
~(1ull << (sig - 1)),
memory_order_acq_rel) &
(1ull << (sig - 1))))
// we believe another thread sniped our signal
return 0;
break;
}
// avoid race conditions and deadlocks with thread suspend process
if (atomic_exchange_explicit(&pt->pt_intoff, 1, memory_order_acquire)) {
// we believe another thread is asynchronously waking the mark
if (atomic_fetch_or_explicit(&pt->tib->tib_sigpending, 1ull << (sig - 1),
memory_order_acq_rel) &
(1ull << (sig - 1)))
// we believe our signal is already being delivered
return 0;
if (atomic_load_explicit(&pt->pt_intoff, memory_order_acquire) ||
atomic_exchange_explicit(&pt->pt_intoff, 1, memory_order_acquire))
// we believe __sig_tramp will deliver our signal
return 0;
if (!(atomic_fetch_and_explicit(&pt->tib->tib_sigpending,
~(1ull << (sig - 1)),
memory_order_acq_rel) &
(1ull << (sig - 1))))
// we believe another thread sniped our signal
return 0;
}
// if there's no handler then killing a thread kills the process
if (rva == (intptr_t)SIG_DFL) {
STRACE("terminating on %G due to no handler", sig);
__sig_terminate(sig);
}
// take control of thread
// suspending the thread happens asynchronously
// however getting the context blocks until it's frozen
uintptr_t th = _pthread_syshand(pt);
if (SuspendThread(th) == -1u) {
STRACE("SuspendThread failed w/ %d", GetLastError());
atomic_store_explicit(&pt->pt_intoff, 0, memory_order_release);
return ESRCH;
}
struct NtContext nc;
nc.ContextFlags = kNtContextFull;
if (!GetThreadContext(th, &nc)) {
STRACE("GetThreadContext failed w/ %d", GetLastError());
ResumeThread(th);
atomic_store_explicit(&pt->pt_intoff, 0, memory_order_release);
return ESRCH;
}
// we can't preempt threads that masked sig or are blocked
// we can't preempt threads that are running in win32 code
// so we shall unblock the thread and let it signal itself
if (!((uintptr_t)__executable_start <= nc.Rip &&
nc.Rip < (uintptr_t)__privileged_start)) {
atomic_fetch_or_explicit(&pt->tib->tib_sigpending, 1ull << (sig - 1),
memory_order_relaxed);
ResumeThread(th);
atomic_store_explicit(&pt->pt_intoff, 0, memory_order_release);
__sig_wake(pt, sig);
return 0;
}
// preferring to live dangerously
// the thread will be signaled asynchronously
if (flags & SA_RESETHAND) {
STRACE("resetting %G handler", sig);
__sighandrvas[sig] = (int32_t)(intptr_t)SIG_DFL;
}
// inject call to trampoline function into thread
uintptr_t sp;
if (__sig_should_use_altstack(flags, pt->tib)) {
sp = (uintptr_t)pt->tib->tib_sigstack_addr + pt->tib->tib_sigstack_size;
} else {
sp = nc.Rsp;
}
sp -= sizeof(struct SignalFrame);
sp &= -16;
struct SignalFrame *sf = (struct SignalFrame *)sp;
_ntcontext2linux(&sf->ctx, &nc);
bzero(&sf->si, sizeof(sf->si));
sf->rva = rva;
sf->flags = flags;
sf->si.si_code = sic;
sf->si.si_signo = sig;
*(uintptr_t *)(sp -= sizeof(uintptr_t)) = nc.Rip;
nc.Rip = (intptr_t)__sig_tramp;
nc.Rdi = (intptr_t)sf;
nc.Rsp = sp;
if (!SetThreadContext(th, &nc)) {
STRACE("SetThreadContext failed w/ %d", GetLastError());
atomic_store_explicit(&pt->pt_intoff, 0, memory_order_release);
return ESRCH;
}
ResumeThread(th);
__sig_wake(pt, sig);
return 0;
}
// sends signal to another specific thread
textwindows int __sig_kill(struct PosixThread *pt, int sig, int sic) {
int rc;
BLOCK_SIGNALS;
rc = __sig_killer(pt, sig, sic);
ALLOW_SIGNALS;
return rc;
}
// sends signal to any other thread
// this should only be called by non-posix threads
textwindows void __sig_generate(int sig, int sic) {
struct Dll *e;
struct PosixThread *pt, *mark = 0;
if (__sig_ignored(sig)) {
STRACE("ignoring %G", sig);
return;
}
if (__sighandrvas[sig] == (intptr_t)SIG_DFL) {
STRACE("terminating on %G due to no handler", sig);
__sig_terminate(sig);
}
if (atomic_load_explicit(__sig.process, memory_order_acquire) &
(1ull << (sig - 1))) {
return;
}
_pthread_lock();
for (e = dll_first(_pthread_list); e; e = dll_next(_pthread_list, e)) {
pt = POSIXTHREAD_CONTAINER(e);
// we don't want to signal ourself
if (pt == _pthread_self())
continue;
// we don't want to signal a thread that isn't running
if (atomic_load_explicit(&pt->pt_status, memory_order_acquire) >=
kPosixThreadTerminated) {
continue;
}
// choose this thread if it isn't masking sig
if (!(atomic_load_explicit(&pt->tib->tib_sigmask, memory_order_acquire) &
(1ull << (sig - 1)))) {
_pthread_ref(pt);
mark = pt;
break;
}
// if a thread is blocking then we check to see if it's planning
// to unblock our sig once the wait operation is completed; when
// that's the case we can cancel the thread's i/o to deliver sig
if (atomic_load_explicit(&pt->pt_blocker, memory_order_acquire) &&
!(pt->pt_blkmask & (1ull << (sig - 1)))) {
_pthread_ref(pt);
mark = pt;
break;
}
}
_pthread_unlock();
if (mark) {
// no lock needed since current thread is nameless and formless
__sig_killer(mark, sig, sic);
_pthread_unref(mark);
} else {
atomic_fetch_or_explicit(__sig.process, 1ull << (sig - 1),
memory_order_relaxed);
}
}
static textwindows char *__sig_stpcpy(char *d, const char *s) {
size_t i;
for (i = 0;; ++i)
if (!(d[i] = s[i]))
return d + i;
}
static textwindows wontreturn void __sig_death(int sig, const char *thing) {
#ifndef TINY
intptr_t hStderr;
char sigbuf[21], s[128], *p;
hStderr = GetStdHandle(kNtStdErrorHandle);
p = __sig_stpcpy(s, "Terminating on ");
p = __sig_stpcpy(p, thing);
p = __sig_stpcpy(p, strsignal_r(sig, sigbuf));
p = __sig_stpcpy(p,
". Pass --strace and/or ShowCrashReports() for details.\n");
WriteFile(hStderr, s, p - s, 0, 0);
#endif
__sig_terminate(sig);
}
static textwindows void __sig_unmaskable(struct NtExceptionPointers *ep,
int code, int sig,
struct CosmoTib *tib) {
// log vital crash information reliably for --strace before doing much
// we don't print this without the flag since raw numbers scare people
// this needs at least one page of stack memory in order to get logged
// otherwise it'll print a warning message about the lack of stack mem
STRACE("win32 vectored exception 0x%08Xu raising %G "
"cosmoaddr2line %s %lx %s",
ep->ExceptionRecord->ExceptionCode, sig,
_weaken(FindDebugBinary) ? _weaken(FindDebugBinary)()
: program_invocation_name,
ep->ContextRecord->Rip,
DescribeBacktrace((struct StackFrame *)ep->ContextRecord->Rbp));
// if the user didn't install a signal handler for this unmaskable
// exception, then print a friendly helpful hint message to stderr
unsigned rva = __sighandrvas[sig];
if (rva == (intptr_t)SIG_DFL || rva == (intptr_t)SIG_IGN)
__sig_death(sig, "uncaught ");
// if this signal handler is configured to auto-reset to the default
// then that reset needs to happen before the user handler is called
unsigned flags = __sighandflags[sig];
if (flags & SA_RESETHAND) {
STRACE("resetting %G handler", sig);
__sighandrvas[sig] = (int32_t)(intptr_t)SIG_DFL;
}
// determine the true memory address at which fault occurred
// if this is a stack overflow then reapply guard protection
void *si_addr;
if (ep->ExceptionRecord->ExceptionCode == kNtSignalGuardPage) {
si_addr = (void *)ep->ExceptionRecord->ExceptionInformation[1];
} else {
si_addr = ep->ExceptionRecord->ExceptionAddress;
}
// call the user signal handler
// and a modifiable view of the faulting code's cpu state
// temporarily replace signal mask while calling crash handler
// abort process if sig is already blocked to avoid crash loop
// note ucontext_t is a hefty data structures on top of NtContext
ucontext_t ctx = {0};
siginfo_t si = {.si_signo = sig, .si_code = code, .si_addr = si_addr};
_ntcontext2linux(&ctx, ep->ContextRecord);
sigset_t blocksigs = __sighandmask[sig];
if (!(flags & SA_NODEFER))
blocksigs |= 1ull << (sig - 1);
ctx.uc_sigmask = atomic_fetch_or_explicit(&tib->tib_sigmask, blocksigs,
memory_order_acquire);
if (ctx.uc_sigmask & (1ull << (sig - 1))) {
__sig_death(sig, "masked ");
__sig_terminate(sig);
}
__sig_handler(rva)(sig, &si, &ctx);
atomic_store_explicit(&tib->tib_sigmask, ctx.uc_sigmask,
memory_order_release);
_ntlinux2context(ep->ContextRecord, &ctx);
}
void __stack_call(struct NtExceptionPointers *, int, int, struct CosmoTib *,
void (*)(struct NtExceptionPointers *, int, int,
struct CosmoTib *),
void *);
// abashed the devil stood
// and felt how awful goodness is
__msabi dontinstrument unsigned __sig_crash(struct NtExceptionPointers *ep) {
// translate win32 to unix si_signo and si_code
int code, sig = __sig_crash_sig(ep->ExceptionRecord->ExceptionCode, &code);
// advance the instruction pointer to skip over debugger breakpoints
// this behavior is consistent with how unix kernels are implemented
if (sig == SIGTRAP) {
ep->ContextRecord->Rip++;
if (__sig_ignored(sig))
return kNtExceptionContinueExecution;
}
// win32 stack overflow detection executes INSIDE the guard page
// thus switch to the alternate signal stack as soon as possible
struct CosmoTib *tib = __get_tls();
unsigned flags = __sighandflags[sig];
if (__sig_should_use_altstack(flags, tib)) {
__stack_call(ep, code, sig, tib, __sig_unmaskable,
tib->tib_sigstack_addr + tib->tib_sigstack_size);
} else {
__sig_unmaskable(ep, code, sig, tib);
}
// resume running user program
// hopefully the user fixed the cpu state
// otherwise the crash will keep happening
return kNtExceptionContinueExecution;
}
static textwindows int __sig_console_sig(uint32_t dwCtrlType) {
switch (dwCtrlType) {
case kNtCtrlCEvent:
return SIGINT;
case kNtCtrlBreakEvent:
return SIGQUIT;
case kNtCtrlCloseEvent:
case kNtCtrlLogoffEvent: // only received by services
case kNtCtrlShutdownEvent: // only received by services
return SIGHUP;
default:
return SIGSTKFLT;
}
}
__msabi textwindows dontinstrument bool32 __sig_console(uint32_t dwCtrlType) {
// win32 launches a thread to deliver ctrl-c and ctrl-break when typed
// it only happens when kNtEnableProcessedInput is in play on console.
// otherwise we need to wait until read-nt.c discovers that keystroke.
struct CosmoTib tls;
__bootstrap_tls(&tls, __builtin_frame_address(0));
__sig_generate(__sig_console_sig(dwCtrlType), SI_KERNEL);
return true;
}
// returns 0 if no signal handlers were called, otherwise a bitmask
// consisting of `1` which means a signal handler was invoked which
// didn't have the SA_RESTART flag, and `2`, which means SA_RESTART
// handlers were called (or `3` if both were the case).
textwindows int __sig_check(void) {
int sig, res = 0;
while ((sig = __sig_get(atomic_load_explicit(&__get_tls()->tib_sigmask,
memory_order_acquire))))
res |= __sig_raise(sig, SI_KERNEL);
return res;
}
// background thread for delivering inter-process signals asynchronously
// this checks for undelivered process-wide signals, once per scheduling
// quantum, which on windows should be every ~15ms or so, unless somehow
// the process was tuned to have more fine-grained event timing. we want
// signals to happen faster when possible; that happens when cancelation
// points, e.g. read need to wait on i/o; they too check for new signals
textwindows dontinstrument static uint32_t __sig_worker(void *arg) {
struct CosmoTib tls;
__bootstrap_tls(&tls, __builtin_frame_address(0));
char *sp = __builtin_frame_address(0);
__maps_track((char *)(((uintptr_t)sp + __pagesize - 1) & -__pagesize) - STKSZ,
STKSZ);
for (;;) {
// dequeue all pending signals and fire them off. if there's no
// thread that can handle them then __sig_generate will requeue
// those signals back to __sig.process; hence the need for xchg
unsigned long sigs =
atomic_exchange_explicit(__sig.process, 0, memory_order_acq_rel);
while (sigs) {
int sig = bsfl(sigs) + 1;
sigs &= ~(1ull << (sig - 1));
__sig_generate(sig, SI_KERNEL);
}
// unblock stalled asynchronous signals in threads
_pthread_lock();
for (struct Dll *e = dll_first(_pthread_list); e;
e = dll_next(_pthread_list, e)) {
struct PosixThread *pt = POSIXTHREAD_CONTAINER(e);
if (atomic_load_explicit(&pt->pt_status, memory_order_acquire) >=
kPosixThreadTerminated) {
break;
}
sigset_t pending =
atomic_load_explicit(&pt->tib->tib_sigpending, memory_order_acquire);
sigset_t mask =
atomic_load_explicit(&pt->tib->tib_sigmask, memory_order_acquire);
if (pending & ~mask) {
_pthread_ref(pt);
_pthread_unlock();
while (!atomic_compare_exchange_weak_explicit(
&pt->tib->tib_sigpending, &pending, pending & ~mask,
memory_order_acq_rel, memory_order_relaxed)) {
}
while ((pending = pending & ~mask)) {
int sig = bsfl(pending) + 1;
pending &= ~(1ull << (sig - 1));
__sig_killer(pt, sig, SI_KERNEL);
}
_pthread_lock();
_pthread_unref(pt);
}
}
_pthread_unlock();
// wait until next scheduler quantum
Sleep(POLL_INTERVAL_MS);
}
return 0;
}
__attribute__((__constructor__(10))) textstartup void __sig_init(void) {
if (!IsWindows())
return;
AddVectoredExceptionHandler(true, (void *)__sig_crash);
SetConsoleCtrlHandler((void *)__sig_console, true);
CreateThread(0, STKSZ, __sig_worker, 0, kNtStackSizeParamIsAReservation, 0);
}
#endif /* __x86_64__ */

View file

@ -1,5 +1,6 @@
#ifndef COSMOPOLITAN_LIBC_COSMO_H_ #ifndef COSMOPOLITAN_LIBC_COSMO_H_
#define COSMOPOLITAN_LIBC_COSMO_H_ #define COSMOPOLITAN_LIBC_COSMO_H_
#include "libc/calls/struct/timespec.h"
COSMOPOLITAN_C_START_ COSMOPOLITAN_C_START_
#ifndef __cplusplus #ifndef __cplusplus
@ -17,6 +18,9 @@ int __is_mangled(const char *) libcesque;
bool32 IsLinuxModern(void) libcesque; bool32 IsLinuxModern(void) libcesque;
int LoadZipArgs(int *, char ***) libcesque; int LoadZipArgs(int *, char ***) libcesque;
int cosmo_args(const char *, char ***) libcesque; int cosmo_args(const char *, char ***) libcesque;
int cosmo_futex_wake(_COSMO_ATOMIC(int) *, int, char);
int cosmo_futex_wait(_COSMO_ATOMIC(int) *, int, char, int,
const struct timespec *);
COSMOPOLITAN_C_END_ COSMOPOLITAN_C_END_
#endif /* COSMOPOLITAN_LIBC_COSMO_H_ */ #endif /* COSMOPOLITAN_LIBC_COSMO_H_ */

View file

@ -30,9 +30,11 @@ LIBC_INTRIN_A_CHECKS = \
LIBC_INTRIN_A_DIRECTDEPS = \ LIBC_INTRIN_A_DIRECTDEPS = \
LIBC_NEXGEN32E \ LIBC_NEXGEN32E \
LIBC_NT_KERNEL32 \ LIBC_NT_KERNEL32 \
LIBC_NT_REALTIME \
LIBC_NT_SYNCHRONIZATION \
LIBC_NT_WS2_32 \ LIBC_NT_WS2_32 \
LIBC_SYSV \ LIBC_SYSV \
LIBC_SYSV_CALLS LIBC_SYSV_CALLS \
LIBC_INTRIN_A_DEPS := \ LIBC_INTRIN_A_DEPS := \
$(call uniq,$(foreach x,$(LIBC_INTRIN_A_DIRECTDEPS),$($(x)))) $(call uniq,$(foreach x,$(LIBC_INTRIN_A_DIRECTDEPS),$($(x))))
@ -106,6 +108,16 @@ o//libc/intrin/demangle.o: private \
CFLAGS += \ CFLAGS += \
-mgeneral-regs-only -mgeneral-regs-only
# ensure that division is optimized
o/$(MODE)/libc/intrin/windowsdurationtotimeval.o \
o/$(MODE)/libc/intrin/windowsdurationtotimespec.o \
o/$(MODE)/libc/intrin/timevaltowindowstime.o \
o/$(MODE)/libc/intrin/timespectowindowstime.o \
o/$(MODE)/libc/intrin/windowstimetotimeval.o \
o/$(MODE)/libc/intrin/windowstimetotimespec.o: private \
CFLAGS += \
-O2
# these assembly files are safe to build on aarch64 # these assembly files are safe to build on aarch64
o/$(MODE)/libc/intrin/aarch64/%.o: libc/intrin/aarch64/%.S o/$(MODE)/libc/intrin/aarch64/%.o: libc/intrin/aarch64/%.S
@$(COMPILE) -AOBJECTIFY.S $(OBJECTIFY.S) $(OUTPUT_OPTION) -c $< @$(COMPILE) -AOBJECTIFY.S $(OBJECTIFY.S) $(OUTPUT_OPTION) -c $<

View file

@ -16,18 +16,14 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE. PERFORMANCE OF THIS SOFTWARE.
*/ */
#include "libc/sysv/consts/futex.h"
#include "libc/assert.h" #include "libc/assert.h"
#include "libc/atomic.h" #include "libc/atomic.h"
#include "libc/calls/calls.h"
#include "libc/calls/internal.h" #include "libc/calls/internal.h"
#include "libc/calls/sig.internal.h" #include "libc/calls/sig.internal.h"
#include "libc/calls/state.internal.h"
#include "libc/calls/struct/sigset.h" #include "libc/calls/struct/sigset.h"
#include "libc/calls/struct/sigset.internal.h" #include "libc/calls/struct/sigset.internal.h"
#include "libc/calls/struct/timespec.h" #include "libc/calls/struct/timespec.h"
#include "libc/calls/struct/timespec.internal.h" #include "libc/calls/struct/timespec.internal.h"
#include "libc/calls/syscall_support-nt.internal.h"
#include "libc/cosmo.h" #include "libc/cosmo.h"
#include "libc/dce.h" #include "libc/dce.h"
#include "libc/errno.h" #include "libc/errno.h"
@ -37,62 +33,56 @@
#include "libc/intrin/ulock.h" #include "libc/intrin/ulock.h"
#include "libc/intrin/weaken.h" #include "libc/intrin/weaken.h"
#include "libc/limits.h" #include "libc/limits.h"
#include "libc/nexgen32e/vendor.internal.h"
#include "libc/nt/runtime.h" #include "libc/nt/runtime.h"
#include "libc/nt/synchronization.h" #include "libc/nt/synchronization.h"
#include "libc/runtime/clktck.h" #include "libc/sysv/consts/clock.h"
#include "libc/sysv/consts/futex.h"
#include "libc/sysv/consts/sicode.h" #include "libc/sysv/consts/sicode.h"
#include "libc/sysv/consts/timer.h"
#include "libc/sysv/errfuns.h" #include "libc/sysv/errfuns.h"
#include "libc/thread/freebsd.internal.h" #include "libc/thread/freebsd.internal.h"
#include "libc/thread/posixthread.internal.h" #include "libc/thread/posixthread.internal.h"
#include "libc/thread/thread.h" #include "libc/thread/thread.h"
#include "libc/thread/tls.h" // clang-format off
#include "third_party/nsync/atomic.h"
#include "third_party/nsync/time.h"
#include "third_party/nsync/common.internal.h"
#include "third_party/nsync/futex.internal.h"
#include "third_party/nsync/time.h"
#define FUTEX_WAIT_BITS_ FUTEX_BITSET_MATCH_ANY #define FUTEX_WAIT_BITS_ FUTEX_BITSET_MATCH_ANY
errno_t _futex (atomic_int *, int, int, const struct timespec *, int *, int); errno_t cosmo_futex_thunk (atomic_int *, int, int, const struct timespec *, int *, int);
errno_t _futex_wake (atomic_int *, int, int) asm ("_futex"); errno_t _futex_wake (atomic_int *, int, int) asm ("cosmo_futex_thunk");
int sys_futex_cp (atomic_int *, int, int, const struct timespec *, int *, int); int sys_futex_cp (atomic_int *, int, int, const struct timespec *, int *, int);
static struct NsyncFutex { static struct CosmoFutex {
atomic_uint once; atomic_uint once;
int FUTEX_WAIT_; int FUTEX_WAIT_;
int FUTEX_PRIVATE_FLAG_; int FUTEX_PRIVATE_FLAG_;
int FUTEX_CLOCK_REALTIME_; int FUTEX_CLOCK_REALTIME_;
bool is_supported; bool is_supported;
bool timeout_is_relative; bool timeout_is_relative;
} nsync_futex_; } g_cosmo_futex;
static void nsync_futex_init_ (void) { static void cosmo_futex_init (void) {
int e; int e;
atomic_int x; atomic_int x;
nsync_futex_.FUTEX_WAIT_ = FUTEX_WAIT; g_cosmo_futex.FUTEX_WAIT_ = FUTEX_WAIT;
if (IsWindows ()) { if (IsWindows ()) {
nsync_futex_.is_supported = true; g_cosmo_futex.is_supported = true;
return; return;
} }
if (IsXnu ()) { if (IsXnu ()) {
nsync_futex_.is_supported = true; g_cosmo_futex.is_supported = true;
nsync_futex_.timeout_is_relative = true; g_cosmo_futex.timeout_is_relative = true;
return; return;
} }
if (IsFreebsd ()) { if (IsFreebsd ()) {
nsync_futex_.is_supported = true; g_cosmo_futex.is_supported = true;
nsync_futex_.FUTEX_PRIVATE_FLAG_ = FUTEX_PRIVATE_FLAG; g_cosmo_futex.FUTEX_PRIVATE_FLAG_ = FUTEX_PRIVATE_FLAG;
return; return;
} }
if (!(nsync_futex_.is_supported = IsLinux () || IsOpenbsd ())) if (!(g_cosmo_futex.is_supported = IsLinux () || IsOpenbsd ()))
return; return;
// In our testing, we found that the monotonic clock on various // In our testing, we found that the monotonic clock on various
@ -100,7 +90,7 @@ static void nsync_futex_init_ (void) {
// better behaved than the realtime clock, and routinely took // better behaved than the realtime clock, and routinely took
// large steps backwards, especially on multiprocessors. Given // large steps backwards, especially on multiprocessors. Given
// that "monotonic" doesn't seem to mean what it says, // that "monotonic" doesn't seem to mean what it says,
// implementers of nsync_time might consider retaining the // implementers of cosmo_time might consider retaining the
// simplicity of a single epoch within an address space, by // simplicity of a single epoch within an address space, by
// configuring any time synchronization mechanism (like ntp) to // configuring any time synchronization mechanism (like ntp) to
// adjust for leap seconds by adjusting the rate, rather than // adjust for leap seconds by adjusting the rate, rather than
@ -108,31 +98,32 @@ static void nsync_futex_init_ (void) {
e = errno; e = errno;
atomic_store_explicit (&x, 0, memory_order_relaxed); atomic_store_explicit (&x, 0, memory_order_relaxed);
if (IsLinux () && if (IsLinux () &&
_futex (&x, FUTEX_WAIT_BITSET | FUTEX_CLOCK_REALTIME, cosmo_futex_thunk (&x, FUTEX_WAIT_BITSET | FUTEX_CLOCK_REALTIME,
1, 0, 0, FUTEX_BITSET_MATCH_ANY) == -EAGAIN) { 1, 0, 0, FUTEX_BITSET_MATCH_ANY) == -EAGAIN) {
nsync_futex_.FUTEX_WAIT_ = FUTEX_WAIT_BITSET; g_cosmo_futex.FUTEX_WAIT_ = FUTEX_WAIT_BITSET;
nsync_futex_.FUTEX_PRIVATE_FLAG_ = FUTEX_PRIVATE_FLAG; g_cosmo_futex.FUTEX_PRIVATE_FLAG_ = FUTEX_PRIVATE_FLAG;
nsync_futex_.FUTEX_CLOCK_REALTIME_ = FUTEX_CLOCK_REALTIME; g_cosmo_futex.FUTEX_CLOCK_REALTIME_ = FUTEX_CLOCK_REALTIME;
} else if (IsOpenbsd () || } else if (IsOpenbsd () ||
(IsLinux () && (IsLinux () &&
!_futex_wake (&x, FUTEX_WAKE_PRIVATE, 1))) { !_futex_wake (&x, FUTEX_WAKE_PRIVATE, 1))) {
nsync_futex_.FUTEX_WAIT_ = FUTEX_WAIT; g_cosmo_futex.FUTEX_WAIT_ = FUTEX_WAIT;
nsync_futex_.FUTEX_PRIVATE_FLAG_ = FUTEX_PRIVATE_FLAG; g_cosmo_futex.FUTEX_PRIVATE_FLAG_ = FUTEX_PRIVATE_FLAG;
nsync_futex_.timeout_is_relative = true; g_cosmo_futex.timeout_is_relative = true;
} else { } else {
nsync_futex_.FUTEX_WAIT_ = FUTEX_WAIT; g_cosmo_futex.FUTEX_WAIT_ = FUTEX_WAIT;
nsync_futex_.timeout_is_relative = true; g_cosmo_futex.timeout_is_relative = true;
} }
errno = e; errno = e;
} }
static uint32_t nsync_time_64to32u (uint64_t duration) { static uint32_t cosmo_time_64to32u (uint64_t duration) {
if (duration <= -1u) if (duration <= -1u)
return duration; return duration;
return -1u; return -1u;
} }
static int nsync_futex_polyfill_ (atomic_int *w, int expect, int clock, struct timespec *abstime) { static int cosmo_futex_polyfill (atomic_int *w, int expect, int clock,
struct timespec *abstime) {
for (;;) { for (;;) {
if (atomic_load_explicit (w, memory_order_acquire) != expect) if (atomic_load_explicit (w, memory_order_acquire) != expect)
return 0; return 0;
@ -148,7 +139,7 @@ static int nsync_futex_polyfill_ (atomic_int *w, int expect, int clock, struct t
} }
} }
static int nsync_futex_wait_win32_ (atomic_int *w, int expect, char pshare, static int cosmo_futex_wait_win32 (atomic_int *w, int expect, char pshare,
int clock, const struct timespec *timeout, int clock, const struct timespec *timeout,
struct PosixThread *pt, struct PosixThread *pt,
sigset_t waitmask) { sigset_t waitmask) {
@ -183,7 +174,7 @@ static int nsync_futex_wait_win32_ (atomic_int *w, int expect, char pshare,
pt->pt_blkmask = waitmask; pt->pt_blkmask = waitmask;
atomic_store_explicit (&pt->pt_blocker, w, memory_order_release); atomic_store_explicit (&pt->pt_blocker, w, memory_order_release);
} }
ok = WaitOnAddress (w, &expect, sizeof(int), nsync_time_64to32u (timespec_tomillis (wait))); ok = WaitOnAddress (w, &expect, sizeof(int), cosmo_time_64to32u (timespec_tomillis (wait)));
if (pt) { if (pt) {
/* __sig_wake wakes our futex without changing `w` after enqueing signals */ /* __sig_wake wakes our futex without changing `w` after enqueing signals */
atomic_store_explicit (&pt->pt_blocker, 0, memory_order_release); atomic_store_explicit (&pt->pt_blocker, 0, memory_order_release);
@ -197,7 +188,7 @@ static int nsync_futex_wait_win32_ (atomic_int *w, int expect, char pshare,
if (ok) { if (ok) {
return 0; return 0;
} else { } else {
ASSERT (GetLastError () == ETIMEDOUT); unassert (GetLastError () == ETIMEDOUT);
} }
} }
#else #else
@ -205,14 +196,14 @@ static int nsync_futex_wait_win32_ (atomic_int *w, int expect, char pshare,
#endif /* __x86_64__ */ #endif /* __x86_64__ */
} }
static int nsync_futex_fix_timeout_ (struct timespec *memory, int clock, static int cosmo_futex_fix_timeout (struct timespec *memory, int clock,
const struct timespec *abstime, const struct timespec *abstime,
struct timespec **result) { struct timespec **result) {
struct timespec now; struct timespec now;
if (!abstime) { if (!abstime) {
*result = 0; *result = 0;
return 0; return 0;
} else if (!nsync_futex_.timeout_is_relative) { } else if (!g_cosmo_futex.timeout_is_relative) {
*memory = *abstime; *memory = *abstime;
*result = memory; *result = memory;
return 0; return 0;
@ -225,7 +216,24 @@ static int nsync_futex_fix_timeout_ (struct timespec *memory, int clock,
} }
} }
int nsync_futex_wait_ (atomic_int *w, int expect, char pshare, /**
* Waits on futex.
*
* This function may be used to ask the OS to park the calling thread
* until cosmo_futex_wake() is called on the memory address `w`.
*
* @param w is your futex
* @param expect is the value `*w` is expected to have on entry
* @param pshare is `PTHREAD_PROCESS_PRIVATE` / `PTHREAD_PROCESS_SHARED`
* @param clock is `CLOCK_MONOTONIC`, `CLOCK_REALTIME`, etc.
* @param abstime is null to wait forever or absolute timestamp to stop
* @return 0 on success, or -errno on error
* @raise EINVAL on bad parameter
* @raise EAGAIN if `*w` wasn't `expect`
* @raise EINTR if a signal handler was called while waiting
* @raise ECANCELED if calling thread was canceled while waiting
*/
int cosmo_futex_wait (atomic_int *w, int expect, char pshare,
int clock, const struct timespec *abstime) { int clock, const struct timespec *abstime) {
int e, rc, op; int e, rc, op;
struct CosmoTib *tib; struct CosmoTib *tib;
@ -233,14 +241,14 @@ int nsync_futex_wait_ (atomic_int *w, int expect, char pshare,
struct timespec tsmem; struct timespec tsmem;
struct timespec *timeout = 0; struct timespec *timeout = 0;
cosmo_once (&nsync_futex_.once, nsync_futex_init_); cosmo_once (&g_cosmo_futex.once, cosmo_futex_init);
op = nsync_futex_.FUTEX_WAIT_; op = g_cosmo_futex.FUTEX_WAIT_;
if (pshare == PTHREAD_PROCESS_PRIVATE) if (pshare == PTHREAD_PROCESS_PRIVATE)
op |= nsync_futex_.FUTEX_PRIVATE_FLAG_; op |= g_cosmo_futex.FUTEX_PRIVATE_FLAG_;
if (clock == CLOCK_REALTIME || if (clock == CLOCK_REALTIME ||
clock == CLOCK_REALTIME_COARSE) clock == CLOCK_REALTIME_COARSE)
op |= nsync_futex_.FUTEX_CLOCK_REALTIME_; op |= g_cosmo_futex.FUTEX_CLOCK_REALTIME_;
if (abstime && timespec_cmp (*abstime, timespec_zero) <= 0) { if (abstime && timespec_cmp (*abstime, timespec_zero) <= 0) {
rc = -ETIMEDOUT; rc = -ETIMEDOUT;
@ -252,7 +260,7 @@ int nsync_futex_wait_ (atomic_int *w, int expect, char pshare,
goto Finished; goto Finished;
} }
if ((rc = nsync_futex_fix_timeout_ (&tsmem, clock, abstime, &timeout))) if ((rc = cosmo_futex_fix_timeout (&tsmem, clock, abstime, &timeout)))
goto Finished; goto Finished;
LOCKTRACE ("futex(%t [%d], %s, %#x, %s) → ...", LOCKTRACE ("futex(%t [%d], %s, %#x, %s) → ...",
@ -263,13 +271,13 @@ int nsync_futex_wait_ (atomic_int *w, int expect, char pshare,
tib = __get_tls(); tib = __get_tls();
pt = (struct PosixThread *)tib->tib_pthread; pt = (struct PosixThread *)tib->tib_pthread;
if (nsync_futex_.is_supported) { if (g_cosmo_futex.is_supported) {
e = errno; e = errno;
if (IsWindows ()) { if (IsWindows ()) {
// Windows 8 futexes don't support multiple processes :( // Windows 8 futexes don't support multiple processes :(
if (pshare) goto Polyfill; if (pshare) goto Polyfill;
sigset_t m = __sig_block (); sigset_t m = __sig_block ();
rc = nsync_futex_wait_win32_ (w, expect, pshare, clock, timeout, pt, m); rc = cosmo_futex_wait_win32 (w, expect, pshare, clock, timeout, pt, m);
__sig_unblock (m); __sig_unblock (m);
} else if (IsXnu ()) { } else if (IsXnu ()) {
@ -293,7 +301,7 @@ int nsync_futex_wait_ (atomic_int *w, int expect, char pshare,
op = UL_COMPARE_AND_WAIT; op = UL_COMPARE_AND_WAIT;
} }
if (timeout) { if (timeout) {
us = nsync_time_64to32u (timespec_tomicros (*timeout)); us = cosmo_time_64to32u (timespec_tomicros (*timeout));
} else { } else {
us = -1u; us = -1u;
} }
@ -333,7 +341,7 @@ int nsync_futex_wait_ (atomic_int *w, int expect, char pshare,
} }
} else { } else {
Polyfill: Polyfill:
rc = nsync_futex_polyfill_ (w, expect, clock, timeout); rc = cosmo_futex_polyfill (w, expect, clock, timeout);
} }
Finished: Finished:
@ -346,18 +354,24 @@ Finished:
return rc; return rc;
} }
int nsync_futex_wake_ (atomic_int *w, int count, char pshare) { /**
* Wakes futex.
*
* @param w is your futex
* @param count is number of threads to wake (usually 1 or `INT_MAX`)
* @param pshare is `PTHREAD_PROCESS_PRIVATE` / `PTHREAD_PROCESS_SHARED`
* @return number of threads woken on success, or -errno on error
*/
int cosmo_futex_wake (atomic_int *w, int count, char pshare) {
int rc, op, fop; int rc, op, fop;
ASSERT (count == 1 || count == INT_MAX); cosmo_once (&g_cosmo_futex.once, cosmo_futex_init);
cosmo_once (&nsync_futex_.once, nsync_futex_init_);
op = FUTEX_WAKE; op = FUTEX_WAKE;
if (pshare == PTHREAD_PROCESS_PRIVATE) if (pshare == PTHREAD_PROCESS_PRIVATE)
op |= nsync_futex_.FUTEX_PRIVATE_FLAG_; op |= g_cosmo_futex.FUTEX_PRIVATE_FLAG_;
if (nsync_futex_.is_supported) { if (g_cosmo_futex.is_supported) {
if (IsWindows ()) { if (IsWindows ()) {
if (pshare) { if (pshare) {
goto Polyfill; goto Polyfill;
@ -379,7 +393,7 @@ int nsync_futex_wake_ (atomic_int *w, int count, char pshare) {
op |= ULF_WAKE_ALL; op |= ULF_WAKE_ALL;
} }
rc = ulock_wake (op, w, 0); rc = ulock_wake (op, w, 0);
ASSERT (!rc || rc == -ENOENT); unassert (!rc || rc == -ENOENT);
if (!rc) { if (!rc) {
rc = 1; rc = 1;
} else if (rc == -ENOENT) { } else if (rc == -ENOENT) {

View file

@ -20,7 +20,7 @@
#include "libc/macros.h" #include "libc/macros.h"
.privileged .privileged
_futex: cosmo_futex_thunk:
#ifdef __x86_64__ #ifdef __x86_64__
push %rbp push %rbp
mov %rsp,%rbp mov %rsp,%rbp
@ -47,4 +47,4 @@ _futex:
#error "unsupported architecture" #error "unsupported architecture"
#endif /* __x86_64__ */ #endif /* __x86_64__ */
1: ret 1: ret
.endfn _futex,globl,hidden .endfn cosmo_futex_thunk,globl,hidden

View file

@ -27,6 +27,6 @@
.ftrace1 .ftrace1
getcontext: getcontext:
.ftrace2 .ftrace2
#include "libc/calls/getcontext.inc" #include "libc/intrin/getcontext.inc"
jmp __getcontextsig jmp __getcontextsig
.endfn getcontext,globl .endfn getcontext,globl

View file

@ -19,6 +19,7 @@
#include "libc/calls/blockcancel.internal.h" #include "libc/calls/blockcancel.internal.h"
#include "libc/calls/calls.h" #include "libc/calls/calls.h"
#include "libc/calls/state.internal.h" #include "libc/calls/state.internal.h"
#include "libc/cosmo.h"
#include "libc/dce.h" #include "libc/dce.h"
#include "libc/errno.h" #include "libc/errno.h"
#include "libc/intrin/atomic.h" #include "libc/intrin/atomic.h"
@ -28,25 +29,8 @@
#include "libc/runtime/internal.h" #include "libc/runtime/internal.h"
#include "libc/thread/lock.h" #include "libc/thread/lock.h"
#include "libc/thread/thread.h" #include "libc/thread/thread.h"
#include "third_party/nsync/futex.internal.h"
#include "third_party/nsync/mu.h" #include "third_party/nsync/mu.h"
static void pthread_mutex_lock_spin(atomic_int *word) {
int backoff = 0;
if (atomic_exchange_explicit(word, 1, memory_order_acquire)) {
LOCKTRACE("acquiring pthread_mutex_lock_spin(%t)...", word);
for (;;) {
for (;;) {
if (!atomic_load_explicit(word, memory_order_relaxed))
break;
backoff = pthread_delay_np(word, backoff);
}
if (!atomic_exchange_explicit(word, 1, memory_order_acquire))
break;
}
}
}
// see "take 3" algorithm in "futexes are tricky" by ulrich drepper // see "take 3" algorithm in "futexes are tricky" by ulrich drepper
// slightly improved to attempt acquiring multiple times b4 syscall // slightly improved to attempt acquiring multiple times b4 syscall
static void pthread_mutex_lock_drepper(atomic_int *futex, char pshare) { static void pthread_mutex_lock_drepper(atomic_int *futex, char pshare) {
@ -59,7 +43,7 @@ static void pthread_mutex_lock_drepper(atomic_int *futex, char pshare) {
word = atomic_exchange_explicit(futex, 2, memory_order_acquire); word = atomic_exchange_explicit(futex, 2, memory_order_acquire);
BLOCK_CANCELATION; BLOCK_CANCELATION;
while (word > 0) { while (word > 0) {
_weaken(nsync_futex_wait_)(futex, 2, pshare, 0, 0); cosmo_futex_wait(futex, 2, pshare, 0, 0);
word = atomic_exchange_explicit(futex, 2, memory_order_acquire); word = atomic_exchange_explicit(futex, 2, memory_order_acquire);
} }
ALLOW_CANCELATION; ALLOW_CANCELATION;
@ -164,11 +148,7 @@ static errno_t pthread_mutex_lock_impl(pthread_mutex_t *mutex) {
// handle normal mutexes // handle normal mutexes
if (MUTEX_TYPE(word) == PTHREAD_MUTEX_NORMAL) { if (MUTEX_TYPE(word) == PTHREAD_MUTEX_NORMAL) {
if (_weaken(nsync_futex_wait_)) {
pthread_mutex_lock_drepper(&mutex->_futex, MUTEX_PSHARED(word)); pthread_mutex_lock_drepper(&mutex->_futex, MUTEX_PSHARED(word));
} else {
pthread_mutex_lock_spin(&mutex->_futex);
}
return 0; return 0;
} }

View file

@ -24,15 +24,8 @@
#include "libc/runtime/internal.h" #include "libc/runtime/internal.h"
#include "libc/thread/lock.h" #include "libc/thread/lock.h"
#include "libc/thread/thread.h" #include "libc/thread/thread.h"
#include "third_party/nsync/futex.internal.h"
#include "third_party/nsync/mu.h" #include "third_party/nsync/mu.h"
static errno_t pthread_mutex_trylock_spin(atomic_int *word) {
if (!atomic_exchange_explicit(word, 1, memory_order_acquire))
return 0;
return EBUSY;
}
static errno_t pthread_mutex_trylock_drepper(atomic_int *futex) { static errno_t pthread_mutex_trylock_drepper(atomic_int *futex) {
int word = 0; int word = 0;
if (atomic_compare_exchange_strong_explicit( if (atomic_compare_exchange_strong_explicit(
@ -142,13 +135,8 @@ errno_t pthread_mutex_trylock(pthread_mutex_t *mutex) {
#endif #endif
// handle normal mutexes // handle normal mutexes
if (MUTEX_TYPE(word) == PTHREAD_MUTEX_NORMAL) { if (MUTEX_TYPE(word) == PTHREAD_MUTEX_NORMAL)
if (_weaken(nsync_futex_wait_)) {
return pthread_mutex_trylock_drepper(&mutex->_futex); return pthread_mutex_trylock_drepper(&mutex->_futex);
} else {
return pthread_mutex_trylock_spin(&mutex->_futex);
}
}
// handle recursive and error checking mutexes // handle recursive and error checking mutexes
#if PTHREAD_USE_NSYNC #if PTHREAD_USE_NSYNC

View file

@ -18,6 +18,7 @@
*/ */
#include "libc/calls/calls.h" #include "libc/calls/calls.h"
#include "libc/calls/state.internal.h" #include "libc/calls/state.internal.h"
#include "libc/cosmo.h"
#include "libc/dce.h" #include "libc/dce.h"
#include "libc/errno.h" #include "libc/errno.h"
#include "libc/intrin/atomic.h" #include "libc/intrin/atomic.h"
@ -26,19 +27,14 @@
#include "libc/runtime/internal.h" #include "libc/runtime/internal.h"
#include "libc/thread/lock.h" #include "libc/thread/lock.h"
#include "libc/thread/thread.h" #include "libc/thread/thread.h"
#include "third_party/nsync/futex.internal.h"
#include "third_party/nsync/mu.h" #include "third_party/nsync/mu.h"
static void pthread_mutex_unlock_spin(atomic_int *word) {
atomic_store_explicit(word, 0, memory_order_release);
}
// see "take 3" algorithm in "futexes are tricky" by ulrich drepper // see "take 3" algorithm in "futexes are tricky" by ulrich drepper
static void pthread_mutex_unlock_drepper(atomic_int *futex, char pshare) { static void pthread_mutex_unlock_drepper(atomic_int *futex, char pshare) {
int word = atomic_fetch_sub_explicit(futex, 1, memory_order_release); int word = atomic_fetch_sub_explicit(futex, 1, memory_order_release);
if (word == 2) { if (word == 2) {
atomic_store_explicit(futex, 0, memory_order_release); atomic_store_explicit(futex, 0, memory_order_release);
_weaken(nsync_futex_wake_)(futex, 1, pshare); cosmo_futex_wake(futex, 1, pshare);
} }
} }
@ -137,11 +133,7 @@ errno_t pthread_mutex_unlock(pthread_mutex_t *mutex) {
// implement barebones normal mutexes // implement barebones normal mutexes
if (MUTEX_TYPE(word) == PTHREAD_MUTEX_NORMAL) { if (MUTEX_TYPE(word) == PTHREAD_MUTEX_NORMAL) {
if (_weaken(nsync_futex_wake_)) {
pthread_mutex_unlock_drepper(&mutex->_futex, MUTEX_PSHARED(word)); pthread_mutex_unlock_drepper(&mutex->_futex, MUTEX_PSHARED(word));
} else {
pthread_mutex_unlock_spin(&mutex->_futex);
}
return 0; return 0;
} }

View file

@ -1,7 +1,7 @@
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ /*-*- 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 vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi
Copyright 2023 Justine Alexandra Roberts Tunney Copyright 2022 Justine Alexandra Roberts Tunney
Permission to use, copy, modify, and/or distribute this software for Permission to use, copy, modify, and/or distribute this software for
any purpose with or without fee is hereby granted, provided that the any purpose with or without fee is hereby granted, provided that the
@ -17,37 +17,701 @@
PERFORMANCE OF THIS SOFTWARE. PERFORMANCE OF THIS SOFTWARE.
*/ */
#include "libc/sysv/consts/sig.h" #include "libc/sysv/consts/sig.h"
#include "ape/sections.internal.h"
#include "libc/calls/calls.h"
#include "libc/calls/sig.internal.h" #include "libc/calls/sig.internal.h"
#include "libc/calls/state.internal.h"
#include "libc/calls/struct/sigaction.h"
#include "libc/calls/struct/siginfo.h"
#include "libc/calls/struct/sigset.internal.h" #include "libc/calls/struct/sigset.internal.h"
#include "libc/calls/struct/ucontext.internal.h"
#include "libc/calls/syscall_support-nt.internal.h"
#include "libc/calls/ucontext.h"
#include "libc/dce.h" #include "libc/dce.h"
#include "libc/errno.h"
#include "libc/intrin/atomic.h" #include "libc/intrin/atomic.h"
#include "libc/intrin/bsf.h"
#include "libc/intrin/describebacktrace.h"
#include "libc/intrin/dll.h"
#include "libc/intrin/maps.h"
#include "libc/intrin/strace.h"
#include "libc/intrin/weaken.h" #include "libc/intrin/weaken.h"
#include "libc/thread/tls.h" #include "libc/nt/console.h"
#include "libc/nt/enum/context.h"
#include "libc/nt/enum/exceptionhandleractions.h"
#include "libc/nt/enum/processcreationflags.h"
#include "libc/nt/enum/signal.h"
#include "libc/nt/enum/status.h"
#include "libc/nt/events.h"
#include "libc/nt/runtime.h"
#include "libc/nt/signals.h"
#include "libc/nt/struct/ntexceptionpointers.h"
#include "libc/nt/synchronization.h"
#include "libc/nt/thread.h"
#include "libc/runtime/internal.h"
#include "libc/runtime/symbols.internal.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/sa.h"
#include "libc/sysv/consts/sicode.h"
#include "libc/sysv/consts/ss.h"
#include "libc/thread/posixthread.internal.h"
#ifdef __x86_64__
struct Signals __sig; /**
* @fileoverview Cosmopolitan Signals for Windows.
*/
sigset_t __sig_block(void) { #define STKSZ 65536
if (IsWindows() || IsMetal()) {
if (__tls_enabled) struct SignalFrame {
return atomic_exchange_explicit(&__get_tls()->tib_sigmask, -1, unsigned rva;
memory_order_acquire); unsigned flags;
else siginfo_t si;
ucontext_t ctx;
};
static textwindows bool __sig_ignored_by_default(int sig) {
return sig == SIGURG || //
sig == SIGCONT || //
sig == SIGCHLD || //
sig == SIGWINCH;
}
textwindows bool __sig_ignored(int sig) {
return __sighandrvas[sig] == (intptr_t)SIG_IGN ||
(__sighandrvas[sig] == (intptr_t)SIG_DFL &&
__sig_ignored_by_default(sig));
}
textwindows void __sig_delete(int sig) {
struct Dll *e;
atomic_fetch_and_explicit(__sig.process, ~(1ull << (sig - 1)),
memory_order_relaxed);
_pthread_lock();
for (e = dll_last(_pthread_list); e; e = dll_prev(_pthread_list, e))
atomic_fetch_and_explicit(&POSIXTHREAD_CONTAINER(e)->tib->tib_sigpending,
~(1ull << (sig - 1)), memory_order_relaxed);
_pthread_unlock();
}
static textwindows int __sig_getter(atomic_ulong *sigs, sigset_t masked) {
int sig;
sigset_t bit, pending, deliverable;
for (;;) {
pending = atomic_load_explicit(sigs, memory_order_acquire);
if ((deliverable = pending & ~masked)) {
sig = bsfl(deliverable) + 1;
bit = 1ull << (sig - 1);
if (atomic_fetch_and_explicit(sigs, ~bit, memory_order_acq_rel) & bit)
return sig;
} else {
return 0; return 0;
} else { }
sigset_t res, neu = -1;
sys_sigprocmask(SIG_SETMASK, &neu, &res);
return res;
} }
} }
void __sig_unblock(sigset_t m) { textwindows int __sig_get(sigset_t masked) {
if (IsWindows() || IsMetal()) { int sig;
if (__tls_enabled) { if (!(sig = __sig_getter(&__get_tls()->tib_sigpending, masked)))
atomic_store_explicit(&__get_tls()->tib_sigmask, m, memory_order_release); sig = __sig_getter(__sig.process, masked);
if (_weaken(__sig_check)) return sig;
_weaken(__sig_check)(); }
static textwindows bool __sig_should_use_altstack(unsigned flags,
struct CosmoTib *tib) {
if (!(flags & SA_ONSTACK))
return false; // signal handler didn't enable it
if (!tib->tib_sigstack_size)
return false; // sigaltstack() wasn't installed on this thread
if (tib->tib_sigstack_flags & SS_DISABLE)
return false; // sigaltstack() on this thread was disabled by user
char *bp = __builtin_frame_address(0);
if (tib->tib_sigstack_addr <= bp &&
bp <= tib->tib_sigstack_addr + tib->tib_sigstack_size)
return false; // we're already on the alternate stack
return true;
}
static textwindows wontreturn void __sig_terminate(int sig) {
TerminateThisProcess(sig);
}
textwindows static bool __sig_wake(struct PosixThread *pt, int sig) {
atomic_int *blocker;
blocker = atomic_load_explicit(&pt->pt_blocker, memory_order_acquire);
if (!blocker)
return false;
// threads can create semaphores on an as-needed basis
if (blocker == PT_BLOCKER_EVENT) {
STRACE("%G set %d's event object", sig, _pthread_tid(pt));
SetEvent(pt->pt_event);
return !!atomic_load_explicit(&pt->pt_blocker, memory_order_acquire);
} }
// all other blocking ops that aren't overlap should use futexes
// we force restartable futexes to churn by waking w/o releasing
STRACE("%G waking %d's futex", sig, _pthread_tid(pt));
WakeByAddressSingle(blocker);
return !!atomic_load_explicit(&pt->pt_blocker, memory_order_acquire);
}
textwindows static bool __sig_start(struct PosixThread *pt, int sig,
unsigned *rva, unsigned *flags) {
*rva = __sighandrvas[sig];
*flags = __sighandflags[sig];
if (*rva == (intptr_t)SIG_IGN ||
(*rva == (intptr_t)SIG_DFL && __sig_ignored_by_default(sig))) {
STRACE("ignoring %G", sig);
return false;
}
if (atomic_load_explicit(&pt->tib->tib_sigmask, memory_order_acquire) &
(1ull << (sig - 1))) {
STRACE("enqueing %G on %d", sig, _pthread_tid(pt));
atomic_fetch_or_explicit(&pt->tib->tib_sigpending, 1ull << (sig - 1),
memory_order_relaxed);
__sig_wake(pt, sig);
return false;
}
if (*rva == (intptr_t)SIG_DFL) {
STRACE("terminating on %G due to no handler", sig);
__sig_terminate(sig);
}
return true;
}
textwindows static sigaction_f __sig_handler(unsigned rva) {
atomic_fetch_add_explicit(&__sig.count, 1, memory_order_relaxed);
return (sigaction_f)(__executable_start + rva);
}
textwindows int __sig_raise(volatile int sig, int sic) {
// bitset of kinds of handlers called
volatile int handler_was_called = 0;
// loop over pending signals
ucontext_t ctx;
getcontext(&ctx);
if (!sig) {
if ((sig = __sig_get(ctx.uc_sigmask))) {
sic = SI_KERNEL;
} else { } else {
sys_sigprocmask(SIG_SETMASK, &m, 0); return handler_was_called;
}
}
// process signal(s)
unsigned rva, flags;
struct PosixThread *pt = _pthread_self();
if (__sig_start(pt, sig, &rva, &flags)) {
if (flags & SA_RESETHAND) {
STRACE("resetting %G handler", sig);
__sighandrvas[sig] = (int32_t)(intptr_t)SIG_DFL;
}
// update the signal mask in preparation for signal handller
sigset_t blocksigs = __sighandmask[sig];
if (!(flags & SA_NODEFER))
blocksigs |= 1ull << (sig - 1);
ctx.uc_sigmask = atomic_fetch_or_explicit(&pt->tib->tib_sigmask, blocksigs,
memory_order_acquire);
// call the user's signal handler
char ssbuf[128];
siginfo_t si = {.si_signo = sig, .si_code = sic};
STRACE("__sig_raise(%G, %t) mask %s", sig, __sig_handler(rva),
_DescribeSigset(ssbuf, 0, (sigset_t *)&pt->tib->tib_sigmask));
__sig_handler(rva)(sig, &si, &ctx);
// record this handler
if (flags & SA_RESTART) {
handler_was_called |= SIG_HANDLED_SA_RESTART;
} else {
handler_was_called |= SIG_HANDLED_NO_RESTART;
}
}
// restore sigmask
// loop back to top
// jump where handler says
sig = 0;
return setcontext(&ctx);
}
textwindows int __sig_relay(int sig, int sic, sigset_t waitmask) {
sigset_t m;
int handler_was_called;
m = atomic_exchange_explicit(&__get_tls()->tib_sigmask, waitmask,
memory_order_acquire);
handler_was_called = __sig_raise(sig, SI_KERNEL);
atomic_store_explicit(&__get_tls()->tib_sigmask, m, memory_order_release);
return handler_was_called;
}
// the user's signal handler callback is wrapped with this trampoline
static textwindows wontreturn void __sig_tramp(struct SignalFrame *sf) {
int sig = sf->si.si_signo;
struct CosmoTib *tib = __get_tls();
struct PosixThread *pt = (struct PosixThread *)tib->tib_pthread;
atomic_store_explicit(&pt->pt_intoff, 0, memory_order_release);
for (;;) {
// update the signal mask in preparation for signal handler
sigset_t blocksigs = __sighandmask[sig];
if (!(sf->flags & SA_NODEFER))
blocksigs |= 1ull << (sig - 1);
sf->ctx.uc_sigmask = atomic_fetch_or_explicit(&tib->tib_sigmask, blocksigs,
memory_order_acquire);
// call the user's signal handler
char ssbuf[2][128];
STRACE("__sig_tramp(%G, %t) mask %s → %s", sig, __sig_handler(sf->rva),
_DescribeSigset(ssbuf[0], 0, &sf->ctx.uc_sigmask),
_DescribeSigset(ssbuf[1], 0, (sigset_t *)&tib->tib_sigmask));
__sig_handler(sf->rva)(sig, &sf->si, &sf->ctx);
// restore the signal mask that was used by the interrupted code
// this may have been modified by the signal handler in the callback
atomic_store_explicit(&tib->tib_sigmask, sf->ctx.uc_sigmask,
memory_order_release);
// jump back into original code if there aren't any pending signals
do {
if (!(sig = __sig_get(sf->ctx.uc_sigmask)))
__sig_restore(&sf->ctx);
} while (!__sig_start(pt, sig, &sf->rva, &sf->flags));
// tail recurse into another signal handler
sf->si.si_signo = sig;
sf->si.si_code = SI_KERNEL;
if (sf->flags & SA_RESETHAND) {
STRACE("resetting %G handler", sig);
__sighandrvas[sig] = (int32_t)(intptr_t)SIG_DFL;
}
} }
} }
// sends signal to another specific thread which is ref'd
static textwindows int __sig_killer(struct PosixThread *pt, int sig, int sic) {
unsigned rva = __sighandrvas[sig];
unsigned flags = __sighandflags[sig];
// do nothing if signal is ignored
if (rva == (intptr_t)SIG_IGN ||
(rva == (intptr_t)SIG_DFL && __sig_ignored_by_default(sig))) {
STRACE("ignoring %G", sig);
return 0;
}
// we can't preempt threads that masked sigs or are blocked on i/o
while ((atomic_load_explicit(&pt->tib->tib_sigmask, memory_order_acquire) &
(1ull << (sig - 1)))) {
if (atomic_fetch_or_explicit(&pt->tib->tib_sigpending, 1ull << (sig - 1),
memory_order_acq_rel) &
(1ull << (sig - 1)))
// we believe signal was already enqueued
return 0;
if (__sig_wake(pt, sig))
// we believe i/o routine will handle signal
return 0;
if (atomic_load_explicit(&pt->tib->tib_sigmask, memory_order_acquire) &
(1ull << (sig - 1)))
// we believe ALLOW_SIGNALS will handle signal
return 0;
if (!(atomic_fetch_and_explicit(&pt->tib->tib_sigpending,
~(1ull << (sig - 1)),
memory_order_acq_rel) &
(1ull << (sig - 1))))
// we believe another thread sniped our signal
return 0;
break;
}
// avoid race conditions and deadlocks with thread suspend process
if (atomic_exchange_explicit(&pt->pt_intoff, 1, memory_order_acquire)) {
// we believe another thread is asynchronously waking the mark
if (atomic_fetch_or_explicit(&pt->tib->tib_sigpending, 1ull << (sig - 1),
memory_order_acq_rel) &
(1ull << (sig - 1)))
// we believe our signal is already being delivered
return 0;
if (atomic_load_explicit(&pt->pt_intoff, memory_order_acquire) ||
atomic_exchange_explicit(&pt->pt_intoff, 1, memory_order_acquire))
// we believe __sig_tramp will deliver our signal
return 0;
if (!(atomic_fetch_and_explicit(&pt->tib->tib_sigpending,
~(1ull << (sig - 1)),
memory_order_acq_rel) &
(1ull << (sig - 1))))
// we believe another thread sniped our signal
return 0;
}
// if there's no handler then killing a thread kills the process
if (rva == (intptr_t)SIG_DFL) {
STRACE("terminating on %G due to no handler", sig);
__sig_terminate(sig);
}
// take control of thread
// suspending the thread happens asynchronously
// however getting the context blocks until it's frozen
uintptr_t th = _pthread_syshand(pt);
if (SuspendThread(th) == -1u) {
STRACE("SuspendThread failed w/ %d", GetLastError());
atomic_store_explicit(&pt->pt_intoff, 0, memory_order_release);
return ESRCH;
}
struct NtContext nc;
nc.ContextFlags = kNtContextFull;
if (!GetThreadContext(th, &nc)) {
STRACE("GetThreadContext failed w/ %d", GetLastError());
ResumeThread(th);
atomic_store_explicit(&pt->pt_intoff, 0, memory_order_release);
return ESRCH;
}
// we can't preempt threads that masked sig or are blocked
// we can't preempt threads that are running in win32 code
// so we shall unblock the thread and let it signal itself
if (!((uintptr_t)__executable_start <= nc.Rip &&
nc.Rip < (uintptr_t)__privileged_start)) {
atomic_fetch_or_explicit(&pt->tib->tib_sigpending, 1ull << (sig - 1),
memory_order_relaxed);
ResumeThread(th);
atomic_store_explicit(&pt->pt_intoff, 0, memory_order_release);
__sig_wake(pt, sig);
return 0;
}
// preferring to live dangerously
// the thread will be signaled asynchronously
if (flags & SA_RESETHAND) {
STRACE("resetting %G handler", sig);
__sighandrvas[sig] = (int32_t)(intptr_t)SIG_DFL;
}
// inject call to trampoline function into thread
uintptr_t sp;
if (__sig_should_use_altstack(flags, pt->tib)) {
sp = (uintptr_t)pt->tib->tib_sigstack_addr + pt->tib->tib_sigstack_size;
} else {
sp = nc.Rsp;
}
sp -= sizeof(struct SignalFrame);
sp &= -16;
struct SignalFrame *sf = (struct SignalFrame *)sp;
_ntcontext2linux(&sf->ctx, &nc);
bzero(&sf->si, sizeof(sf->si));
sf->rva = rva;
sf->flags = flags;
sf->si.si_code = sic;
sf->si.si_signo = sig;
*(uintptr_t *)(sp -= sizeof(uintptr_t)) = nc.Rip;
nc.Rip = (intptr_t)__sig_tramp;
nc.Rdi = (intptr_t)sf;
nc.Rsp = sp;
if (!SetThreadContext(th, &nc)) {
STRACE("SetThreadContext failed w/ %d", GetLastError());
atomic_store_explicit(&pt->pt_intoff, 0, memory_order_release);
return ESRCH;
}
ResumeThread(th);
__sig_wake(pt, sig);
return 0;
}
// sends signal to another specific thread
textwindows int __sig_kill(struct PosixThread *pt, int sig, int sic) {
int rc;
BLOCK_SIGNALS;
rc = __sig_killer(pt, sig, sic);
ALLOW_SIGNALS;
return rc;
}
// sends signal to any other thread
// this should only be called by non-posix threads
textwindows void __sig_generate(int sig, int sic) {
struct Dll *e;
struct PosixThread *pt, *mark = 0;
if (__sig_ignored(sig)) {
STRACE("ignoring %G", sig);
return;
}
if (__sighandrvas[sig] == (intptr_t)SIG_DFL) {
STRACE("terminating on %G due to no handler", sig);
__sig_terminate(sig);
}
if (atomic_load_explicit(__sig.process, memory_order_acquire) &
(1ull << (sig - 1))) {
return;
}
_pthread_lock();
for (e = dll_first(_pthread_list); e; e = dll_next(_pthread_list, e)) {
pt = POSIXTHREAD_CONTAINER(e);
// we don't want to signal ourself
if (pt == _pthread_self())
continue;
// we don't want to signal a thread that isn't running
if (atomic_load_explicit(&pt->pt_status, memory_order_acquire) >=
kPosixThreadTerminated) {
continue;
}
// choose this thread if it isn't masking sig
if (!(atomic_load_explicit(&pt->tib->tib_sigmask, memory_order_acquire) &
(1ull << (sig - 1)))) {
_pthread_ref(pt);
mark = pt;
break;
}
// if a thread is blocking then we check to see if it's planning
// to unblock our sig once the wait operation is completed; when
// that's the case we can cancel the thread's i/o to deliver sig
if (atomic_load_explicit(&pt->pt_blocker, memory_order_acquire) &&
!(pt->pt_blkmask & (1ull << (sig - 1)))) {
_pthread_ref(pt);
mark = pt;
break;
}
}
_pthread_unlock();
if (mark) {
// no lock needed since current thread is nameless and formless
__sig_killer(mark, sig, sic);
_pthread_unref(mark);
} else {
atomic_fetch_or_explicit(__sig.process, 1ull << (sig - 1),
memory_order_relaxed);
}
}
static textwindows char *__sig_stpcpy(char *d, const char *s) {
size_t i;
for (i = 0;; ++i)
if (!(d[i] = s[i]))
return d + i;
}
static textwindows wontreturn void __sig_death(int sig, const char *thing) {
#ifndef TINY
intptr_t hStderr;
char sigbuf[21], s[128], *p;
hStderr = GetStdHandle(kNtStdErrorHandle);
p = __sig_stpcpy(s, "Terminating on ");
p = __sig_stpcpy(p, thing);
p = __sig_stpcpy(p, strsignal_r(sig, sigbuf));
p = __sig_stpcpy(p,
". Pass --strace and/or ShowCrashReports() for details.\n");
WriteFile(hStderr, s, p - s, 0, 0);
#endif
__sig_terminate(sig);
}
static textwindows void __sig_unmaskable(struct NtExceptionPointers *ep,
int code, int sig,
struct CosmoTib *tib) {
// log vital crash information reliably for --strace before doing much
// we don't print this without the flag since raw numbers scare people
// this needs at least one page of stack memory in order to get logged
// otherwise it'll print a warning message about the lack of stack mem
STRACE("win32 vectored exception 0x%08Xu raising %G "
"cosmoaddr2line %s %lx %s",
ep->ExceptionRecord->ExceptionCode, sig,
_weaken(FindDebugBinary) ? _weaken(FindDebugBinary)()
: program_invocation_name,
ep->ContextRecord->Rip,
DescribeBacktrace((struct StackFrame *)ep->ContextRecord->Rbp));
// if the user didn't install a signal handler for this unmaskable
// exception, then print a friendly helpful hint message to stderr
unsigned rva = __sighandrvas[sig];
if (rva == (intptr_t)SIG_DFL || rva == (intptr_t)SIG_IGN)
__sig_death(sig, "uncaught ");
// if this signal handler is configured to auto-reset to the default
// then that reset needs to happen before the user handler is called
unsigned flags = __sighandflags[sig];
if (flags & SA_RESETHAND) {
STRACE("resetting %G handler", sig);
__sighandrvas[sig] = (int32_t)(intptr_t)SIG_DFL;
}
// determine the true memory address at which fault occurred
// if this is a stack overflow then reapply guard protection
void *si_addr;
if (ep->ExceptionRecord->ExceptionCode == kNtSignalGuardPage) {
si_addr = (void *)ep->ExceptionRecord->ExceptionInformation[1];
} else {
si_addr = ep->ExceptionRecord->ExceptionAddress;
}
// call the user signal handler
// and a modifiable view of the faulting code's cpu state
// temporarily replace signal mask while calling crash handler
// abort process if sig is already blocked to avoid crash loop
// note ucontext_t is a hefty data structures on top of NtContext
ucontext_t ctx = {0};
siginfo_t si = {.si_signo = sig, .si_code = code, .si_addr = si_addr};
_ntcontext2linux(&ctx, ep->ContextRecord);
sigset_t blocksigs = __sighandmask[sig];
if (!(flags & SA_NODEFER))
blocksigs |= 1ull << (sig - 1);
ctx.uc_sigmask = atomic_fetch_or_explicit(&tib->tib_sigmask, blocksigs,
memory_order_acquire);
if (ctx.uc_sigmask & (1ull << (sig - 1))) {
__sig_death(sig, "masked ");
__sig_terminate(sig);
}
__sig_handler(rva)(sig, &si, &ctx);
atomic_store_explicit(&tib->tib_sigmask, ctx.uc_sigmask,
memory_order_release);
_ntlinux2context(ep->ContextRecord, &ctx);
}
void __stack_call(struct NtExceptionPointers *, int, int, struct CosmoTib *,
void (*)(struct NtExceptionPointers *, int, int,
struct CosmoTib *),
void *);
// abashed the devil stood
// and felt how awful goodness is
__msabi dontinstrument unsigned __sig_crash(struct NtExceptionPointers *ep) {
// translate win32 to unix si_signo and si_code
int code, sig = __sig_crash_sig(ep->ExceptionRecord->ExceptionCode, &code);
// advance the instruction pointer to skip over debugger breakpoints
// this behavior is consistent with how unix kernels are implemented
if (sig == SIGTRAP) {
ep->ContextRecord->Rip++;
if (__sig_ignored(sig))
return kNtExceptionContinueExecution;
}
// win32 stack overflow detection executes INSIDE the guard page
// thus switch to the alternate signal stack as soon as possible
struct CosmoTib *tib = __get_tls();
unsigned flags = __sighandflags[sig];
if (__sig_should_use_altstack(flags, tib)) {
__stack_call(ep, code, sig, tib, __sig_unmaskable,
tib->tib_sigstack_addr + tib->tib_sigstack_size);
} else {
__sig_unmaskable(ep, code, sig, tib);
}
// resume running user program
// hopefully the user fixed the cpu state
// otherwise the crash will keep happening
return kNtExceptionContinueExecution;
}
static textwindows int __sig_console_sig(uint32_t dwCtrlType) {
switch (dwCtrlType) {
case kNtCtrlCEvent:
return SIGINT;
case kNtCtrlBreakEvent:
return SIGQUIT;
case kNtCtrlCloseEvent:
case kNtCtrlLogoffEvent: // only received by services
case kNtCtrlShutdownEvent: // only received by services
return SIGHUP;
default:
return SIGSTKFLT;
}
}
__msabi textwindows dontinstrument bool32 __sig_console(uint32_t dwCtrlType) {
// win32 launches a thread to deliver ctrl-c and ctrl-break when typed
// it only happens when kNtEnableProcessedInput is in play on console.
// otherwise we need to wait until read-nt.c discovers that keystroke.
struct CosmoTib tls;
__bootstrap_tls(&tls, __builtin_frame_address(0));
__sig_generate(__sig_console_sig(dwCtrlType), SI_KERNEL);
return true;
}
// returns 0 if no signal handlers were called, otherwise a bitmask
// consisting of `1` which means a signal handler was invoked which
// didn't have the SA_RESTART flag, and `2`, which means SA_RESTART
// handlers were called (or `3` if both were the case).
textwindows int __sig_check(void) {
int sig, res = 0;
while ((sig = __sig_get(atomic_load_explicit(&__get_tls()->tib_sigmask,
memory_order_acquire))))
res |= __sig_raise(sig, SI_KERNEL);
return res;
}
// background thread for delivering inter-process signals asynchronously
// this checks for undelivered process-wide signals, once per scheduling
// quantum, which on windows should be every ~15ms or so, unless somehow
// the process was tuned to have more fine-grained event timing. we want
// signals to happen faster when possible; that happens when cancelation
// points, e.g. read need to wait on i/o; they too check for new signals
textwindows dontinstrument static uint32_t __sig_worker(void *arg) {
struct CosmoTib tls;
__bootstrap_tls(&tls, __builtin_frame_address(0));
char *sp = __builtin_frame_address(0);
__maps_track((char *)(((uintptr_t)sp + __pagesize - 1) & -__pagesize) - STKSZ,
STKSZ);
for (;;) {
// dequeue all pending signals and fire them off. if there's no
// thread that can handle them then __sig_generate will requeue
// those signals back to __sig.process; hence the need for xchg
unsigned long sigs =
atomic_exchange_explicit(__sig.process, 0, memory_order_acq_rel);
while (sigs) {
int sig = bsfl(sigs) + 1;
sigs &= ~(1ull << (sig - 1));
__sig_generate(sig, SI_KERNEL);
}
// unblock stalled asynchronous signals in threads
_pthread_lock();
for (struct Dll *e = dll_first(_pthread_list); e;
e = dll_next(_pthread_list, e)) {
struct PosixThread *pt = POSIXTHREAD_CONTAINER(e);
if (atomic_load_explicit(&pt->pt_status, memory_order_acquire) >=
kPosixThreadTerminated) {
break;
}
sigset_t pending =
atomic_load_explicit(&pt->tib->tib_sigpending, memory_order_acquire);
sigset_t mask =
atomic_load_explicit(&pt->tib->tib_sigmask, memory_order_acquire);
if (pending & ~mask) {
_pthread_ref(pt);
_pthread_unlock();
while (!atomic_compare_exchange_weak_explicit(
&pt->tib->tib_sigpending, &pending, pending & ~mask,
memory_order_acq_rel, memory_order_relaxed)) {
}
while ((pending = pending & ~mask)) {
int sig = bsfl(pending) + 1;
pending &= ~(1ull << (sig - 1));
__sig_killer(pt, sig, SI_KERNEL);
}
_pthread_lock();
_pthread_unref(pt);
}
}
_pthread_unlock();
// wait until next scheduler quantum
Sleep(POLL_INTERVAL_MS);
}
return 0;
}
__attribute__((__constructor__(10))) textstartup void __sig_init(void) {
if (!IsWindows())
return;
AddVectoredExceptionHandler(true, (void *)__sig_crash);
SetConsoleCtrlHandler((void *)__sig_console, true);
CreateThread(0, STKSZ, __sig_worker, 0, kNtStackSizeParamIsAReservation, 0);
}
#endif /* __x86_64__ */

53
libc/intrin/sigblock.c Normal file
View file

@ -0,0 +1,53 @@
/*-*- 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 2023 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/sig.h"
#include "libc/calls/sig.internal.h"
#include "libc/calls/struct/sigset.internal.h"
#include "libc/dce.h"
#include "libc/intrin/atomic.h"
#include "libc/intrin/weaken.h"
#include "libc/thread/tls.h"
struct Signals __sig;
sigset_t __sig_block(void) {
if (IsWindows() || IsMetal()) {
if (__tls_enabled)
return atomic_exchange_explicit(&__get_tls()->tib_sigmask, -1,
memory_order_acquire);
else
return 0;
} else {
sigset_t res, neu = -1;
sys_sigprocmask(SIG_SETMASK, &neu, &res);
return res;
}
}
void __sig_unblock(sigset_t m) {
if (IsWindows() || IsMetal()) {
if (__tls_enabled) {
atomic_store_explicit(&__get_tls()->tib_sigmask, m, memory_order_release);
if (_weaken(__sig_check))
_weaken(__sig_check)();
}
} else {
sys_sigprocmask(SIG_SETMASK, &m, 0);
}
}

View file

@ -32,7 +32,7 @@
.ftrace1 .ftrace1
swapcontext: swapcontext:
.ftrace2 .ftrace2
#include "libc/calls/getcontext.inc" #include "libc/intrin/getcontext.inc"
#ifdef __x86_64__ #ifdef __x86_64__
push %rbp push %rbp
mov %rsp,%rbp mov %rsp,%rbp

View file

@ -77,13 +77,7 @@ o/$(MODE)/libc/str/iswseparator.o: private \
# ensure that division is optimized # ensure that division is optimized
o/$(MODE)/libc/str/bcmp.o \ o/$(MODE)/libc/str/bcmp.o \
o/$(MODE)/libc/str/strcmp.o \ o/$(MODE)/libc/str/strcmp.o: private \
o/$(MODE)/libc/str/windowsdurationtotimeval.o \
o/$(MODE)/libc/str/windowsdurationtotimespec.o \
o/$(MODE)/libc/str/timevaltowindowstime.o \
o/$(MODE)/libc/str/timespectowindowstime.o \
o/$(MODE)/libc/str/windowstimetotimeval.o \
o/$(MODE)/libc/str/windowstimetotimespec.o: private \
CFLAGS += \ CFLAGS += \
-O2 -O2

View file

@ -17,11 +17,11 @@
PERFORMANCE OF THIS SOFTWARE. PERFORMANCE OF THIS SOFTWARE.
*/ */
#include "libc/calls/blockcancel.internal.h" #include "libc/calls/blockcancel.internal.h"
#include "libc/cosmo.h"
#include "libc/errno.h" #include "libc/errno.h"
#include "libc/intrin/atomic.h" #include "libc/intrin/atomic.h"
#include "libc/limits.h" #include "libc/limits.h"
#include "libc/thread/thread.h" #include "libc/thread/thread.h"
#include "third_party/nsync/futex.internal.h"
/** /**
* Waits for all threads to arrive at barrier. * Waits for all threads to arrive at barrier.
@ -54,14 +54,14 @@ errno_t pthread_barrier_wait(pthread_barrier_t *barrier) {
atomic_store_explicit(&barrier->_counter, barrier->_count, atomic_store_explicit(&barrier->_counter, barrier->_count,
memory_order_release); memory_order_release);
atomic_store_explicit(&barrier->_waiters, 0, memory_order_release); atomic_store_explicit(&barrier->_waiters, 0, memory_order_release);
nsync_futex_wake_(&barrier->_waiters, INT_MAX, barrier->_pshared); cosmo_futex_wake(&barrier->_waiters, INT_MAX, barrier->_pshared);
return PTHREAD_BARRIER_SERIAL_THREAD; return PTHREAD_BARRIER_SERIAL_THREAD;
} }
// wait for everyone else to arrive at barrier // wait for everyone else to arrive at barrier
BLOCK_CANCELATION; BLOCK_CANCELATION;
while ((n = atomic_load_explicit(&barrier->_waiters, memory_order_acquire))) while ((n = atomic_load_explicit(&barrier->_waiters, memory_order_acquire)))
nsync_futex_wait_(&barrier->_waiters, n, barrier->_pshared, 0, 0); cosmo_futex_wait(&barrier->_waiters, n, barrier->_pshared, 0, 0);
ALLOW_CANCELATION; ALLOW_CANCELATION;
return 0; return 0;

View file

@ -16,12 +16,12 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE. PERFORMANCE OF THIS SOFTWARE.
*/ */
#include "libc/cosmo.h"
#include "libc/dce.h" #include "libc/dce.h"
#include "libc/intrin/atomic.h" #include "libc/intrin/atomic.h"
#include "libc/limits.h" #include "libc/limits.h"
#include "libc/thread/thread.h" #include "libc/thread/thread.h"
#include "third_party/nsync/cv.h" #include "third_party/nsync/cv.h"
#include "third_party/nsync/futex.internal.h"
__static_yoink("nsync_mu_lock"); __static_yoink("nsync_mu_lock");
__static_yoink("nsync_mu_unlock"); __static_yoink("nsync_mu_unlock");
@ -63,6 +63,6 @@ errno_t pthread_cond_broadcast(pthread_cond_t *cond) {
// roll forward the monotonic sequence // roll forward the monotonic sequence
atomic_fetch_add_explicit(&cond->_sequence, 1, memory_order_acq_rel); atomic_fetch_add_explicit(&cond->_sequence, 1, memory_order_acq_rel);
if (atomic_load_explicit(&cond->_waiters, memory_order_acquire)) if (atomic_load_explicit(&cond->_waiters, memory_order_acquire))
nsync_futex_wake_((atomic_int *)&cond->_sequence, INT_MAX, cond->_pshared); cosmo_futex_wake((atomic_int *)&cond->_sequence, INT_MAX, cond->_pshared);
return 0; return 0;
} }

View file

@ -16,11 +16,11 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE. PERFORMANCE OF THIS SOFTWARE.
*/ */
#include "libc/cosmo.h"
#include "libc/dce.h" #include "libc/dce.h"
#include "libc/intrin/atomic.h" #include "libc/intrin/atomic.h"
#include "libc/thread/thread.h" #include "libc/thread/thread.h"
#include "third_party/nsync/cv.h" #include "third_party/nsync/cv.h"
#include "third_party/nsync/futex.internal.h"
__static_yoink("nsync_mu_lock"); __static_yoink("nsync_mu_lock");
__static_yoink("nsync_mu_unlock"); __static_yoink("nsync_mu_unlock");
@ -62,6 +62,6 @@ errno_t pthread_cond_signal(pthread_cond_t *cond) {
// roll forward the monotonic sequence // roll forward the monotonic sequence
atomic_fetch_add_explicit(&cond->_sequence, 1, memory_order_acq_rel); atomic_fetch_add_explicit(&cond->_sequence, 1, memory_order_acq_rel);
if (atomic_load_explicit(&cond->_waiters, memory_order_acquire)) if (atomic_load_explicit(&cond->_waiters, memory_order_acquire))
nsync_futex_wake_((atomic_int *)&cond->_sequence, 1, cond->_pshared); cosmo_futex_wake((atomic_int *)&cond->_sequence, 1, cond->_pshared);
return 0; return 0;
} }

View file

@ -18,6 +18,8 @@
*/ */
#include "libc/calls/calls.h" #include "libc/calls/calls.h"
#include "libc/calls/cp.internal.h" #include "libc/calls/cp.internal.h"
#include "libc/calls/struct/timespec.h"
#include "libc/cosmo.h"
#include "libc/dce.h" #include "libc/dce.h"
#include "libc/errno.h" #include "libc/errno.h"
#include "libc/intrin/atomic.h" #include "libc/intrin/atomic.h"
@ -28,7 +30,6 @@
#include "libc/thread/thread2.h" #include "libc/thread/thread2.h"
#include "third_party/nsync/common.internal.h" #include "third_party/nsync/common.internal.h"
#include "third_party/nsync/cv.h" #include "third_party/nsync/cv.h"
#include "third_party/nsync/futex.internal.h"
#include "third_party/nsync/time.h" #include "third_party/nsync/time.h"
__static_yoink("nsync_mu_lock"); __static_yoink("nsync_mu_lock");
@ -74,7 +75,7 @@ static errno_t pthread_cond_timedwait_impl(pthread_cond_t *cond,
int rc; int rc;
struct PthreadWait waiter = {cond, mutex}; struct PthreadWait waiter = {cond, mutex};
pthread_cleanup_push(pthread_cond_leave, &waiter); pthread_cleanup_push(pthread_cond_leave, &waiter);
rc = nsync_futex_wait_((atomic_int *)&cond->_sequence, seq1, cond->_pshared, rc = cosmo_futex_wait((atomic_int *)&cond->_sequence, seq1, cond->_pshared,
cond->_clock, abstime); cond->_clock, abstime);
pthread_cleanup_pop(true); pthread_cleanup_pop(true);
if (rc == -EAGAIN) if (rc == -EAGAIN)

View file

@ -18,6 +18,7 @@
*/ */
#include "libc/assert.h" #include "libc/assert.h"
#include "libc/atomic.h" #include "libc/atomic.h"
#include "libc/cosmo.h"
#include "libc/cxxabi.h" #include "libc/cxxabi.h"
#include "libc/dce.h" #include "libc/dce.h"
#include "libc/intrin/atomic.h" #include "libc/intrin/atomic.h"
@ -33,7 +34,6 @@
#include "libc/thread/posixthread.internal.h" #include "libc/thread/posixthread.internal.h"
#include "libc/thread/thread.h" #include "libc/thread/thread.h"
#include "libc/thread/tls.h" #include "libc/thread/tls.h"
#include "third_party/nsync/futex.internal.h"
#include "third_party/nsync/wait_s.internal.h" #include "third_party/nsync/wait_s.internal.h"
/** /**
@ -137,7 +137,7 @@ wontreturn void pthread_exit(void *rc) {
// note that the main thread is joinable by child threads // note that the main thread is joinable by child threads
if (pt->pt_flags & PT_STATIC) { if (pt->pt_flags & PT_STATIC) {
atomic_store_explicit(&tib->tib_tid, 0, memory_order_release); atomic_store_explicit(&tib->tib_tid, 0, memory_order_release);
nsync_futex_wake_((atomic_int *)&tib->tib_tid, INT_MAX, cosmo_futex_wake((atomic_int *)&tib->tib_tid, INT_MAX,
!IsWindows() && !IsXnu()); !IsWindows() && !IsXnu());
_Exit1(0); _Exit1(0);
} }

View file

@ -20,6 +20,8 @@
#include "libc/calls/cp.internal.h" #include "libc/calls/cp.internal.h"
#include "libc/calls/struct/timespec.h" #include "libc/calls/struct/timespec.h"
#include "libc/calls/struct/timespec.internal.h" #include "libc/calls/struct/timespec.internal.h"
#include "libc/cosmo.h"
#include "libc/dce.h"
#include "libc/errno.h" #include "libc/errno.h"
#include "libc/fmt/itoa.h" #include "libc/fmt/itoa.h"
#include "libc/intrin/atomic.h" #include "libc/intrin/atomic.h"
@ -30,7 +32,6 @@
#include "libc/thread/posixthread.internal.h" #include "libc/thread/posixthread.internal.h"
#include "libc/thread/thread2.h" #include "libc/thread/thread2.h"
#include "libc/thread/tls.h" #include "libc/thread/tls.h"
#include "third_party/nsync/futex.internal.h"
static const char *DescribeReturnValue(char buf[30], int err, void **value) { static const char *DescribeReturnValue(char buf[30], int err, void **value) {
char *p = buf; char *p = buf;
@ -75,7 +76,7 @@ static errno_t _pthread_wait(atomic_int *ctid, struct timespec *abstime) {
if (!(err = pthread_testcancel_np())) { if (!(err = pthread_testcancel_np())) {
BEGIN_CANCELATION_POINT; BEGIN_CANCELATION_POINT;
while ((x = atomic_load_explicit(ctid, memory_order_acquire))) { while ((x = atomic_load_explicit(ctid, memory_order_acquire))) {
e = nsync_futex_wait_(ctid, x, !IsWindows() && !IsXnu(), CLOCK_REALTIME, e = cosmo_futex_wait(ctid, x, !IsWindows() && !IsXnu(), CLOCK_REALTIME,
abstime); abstime);
if (e == -ECANCELED) { if (e == -ECANCELED) {
err = ECANCELED; err = ECANCELED;

View file

@ -19,6 +19,7 @@
#include "libc/assert.h" #include "libc/assert.h"
#include "libc/calls/calls.h" #include "libc/calls/calls.h"
#include "libc/calls/syscall-sysv.internal.h" #include "libc/calls/syscall-sysv.internal.h"
#include "libc/cosmo.h"
#include "libc/dce.h" #include "libc/dce.h"
#include "libc/errno.h" #include "libc/errno.h"
#include "libc/intrin/atomic.h" #include "libc/intrin/atomic.h"
@ -26,7 +27,6 @@
#include "libc/runtime/syslib.internal.h" #include "libc/runtime/syslib.internal.h"
#include "libc/sysv/errfuns.h" #include "libc/sysv/errfuns.h"
#include "libc/thread/semaphore.h" #include "libc/thread/semaphore.h"
#include "third_party/nsync/futex.internal.h"
/** /**
* Unlocks semaphore. * Unlocks semaphore.
@ -46,7 +46,7 @@ int sem_post(sem_t *sem) {
old = atomic_fetch_add_explicit(&sem->sem_value, 1, memory_order_acq_rel); old = atomic_fetch_add_explicit(&sem->sem_value, 1, memory_order_acq_rel);
unassert(old > INT_MIN); unassert(old > INT_MIN);
if (old >= 0) { if (old >= 0) {
wakeups = nsync_futex_wake_(&sem->sem_value, 1, sem->sem_pshared); wakeups = cosmo_futex_wake(&sem->sem_value, 1, sem->sem_pshared);
npassert(wakeups >= 0); npassert(wakeups >= 0);
rc = 0; rc = 0;
} else { } else {

View file

@ -22,6 +22,7 @@
#include "libc/calls/struct/timespec.h" #include "libc/calls/struct/timespec.h"
#include "libc/calls/struct/timespec.internal.h" #include "libc/calls/struct/timespec.internal.h"
#include "libc/calls/syscall-sysv.internal.h" #include "libc/calls/syscall-sysv.internal.h"
#include "libc/cosmo.h"
#include "libc/dce.h" #include "libc/dce.h"
#include "libc/errno.h" #include "libc/errno.h"
#include "libc/intrin/atomic.h" #include "libc/intrin/atomic.h"
@ -32,7 +33,6 @@
#include "libc/sysv/errfuns.h" #include "libc/sysv/errfuns.h"
#include "libc/thread/semaphore.h" #include "libc/thread/semaphore.h"
#include "libc/thread/thread.h" #include "libc/thread/thread.h"
#include "third_party/nsync/futex.internal.h"
static void sem_delay(int n) { static void sem_delay(int n) {
volatile int i; volatile int i;
@ -119,7 +119,7 @@ int sem_timedwait(sem_t *sem, const struct timespec *abstime) {
do { do {
if (!(v = atomic_load_explicit(&sem->sem_value, memory_order_relaxed))) { if (!(v = atomic_load_explicit(&sem->sem_value, memory_order_relaxed))) {
rc = nsync_futex_wait_(&sem->sem_value, v, sem->sem_pshared, rc = cosmo_futex_wait(&sem->sem_value, v, sem->sem_pshared,
CLOCK_REALTIME, abstime); CLOCK_REALTIME, abstime);
if (rc == -EINTR || rc == -ECANCELED) { if (rc == -EINTR || rc == -ECANCELED) {
errno = -rc; errno = -rc;

View file

@ -9,8 +9,6 @@
#ifdef __COSMOPOLITAN__ #ifdef __COSMOPOLITAN__
#include <cosmo.h> #include <cosmo.h>
#include "libc/thread/thread.h"
#include "third_party/nsync/futex.internal.h"
#endif #endif
#include <assert.h> #include <assert.h>

View file

@ -109,8 +109,9 @@
#include "third_party/lua/lgc.h" #include "third_party/lua/lgc.h"
#include "third_party/lua/lua.h" #include "third_party/lua/lua.h"
#include "third_party/lua/luaconf.h" #include "third_party/lua/luaconf.h"
#include "third_party/nsync/futex.internal.h"
#include "libc/sysv/consts/clock.h" #include "libc/sysv/consts/clock.h"
#include "libc/cosmo.h"
#include "libc/cosmo.h"
#include "tool/net/luacheck.h" #include "tool/net/luacheck.h"
#define DNS_NAME_MAX 253 #define DNS_NAME_MAX 253
@ -2855,7 +2856,7 @@ static int LuaUnixMemoryWait(lua_State *L) {
deadline = &ts; deadline = &ts;
} }
BEGIN_CANCELATION_POINT; BEGIN_CANCELATION_POINT;
rc = nsync_futex_wait_((atomic_int *)GetWord(L), expect, rc = cosmo_futex_wait((atomic_int *)GetWord(L), expect,
PTHREAD_PROCESS_SHARED, CLOCK_REALTIME, deadline); PTHREAD_PROCESS_SHARED, CLOCK_REALTIME, deadline);
END_CANCELATION_POINT; END_CANCELATION_POINT;
if (rc < 0) errno = -rc, rc = -1; if (rc < 0) errno = -rc, rc = -1;
@ -2867,7 +2868,7 @@ static int LuaUnixMemoryWait(lua_State *L) {
static int LuaUnixMemoryWake(lua_State *L) { static int LuaUnixMemoryWake(lua_State *L) {
int count, woken; int count, woken;
count = luaL_optinteger(L, 3, INT_MAX); count = luaL_optinteger(L, 3, INT_MAX);
woken = nsync_futex_wake_((atomic_int *)GetWord(L), count, woken = cosmo_futex_wake((atomic_int *)GetWord(L), count,
PTHREAD_PROCESS_SHARED); PTHREAD_PROCESS_SHARED);
npassert(woken >= 0); npassert(woken >= 0);
return ReturnInteger(L, woken); return ReturnInteger(L, woken);

View file

@ -27,7 +27,6 @@ THIRD_PARTY_NSYNC_A_DIRECTDEPS = \
LIBC_INTRIN \ LIBC_INTRIN \
LIBC_NEXGEN32E \ LIBC_NEXGEN32E \
LIBC_NT_KERNEL32 \ LIBC_NT_KERNEL32 \
LIBC_NT_SYNCHRONIZATION \
LIBC_STR \ LIBC_STR \
LIBC_SYSV \ LIBC_SYSV \
LIBC_SYSV_CALLS LIBC_SYSV_CALLS

View file

@ -1,17 +0,0 @@
#ifndef NSYNC_FUTEX_INTERNAL_H_
#define NSYNC_FUTEX_INTERNAL_H_
#include "libc/calls/struct/timespec.h"
#include "libc/dce.h"
COSMOPOLITAN_C_START_
#ifndef __cplusplus
#define _FUTEX_ATOMIC(x) _Atomic(x)
#else
#define _FUTEX_ATOMIC(x) x
#endif
int nsync_futex_wake_(_FUTEX_ATOMIC(int) *, int, char);
int nsync_futex_wait_(_FUTEX_ATOMIC(int) *, int, char, int, const struct timespec *);
COSMOPOLITAN_C_END_
#endif /* NSYNC_FUTEX_INTERNAL_H_ */

View file

@ -21,7 +21,9 @@
#include "libc/thread/thread.h" #include "libc/thread/thread.h"
#include "third_party/nsync/atomic.h" #include "third_party/nsync/atomic.h"
#include "third_party/nsync/atomic.internal.h" #include "third_party/nsync/atomic.internal.h"
#include "third_party/nsync/futex.internal.h" #include "libc/cosmo.h"
#include "libc/calls/struct/timespec.h"
#include "libc/cosmo.h"
#include "third_party/nsync/mu_semaphore.internal.h" #include "third_party/nsync/mu_semaphore.internal.h"
/** /**
@ -61,7 +63,7 @@ errno_t nsync_mu_semaphore_p_futex (nsync_semaphore *s) {
i = ATM_LOAD ((nsync_atomic_uint32_ *) &f->i); i = ATM_LOAD ((nsync_atomic_uint32_ *) &f->i);
if (i == 0) { if (i == 0) {
int futex_result; int futex_result;
futex_result = -nsync_futex_wait_ ( futex_result = -cosmo_futex_wait (
(atomic_int *)&f->i, i, (atomic_int *)&f->i, i,
PTHREAD_PROCESS_PRIVATE, 0, 0); PTHREAD_PROCESS_PRIVATE, 0, 0);
ASSERT (futex_result == 0 || ASSERT (futex_result == 0 ||
@ -100,7 +102,7 @@ errno_t nsync_mu_semaphore_p_with_deadline_futex (nsync_semaphore *s, int clock,
ts_buf.tv_nsec = NSYNC_TIME_NSEC (abs_deadline); ts_buf.tv_nsec = NSYNC_TIME_NSEC (abs_deadline);
ts = &ts_buf; ts = &ts_buf;
} }
futex_result = nsync_futex_wait_ ((atomic_int *)&f->i, i, futex_result = cosmo_futex_wait ((atomic_int *)&f->i, i,
PTHREAD_PROCESS_PRIVATE, PTHREAD_PROCESS_PRIVATE,
clock, ts); clock, ts);
ASSERT (futex_result == 0 || ASSERT (futex_result == 0 ||
@ -136,5 +138,5 @@ void nsync_mu_semaphore_v_futex (nsync_semaphore *s) {
(nsync_atomic_uint32_ *) &f->i, &old_value, old_value+1, (nsync_atomic_uint32_ *) &f->i, &old_value, old_value+1,
memory_order_release, memory_order_relaxed)) { memory_order_release, memory_order_relaxed)) {
} }
ASSERT (nsync_futex_wake_ ((atomic_int *)&f->i, 1, PTHREAD_PROCESS_PRIVATE) >= 0); ASSERT (cosmo_futex_wake ((atomic_int *)&f->i, 1, PTHREAD_PROCESS_PRIVATE) >= 0);
} }

View file

@ -23,7 +23,7 @@
#if KMP_USE_FUTEX #if KMP_USE_FUTEX
#ifdef __COSMOPOLITAN__ #ifdef __COSMOPOLITAN__
#include "third_party/nsync/futex.internal.h" #include <cosmo.h>
#else #else
#include <sys/syscall.h> #include <sys/syscall.h>
#include <unistd.h> #include <unistd.h>
@ -380,7 +380,7 @@ __kmp_acquire_futex_lock_timed_template(kmp_futex_lock_t *lck, kmp_int32 gtid) {
long rc; long rc;
#ifdef __COSMOPOLITAN__ #ifdef __COSMOPOLITAN__
if ((rc = nsync_futex_wait_((int *)&(lck->lk.poll), poll_val, false, 0, NULL)) != 0) { if ((rc = cosmo_futex_wait((int *)&(lck->lk.poll), poll_val, false, 0, NULL)) != 0) {
#else #else
if ((rc = syscall(__NR_futex, (int *)&(lck->lk.poll), FUTEX_WAIT, poll_val, NULL, if ((rc = syscall(__NR_futex, (int *)&(lck->lk.poll), FUTEX_WAIT, poll_val, NULL,
NULL, 0)) != 0) { NULL, 0)) != 0) {
@ -462,7 +462,7 @@ int __kmp_release_futex_lock(kmp_futex_lock_t *lck, kmp_int32 gtid) {
("__kmp_release_futex_lock: lck:%p, T#%d futex_wake 1 thread\n", ("__kmp_release_futex_lock: lck:%p, T#%d futex_wake 1 thread\n",
lck, gtid)); lck, gtid));
#ifdef __COSMOPOLITAN__ #ifdef __COSMOPOLITAN__
nsync_futex_wake_((int *)&(lck->lk.poll), 1, false); cosmo_futex_wake((int *)&(lck->lk.poll), 1, false);
#else #else
syscall(__NR_futex, &(lck->lk.poll), FUTEX_WAKE, KMP_LOCK_BUSY(1, futex), syscall(__NR_futex, &(lck->lk.poll), FUTEX_WAKE, KMP_LOCK_BUSY(1, futex),
NULL, NULL, 0); NULL, NULL, 0);