diff --git a/examples/thread.c b/examples/thread.c index 106bb32fe..06ae57914 100644 --- a/examples/thread.c +++ b/examples/thread.c @@ -12,9 +12,14 @@ #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); @@ -25,9 +30,12 @@ int worker(void* arg) { } 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 @@ -36,6 +44,8 @@ int main() { 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); diff --git a/libc/sysv/consts.sh b/libc/sysv/consts.sh index 64b8313ce..c593852fe 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 4f080ff91..3700a2d98 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 ca41403a2..f8f1f0b9e 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 26f1d5edd..97a3ad3df 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 8f9d0f411..01189de70 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 e4d3eeb8a..5342bb7c3 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 e5db0fd84..1411c7592 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/join.h b/libc/thread/join.h index 4676ad7d2..986bf67d2 100644 --- a/libc/thread/join.h +++ b/libc/thread/join.h @@ -13,4 +13,3 @@ 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/nativesem.c b/libc/thread/nativesem.c new file mode 100644 index 000000000..3930dd669 --- /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 000000000..df1b8583c --- /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/yield.c b/libc/thread/yield.c new file mode 100644 index 000000000..af0c9dba0 --- /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 000000000..85d5a827c --- /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_ */