aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/internal/futex
diff options
context:
space:
mode:
Diffstat (limited to 'src/internal/futex')
-rw-r--r--src/internal/futex/futex.go72
-rw-r--r--src/internal/futex/futex_darwin.c49
-rw-r--r--src/internal/futex/futex_linux.c33
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);
+}