diff --git a/core/sync/futex_darwin.odin b/core/sync/futex_darwin.odin
index 44746e57b..6ea177d1b 100644
--- a/core/sync/futex_darwin.odin
+++ b/core/sync/futex_darwin.odin
@@ -3,6 +3,7 @@
package sync
import "core:c"
+import "core:sys/darwin"
import "core:time"
foreign import System "system:System.framework"
@@ -29,8 +30,29 @@ _futex_wait :: proc "contextless" (f: ^Futex, expected: u32) -> bool {
}
_futex_wait_with_timeout :: proc "contextless" (f: ^Futex, expected: u32, duration: time.Duration) -> bool {
+ when darwin.WAIT_ON_ADDRESS_AVAILABLE {
+ s: i32
+ if duration > 0 {
+ s = darwin.os_sync_wait_on_address_with_timeout(f, u64(expected), size_of(Futex), {}, .MACH_ABSOLUTE_TIME, u64(duration))
+ } else {
+ s = darwin.os_sync_wait_on_address(f, u64(expected), size_of(Futex), {})
+ }
+
+ if s >= 0 {
+ return true
+ }
+
+ switch darwin.errno() {
+ case -EINTR, -EFAULT:
+ return true
+ case -ETIMEDOUT:
+ return false
+ case:
+ _panic("darwin.os_sync_wait_on_address_with_timeout failure")
+ }
+ } else {
+
timeout_ns := u32(duration) * 1000
-
s := __ulock_wait(UL_COMPARE_AND_WAIT | ULF_NO_ERRNO, f, u64(expected), timeout_ns)
if s >= 0 {
return true
@@ -45,9 +67,27 @@ _futex_wait_with_timeout :: proc "contextless" (f: ^Futex, expected: u32, durati
}
return true
+ }
}
_futex_signal :: proc "contextless" (f: ^Futex) {
+ when darwin.WAIT_ON_ADDRESS_AVAILABLE {
+ loop: for {
+ s := darwin.os_sync_wake_by_address_any(f, size_of(Futex), {})
+ if s >= 0 {
+ return
+ }
+ switch darwin.errno() {
+ case -EINTR, -EFAULT:
+ continue loop
+ case -ENOENT:
+ return
+ case:
+ _panic("darwin.os_sync_wake_by_address_any failure")
+ }
+ }
+ } else {
+
loop: for {
s := __ulock_wake(UL_COMPARE_AND_WAIT | ULF_NO_ERRNO, f, 0)
if s >= 0 {
@@ -62,9 +102,28 @@ _futex_signal :: proc "contextless" (f: ^Futex) {
_panic("futex_wake_single failure")
}
}
+
+ }
}
_futex_broadcast :: proc "contextless" (f: ^Futex) {
+ when darwin.WAIT_ON_ADDRESS_AVAILABLE {
+ loop: for {
+ s := darwin.os_sync_wake_by_address_all(f, size_of(Futex), {})
+ if s >= 0 {
+ return
+ }
+ switch darwin.errno() {
+ case -EINTR, -EFAULT:
+ continue loop
+ case -ENOENT:
+ return
+ case:
+ _panic("darwin.os_sync_wake_by_address_all failure")
+ }
+ }
+ } else {
+
loop: for {
s := __ulock_wake(UL_COMPARE_AND_WAIT | ULF_NO_ERRNO | ULF_WAKE_ALL, f, 0)
if s >= 0 {
@@ -79,5 +138,6 @@ _futex_broadcast :: proc "contextless" (f: ^Futex) {
_panic("futex_wake_all failure")
}
}
-}
+ }
+}
diff --git a/core/sys/darwin/darwin.odin b/core/sys/darwin/darwin.odin
index a3e07277c..ddd25a76c 100644
--- a/core/sys/darwin/darwin.odin
+++ b/core/sys/darwin/darwin.odin
@@ -3,6 +3,8 @@ package darwin
import "core:c"
+foreign import system "system:System.framework"
+
Bool :: b8
RUsage :: struct {
@@ -24,3 +26,10 @@ RUsage :: struct {
ru_nivcsw: c.long,
}
+foreign system {
+ __error :: proc() -> ^i32 ---
+}
+
+errno :: #force_inline proc "contextless" () -> i32 {
+ return __error()^
+}
diff --git a/core/sys/darwin/sync.odin b/core/sys/darwin/sync.odin
new file mode 100644
index 000000000..b9fc82ecc
--- /dev/null
+++ b/core/sys/darwin/sync.odin
@@ -0,0 +1,309 @@
+package darwin
+
+foreign import system "system:System.framework"
+
+// #define OS_WAIT_ON_ADDR_AVAILABILITY \
+// __API_AVAILABLE(macos(14.4), ios(17.4), tvos(17.4), watchos(10.4))
+when ODIN_OS == .Darwin {
+ when ODIN_PLATFORM_SUBTARGET == .iOS && MINIMUM_OS_VERSION > 17_04_00 {
+ WAIT_ON_ADDRESS_AVAILABLE :: true
+ } else when MINIMUM_OS_VERSION > 14_04_00 {
+ WAIT_ON_ADDRESS_AVAILABLE :: true
+ } else {
+ WAIT_ON_ADDRESS_AVAILABLE :: false
+ }
+} else {
+ WAIT_ON_ADDRESS_AVAILABLE :: false
+}
+
+os_sync_wait_on_address_flag :: enum u32 {
+ // This flag should be used as a default flag when no other flags listed below are required.
+ NONE,
+
+ // This flag should be used when synchronizing among multiple processes by
+ // placing the @addr passed to os_sync_wait_on_address and its variants
+ // in a shared memory region.
+ //
+ // When using this flag, it is important to pass OS_SYNC_WAKE_BY_ADDRESS_SHARED
+ // flag along with the exact same @addr to os_sync_wake_by_address_any and
+ // its variants to correctly find and wake up blocked waiters on the @addr.
+ //
+ // This flag should not be used when synchronizing among multiple threads of
+ // a single process. It allows the kernel to perform performance optimizations
+ // as the @addr is local to the calling process.
+ SHARED,
+}
+
+os_sync_wait_on_address_flags :: bit_set[os_sync_wait_on_address_flag; u32]
+
+os_sync_wake_by_address_flag :: enum u32 {
+ // This flag should be used as a default flag when no other flags listed below are required.
+ NONE,
+
+ // This flag should be used when synchronizing among multiple processes by
+ // placing the @addr passed to os_sync_wake_by_address_any and its variants
+ // in a shared memory region.
+ //
+ // When using this flag, it is important to pass OS_SYNC_WAIT_ON_ADDRESS_SHARED
+ // flag along with the exact same @addr to os_sync_wait_on_address and
+ // its variants to correctly find and wake up blocked waiters on the @addr.
+ //
+ // This flag should not be used when synchronizing among multiple threads of
+ // a single process. It allows the kernel to perform performance optimizations
+ // as the @addr is local the calling process.
+ SHARED,
+}
+
+os_sync_wake_by_address_flags :: bit_set[os_sync_wake_by_address_flag; u32]
+
+os_clockid :: enum u32 {
+ MACH_ABSOLUTE_TIME = 32,
+}
+
+foreign system {
+ // This function provides an atomic compare-and-wait functionality that
+ // can be used to implement other higher level synchronization primitives.
+ //
+ // It reads a value from @addr, compares it to expected @value and blocks
+ // the calling thread if they are equal. This sequence of operations is
+ // done atomically with respect to other concurrent operations that can
+ // be performed on this @addr by other threads using this same function
+ // or os_sync_wake_by_addr variants. At this point, the blocked calling
+ // thread is considered to be a waiter on this @addr, waiting to be woken
+ // up by a call to os_sync_wake_by_addr variants. If the value at @addr
+ // turns out to be different than expected, the calling thread returns
+ // immediately without blocking.
+ //
+ // This function is expected to be used for implementing synchronization
+ // primitives that do not have a sense of ownership (e.g. condition
+ // variables, semaphores) as it does not provide priority inversion avoidance.
+ // For locking primitives, it is recommended that you use existing OS
+ // primitives such as os_unfair_lock API family / pthread mutex or
+ // std::mutex.
+ //
+ // @param addr
+ // The userspace address to be used for atomic compare-and-wait.
+ // This address must be aligned to @size.
+ //
+ // @param value
+ // The value expected at @addr.
+ //
+ // @param size
+ // The size of @value, in bytes. This can be either 4 or 8 today.
+ // For @value of @size 4 bytes, the upper 4 bytes of @value are ignored.
+ //
+ // @param flags
+ // Flags to alter behavior of os_sync_wait_on_address.
+ // See os_sync_wait_on_address_flags_t.
+ //
+ // @return
+ // If the calling thread is woken up by a call to os_sync_wake_by_addr
+ // variants or the value at @addr is different than expected, this function
+ // returns successfully and the return value indicates the number
+ // of outstanding waiters blocked on this address.
+ // In the event of an error, returns -1 with errno set to indicate the error.
+ //
+ // EINVAL : Invalid flags or size.
+ // EINVAL : The @addr passed is NULL or misaligned.
+ // EINVAL : The operation associated with existing kernel state
+ // at this @addr is inconsistent with what the caller
+ // has requested.
+ // It is important to make sure consistent values are
+ // passed across wait and wake APIs for @addr, @size
+ // and the shared memory specification
+ // (See os_sync_wait_on_address_flags_t).
+ //
+ // It is possible for the os_sync_wait_on_address and its variants to perform
+ // an early return in the event of following errors where user may want to
+ // re-try the wait operation. E.g. low memory conditions could cause such early
+ // return.
+ // It is important to read the current value at the @addr before re-trying
+ // to ensure that the new value still requires waiting on @addr.
+ //
+ // ENOMEM : Unable to allocate memory for kernel internal data
+ // structures.
+ // EINTR : The syscall was interrupted / spurious wake up.
+ // EFAULT : Unable to read value from the @addr. Kernel copyin failed.
+ // It is possible to receive EFAULT error in following cases:
+ // 1. The @addr is an invalid address. This is a programmer error.
+ // 2. The @addr is valid; but, this is a transient error such as
+ // due to low memory conditions. User may want to re-try the wait
+ // operation.
+ // Following code snippet illustrates a possible re-try loop.
+ //
+ // retry:
+ // current = atomic_load_explicit(addr, memory_order_relaxed);
+ // if (current != expected) {
+ // int ret = os_sync_wait_on_address(addr, current, size, flags);
+ // if ((ret < 0) && ((errno == EINTR) || (errno == EFAULT))) {
+ // goto retry;
+ // }
+ // }
+ //
+ os_sync_wait_on_address :: proc(
+ addr: rawptr,
+ value: u64,
+ size: uint,
+ flags: os_sync_wait_on_address_flags,
+ ) -> i32 ---
+
+ // This function is a variant of os_sync_wait_on_address that
+ // allows the calling thread to specify a deadline
+ // until which it is willing to block.
+ //
+ // @param addr
+ // The userspace address to be used for atomic compare-and-wait.
+ // This address must be aligned to @size.
+ //
+ // @param value
+ // The value expected at @addr.
+ //
+ // @param size
+ // The size of @value, in bytes. This can be either 4 or 8 today.
+ // For @value of @size 4 bytes, the upper 4 bytes of @value are ignored.
+ //
+ // @param flags
+ // Flags to alter behavior of os_sync_wait_on_address_with_deadline.
+ // See os_sync_wait_on_address_flags_t.
+ //
+ // @param clockid
+ // This value anchors @deadline argument to a specific clock id.
+ // See os_clockid_t.
+ //
+ // @param deadline
+ // This value is used to specify a deadline until which the calling
+ // thread is willing to block.
+ // Passing zero for the @deadline results in an error being returned.
+ // It is recommended to use os_sync_wait_on_address API to block
+ // indefinitely until woken up by a call to os_sync_wake_by_address_any
+ // or os_sync_wake_by_address_all APIs.
+ //
+ // @return
+ // If the calling thread is woken up by a call to os_sync_wake_by_addr
+ // variants or the value at @addr is different than expected, this function
+ // returns successfully and the return value indicates the number
+ // of outstanding waiters blocked on this address.
+ // In the event of an error, returns -1 with errno set to indicate the error.
+ //
+ // In addition to errors returned by os_sync_wait_on_address, this function
+ // can return the following additional error codes.
+ //
+ // EINVAL : Invalid clock id.
+ // EINVAL : The @deadline passed is 0.
+ // ETIMEDOUT : Deadline expired.
+ os_sync_wait_on_address_with_deadline :: proc(
+ addr: rawptr,
+ value: u64,
+ size: uint,
+ flags: os_sync_wait_on_address_flags,
+ clockid: os_clockid,
+ deadline: u64,
+ ) -> i32 ---
+
+ // This function is a variant of os_sync_wait_on_address that
+ // allows the calling thread to specify a timeout
+ // until which it is willing to block.
+ //
+ // @param addr
+ // The userspace address to be used for atomic compare-and-wait.
+ // This address must be aligned to @size.
+ //
+ // @param value
+ // The value expected at @addr.
+ //
+ // @param size
+ // The size of @value, in bytes. This can be either 4 or 8 today.
+ // For @value of @size 4 bytes, the upper 4 bytes of @value are ignored.
+ //
+ // @param flags
+ // Flags to alter behavior of os_sync_wait_on_address_with_timeout.
+ // See os_sync_wait_on_address_flags_t.
+ //
+ // @param clockid
+ // This value anchors @timeout_ns argument to a specific clock id.
+ // See os_clockid_t.
+ //
+ // @param timeout_ns
+ // This value is used to specify a timeout in nanoseconds until which
+ // the calling thread is willing to block.
+ // Passing zero for the @timeout_ns results in an error being returned.
+ // It is recommended to use os_sync_wait_on_address API to block
+ // indefinitely until woken up by a call to os_sync_wake_by_address_any
+ // or os_sync_wake_by_address_all APIs.
+ //
+ // @return
+ // If the calling thread is woken up by a call to os_sync_wake_by_address
+ // variants or the value at @addr is different than expected, this function
+ // returns successfully and the return value indicates the number
+ // of outstanding waiters blocked on this address.
+ // In the event of an error, returns -1 with errno set to indicate the error.
+ //
+ // In addition to errors returned by os_sync_wait_on_address, this function
+ // can return the following additional error codes.
+ //
+ // EINVAL : Invalid clock id.
+ // EINVAL : The @timeout_ns passed is 0.
+ // ETIMEDOUT : Timeout expired.
+ os_sync_wait_on_address_with_timeout :: proc(
+ addr: rawptr,
+ value: u64,
+ size: uint,
+ flags: os_sync_wait_on_address_flags,
+ clockid: os_clockid,
+ timeout_ns: u64,
+ ) -> i32 ---
+
+ // This function wakes up one waiter out of all those blocked in os_sync_wait_on_address
+ // or its variants on the @addr. No guarantee is provided about which
+ // specific waiter is woken up.
+ //
+ // @param addr
+ // The userspace address to be used for waking up the blocked waiter.
+ // It should be same as what is passed to os_sync_wait_on_address or its variants.
+ //
+ // @param size
+ // The size of lock value, in bytes. This can be either 4 or 8 today.
+ // It should be same as what is passed to os_sync_wait_on_address or its variants.
+ //
+ // @param flags
+ // Flags to alter behavior of os_sync_wake_by_address_any.
+ // See os_sync_wake_by_address_flags_t.
+ //
+ // @return
+ // Returns 0 on success.
+ // In the event of an error, returns -1 with errno set to indicate the error.
+ //
+ // EINVAL : Invalid flags or size.
+ // EINVAL : The @addr passed is NULL.
+ // EINVAL : The operation associated with existing kernel state
+ // at this @addr is inconsistent with what caller
+ // has requested.
+ // It is important to make sure consistent values are
+ // passed across wait and wake APIs for @addr, @size
+ // and the shared memory specification
+ // (See os_sync_wake_by_address_flags_t).
+ // ENOENT : No waiter(s) found waiting on the @addr.
+ os_sync_wake_by_address_any :: proc(addr: rawptr, size: uint, flags: os_sync_wait_on_address_flags) -> i32 ---
+
+ // This function is a variant of os_sync_wake_by_address_any that wakes up all waiters
+ // blocked in os_sync_wait_on_address or its variants.
+ //
+ // @param addr
+ // The userspace address to be used for waking up the blocked waiters.
+ // It should be same as what is passed to os_sync_wait_on_address or its variants.
+ //
+ // @param size
+ // The size of lock value, in bytes. This can be either 4 or 8 today.
+ // It should be same as what is passed to os_sync_wait_on_address or its variants.
+ //
+ // @param flags
+ // Flags to alter behavior of os_sync_wake_by_address_all.
+ // See os_sync_wake_by_address_flags_t.
+ //
+ // @return
+ // Returns 0 on success.
+ // In the event of an error, returns -1 with errno set to indicate the error.
+ //
+ // This function returns same error codes as returned by os_sync_wait_on_address.
+ os_sync_wake_by_address_all :: proc(addr: rawptr, size: uint, flags: os_sync_wait_on_address_flags) -> i32 ---
+}
diff --git a/src/checker.cpp b/src/checker.cpp
index 72c0ae574..797cdb5f1 100644
--- a/src/checker.cpp
+++ b/src/checker.cpp
@@ -1097,6 +1097,15 @@ gb_internal void init_universal(void) {
scope_insert(intrinsics_pkg->scope, t_atomic_memory_order->Named.type_name);
}
+ {
+ int minimum_os_version = 0;
+ if (build_context.minimum_os_version_string != "") {
+ int major, minor, revision = 0;
+ sscanf(cast(const char *)(build_context.minimum_os_version_string.text), "%d.%d.%d", &major, &minor, &revision);
+ minimum_os_version = (major*10000)+(minor*100)+revision;
+ }
+ add_global_constant("MINIMUM_OS_VERSION", t_untyped_integer, exact_value_i64(minimum_os_version));
+ }
add_global_bool_constant("ODIN_DEBUG", bc->ODIN_DEBUG);
add_global_bool_constant("ODIN_DISABLE_ASSERT", bc->ODIN_DISABLE_ASSERT);
diff --git a/src/threading.cpp b/src/threading.cpp
index a469435d2..3197b19a3 100644
--- a/src/threading.cpp
+++ b/src/threading.cpp
@@ -760,6 +760,11 @@ gb_internal void futex_wait(Futex *f, Footex val) {
#elif defined(GB_SYSTEM_OSX)
+#if __has_include()
+ #define DARWIN_WAIT_ON_ADDRESS_AVAILABLE
+ #include
+#endif
+
#define UL_COMPARE_AND_WAIT 0x00000001
#define ULF_NO_ERRNO 0x01000000
@@ -767,6 +772,23 @@ extern "C" int __ulock_wait(uint32_t operation, void *addr, uint64_t value, uint
extern "C" int __ulock_wake(uint32_t operation, void *addr, uint64_t wake_value);
gb_internal void futex_signal(Futex *f) {
+ #ifdef DARWIN_WAIT_ON_ADDRESS_AVAILABLE
+ if (__builtin_available(macOS 14.4, *)) {
+ for (;;) {
+ int ret = os_sync_wake_by_address_any(f, sizeof(Futex), OS_SYNC_WAKE_BY_ADDRESS_NONE);
+ if (ret >= 0) {
+ return;
+ }
+ if (errno == EINTR || errno == EFAULT) {
+ continue;
+ }
+ if (errno == ENOENT) {
+ return;
+ }
+ GB_PANIC("Failed in futex wake %d %d!\n", ret, errno);
+ }
+ } else {
+ #endif
for (;;) {
int ret = __ulock_wake(UL_COMPARE_AND_WAIT | ULF_NO_ERRNO, f, 0);
if (ret >= 0) {
@@ -780,9 +802,29 @@ gb_internal void futex_signal(Futex *f) {
}
GB_PANIC("Failed in futex wake!\n");
}
+ #ifdef DARWIN_WAIT_ON_ADDRESS_AVAILABLE
+ }
+ #endif
}
gb_internal void futex_broadcast(Futex *f) {
+ #ifdef DARWIN_WAIT_ON_ADDRESS_AVAILABLE
+ if (__builtin_available(macOS 14.4, *)) {
+ for (;;) {
+ int ret = os_sync_wake_by_address_all(f, sizeof(Footex), OS_SYNC_WAKE_BY_ADDRESS_NONE);
+ if (ret >= 0) {
+ return;
+ }
+ if (errno == EINTR || errno == EFAULT) {
+ continue;
+ }
+ if (errno == ENOENT) {
+ return;
+ }
+ GB_PANIC("Failed in futext wake %d %d!\n", ret, errno);
+ }
+ } else {
+ #endif
for (;;) {
enum { ULF_WAKE_ALL = 0x00000100 };
int ret = __ulock_wake(UL_COMPARE_AND_WAIT | ULF_NO_ERRNO | ULF_WAKE_ALL, f, 0);
@@ -797,9 +839,32 @@ gb_internal void futex_broadcast(Futex *f) {
}
GB_PANIC("Failed in futex wake!\n");
}
+ #ifdef DARWIN_WAIT_ON_ADDRESS_AVAILABLE
+ }
+ #endif
}
gb_internal void futex_wait(Futex *f, Footex val) {
+ #ifdef DARWIN_WAIT_ON_ADDRESS_AVAILABLE
+ if (__builtin_available(macOS 14.4, *)) {
+ for (;;) {
+ int ret = os_sync_wait_on_address(f, cast(uint64_t)(val), sizeof(Footex), OS_SYNC_WAIT_ON_ADDRESS_NONE);
+ if (ret >= 0) {
+ if (*f != val) {
+ return;
+ }
+ continue;
+ }
+ if (errno == EINTR || errno == EFAULT) {
+ continue;
+ }
+ if (errno == ENOENT) {
+ return;
+ }
+ GB_PANIC("Failed in futex wait %d %d!\n", ret, errno);
+ }
+ } else {
+ #endif
for (;;) {
int ret = __ulock_wait(UL_COMPARE_AND_WAIT | ULF_NO_ERRNO, f, val, 0);
if (ret >= 0) {
@@ -817,7 +882,11 @@ gb_internal void futex_wait(Futex *f, Footex val) {
GB_PANIC("Failed in futex wait!\n");
}
+ #ifdef DARWIN_WAIT_ON_ADDRESS_AVAILABLE
+ }
+ #endif
}
+
#elif defined(GB_SYSTEM_WINDOWS)
gb_internal void futex_signal(Futex *f) {