Make improvements

- Document redbean's argon2 module
- Fix regressions in cthreads library
- Make testlib work better with threads
- Give the cthreads library lots of love
- Remove some of the stdio assembly code
- Implement getloadavg() across platforms
- Code size optimizations for errnos, etc.
- Only check for signals in main thread on Windows
- Make errnos for dup2 / dup3 consistent with posix

This change also fixes a bug in the argon2 module, where the NUL
terminator was being included in the hash encoded ascii string. This
shouldn't require any database migrations to folks who found this module
and productionized it, since the argon2 library treats it as a c string.
This commit is contained in:
Justine Tunney 2022-05-27 13:25:46 -07:00
parent cb67223051
commit de5de19004
234 changed files with 1728 additions and 1993 deletions

View file

@ -17,15 +17,16 @@
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/errno.h"
#include "libc/runtime/stack.h"
#include "libc/thread/attr.h"
#define MIN_STACKSIZE (8 * PAGESIZE)
#define MIN_STACKSIZE (8 * PAGESIZE) // includes guard, rounds up to FRAMESIZE
#define MIN_GUARDSIZE PAGESIZE
// CTOR/DTOR
int cthread_attr_init(cthread_attr_t* attr) {
attr->stacksize = 1024 * PAGESIZE; // 4 MiB
attr->guardsize = 16 * PAGESIZE; // 64 KiB
attr->stacksize = GetStackSize();
attr->guardsize = PAGESIZE;
attr->mode = CTHREAD_CREATE_JOINABLE;
return 0;
}

View file

@ -16,108 +16,117 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/bits/atomic.h"
#include "libc/calls/calls.h"
#include "libc/calls/strace.internal.h"
#include "libc/errno.h"
#include "libc/linux/clone.h"
#include "libc/intrin/setjmp.internal.h"
#include "libc/macros.internal.h"
#include "libc/runtime/internal.h"
#include "libc/runtime/runtime.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/clone.h"
#include "libc/sysv/consts/map.h"
#include "libc/sysv/consts/nr.h"
#include "libc/sysv/consts/prot.h"
#include "libc/thread/attr.h"
#include "libc/thread/create.h"
#include "libc/thread/descriptor.h"
#include "libc/thread/zombie.h"
STATIC_YOINK("_main_thread_ctor");
// TLS boundaries
extern char _tbss_start, _tbss_end, _tdata_start, _tdata_end;
static cthread_t _thread_allocate(const cthread_attr_t* attr) {
size_t stacksize = attr->stacksize;
size_t guardsize = attr->guardsize;
size_t tbsssize = &_tbss_end - &_tbss_start;
size_t tdatasize = &_tdata_end - &_tdata_start;
size_t tlssize = tbsssize + tdatasize;
size_t totalsize =
3 * guardsize + stacksize + tlssize + sizeof(struct cthread_descriptor_t);
totalsize = (totalsize + PAGESIZE - 1) & -PAGESIZE;
uintptr_t mem = (uintptr_t)mmap(NULL, totalsize, PROT_NONE,
MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
if (mem == -1) return NULL;
void* alloc_bottom = (void*)mem;
void* stack_bottom = (void*)(mem + guardsize);
void* stack_top = (void*)(mem + guardsize + stacksize);
void* tls_bottom = (void*)(mem + guardsize + stacksize + guardsize);
void* tls_top = (void*)(mem + totalsize - guardsize);
void* alloc_top = (void*)(mem + totalsize);
if (mprotect(stack_bottom, (uintptr_t)stack_top - (uintptr_t)stack_bottom,
PROT_READ | PROT_WRITE) != 0 ||
mprotect(tls_bottom, (uintptr_t)tls_top - (uintptr_t)tls_bottom,
PROT_READ | PROT_WRITE) != 0) {
munmap(alloc_bottom, totalsize);
return NULL;
static cthread_t cthread_allocate(const cthread_attr_t *attr) {
char *mem;
size_t size;
cthread_t td;
size = ROUNDUP(
attr->stacksize +
ROUNDUP((uintptr_t)_tls_size + sizeof(struct cthread_descriptor_t),
PAGESIZE),
FRAMESIZE);
mem = mmap(0, size, PROT_READ | PROT_WRITE, MAP_STACK | MAP_ANONYMOUS, -1, 0);
if (mem == MAP_FAILED) return 0;
if (attr->guardsize > PAGESIZE) {
mprotect(mem, attr->guardsize, PROT_NONE);
}
cthread_t td = (cthread_t)tls_top - 1;
td = (cthread_t)(mem + size - sizeof(struct cthread_descriptor_t));
td->self = td;
td->stack.top = stack_top;
td->stack.bottom = stack_bottom;
td->tls.top = tls_top;
td->tls.bottom = tls_bottom;
td->alloc.top = alloc_top;
td->alloc.bottom = alloc_bottom;
td->state = (attr->mode & CTHREAD_CREATE_DETACHED) ? cthread_detached
: cthread_started;
td->self2 = td;
td->err = errno;
td->tid = -1;
td->stack.bottom = mem;
td->stack.top = mem + attr->stacksize;
td->alloc.bottom = mem;
td->alloc.top = mem + size;
if (attr->mode & CTHREAD_CREATE_DETACHED) {
td->state = cthread_detached;
} else {
td->state = cthread_started;
}
// Initialize TLS with content of .tdata section
memmove((void*)((uintptr_t)td - tlssize), &_tdata_start, tdatasize);
memmove((void *)((intptr_t)td - (intptr_t)_tls_size), _tdata_start,
(intptr_t)_tdata_size);
return td;
}
int cthread_create(cthread_t* restrict p, const cthread_attr_t* restrict attr,
int (*func)(void*), void* restrict arg) {
extern wontreturn void _thread_run(int (*func)(void*), void* arg);
cthread_attr_t default_attr;
cthread_attr_init(&default_attr);
cthread_t td = _thread_allocate(attr ? attr : &default_attr);
cthread_attr_destroy(&default_attr);
if (!td) return errno;
*p = td;
register cthread_t td_ asm("r8") = td;
register int* ptid_ asm("rdx") = &td->tid;
register int* ctid_ asm("r10") = &td->tid;
register int (*func_)(void*) asm("r12") = func;
register void* arg_ asm("r13") = arg;
long flags = CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND |
CLONE_PARENT | CLONE_THREAD | /*CLONE_IO |*/ CLONE_SETTLS |
CLONE_PARENT_SETTID | CLONE_CHILD_CLEARTID;
int rc;
// asm ensures the (empty) stack of the child thread is not used
asm volatile("syscall\n\t" // clone
"test\t%0, %0\n\t" // if not child
"jne\t.L.cthread_create.%=\n\t" // jump to `parent` label
"xor\t%%rbp, %%rbp\n\t" // reset stack frame pointer
"mov\t%2, %%rdi\n\t"
"call\t*%1\n\t" // call `func(arg)`
"mov\t%%rax, %%rdi\n\t"
"jmp\tcthread_exit\n" // exit thread
".L.cthread_create.%=:"
: "=a"(rc)
: "r"(func_), "r"(arg_), "0"(__NR_clone), "D"(flags),
"S"(td->stack.top), "r"(ptid_), "r"(ctid_), "r"(td_)
: "rcx", "r11", "cc", "memory");
if (__builtin_expect(rc < 0, 0)) {
// `clone` has failed. The thread must be deallocated.
size_t size = (intptr_t)(td->alloc.top) - (intptr_t)(td->alloc.bottom);
munmap(td->alloc.bottom, size);
return -rc;
static int cthread_start(void *arg) {
axdx_t rc;
void *exitcode;
cthread_t td = arg;
if (!(rc = setlongerjmp(td->exiter)).ax) {
exitcode = td->func(td->arg);
} else {
exitcode = (void *)rc.dx;
}
td->exitcode = exitcode;
if (atomic_load(&td->state) & cthread_detached) {
// we're still using the stack
// thus we can't munmap it yet
// kick the can down the road!
cthread_zombies_add(td);
}
atomic_fetch_add(&td->state, cthread_finished);
return 0;
}
/**
* Creates thread.
*
* @param ptd will receive pointer to new thread descriptor
* @param attr contains special configuration if non-null
* @param func is thread callback function
* @param arg is argument supplied to `func`
* @return 0 on success, or error number on failure
* @threadsafe
*/
int cthread_create(cthread_t *restrict ptd, const cthread_attr_t *restrict attr,
void *(*func)(void *), void *restrict arg) {
int rc, tid;
cthread_t td;
cthread_attr_t default_attr;
cthread_zombies_reap();
cthread_attr_init(&default_attr);
if ((td = cthread_allocate(attr ? attr : &default_attr))) {
td->func = func;
td->arg = arg;
cthread_attr_destroy(&default_attr);
tid =
clone(cthread_start, td->stack.bottom, td->stack.top - td->stack.bottom,
CLONE_THREAD | CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND |
CLONE_SETTLS | CLONE_CHILD_SETTID | CLONE_CHILD_CLEARTID,
td, 0, td, sizeof(struct cthread_descriptor_t), &td->tid);
if (tid != -1) {
*ptd = td;
rc = 0;
} else {
rc = errno;
munmap(td->alloc.bottom, td->alloc.top - td->alloc.bottom);
}
} else {
rc = errno;
tid = -1;
}
STRACE("cthread_create([%d], %p, %p, %p) → %s", tid, attr, func, arg,
!rc ? "0" : strerrno(rc));
return rc;
}

View file

@ -9,8 +9,8 @@ COSMOPOLITAN_C_START_
* @fileoverview Create a cosmopolitan thread
*/
int cthread_create(cthread_t* restrict, const cthread_attr_t* restrict,
int (*)(void*), void* restrict);
int cthread_create(cthread_t *restrict, const cthread_attr_t *restrict,
void *(*)(void *), void *restrict);
COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */

View file

@ -2,6 +2,7 @@
#define COSMOPOLITAN_LIBC_THREAD_DESCRIPTOR_H_
#if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_
#include "libc/runtime/runtime.h"
/**
* @fileoverview thread types
@ -16,17 +17,24 @@ enum cthread_state {
};
struct cthread_descriptor_t {
struct cthread_descriptor_t* self; /* mandatory for TLS */
struct cthread_descriptor_t *self; /* 0x00 */
void *(*func)(void *); /* 0x08 */
int32_t __pad0; /* 0x10 */
int32_t state; /* 0x14 */
void *arg; /* 0x18 */
void *pthread_ret_ptr; /* 0x20 */
int64_t __pad1; /* 0x28 */
struct cthread_descriptor_t *self2; /* 0x30 */
int32_t tid; /* 0x38 */
int32_t err; /* 0x3c */
void *exitcode;
struct {
void *top, *bottom;
} stack, tls, alloc;
int state;
int tid;
int rc;
void* pthread_ret_ptr;
char *top, *bottom;
} stack, alloc;
jmp_buf exiter;
};
typedef struct cthread_descriptor_t* cthread_t;
typedef struct cthread_descriptor_t *cthread_t;
COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */

View file

@ -16,19 +16,44 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/runtime/runtime.h"
#include "libc/bits/atomic.h"
#include "libc/calls/calls.h"
#include "libc/calls/strace.internal.h"
#include "libc/errno.h"
#include "libc/intrin/asan.internal.h"
#include "libc/str/str.h"
#include "libc/thread/descriptor.h"
#include "libc/thread/detach.h"
/**
* Detaches thread.
*
* Calling this function will cause the thread to free its own memory
* once it exits. Using this function is mutually exclusive from the
* chtread_join() API.
*
* @return 0 on success or errno number on failure
* @raises EINVAL if thread isn't joinable
* @raises ESRCH if no such thread exists
* @threadsafe
*/
int cthread_detach(cthread_t td) {
int state;
asm volatile("lock xadd\t%1, %0"
: "+m"(td->state), "=r"(state)
: "1"(cthread_detached)
: "cc");
if ((state & cthread_finished)) {
size_t size = (intptr_t)(td->alloc.top) - (intptr_t)(td->alloc.bottom);
munmap(td->alloc.bottom, size);
int rc, tid;
if (!td || (IsAsan() && !__asan_is_valid(td, sizeof(*td)))) {
rc = ESRCH;
tid = -1;
} else if ((tid = td->tid) == gettid()) {
rc = EDEADLK;
} else if (atomic_load(&td->state) & (cthread_detached | cthread_joining)) {
rc = EINVAL;
} else if (!atomic_fetch_add(&td->state, cthread_detached) &
cthread_finished) {
rc = 0;
} else if (!munmap(td->alloc.bottom, td->alloc.top - td->alloc.bottom)) {
rc = 0;
} else {
rc = errno;
}
return 0;
STRACE("cthread_detached(%d) → %s", tid, !rc ? "0" : strerrno(rc));
return rc;
}

View file

@ -17,30 +17,18 @@
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/calls/strace.internal.h"
#include "libc/calls/syscall-sysv.internal.h"
#include "libc/intrin/lockxadd.h"
#include "libc/runtime/runtime.h"
#include "libc/thread/descriptor.h"
#include "libc/thread/exit.h"
#include "libc/thread/self.h"
/**
* Exits cosmopolitan thread.
*
* @param rc is exit code
* @see _Exit1() for the raw system call
* @param exitcode is passed along to cthread_join()
* @threadsafe
* @noreturn
*/
wontreturn void cthread_exit(int rc) {
cthread_t td;
STRACE("cthread_exit(%d)", rc);
td = cthread_self();
td->rc = rc;
_lockxadd(&td->state, cthread_finished);
if (~td->state & cthread_detached) {
sys_munmap(td->alloc.bottom,
(intptr_t)td->alloc.top - (intptr_t)td->alloc.bottom);
}
_Exit1(rc);
wontreturn void cthread_exit(void *exitcode) {
STRACE("cthread_exit(%p)", exitcode);
longerjmp(cthread_self()->exiter, (intptr_t)exitcode);
}

View file

@ -3,11 +3,7 @@
#if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_
/**
* @fileoverview exit the current thread
*/
wontreturn void cthread_exit(int);
wontreturn void cthread_exit(void *);
COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */

View file

@ -16,59 +16,59 @@
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/dce.h"
#include "libc/errno.h"
#include "libc/macros.internal.h"
#include "libc/nexgen32e/threaded.h"
#include "libc/runtime/internal.h"
#include "libc/runtime/runtime.h"
#include "libc/runtime/stack.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/map.h"
#include "libc/sysv/consts/nr.h"
#include "libc/sysv/consts/prot.h"
#include "libc/thread/descriptor.h"
// TLS boundaries
extern char _tbss_start, _tbss_end, _tdata_start, _tdata_end;
#include "libc/thread/self.h"
static textstartup void _main_thread_init(void) {
if (!IsLinux()) return; /* TODO */
size_t tbsssize = &_tbss_end - &_tbss_start;
size_t tdatasize = &_tdata_end - &_tdata_start;
size_t tlssize = tbsssize + tdatasize;
size_t totalsize = tlssize + sizeof(struct cthread_descriptor_t);
totalsize = (totalsize + PAGESIZE - 1) & -PAGESIZE;
_Static_assert(offsetof(struct cthread_descriptor_t, self) == 0x00, "");
_Static_assert(offsetof(struct cthread_descriptor_t, self2) == 0x30, "");
_Static_assert(offsetof(struct cthread_descriptor_t, tid) == 0x38, "");
_Static_assert(offsetof(struct cthread_descriptor_t, err) == 0x3c, "");
cthread_t td;
size_t totalsize;
char *mem, *bottom, *top;
uintptr_t mem = (uintptr_t)mmap(NULL, totalsize, PROT_READ | PROT_WRITE,
MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
if (mem == -1) {
abort();
}
totalsize = ROUNDUP(
(uintptr_t)_tls_size + sizeof(struct cthread_descriptor_t), FRAMESIZE);
void* bottom = (void*)mem;
void* top = (void*)(mem + totalsize);
mem = mmap(0, totalsize, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE,
-1, 0);
assert(mem != MAP_FAILED);
cthread_t td = (cthread_t)top - 1;
bottom = mem;
top = mem + totalsize;
td = (cthread_t)(top - sizeof(struct cthread_descriptor_t));
td->self = td;
td->stack.top = NULL;
td->stack.bottom = NULL;
td->tls.top = top;
td->tls.bottom = bottom;
td->alloc.top = top;
td->self2 = td;
td->err = errno;
td->tid = gettid();
td->alloc.bottom = bottom;
td->alloc.top = top;
td->stack.bottom = GetStackAddr(0);
td->stack.top = td->stack.bottom + GetStackSize();
td->state = cthread_main;
// Initialize TLS with content of .tdata section
memmove((void*)((uintptr_t)td - tlssize), &_tdata_start, tdatasize);
// Get TID of main thread
int gettid = __NR_gettid;
if (gettid == 0xfff) gettid = __NR_getpid;
td->tid = syscall(gettid);
memmove((void *)((uintptr_t)td - (uintptr_t)_tls_size), _tdata_start,
(uintptr_t)_tdata_size);
// Set FS
if (arch_prctl(ARCH_SET_FS, td) != 0) {
abort();
}
__install_tls((char *)td);
assert(cthread_self()->tid == gettid());
}
const void* const _main_thread_ctor[] initarray = {
const void *const _main_thread_ctor[] initarray = {
_main_thread_init,
};

View file

@ -16,38 +16,61 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/bits/atomic.h"
#include "libc/calls/calls.h"
#include "libc/calls/strace.internal.h"
#include "libc/dce.h"
#include "libc/errno.h"
#include "libc/intrin/asan.internal.h"
#include "libc/intrin/spinlock.h"
#include "libc/runtime/runtime.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/futex.h"
#include "libc/sysv/consts/nr.h"
#include "libc/thread/descriptor.h"
#include "libc/thread/join.h"
int cthread_join(cthread_t td, int* rc) {
int tid = td->tid; // tid must be loaded before lock xadd
// otherwise, tid could be set to 0 even though `state` is not finished
// mark thread as joining
int state;
asm volatile("lock xadd\t%1, %0"
: "+m"(td->state), "=r"(state)
: "1"(cthread_joining)
: "cc");
if (!(state & cthread_finished)) {
int ax;
int flags = FUTEX_WAIT; // PRIVATE makes it hang
struct timespec* timeout = NULL;
asm volatile("mov\t%5,%%r10\n\t" // timeout
"syscall"
: "=a"(ax)
: "0"(__NR_futex), "D"(&td->tid), "S"(flags), "d"(tid),
"g"(timeout)
: "rcx", "r10", "r11", "cc", "memory");
/**
* Waits for thread to terminate and frees its memory.
*
* @param td is thread descriptor memory
* @param exitcode optionally receives value returned by thread
* @return 0 on success, or error number on failure
* @raises EDEADLK when trying to join this thread
* @raises EINVAL if another thread is joining
* @raises ESRCH if no such thread exists
* @raises EINVAL if not joinable
* @threadsafe
*/
int cthread_join(cthread_t td, void **exitcode) {
int rc, tid;
// otherwise, tid could be set to 0 even though `state` is not
// finished mark thread as joining
if (!td || (IsAsan() && !__asan_is_valid(td, sizeof(*td)))) {
rc = ESRCH;
tid = -1;
} else if ((tid = td->tid) == gettid()) { // tid must load before lock xadd
rc = EDEADLK;
} else if (atomic_load(&td->state) & (cthread_detached | cthread_joining)) {
rc = EINVAL;
} else {
if (~atomic_fetch_add(&td->state, cthread_joining) & cthread_finished) {
if (IsLinux() || IsOpenbsd()) {
// FUTEX_WAIT_PRIVATE makes it hang
futex((uint32_t *)&td->tid, FUTEX_WAIT, tid, 0, 0);
}
_spinlock(&td->tid);
}
if (exitcode) {
*exitcode = td->exitcode;
}
if (!munmap(td->alloc.bottom, td->alloc.top - td->alloc.bottom)) {
rc = 0;
} else {
rc = errno;
}
}
*rc = td->rc;
size_t size = (intptr_t)(td->alloc.top) - (intptr_t)(td->alloc.bottom);
munmap(td->alloc.bottom, size);
return 0;
STRACE("cthread_join(%d, [%p]) → %s", tid, !rc && exitcode ? *exitcode : 0,
!rc ? "0" : strerrno(rc));
return rc;
}

View file

@ -4,11 +4,7 @@
#if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_
/**
* @fileoverview join a thread
*/
int cthread_join(cthread_t, int*);
int cthread_join(cthread_t, void **);
COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */

View file

@ -1,7 +1,7 @@
/*-*- 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
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
@ -18,4 +18,11 @@
*/
#include "libc/thread/self.h"
extern inline cthread_t cthread_self(void);
STATIC_YOINK("_main_thread_ctor");
/**
* Returns thread descriptor of the current thread.
*/
cthread_t(cthread_self)(void) {
return cthread_self();
}

View file

@ -1,19 +1,18 @@
#ifndef COSMOPOLITAN_LIBC_THREAD_SELF_H_
#define COSMOPOLITAN_LIBC_THREAD_SELF_H_
#include "libc/nexgen32e/threaded.h"
#include "libc/thread/descriptor.h"
#if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_
/**
* @fileoverview get the thread descriptor of the current thread
*/
extern const void *const _main_thread_ctor[];
#if defined(__GNUC__) && !defined(__STRICT_ANSI__)
inline cthread_t cthread_self(void) {
cthread_t self;
asm("mov\t%%fs:0,%0" : "=r"(self));
return self;
}
#define cthread_self() \
({ \
YOINK(_main_thread_ctor); \
((cthread_t)__get_tls()); \
})
#else
cthread_t cthread_self(void);
#endif

View file

@ -21,6 +21,8 @@
#include "libc/thread/wait.h"
#include "libc/thread/yield.h"
STATIC_YOINK("_main_thread_ctor");
#define CTHREAD_THREAD_VAL_BITS 32
static void pause(int attempt) {
@ -33,40 +35,45 @@ static void pause(int attempt) {
}
}
/**
* Initializes semaphore.
*/
int cthread_sem_init(cthread_sem_t* sem, int count) {
sem->linux.count = count;
return 0;
}
/**
* Destroys semaphore.
*/
int cthread_sem_destroy(cthread_sem_t* sem) {
(void)sem;
return 0;
}
/**
* Notifies a thread waiting on semaphore.
*/
int cthread_sem_signal(cthread_sem_t* sem) {
uint64_t count;
asm volatile("lock xadd\t%1,%0"
: "+m"(sem->linux.count), "=r"(count)
: "1"(1)
: "cc");
count = atomic_fetch_add(&sem->linux.count, 1);
if ((count >> CTHREAD_THREAD_VAL_BITS)) {
// WARNING: an offset of 4 bytes would be required on little-endian archs
void* wait_address = &sem->linux.count;
cthread_memory_wake32(wait_address, 1);
}
return 0;
}
/**
* Waits on semaphore with kernel assistance.
*/
int cthread_sem_wait_futex(cthread_sem_t* sem, const struct timespec* timeout) {
uint64_t count;
// record current thread as waiter
asm volatile("lock xadd\t%1,%0"
: "+m"(sem->linux.count), "=r"(count)
: "1"((uint64_t)1 << CTHREAD_THREAD_VAL_BITS)
: "cc");
count = atomic_fetch_add(&sem->linux.count,
(uint64_t)1 << CTHREAD_THREAD_VAL_BITS);
for (;;) {
// try to acquire the semaphore, as well as remove itself from waiters
@ -88,6 +95,9 @@ int cthread_sem_wait_futex(cthread_sem_t* sem, const struct timespec* timeout) {
return 0;
}
/**
* Waits on semaphore without kernel assistance.
*/
int cthread_sem_wait_spin(cthread_sem_t* sem, uint64_t count, int spin,
const struct timespec* timeout) {
// spin on pause
@ -106,6 +116,9 @@ int cthread_sem_wait_spin(cthread_sem_t* sem, uint64_t count, int spin,
return cthread_sem_wait_futex(sem, timeout);
}
/**
* Waits on semaphore.
*/
int cthread_sem_wait(cthread_sem_t* sem, int spin,
const struct timespec* timeout) {
uint64_t count = atomic_load(&sem->linux.count);

View file

@ -1,5 +1,6 @@
#ifndef COSMOPOLITAN_LIBC_THREAD_NATIVESEM_H_
#define COSMOPOLITAN_LIBC_THREAD_NATIVESEM_H_
#include "libc/calls/struct/timespec.h"
#if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_
@ -13,16 +14,11 @@ typedef union cthread_sem_t {
} linux;
} cthread_sem_t;
struct timespec;
int cthread_sem_init(cthread_sem_t*, int);
int cthread_sem_destroy(cthread_sem_t*);
int cthread_sem_wait(cthread_sem_t*, int, const struct timespec*);
int cthread_sem_signal(cthread_sem_t*);
int cthread_sem_init(cthread_sem_t *, int);
int cthread_sem_destroy(cthread_sem_t *);
int cthread_sem_wait(cthread_sem_t *, int, const struct timespec *);
int cthread_sem_signal(cthread_sem_t *);
COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
#endif /* COSMOPOLITAN_LIBC_THREAD_SELF_H_ */
#endif /* COSMOPOLITAN_LIBC_THREAD_SELF_H_ */

View file

@ -11,43 +11,43 @@ LIBC_THREAD_A_HDRS = $(filter %.h,$(LIBC_THREAD_A_FILES))
LIBC_THREAD_A_SRCS_S = $(filter %.S,$(LIBC_THREAD_A_FILES))
LIBC_THREAD_A_SRCS_C = $(filter %.c,$(LIBC_THREAD_A_FILES))
LIBC_THREAD_A_SRCS = \
$(LIBC_THREAD_A_SRCS_S) \
LIBC_THREAD_A_SRCS = \
$(LIBC_THREAD_A_SRCS_S) \
$(LIBC_THREAD_A_SRCS_C)
LIBC_THREAD_A_OBJS = \
LIBC_THREAD_A_OBJS = \
$(LIBC_THREAD_A_SRCS_S:%.S=o/$(MODE)/%.o) \
$(LIBC_THREAD_A_SRCS_C:%.c=o/$(MODE)/%.o)
LIBC_THREAD_A_CHECKS = \
$(LIBC_THREAD_A).pkg \
LIBC_THREAD_A_CHECKS = \
$(LIBC_THREAD_A).pkg \
$(LIBC_THREAD_A_HDRS:%=o/$(MODE)/%.ok)
LIBC_THREAD_A_DIRECTDEPS = \
LIBC_STUBS \
LIBC_CALLS \
LIBC_INTRIN \
LIBC_BITS \
LIBC_MEM \
LIBC_RUNTIME \
LIBC_SYSV \
LIBC_SYSV_CALLS \
LIBC_THREAD_A_DIRECTDEPS = \
LIBC_STUBS \
LIBC_CALLS \
LIBC_INTRIN \
LIBC_BITS \
LIBC_MEM \
LIBC_RUNTIME \
LIBC_SYSV \
LIBC_SYSV_CALLS \
LIBC_NEXGEN32E
LIBC_THREAD_A_DEPS := \
LIBC_THREAD_A_DEPS := \
$(call uniq,$(foreach x,$(LIBC_THREAD_A_DIRECTDEPS),$($(x))))
$(LIBC_THREAD_A): \
libc/thread/ \
$(LIBC_THREAD_A).pkg \
$(LIBC_THREAD_A): \
libc/thread/ \
$(LIBC_THREAD_A).pkg \
$(LIBC_THREAD_A_OBJS)
$(LIBC_THREAD_A).pkg: \
$(LIBC_THREAD_A_OBJS) \
$(LIBC_THREAD_A).pkg: \
$(LIBC_THREAD_A_OBJS) \
$(foreach x,$(LIBC_THREAD_A_DIRECTDEPS),$($(x)_A).pkg)
o/tinylinux/libc/thread/clone.o: \
OVERRIDE_CFLAGS += \
o/tinylinux/libc/thread/clone.o: \
OVERRIDE_CFLAGS += \
-ffunction-sections
LIBC_THREAD_LIBS = $(foreach x,$(LIBC_THREAD_ARTIFACTS),$($(x)))

View file

@ -16,35 +16,31 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/bits/atomic.h"
#include "libc/calls/calls.h"
#include "libc/dce.h"
#include "libc/sysv/consts/futex.h"
#include "libc/sysv/consts/nr.h"
#include "libc/thread/wait.h"
int cthread_memory_wait32(uint32_t* addr, uint32_t val,
const struct timespec* timeout) {
if (__NR_futex != 0xfff) {
int flags = FUTEX_WAIT;
int rc;
asm volatile("mov\t%5,%%r10\n\t" // timeout
"syscall"
: "=a"(rc)
: "0"(__NR_futex), "D"(addr), "S"(flags), "d"(val),
"g"(timeout)
: "rcx", "r10", "r11", "cc", "memory");
return rc;
if (IsLinux() || IsOpenbsd()) {
return futex(addr, FUTEX_WAIT, val, timeout, 0);
} else {
unsigned tries;
for (tries = 1; atomic_load(addr) == val; ++tries) {
if (tries & 7) {
__builtin_ia32_pause();
} else {
sched_yield();
}
}
return 0;
}
return -1;
}
int cthread_memory_wake32(uint32_t* addr, int n) {
if (__NR_futex != 0xfff) {
int flags = FUTEX_WAKE;
int rc;
asm volatile("syscall"
: "=a"(rc)
: "0"(__NR_futex), "D"(addr), "S"(flags), "d"(n)
: "rcx", "r11", "cc", "memory");
return rc;
if (IsLinux() || IsOpenbsd()) {
return futex(addr, FUTEX_WAKE, n, 0, 0);
}
return -1;
}

View file

@ -19,6 +19,9 @@
#include "libc/calls/calls.h"
#include "libc/thread/yield.h"
/**
* Asks operating system to handoff remaining time slice.
*/
int cthread_yield(void) {
return sched_yield();
}

50
libc/thread/zombie.c Normal file
View file

@ -0,0 +1,50 @@
/*-*- 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/bits/atomic.h"
#include "libc/mem/mem.h"
#include "libc/runtime/runtime.h"
#include "libc/thread/zombie.h"
static struct Zombie {
struct Zombie *next;
cthread_t td;
} * cthread_zombies;
void cthread_zombies_add(cthread_t td) {
struct Zombie *z;
if ((z = malloc(sizeof(struct Zombie)))) {
z->td = td;
z->next = atomic_load(&cthread_zombies);
for (;;) {
if (atomic_compare_exchange_weak(&cthread_zombies, &z->next, z)) {
break;
}
}
}
}
void cthread_zombies_reap(void) {
struct Zombie *z;
while ((z = atomic_load(&cthread_zombies)) && !atomic_load(&z->td->tid)) {
if (atomic_compare_exchange_weak(&cthread_zombies, &z, z->next)) {
munmap(z->td->alloc.bottom, z->td->alloc.top - z->td->alloc.bottom);
free(z);
}
}
}

12
libc/thread/zombie.h Normal file
View file

@ -0,0 +1,12 @@
#ifndef COSMOPOLITAN_LIBC_THREAD_ZOMBIE_H_
#define COSMOPOLITAN_LIBC_THREAD_ZOMBIE_H_
#include "libc/thread/descriptor.h"
#if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_
void cthread_zombies_add(cthread_t);
void cthread_zombies_reap(void);
COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
#endif /* COSMOPOLITAN_LIBC_THREAD_ZOMBIE_H_ */