diff --git a/examples/thread.c b/examples/thread.c index 4b4073922..106bb32fe 100644 --- a/examples/thread.c +++ b/examples/thread.c @@ -9,19 +9,34 @@ #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/time/time.h" int worker(void* arg) { + cthread_t self = cthread_self(); + int tid = self->tid; + sleep(1); + //sleep(10000); + //printf("[%p] %d\n", self, tid); (void)arg; - return 0; + return 4; } int main() { cthread_t thread; int rc = cthread_create(&thread, NULL, &worker, NULL); if (rc == 0) { - printf("thread created: %p\n", thread); - sleep(1000); + //printf("thread created: %p\n", thread); + sleep(1); +#if 1 + cthread_join(thread, &rc); +#else + rc = cthread_detach(thread); + sleep(2); +#endif + //printf("thread joined: %p -> %d\n", thread, rc); } else { printf("ERROR: thread could not be started: %d\n", rc); } diff --git a/libc/thread/create.c b/libc/thread/create.c index 3f016defc..2f1c57093 100644 --- a/libc/thread/create.c +++ b/libc/thread/create.c @@ -19,7 +19,7 @@ #include "libc/thread/create.h" #include "libc/linux/clone.h" #include "libc/runtime/runtime.h" -#include "libc/linux/mmap.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" @@ -28,7 +28,7 @@ static cthread_t _thread_allocate(const cthread_attr_t* attr) { size_t stacksize = attr->stacksize; - size_t guardsize = 0;//attr->guardsize; + size_t guardsize = attr->guardsize; // FIXME: properly count TLS size size_t tlssize = 0; @@ -59,7 +59,7 @@ static cthread_t _thread_allocate(const cthread_attr_t* attr) { td->tls.bottom = tls_bottom; td->alloc.top = alloc_top; td->alloc.bottom = alloc_bottom; - td->tid = 0; + td->state = (attr->mode & CTHREAD_CREATE_DETACHED) ? cthread_detached : cthread_started; return td; } @@ -68,41 +68,41 @@ int cthread_create(cthread_t*restrict p, const cthread_attr_t*restrict attr, int extern wontreturn void _thread_run(int(*func)(void*), void* arg); cthread_attr_t default_attr; - if (!attr) cthread_attr_init(&default_attr); + cthread_attr_init(&default_attr); cthread_t td = _thread_allocate(attr ? attr : &default_attr); - if (!attr) cthread_attr_destroy(&default_attr); + cthread_attr_destroy(&default_attr); if (!td) return errno; *p = td; - uintptr_t stack = (uintptr_t)(td->stack.top); + 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; - stack -= sizeof(void*); *(void**)stack = func; - stack -= sizeof(void*); *(void**)stack = arg; - - long flags = CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | CLONE_PARENT | CLONE_THREAD | /*CLONE_IO |*/ CLONE_SETTLS; - - // It is not necessary to check the return of the syscall here as the return address for the thread is setup to to `_thread_run`. - // In case of success: the parent callee returns immediately to the caller forwarding the success to the callee - // the child return immediately to the entry point of `thread_spawn` - // In case of error: the parent callee returns immediately to the caller forwarding the error - // the child is never created (and so cannot returned in the wrong place) - int rc = LinuxClone(flags, (void*)stack, NULL, NULL, (void*)td); - if (!rc) { - // child - asm volatile( - "xor %rbp,%rbp\n\t" - /* pop arguments */ - "pop %rdi\n\t" - "pop %rax\n\t" - /* call function */ - "call *%rax\n\t" - /* thread exit */ - "mov %rax, %rdi\n\t" - "jmp cthread_exit" - ); - unreachable; + 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; } - if (rc < 0) return rc; return 0; } diff --git a/libc/thread/create.h b/libc/thread/create.h index 35007ba92..b6e77b950 100644 --- a/libc/thread/create.h +++ b/libc/thread/create.h @@ -1,15 +1,14 @@ #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 thread + * @fileoverview Create a cosmopolitan thread */ -#include "libc/thread/attr.h" -#include "libc/thread/descriptor.h" - int cthread_create(cthread_t*restrict, const cthread_attr_t*restrict, int (*)(void*), void*restrict); diff --git a/libc/thread/descriptor.h b/libc/thread/descriptor.h index 7d2ee0661..793590ffd 100644 --- a/libc/thread/descriptor.h +++ b/libc/thread/descriptor.h @@ -7,12 +7,23 @@ 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 tid; + 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; diff --git a/libc/thread/detach.c b/libc/thread/detach.c new file mode 100644 index 000000000..a6defc262 --- /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 000000000..4185792f5 --- /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 index b7e7a598d..f12260744 100644 --- a/libc/thread/exit.c +++ b/libc/thread/exit.c @@ -17,30 +17,28 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/thread/exit.h" +#include "libc/thread/self.h" #include "libc/thread/descriptor.h" -#include "libc/linux/munmap.h" +#include "libc/sysv/consts/nr.h" -static wontreturn void _self_exit(void* p, size_t s, int r) { - // asm is necessary as the stack does not exist between the unmap and the actual exit +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( - "mov $11, %%rax\n\t" - "syscall\n\t" - "mov %%rbx, %%rdi\n\t" - "mov $60, %%rax\n\t" - "syscall" - : - : "D"(p), "S"(s), "b"(r) - : "memory" + "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; } - -wontreturn void cthread_exit(int rc) { - // TODO: wait joiners - - cthread_t td; - asm("mov %%fs:0, %0" : "=r"(td)); - size_t size = (intptr_t)(td->alloc.top) - (intptr_t)(td->alloc.bottom); - - _self_exit(td->alloc.top, size, rc); -} diff --git a/libc/thread/exit.h b/libc/thread/exit.h index ad4844ddd..679b472c2 100644 --- a/libc/thread/exit.h +++ b/libc/thread/exit.h @@ -4,7 +4,7 @@ COSMOPOLITAN_C_START_ /** - * @fileoverview internal function called after a thread exits + * @fileoverview exit the current thread */ wontreturn void cthread_exit(int); diff --git a/libc/thread/join.c b/libc/thread/join.c new file mode 100644 index 000000000..bf21ee9a9 --- /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 000000000..4676ad7d2 --- /dev/null +++ b/libc/thread/join.h @@ -0,0 +1,16 @@ +#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_ */ + \ No newline at end of file diff --git a/libc/thread/self.c b/libc/thread/self.c new file mode 100644 index 000000000..d3b3098d7 --- /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 000000000..4ab15498c --- /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_ */