diff --git a/Makefile b/Makefile index b6e93dea2df..6ae547ffbd8 100644 --- a/Makefile +++ b/Makefile @@ -114,6 +114,7 @@ include third_party/gdtoa/gdtoa.mk # │ You can finally call malloc() include libc/time/time.mk # │ include libc/alg/alg.mk # │ include libc/stdio/stdio.mk # │ +include libc/thread/thread.mk # │ include net/net.mk # │ include libc/log/log.mk # │ include third_party/bzip2/bzip2.mk # │ @@ -284,6 +285,7 @@ COSMOPOLITAN_OBJECTS = \ LIBC_NT_ADVAPI32 \ LIBC_FMT \ THIRD_PARTY_COMPILER_RT \ + LIBC_THREAD \ LIBC_TINYMATH \ LIBC_STR \ LIBC_SYSV \ @@ -311,6 +313,7 @@ COSMOPOLITAN_HEADERS = \ LIBC_STDIO \ LIBC_STR \ LIBC_SYSV \ + LIBC_THREAD \ LIBC_TIME \ LIBC_TINYMATH \ LIBC_UNICODE \ diff --git a/examples/examples.mk b/examples/examples.mk index fccdf1b5e99..210e3138803 100644 --- a/examples/examples.mk +++ b/examples/examples.mk @@ -61,6 +61,7 @@ EXAMPLES_DIRECTDEPS = \ LIBC_SYSV \ LIBC_SYSV_CALLS \ LIBC_TESTLIB \ + LIBC_THREAD \ LIBC_TIME \ LIBC_TINYMATH \ LIBC_UNICODE \ diff --git a/examples/thread.c b/examples/thread.c new file mode 100644 index 00000000000..06ae5791446 --- /dev/null +++ b/examples/thread.c @@ -0,0 +1,54 @@ +#if 0 +/*─────────────────────────────────────────────────────────────────╗ +│ To the extent possible under law, Justine Tunney has waived │ +│ all copyright and related or neighboring rights to this file, │ +│ as it is written in the following disclaimers: │ +│ • http://unlicense.org/ │ +│ • http://creativecommons.org/publicdomain/zero/1.0/ │ +╚─────────────────────────────────────────────────────────────────*/ +#endif +#include "libc/stdio/stdio.h" +#include "libc/thread/create.h" +#include "libc/thread/self.h" +#include "libc/thread/detach.h" +#include "libc/thread/join.h" +#include "libc/thread/nativesem.h" +#include "libc/time/time.h" + +cthread_native_sem_t semaphore; + +int worker(void* arg) { + cthread_native_sem_signal(&semaphore); + + cthread_t self = cthread_self(); + int tid = self->tid; + sleep(1); + //sleep(10000); + //printf("[%p] %d\n", self, tid); + (void)arg; + return 4; +} + +int main() { + cthread_native_sem_init(&semaphore, 0); + + cthread_t thread; + int rc = cthread_create(&thread, NULL, &worker, NULL); + if (rc == 0) { + cthread_native_sem_wait(&semaphore, 0, 0, NULL); + //printf("thread created: %p\n", thread); + sleep(1); +#if 1 + cthread_join(thread, &rc); +#else + rc = cthread_detach(thread); + sleep(2); +#endif + cthread_native_sem_signal(&semaphore); + cthread_native_sem_wait(&semaphore, 0, 0, NULL); + //printf("thread joined: %p -> %d\n", thread, rc); + } else { + printf("ERROR: thread could not be started: %d\n", rc); + } + return 0; +} diff --git a/libc/libc.mk b/libc/libc.mk index 91a0d9be02e..8540996062f 100644 --- a/libc/libc.mk +++ b/libc/libc.mk @@ -31,6 +31,7 @@ o/$(MODE)/libc: o/$(MODE)/libc/alg \ o/$(MODE)/libc/stubs \ o/$(MODE)/libc/sysv \ o/$(MODE)/libc/testlib \ + o/$(MODE)/libc/thread \ o/$(MODE)/libc/time \ o/$(MODE)/libc/tinymath \ o/$(MODE)/libc/unicode \ diff --git a/libc/linux/clone.h b/libc/linux/clone.h new file mode 100644 index 00000000000..9e5a862225f --- /dev/null +++ b/libc/linux/clone.h @@ -0,0 +1,17 @@ +#ifndef COSMOPOLITAN_LIBC_LINUX_CLONE_H_ +#define COSMOPOLITAN_LIBC_LINUX_CLONE_H_ +#if !(__ASSEMBLER__ + __LINKER__ + 0) + +forceinline long LinuxClone(unsigned long flags, void* stack, int* parent_tid, int* child_tid, void* tls) { + long rc; + register int* child_tid_ asm("r10") = child_tid; + register void* tls_ asm("r8") = tls; + asm volatile("syscall" + : "=a"(rc) + : "0"(56), "D"(flags), "S"(stack), "d"(parent_tid), "r"(child_tid_), "r"(tls_) + : "rcx", "r11", "memory"); + return rc; +} + +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_LINUX_MMAP_H_ */ diff --git a/libc/sysv/consts.sh b/libc/sysv/consts.sh index 64b8313ce62..c593852fe24 100755 --- a/libc/sysv/consts.sh +++ b/libc/sysv/consts.sh @@ -1928,13 +1928,13 @@ syscon misc DAY_5 0x02000b 11 11 10 10 0 syscon misc DAY_6 0x02000c 12 12 11 11 0 syscon misc DAY_7 0x02000d 13 13 12 12 0 -syscon misc FUTEX_PRIVATE_FLAG 0 0 0 0x80 0x80 0 -syscon misc FUTEX_REQUEUE 0 0 0 3 3 0 -syscon misc FUTEX_REQUEUE_PRIVATE 0 0 0 131 131 0 +syscon misc FUTEX_PRIVATE_FLAG 128 0 0 0x80 0x80 0 +syscon misc FUTEX_REQUEUE 3 0 0 3 3 0 +syscon misc FUTEX_REQUEUE_PRIVATE 131 0 0 131 131 0 syscon misc FUTEX_WAIT 0 0 0 1 1 0 -syscon misc FUTEX_WAIT_PRIVATE 0 0 0 129 129 0 -syscon misc FUTEX_WAKE 0 0 0 2 2 0 -syscon misc FUTEX_WAKE_PRIVATE 0 0 0 130 130 0 +syscon misc FUTEX_WAIT_PRIVATE 128 0 0 129 129 0 +syscon misc FUTEX_WAKE 1 0 0 2 2 0 +syscon misc FUTEX_WAKE_PRIVATE 129 0 0 130 130 0 syscon misc HOST_NOT_FOUND 1 1 1 1 1 0x2af9 # unix consensus syscon misc HOST_NAME_MAX 0x40 0 0 255 255 0 diff --git a/libc/sysv/consts/FUTEX_PRIVATE_FLAG.S b/libc/sysv/consts/FUTEX_PRIVATE_FLAG.S index 4f080ff9193..3700a2d9830 100644 --- a/libc/sysv/consts/FUTEX_PRIVATE_FLAG.S +++ b/libc/sysv/consts/FUTEX_PRIVATE_FLAG.S @@ -1,2 +1,2 @@ #include "libc/sysv/consts/syscon.internal.h" -.syscon misc,FUTEX_PRIVATE_FLAG,0,0,0,0x80,0x80,0 +.syscon misc,FUTEX_PRIVATE_FLAG,128,0,0,0x80,0x80,0 diff --git a/libc/sysv/consts/FUTEX_REQUEUE.S b/libc/sysv/consts/FUTEX_REQUEUE.S index ca41403a2f7..f8f1f0b9e21 100644 --- a/libc/sysv/consts/FUTEX_REQUEUE.S +++ b/libc/sysv/consts/FUTEX_REQUEUE.S @@ -1,2 +1,2 @@ #include "libc/sysv/consts/syscon.internal.h" -.syscon misc,FUTEX_REQUEUE,0,0,0,3,3,0 +.syscon misc,FUTEX_REQUEUE,3,0,0,3,3,0 diff --git a/libc/sysv/consts/FUTEX_REQUEUE_PRIVATE.S b/libc/sysv/consts/FUTEX_REQUEUE_PRIVATE.S index 26f1d5edd4a..97a3ad3dfa2 100644 --- a/libc/sysv/consts/FUTEX_REQUEUE_PRIVATE.S +++ b/libc/sysv/consts/FUTEX_REQUEUE_PRIVATE.S @@ -1,2 +1,2 @@ #include "libc/sysv/consts/syscon.internal.h" -.syscon misc,FUTEX_REQUEUE_PRIVATE,0,0,0,131,131,0 +.syscon misc,FUTEX_REQUEUE_PRIVATE,131,0,0,131,131,0 diff --git a/libc/sysv/consts/FUTEX_WAIT_PRIVATE.S b/libc/sysv/consts/FUTEX_WAIT_PRIVATE.S index 8f9d0f41174..01189de7034 100644 --- a/libc/sysv/consts/FUTEX_WAIT_PRIVATE.S +++ b/libc/sysv/consts/FUTEX_WAIT_PRIVATE.S @@ -1,2 +1,2 @@ #include "libc/sysv/consts/syscon.internal.h" -.syscon misc,FUTEX_WAIT_PRIVATE,0,0,0,129,129,0 +.syscon misc,FUTEX_WAIT_PRIVATE,128,0,0,129,129,0 diff --git a/libc/sysv/consts/FUTEX_WAKE.S b/libc/sysv/consts/FUTEX_WAKE.S index e4d3eeb8ae8..5342bb7c319 100644 --- a/libc/sysv/consts/FUTEX_WAKE.S +++ b/libc/sysv/consts/FUTEX_WAKE.S @@ -1,2 +1,2 @@ #include "libc/sysv/consts/syscon.internal.h" -.syscon misc,FUTEX_WAKE,0,0,0,2,2,0 +.syscon misc,FUTEX_WAKE,1,0,0,2,2,0 diff --git a/libc/sysv/consts/FUTEX_WAKE_PRIVATE.S b/libc/sysv/consts/FUTEX_WAKE_PRIVATE.S index e5db0fd8471..1411c75928d 100644 --- a/libc/sysv/consts/FUTEX_WAKE_PRIVATE.S +++ b/libc/sysv/consts/FUTEX_WAKE_PRIVATE.S @@ -1,2 +1,2 @@ #include "libc/sysv/consts/syscon.internal.h" -.syscon misc,FUTEX_WAKE_PRIVATE,0,0,0,130,130,0 +.syscon misc,FUTEX_WAKE_PRIVATE,129,0,0,130,130,0 diff --git a/libc/thread/attr.c b/libc/thread/attr.c new file mode 100644 index 00000000000..506ee800a0c --- /dev/null +++ b/libc/thread/attr.c @@ -0,0 +1,67 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ Permission to use, copy, modify, and/or distribute this software for │ +│ any purpose with or without fee is hereby granted, provided that the │ +│ above copyright notice and this permission notice appear in all copies. │ +│ │ +│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ +│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ +│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ +│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ +│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ +│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ +│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ +│ PERFORMANCE OF THIS SOFTWARE. │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/thread/attr.h" +#include "libc/errno.h" + +#define MIN_STACKSIZE (8*PAGESIZE) +#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->mode = CTHREAD_CREATE_JOINABLE; + return 0; +} +int cthread_attr_destroy(cthread_attr_t* attr) { + (void)attr; + return 0; +} + +// stacksize +int cthread_attr_setstacksize(cthread_attr_t* attr, size_t size) { + if (size & (PAGESIZE-1)) return EINVAL; + if (size < MIN_STACKSIZE) return EINVAL; + attr->stacksize = size; + return 0; +} +size_t cthread_attr_getstacksize(const cthread_attr_t* attr) { + return attr->stacksize; +} + +// guardsize +int cthread_attr_setguardsize(cthread_attr_t* attr, size_t size) { + if (size & (PAGESIZE-1)) return EINVAL; + if (size < MIN_GUARDSIZE) return EINVAL; + attr->guardsize = size; + return 0; +} +size_t cthread_attr_getguardsize(const cthread_attr_t* attr) { + return attr->guardsize; +} + +// detachstate +int cthread_attr_setdetachstate(cthread_attr_t* attr, int mode) { + if (mode & ~(CTHREAD_CREATE_JOINABLE | CTHREAD_CREATE_DETACHED)) return EINVAL; + attr->mode = mode; + return 0; +} +int cthread_attr_getdetachstate(const cthread_attr_t* attr) { + return attr->mode; +} diff --git a/libc/thread/attr.h b/libc/thread/attr.h new file mode 100644 index 00000000000..cc682d530c5 --- /dev/null +++ b/libc/thread/attr.h @@ -0,0 +1,36 @@ +#ifndef COSMOPOLITAN_LIBC_THREAD_ATTR_H_ +#define COSMOPOLITAN_LIBC_THREAD_ATTR_H_ +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +/** + * @fileoverview cosmopolitan thread attributes + */ + +#define CTHREAD_CREATE_DETACHED 1 +#define CTHREAD_CREATE_JOINABLE 0 + +typedef struct cthread_attr_t { + size_t stacksize, guardsize; + int mode; +} cthread_attr_t; + +// CTOR/DTOR +int cthread_attr_init(cthread_attr_t*); +int cthread_attr_destroy(cthread_attr_t*); + +// stacksize +int cthread_attr_setstacksize(cthread_attr_t*, size_t); +size_t thread_attr_getstacksize(const cthread_attr_t*); + +// guardsize +int cthread_attr_setguardsize(cthread_attr_t*, size_t); +size_t cthread_attr_getguardsize(const cthread_attr_t*); + +// detachstate +int cthread_attr_setdetachstate(cthread_attr_t*, int); +int cthread_attr_getdetachstate(const cthread_attr_t*); + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_THREAD_ATTR_H_ */ diff --git a/libc/thread/create.c b/libc/thread/create.c new file mode 100644 index 00000000000..2f1c570931a --- /dev/null +++ b/libc/thread/create.c @@ -0,0 +1,108 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ Permission to use, copy, modify, and/or distribute this software for │ +│ any purpose with or without fee is hereby granted, provided that the │ +│ above copyright notice and this permission notice appear in all copies. │ +│ │ +│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ +│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ +│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ +│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ +│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ +│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ +│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ +│ PERFORMANCE OF THIS SOFTWARE. │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/thread/create.h" +#include "libc/linux/clone.h" +#include "libc/runtime/runtime.h" +#include "libc/sysv/consts/nr.h" +#include "libc/sysv/consts/clone.h" +#include "libc/sysv/consts/map.h" +#include "libc/sysv/consts/prot.h" +#include "libc/errno.h" + + +static cthread_t _thread_allocate(const cthread_attr_t* attr) { + size_t stacksize = attr->stacksize; + size_t guardsize = attr->guardsize; + // FIXME: properly count TLS size + size_t tlssize = 0; + + 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; + } + + cthread_t td = (cthread_t)tls_top - 1; + 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; + + 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; + } + return 0; +} diff --git a/libc/thread/create.h b/libc/thread/create.h new file mode 100644 index 00000000000..b6e77b95093 --- /dev/null +++ b/libc/thread/create.h @@ -0,0 +1,17 @@ +#ifndef COSMOPOLITAN_LIBC_THREAD_CREATE_H_ +#define COSMOPOLITAN_LIBC_THREAD_CREATE_H_ +#include "libc/thread/attr.h" +#include "libc/thread/descriptor.h" +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +/** + * @fileoverview Create a cosmopolitan thread + */ + +int cthread_create(cthread_t*restrict, const cthread_attr_t*restrict, int (*)(void*), void*restrict); + + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_THREAD_CREATE_H_ */ diff --git a/libc/thread/descriptor.h b/libc/thread/descriptor.h new file mode 100644 index 00000000000..793590ffd0c --- /dev/null +++ b/libc/thread/descriptor.h @@ -0,0 +1,33 @@ +#ifndef COSMOPOLITAN_LIBC_THREAD_DESCRIPTOR_H_ +#define COSMOPOLITAN_LIBC_THREAD_DESCRIPTOR_H_ +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +/** + * @fileoverview thread types + */ + +enum cthread_state { + cthread_started = 0, + cthread_joining = 1, + cthread_finished = 2, + cthread_detached = 4, +}; + + +struct cthread_descriptor_t { + struct cthread_descriptor_t* self; // mandatory for TLS + struct { + void *top, *bottom; + } stack, tls, alloc; + int state; + int tid; + int rc; + void* pthread_ret_ptr; +}; + +typedef struct cthread_descriptor_t* cthread_t; + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_THREAD_DESCRIPTOR_H_ */ diff --git a/libc/thread/detach.c b/libc/thread/detach.c new file mode 100644 index 00000000000..a6defc26289 --- /dev/null +++ b/libc/thread/detach.c @@ -0,0 +1,31 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ Permission to use, copy, modify, and/or distribute this software for │ +│ any purpose with or without fee is hereby granted, provided that the │ +│ above copyright notice and this permission notice appear in all copies. │ +│ │ +│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ +│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ +│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ +│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ +│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ +│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ +│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ +│ PERFORMANCE OF THIS SOFTWARE. │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/thread/detach.h" +#include "libc/thread/descriptor.h" +#include "libc/runtime/runtime.h" + +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); + } + return 0; +} diff --git a/libc/thread/detach.h b/libc/thread/detach.h new file mode 100644 index 00000000000..4185792f565 --- /dev/null +++ b/libc/thread/detach.h @@ -0,0 +1,16 @@ +#ifndef COSMOPOLITAN_LIBC_THREAD_DETACH_H_ +#define COSMOPOLITAN_LIBC_THREAD_DETACH_H_ +#include "libc/thread/descriptor.h" +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +/** + * @fileoverview detach a thread + */ + +int cthread_detach(cthread_t); + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_THREAD_DETACH_H_ */ + diff --git a/libc/thread/exit.c b/libc/thread/exit.c new file mode 100644 index 00000000000..f1226074424 --- /dev/null +++ b/libc/thread/exit.c @@ -0,0 +1,44 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ Permission to use, copy, modify, and/or distribute this software for │ +│ any purpose with or without fee is hereby granted, provided that the │ +│ above copyright notice and this permission notice appear in all copies. │ +│ │ +│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ +│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ +│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ +│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ +│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ +│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ +│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ +│ PERFORMANCE OF THIS SOFTWARE. │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/thread/exit.h" +#include "libc/thread/self.h" +#include "libc/thread/descriptor.h" +#include "libc/sysv/consts/nr.h" + +wontreturn void cthread_exit(int rc) { + cthread_t td = cthread_self(); + td->rc = rc; + size_t size = (intptr_t)(td->alloc.top) - (intptr_t)(td->alloc.bottom); + + int state; + asm volatile( + "lock xadd\t%1, %0\n\t" // mark thread as finished + "test\t%2, %b1\n\t" // test if thread was detached + "jz .L.cthread_exit.%=\n\t" // skip unmap if not detached + "syscall\n" // unmap thread + ".L.cthread_exit.%=:\n\t" + "mov\t%%rbx, %%rdi\n\t" //rc + "mov\t$60, %%rax\n\t" + "syscall" // thread exit + : "+m"(td->state), "=&r"(state) + : "I"(cthread_detached), "1"(cthread_finished), "a"(__NR_munmap), "b"(rc), "D"(td->alloc.bottom), "S"(size) + : "rcx", "r11", "cc", "memory" + ); + unreachable; +} diff --git a/libc/thread/exit.h b/libc/thread/exit.h new file mode 100644 index 00000000000..679b472c2a1 --- /dev/null +++ b/libc/thread/exit.h @@ -0,0 +1,14 @@ +#ifndef COSMOPOLITAN_LIBC_THREAD_EXIT_H_ +#define COSMOPOLITAN_LIBC_THREAD_EXIT_H_ +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +/** + * @fileoverview exit the current thread + */ + +wontreturn void cthread_exit(int); + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_THREAD_EXIT_H_ */ diff --git a/libc/thread/join.c b/libc/thread/join.c new file mode 100644 index 00000000000..bf21ee9a913 --- /dev/null +++ b/libc/thread/join.c @@ -0,0 +1,49 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ Permission to use, copy, modify, and/or distribute this software for │ +│ any purpose with or without fee is hereby granted, provided that the │ +│ above copyright notice and this permission notice appear in all copies. │ +│ │ +│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ +│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ +│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ +│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ +│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ +│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ +│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ +│ PERFORMANCE OF THIS SOFTWARE. │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/thread/join.h" +#include "libc/thread/descriptor.h" +#include "libc/runtime/runtime.h" +#include "libc/sysv/consts/nr.h" +#include "libc/sysv/consts/futex.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 flags = FUTEX_WAIT; // PRIVATE makes it hang + register struct timespec* timeout asm("r10") = NULL; + asm volatile ( + "syscall" + : + : "a"(__NR_futex), "D"(&td->tid), "S"(flags), "d"(tid), "r"(timeout) + : "rcx", "r11", "cc", "memory" + ); + } + + *rc = td->rc; + + size_t size = (intptr_t)(td->alloc.top) - (intptr_t)(td->alloc.bottom); + munmap(td->alloc.bottom, size); + return 0; +} diff --git a/libc/thread/join.h b/libc/thread/join.h new file mode 100644 index 00000000000..986bf67d286 --- /dev/null +++ b/libc/thread/join.h @@ -0,0 +1,15 @@ +#ifndef COSMOPOLITAN_LIBC_THREAD_JOIN_H_ +#define COSMOPOLITAN_LIBC_THREAD_JOIN_H_ +#include "libc/thread/descriptor.h" +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +/** + * @fileoverview join a thread + */ + +int cthread_join(cthread_t, int*); + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_THREAD_JOIN_H_ */ diff --git a/libc/thread/nativesem.c b/libc/thread/nativesem.c new file mode 100644 index 00000000000..3930dd669d3 --- /dev/null +++ b/libc/thread/nativesem.c @@ -0,0 +1,112 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ Permission to use, copy, modify, and/or distribute this software for │ +│ any purpose with or without fee is hereby granted, provided that the │ +│ above copyright notice and this permission notice appear in all copies. │ +│ │ +│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ +│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ +│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ +│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ +│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ +│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ +│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ +│ PERFORMANCE OF THIS SOFTWARE. │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/thread/nativesem.h" +#include "libc/thread/yield.h" +#include "libc/sysv/consts/nr.h" +#include "libc/sysv/consts/futex.h" +#include "libc/bits/atomic.h" + +#define CTHREAD_THREAD_VAL_BITS 32 + +int cthread_native_sem_init(cthread_native_sem_t* sem, int count) { + sem->linux.count = count; + return 0; +} +int cthread_native_sem_destroy(cthread_native_sem_t* sem) { + (void)sem; + return 0; +} + +int cthread_native_sem_signal(cthread_native_sem_t* sem) { + uint64_t count; + asm volatile("lock xadd\t%1, %0" : "+m"(sem->linux.count), "=r"(count) : "1"(1) : "cc"); + + if ((count >> CTHREAD_THREAD_VAL_BITS)) { + int flags = FUTEX_WAKE; + + // WARNING: an offset of 4 bytes would be required on little-endian archs + void* wait_address = &sem->linux.count; + asm volatile ( + "syscall" + : + : "a"(__NR_futex), "D"(wait_address), "S"(flags), "d"(1) + : "rcx", "r11", "cc", "memory" + ); + } + + return 0; +} + +int cthread_native_sem_wait_slow(cthread_native_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"); + + for (;;) { + // try to acquire the semaphore, as well as remove itself from waiters + if ((uint32_t)count > 0 && atomic_compare_exchange_weak(&sem->linux.count, count, count - 1 - ((uint64_t)1 << CTHREAD_THREAD_VAL_BITS))) break; + + int flags = FUTEX_WAIT; + register struct timespec* timeout_ asm("r10") = timeout; + + // WARNING: an offset of 4 bytes would be required on little-endian archs + void* wait_address = &sem->linux.count; + asm volatile ( + "syscall" + : + : "a"(__NR_futex), "D"(wait_address), "S"(flags), "d"(count), "r"(timeout_) + : "rcx", "r11", "cc", "memory" + ); + count = atomic_load(&sem->linux.count); + } + + return 0; +} + +int cthread_native_sem_wait_spin_yield(cthread_native_sem_t* sem, uint64_t count, int yield, const struct timespec* timeout) { + // spin on yield + while (yield-- > 0) { + if ((count >> CTHREAD_THREAD_VAL_BITS) != 0) break; // a thread is already waiting in queue + if ((uint32_t)count > 0 && atomic_compare_exchange_weak(&sem->linux.count, count, count-1)) return 0; + cthread_yield(); + } + + return cthread_native_sem_wait_slow(sem, timeout); +} + +int cthread_native_sem_wait_spin(cthread_native_sem_t* sem, uint64_t count, int spin, int yield, const struct timespec* timeout) { + // spin on pause + while (spin-- > 0) { + if ((count >> CTHREAD_THREAD_VAL_BITS) != 0) break; + if ((uint32_t)count > 0 && atomic_compare_exchange_weak(&sem->linux.count, count, count-1)) return 0; + asm volatile ("pause"); + } + + return cthread_native_sem_wait_spin_yield(sem, count, yield, timeout); +} + +int cthread_native_sem_wait(cthread_native_sem_t* sem, int spin, int yield, const struct timespec* timeout) { + uint64_t count = atomic_load(&sem->linux.count); + + // uncontended + if ((count >> 32) == 0 && (uint32_t)count > 0 && atomic_compare_exchange_weak(&sem->linux.count, count, count-1)) return 0; + + return cthread_native_sem_wait_spin(sem, count, spin, yield, timeout); +} diff --git a/libc/thread/nativesem.h b/libc/thread/nativesem.h new file mode 100644 index 00000000000..df1b8583ca5 --- /dev/null +++ b/libc/thread/nativesem.h @@ -0,0 +1,28 @@ +#ifndef COSMOPOLITAN_LIBC_THREAD_NATIVESEM_H_ +#define COSMOPOLITAN_LIBC_THREAD_NATIVESEM_H_ +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +/** + * @fileoverview native semaphore for implementation details + */ + +typedef union cthread_native_sem_t { + struct { + uint64_t count; + } linux; +} cthread_native_sem_t; + +struct timespec; + +int cthread_native_sem_init(cthread_native_sem_t*, int); +int cthread_native_sem_destroy(cthread_native_sem_t*); + +int cthread_native_sem_wait(cthread_native_sem_t*, int, int, const struct timespec*); +int cthread_native_sem_signal(cthread_native_sem_t*); + + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_THREAD_SELF_H_ */ + diff --git a/libc/thread/self.c b/libc/thread/self.c new file mode 100644 index 00000000000..d3b3098d7fb --- /dev/null +++ b/libc/thread/self.c @@ -0,0 +1,21 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ Permission to use, copy, modify, and/or distribute this software for │ +│ any purpose with or without fee is hereby granted, provided that the │ +│ above copyright notice and this permission notice appear in all copies. │ +│ │ +│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ +│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ +│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ +│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ +│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ +│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ +│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ +│ PERFORMANCE OF THIS SOFTWARE. │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/thread/self.h" + +extern inline cthread_t cthread_self(void); diff --git a/libc/thread/self.h b/libc/thread/self.h new file mode 100644 index 00000000000..4ab15498c1c --- /dev/null +++ b/libc/thread/self.h @@ -0,0 +1,20 @@ +#ifndef COSMOPOLITAN_LIBC_THREAD_SELF_H_ +#define COSMOPOLITAN_LIBC_THREAD_SELF_H_ +#include "libc/thread/descriptor.h" +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +/** + * @fileoverview get the thread descriptor of the current thread + */ + +inline cthread_t cthread_self(void) { + cthread_t self; + asm ("mov %%fs:0, %0" : "=r"(self)); + return self; +} + + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_THREAD_SELF_H_ */ diff --git a/libc/thread/thread.mk b/libc/thread/thread.mk new file mode 100644 index 00000000000..137c3e0019a --- /dev/null +++ b/libc/thread/thread.mk @@ -0,0 +1,55 @@ +#-*-mode:makefile-gmake;indent-tabs-mode:t;tab-width:8;coding:utf-8-*-┐ +#───vi: set et ft=make ts=8 tw=8 fenc=utf-8 :vi───────────────────────┘ + +PKGS += LIBC_THREAD + +LIBC_THREAD_ARTIFACTS += LIBC_THREAD_A +LIBC_THREAD = $(LIBC_THREAD_A_DEPS) $(LIBC_THREAD_A) +LIBC_THREAD_A = o/$(MODE)/libc/thread/thread.a +LIBC_THREAD_A_FILES := $(wildcard libc/thread/*) +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_C) + +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_HDRS:%=o/$(MODE)/%.ok) + +LIBC_THREAD_A_DIRECTDEPS = \ + LIBC_STUBS \ + LIBC_CALLS \ + LIBC_INTRIN \ + LIBC_BITS \ + LIBC_RUNTIME \ + LIBC_SYSV \ + LIBC_SYSV_CALLS \ + LIBC_NEXGEN32E + +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_OBJS) + +$(LIBC_THREAD_A).pkg: \ + $(LIBC_THREAD_A_OBJS) \ + $(foreach x,$(LIBC_THREAD_A_DIRECTDEPS),$($(x)_A).pkg) + +LIBC_THREAD_LIBS = $(foreach x,$(LIBC_THREAD_ARTIFACTS),$($(x))) +LIBC_THREAD_SRCS = $(foreach x,$(LIBC_THREAD_ARTIFACTS),$($(x)_SRCS)) +LIBC_THREAD_HDRS = $(foreach x,$(LIBC_THREAD_ARTIFACTS),$($(x)_HDRS)) +LIBC_THREAD_CHECKS = $(foreach x,$(LIBC_THREAD_ARTIFACTS),$($(x)_CHECKS)) +LIBC_THREAD_OBJS = $(foreach x,$(LIBC_THREAD_ARTIFACTS),$($(x)_OBJS)) +$(LIBC_THREAD_OBJS): $(BUILD_FILES) libc/thread/thread.mk + +.PHONY: o/$(MODE)/libc/thread +o/$(MODE)/libc/thread: $(LIBC_THREAD_CHECKS) diff --git a/libc/thread/yield.c b/libc/thread/yield.c new file mode 100644 index 00000000000..af0c9dba0da --- /dev/null +++ b/libc/thread/yield.c @@ -0,0 +1,24 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ Permission to use, copy, modify, and/or distribute this software for │ +│ any purpose with or without fee is hereby granted, provided that the │ +│ above copyright notice and this permission notice appear in all copies. │ +│ │ +│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ +│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ +│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ +│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ +│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ +│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ +│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ +│ PERFORMANCE OF THIS SOFTWARE. │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/thread/yield.h" +#include "libc/calls/calls.h" + +int cthread_yield(void) { + return sched_yield(); +} diff --git a/libc/thread/yield.h b/libc/thread/yield.h new file mode 100644 index 00000000000..85d5a827c00 --- /dev/null +++ b/libc/thread/yield.h @@ -0,0 +1,14 @@ +#ifndef COSMOPOLITAN_LIBC_THREAD_YIELD_H_ +#define COSMOPOLITAN_LIBC_THREAD_YIELD_H_ +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +/** + * @fileoverview yield thread to OS scheduler + */ + +int cthread_yield(void); + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_THREAD_YIELD_H_ */