diff options
Diffstat (limited to 'src/internal/futex')
-rw-r--r-- | src/internal/futex/futex.go | 72 | ||||
-rw-r--r-- | src/internal/futex/futex_darwin.c | 49 | ||||
-rw-r--r-- | src/internal/futex/futex_linux.c | 33 |
3 files changed, 154 insertions, 0 deletions
diff --git a/src/internal/futex/futex.go b/src/internal/futex/futex.go new file mode 100644 index 000000000..5ecdd79c2 --- /dev/null +++ b/src/internal/futex/futex.go @@ -0,0 +1,72 @@ +package futex + +// Cross platform futex implementation. +// Futexes are supported on all major operating systems and on WebAssembly. +// +// For more information, see: https://outerproduct.net/futex-dictionary.html + +import ( + "sync/atomic" + "unsafe" +) + +// A futex is a way for userspace to wait with the pointer as the key, and for +// another thread to wake one or all waiting threads keyed on the same pointer. +// +// A futex does not change the underlying value, it only reads it before going +// to sleep (atomically) to prevent lost wake-ups. +type Futex struct { + atomic.Uint32 +} + +// Atomically check for cmp to still be equal to the futex value and if so, go +// to sleep. Return true if we were definitely awoken by a call to Wake or +// WakeAll, and false if we can't be sure of that. +func (f *Futex) Wait(cmp uint32) bool { + tinygo_futex_wait((*uint32)(unsafe.Pointer(&f.Uint32)), cmp) + + // We *could* detect a zero return value from the futex system call which + // would indicate we got awoken by a Wake or WakeAll call. However, this is + // what the manual page has to say: + // + // > Note that a wake-up can also be caused by common futex usage patterns + // > in unrelated code that happened to have previously used the futex + // > word's memory location (e.g., typical futex-based implementations of + // > Pthreads mutexes can cause this under some conditions). Therefore, + // > callers should always conservatively assume that a return value of 0 + // > can mean a spurious wake-up, and use the futex word's value (i.e., the + // > user-space synchronization scheme) to decide whether to continue to + // > block or not. + // + // I'm not sure whether we do anything like pthread does, so to be on the + // safe side we say we don't know whether the wakeup was spurious or not and + // return false. + return false +} + +// Like Wait, but times out after the number of nanoseconds in timeout. +func (f *Futex) WaitUntil(cmp uint32, timeout uint64) { + tinygo_futex_wait_timeout((*uint32)(unsafe.Pointer(&f.Uint32)), cmp, timeout) +} + +// Wake a single waiter. +func (f *Futex) Wake() { + tinygo_futex_wake((*uint32)(unsafe.Pointer(&f.Uint32))) +} + +// Wake all waiters. +func (f *Futex) WakeAll() { + tinygo_futex_wake_all((*uint32)(unsafe.Pointer(&f.Uint32))) +} + +//export tinygo_futex_wait +func tinygo_futex_wait(addr *uint32, cmp uint32) + +//export tinygo_futex_wait_timeout +func tinygo_futex_wait_timeout(addr *uint32, cmp uint32, timeout uint64) + +//export tinygo_futex_wake +func tinygo_futex_wake(addr *uint32) + +//export tinygo_futex_wake_all +func tinygo_futex_wake_all(addr *uint32) diff --git a/src/internal/futex/futex_darwin.c b/src/internal/futex/futex_darwin.c new file mode 100644 index 000000000..358a87655 --- /dev/null +++ b/src/internal/futex/futex_darwin.c @@ -0,0 +1,49 @@ +//go:build none + +// This file is manually included, to avoid CGo which would cause a circular +// import. + +#include <stdint.h> + +// This API isn't documented by Apple, but it is used by LLVM libc++ (so should +// be stable) and has been documented extensively here: +// https://outerproduct.net/futex-dictionary.html + +int __ulock_wait(uint32_t operation, void *addr, uint64_t value, uint32_t timeout_us); +int __ulock_wait2(uint32_t operation, void *addr, uint64_t value, uint64_t timeout_ns, uint64_t value2); +int __ulock_wake(uint32_t operation, void *addr, uint64_t wake_value); + +// Operation code. +#define UL_COMPARE_AND_WAIT 1 + +// Flags to the operation value. +#define ULF_WAKE_ALL 0x00000100 +#define ULF_NO_ERRNO 0x01000000 + +void tinygo_futex_wait(uint32_t *addr, uint32_t cmp) { + __ulock_wait(UL_COMPARE_AND_WAIT|ULF_NO_ERRNO, addr, (uint64_t)cmp, 0); +} + +void tinygo_futex_wait_timeout(uint32_t *addr, uint32_t cmp, uint64_t timeout) { + // Make sure that an accidental use of a zero timeout is not treated as an + // infinite timeout. Return if it's zero since it wouldn't be waiting for + // any significant time anyway. + // Probably unnecessary, but guards against potential bugs. + if (timeout == 0) { + return; + } + + // Note: __ulock_wait2 is available since MacOS 11. + // I think that's fine, since the version before that (MacOS 10.15) is EOL + // since 2022. Though if needed, we could certainly use __ulock_wait instead + // and deal with the smaller timeout value. + __ulock_wait2(UL_COMPARE_AND_WAIT|ULF_NO_ERRNO, addr, (uint64_t)cmp, timeout, 0); +} + +void tinygo_futex_wake(uint32_t *addr) { + __ulock_wake(UL_COMPARE_AND_WAIT|ULF_NO_ERRNO, addr, 0); +} + +void tinygo_futex_wake_all(uint32_t *addr) { + __ulock_wake(UL_COMPARE_AND_WAIT|ULF_NO_ERRNO|ULF_WAKE_ALL, addr, 0); +} diff --git a/src/internal/futex/futex_linux.c b/src/internal/futex/futex_linux.c new file mode 100644 index 000000000..ffefc97e4 --- /dev/null +++ b/src/internal/futex/futex_linux.c @@ -0,0 +1,33 @@ +//go:build none + +// This file is manually included, to avoid CGo which would cause a circular +// import. + +#include <limits.h> +#include <stdint.h> +#include <sys/syscall.h> +#include <time.h> +#include <unistd.h> + +#define FUTEX_WAIT 0 +#define FUTEX_WAKE 1 +#define FUTEX_PRIVATE_FLAG 128 + +void tinygo_futex_wait(uint32_t *addr, uint32_t cmp) { + syscall(SYS_futex, addr, FUTEX_WAIT|FUTEX_PRIVATE_FLAG, cmp, NULL, NULL, 0); +} + +void tinygo_futex_wait_timeout(uint32_t *addr, uint32_t cmp, uint64_t timeout) { + struct timespec ts = {0}; + ts.tv_sec = timeout / 1000000000; + ts.tv_nsec = timeout % 1000000000; + syscall(SYS_futex, addr, FUTEX_WAIT|FUTEX_PRIVATE_FLAG, cmp, &ts, NULL, 0); +} + +void tinygo_futex_wake(uint32_t *addr) { + syscall(SYS_futex, addr, FUTEX_WAKE|FUTEX_PRIVATE_FLAG, 1, NULL, NULL, 0); +} + +void tinygo_futex_wake_all(uint32_t *addr) { + syscall(SYS_futex, addr, FUTEX_WAKE|FUTEX_PRIVATE_FLAG, INT_MAX, NULL, NULL, 0); +} |