This also bumps the minimum supported version of macOS to 10.12 to unconditionally assume the ulock syscalls are present.
As discussed with @Gcenx this shouldn't be an issue, as the current minimum support version of 10.8 does not work anyways at the moment with only 10.10+ being useable and even then virtually no-one using wine on versions < 10.12 (and also being unsupported by the prebuilt binaries).
Bumping to 10.12 would also have the side-benefit of allowing some cleanup of deprecated functions or legacy codepaths (like `mach_continuous_time` vs `mach_absolute_time` and probably much more).
In my testing ulock futexes are about 2x faster for synchronization than kevents. Using the syscall wrappers here should provide protection against changing syscall numbers, and given that ulock is also directly used by libc++, the probability of an incompatible API change here is basically zero imo.
By default ulock is also process-private, similar to the current linux implementation, cross-process synchronization would require darwin 19+ (macOS 10.15+) with `UL_COMPARE_AND_WAIT_SHARED`.
The diff is a bit bigger than it actually is in effect, since https://gitlab.winehq.org/mzent/wine/-/commit/28a8a991c165f0d37dbf78ca3cdee4... is only moving some code around and not actually changing anything.
-- v4: ntdll: Implement futex_wait() and futex_wake_one() on macOS. ntdll: Simplify futex interface from futex_wake() to futex_wake_one(). ntdll: Use USE_FUTEX to indicate futex support. ntdll: Prefer futex for thread-ID alerts over kqueue.
From: Marc-Aurel Zent mzent@codeweavers.com
--- dlls/ntdll/unix/sync.c | 122 ++++++++++++++++++----------------------- 1 file changed, 54 insertions(+), 68 deletions(-)
diff --git a/dlls/ntdll/unix/sync.c b/dlls/ntdll/unix/sync.c index 4b2d7c1ccbc..a90e2615a54 100644 --- a/dlls/ntdll/unix/sync.c +++ b/dlls/ntdll/unix/sync.c @@ -2409,10 +2409,10 @@ NTSTATUS WINAPI NtQueryInformationAtom( RTL_ATOM atom, ATOM_INFORMATION_CLASS cl
union tid_alert_entry { -#ifdef HAVE_KQUEUE - int kq; -#elif defined(__linux__) +#ifdef __linux__ LONG futex; +#elif defined(HAVE_KQUEUE) + int kq; #else HANDLE event; #endif @@ -2450,7 +2450,9 @@ static union tid_alert_entry *get_tid_alert_entry( HANDLE tid )
entry = &tid_alert_blocks[block_idx][idx % TID_ALERT_BLOCK_SIZE];
-#ifdef HAVE_KQUEUE +#ifdef __linux__ + return entry; +#elif defined(HAVE_KQUEUE) if (!entry->kq) { int kq = kqueue(); @@ -2480,8 +2482,6 @@ static union tid_alert_entry *get_tid_alert_entry( HANDLE tid ) if (InterlockedCompareExchange( (LONG *)&entry->kq, kq, 0 )) close( kq ); } -#elif defined(__linux__) - return entry; #else if (!entry->event) { @@ -2509,7 +2509,14 @@ NTSTATUS WINAPI NtAlertThreadByThreadId( HANDLE tid )
if (!entry) return STATUS_INVALID_CID;
-#ifdef HAVE_KQUEUE +#ifdef __linux__ + { + LONG *futex = &entry->futex; + if (!InterlockedExchange( futex, 1 )) + futex_wake( futex, 1 ); + return STATUS_SUCCESS; + } +#elif defined(HAVE_KQUEUE) { static const struct kevent signal_event = { @@ -2524,13 +2531,6 @@ NTSTATUS WINAPI NtAlertThreadByThreadId( HANDLE tid ) kevent( entry->kq, &signal_event, 1, NULL, 0, NULL ); return STATUS_SUCCESS; } -#elif defined(__linux__) - { - LONG *futex = &entry->futex; - if (!InterlockedExchange( futex, 1 )) - futex_wake( futex, 1 ); - return STATUS_SUCCESS; - } #else return NtSetEvent( entry->event, NULL ); #endif @@ -2560,59 +2560,6 @@ static LONGLONG update_timeout( ULONGLONG end ) #endif
-#ifdef HAVE_KQUEUE - -/*********************************************************************** - * NtWaitForAlertByThreadId (NTDLL.@) - */ -NTSTATUS WINAPI NtWaitForAlertByThreadId( const void *address, const LARGE_INTEGER *timeout ) -{ - union tid_alert_entry *entry = get_tid_alert_entry( NtCurrentTeb()->ClientId.UniqueThread ); - ULONGLONG end; - int ret; - struct timespec timespec; - struct kevent wait_event; - - TRACE( "%p %s\n", address, debugstr_timeout( timeout ) ); - - if (!entry) return STATUS_INVALID_CID; - - if (timeout) - { - if (timeout->QuadPart == TIMEOUT_INFINITE) - timeout = NULL; - else - end = get_absolute_timeout( timeout ); - } - - do - { - if (timeout) - { - LONGLONG timeleft = update_timeout( end ); - - timespec.tv_sec = timeleft / (ULONGLONG)TICKSPERSEC; - timespec.tv_nsec = (timeleft % TICKSPERSEC) * 100; - if (timespec.tv_sec > 0x7FFFFFFF) timeout = NULL; - } - - ret = kevent( entry->kq, NULL, 0, &wait_event, 1, timeout ? ×pec : NULL ); - } while (ret == -1 && errno == EINTR); - - switch (ret) - { - case 1: - return STATUS_ALERTED; - case 0: - return STATUS_TIMEOUT; - default: - ERR( "kevent failed with error: %d (%s)\n", errno, strerror( errno ) ); - return STATUS_INVALID_HANDLE; - } -} - -#else - /*********************************************************************** * NtWaitForAlertByThreadId (NTDLL.@) */ @@ -2656,6 +2603,46 @@ NTSTATUS WINAPI NtWaitForAlertByThreadId( const void *address, const LARGE_INTEG } return STATUS_ALERTED; } +#elif defined(HAVE_KQUEUE) + { + ULONGLONG end; + int ret; + struct timespec timespec; + struct kevent wait_event; + + if (timeout) + { + if (timeout->QuadPart == TIMEOUT_INFINITE) + timeout = NULL; + else + end = get_absolute_timeout( timeout ); + } + + do + { + if (timeout) + { + LONGLONG timeleft = update_timeout( end ); + + timespec.tv_sec = timeleft / (ULONGLONG)TICKSPERSEC; + timespec.tv_nsec = (timeleft % TICKSPERSEC) * 100; + if (timespec.tv_sec > 0x7FFFFFFF) timeout = NULL; + } + + ret = kevent( entry->kq, NULL, 0, &wait_event, 1, timeout ? ×pec : NULL ); + } while (ret == -1 && errno == EINTR); + + switch (ret) + { + case 1: + return STATUS_ALERTED; + case 0: + return STATUS_TIMEOUT; + default: + ERR( "kevent failed with error: %d (%s)\n", errno, strerror( errno ) ); + return STATUS_INVALID_HANDLE; + } + } #else { NTSTATUS status = NtWaitForSingleObject( entry->event, FALSE, timeout ); @@ -2665,7 +2652,6 @@ NTSTATUS WINAPI NtWaitForAlertByThreadId( const void *address, const LARGE_INTEG #endif }
-#endif
/* Notify direct completion of async and close the wait handle if it is no longer needed. */
From: Marc-Aurel Zent mzent@codeweavers.com
--- dlls/ntdll/unix/sync.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-)
diff --git a/dlls/ntdll/unix/sync.c b/dlls/ntdll/unix/sync.c index a90e2615a54..dbaeba3c5ac 100644 --- a/dlls/ntdll/unix/sync.c +++ b/dlls/ntdll/unix/sync.c @@ -103,9 +103,10 @@ static inline ULONGLONG monotonic_counter(void) return ticks_from_time_t( now.tv_sec ) + now.tv_usec * 10 - server_start_time; }
- #ifdef __linux__
+#define USE_FUTEX + #include <linux/futex.h>
static inline int futex_wait( const LONG *addr, int val, struct timespec *timeout ) @@ -129,8 +130,7 @@ static inline int futex_wake( const LONG *addr, int val ) return syscall( __NR_futex, addr, FUTEX_WAKE_PRIVATE, val, NULL, 0, 0 ); }
-#endif - +#endif /* __linux__ */
/* create a struct security_descriptor and contained information in one contiguous piece of memory */ unsigned int alloc_object_attributes( const OBJECT_ATTRIBUTES *attr, struct object_attributes **ret, @@ -2409,7 +2409,7 @@ NTSTATUS WINAPI NtQueryInformationAtom( RTL_ATOM atom, ATOM_INFORMATION_CLASS cl
union tid_alert_entry { -#ifdef __linux__ +#ifdef USE_FUTEX LONG futex; #elif defined(HAVE_KQUEUE) int kq; @@ -2450,7 +2450,7 @@ static union tid_alert_entry *get_tid_alert_entry( HANDLE tid )
entry = &tid_alert_blocks[block_idx][idx % TID_ALERT_BLOCK_SIZE];
-#ifdef __linux__ +#ifdef USE_FUTEX return entry; #elif defined(HAVE_KQUEUE) if (!entry->kq) @@ -2509,7 +2509,7 @@ NTSTATUS WINAPI NtAlertThreadByThreadId( HANDLE tid )
if (!entry) return STATUS_INVALID_CID;
-#ifdef __linux__ +#ifdef USE_FUTEX { LONG *futex = &entry->futex; if (!InterlockedExchange( futex, 1 )) @@ -2537,7 +2537,7 @@ NTSTATUS WINAPI NtAlertThreadByThreadId( HANDLE tid ) }
-#if defined(__linux__) || defined(HAVE_KQUEUE) +#if defined(USE_FUTEX) || defined(HAVE_KQUEUE) static LONGLONG get_absolute_timeout( const LARGE_INTEGER *timeout ) { LARGE_INTEGER now; @@ -2571,7 +2571,7 @@ NTSTATUS WINAPI NtWaitForAlertByThreadId( const void *address, const LARGE_INTEG
if (!entry) return STATUS_INVALID_CID;
-#ifdef __linux__ +#ifdef USE_FUTEX { LONG *futex = &entry->futex; ULONGLONG end;
From: Marc-Aurel Zent mzent@codeweavers.com
--- dlls/ntdll/unix/sync.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/dlls/ntdll/unix/sync.c b/dlls/ntdll/unix/sync.c index dbaeba3c5ac..a3457cd581d 100644 --- a/dlls/ntdll/unix/sync.c +++ b/dlls/ntdll/unix/sync.c @@ -125,9 +125,9 @@ static inline int futex_wait( const LONG *addr, int val, struct timespec *timeou return syscall( __NR_futex, addr, FUTEX_WAIT_PRIVATE, val, timeout, 0, 0 ); }
-static inline int futex_wake( const LONG *addr, int val ) +static inline int futex_wake_one( const LONG *addr ) { - return syscall( __NR_futex, addr, FUTEX_WAKE_PRIVATE, val, NULL, 0, 0 ); + return syscall( __NR_futex, addr, FUTEX_WAKE_PRIVATE, 1, NULL, 0, 0 ); }
#endif /* __linux__ */ @@ -2513,7 +2513,7 @@ NTSTATUS WINAPI NtAlertThreadByThreadId( HANDLE tid ) { LONG *futex = &entry->futex; if (!InterlockedExchange( futex, 1 )) - futex_wake( futex, 1 ); + futex_wake_one( futex ); return STATUS_SUCCESS; } #elif defined(HAVE_KQUEUE)
From: Marc-Aurel Zent mzent@codeweavers.com
--- README.md | 2 +- dlls/ntdll/unix/sync.c | 66 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 66 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md index 8a804b30dfc..28ab8fe7299 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ To compile and run Wine, you must have one of the following: - FreeBSD 12.4 or later - Solaris x86 9 or later - NetBSD-current -- Mac OS X 10.8 or later +- Mac OS X 10.12 or later
As Wine requires kernel-level thread support to run, only the operating systems mentioned above are supported. Other operating systems which diff --git a/dlls/ntdll/unix/sync.c b/dlls/ntdll/unix/sync.c index a3457cd581d..6be5fdcf525 100644 --- a/dlls/ntdll/unix/sync.c +++ b/dlls/ntdll/unix/sync.c @@ -130,7 +130,71 @@ static inline int futex_wake_one( const LONG *addr ) return syscall( __NR_futex, addr, FUTEX_WAKE_PRIVATE, 1, NULL, 0, 0 ); }
-#endif /* __linux__ */ +#elif defined(__APPLE__) + +#define USE_FUTEX + +#include <AvailabilityMacros.h> + +#ifdef MAC_OS_VERSION_14_4 +#include <os/os_sync_wait_on_address.h> +#endif + +#define UL_COMPARE_AND_WAIT 1 + +extern int __ulock_wait( uint32_t operation, void *addr, uint64_t value, uint32_t timeout ); + +extern int __ulock_wake( uint32_t operation, void *addr, uint64_t wake_value ); + +static inline int futex_wait( const LONG *addr, int val, struct timespec *timeout ) +{ +#ifdef MAC_OS_VERSION_14_4 + if (__builtin_available( macOS 14.4, * )) + { + /* 18446744073 seconds could overflow a uint64_t in nanoseconds */ + if (timeout && timeout->tv_sec < 18446744073) + { + uint64_t ns_timeout = (timeout->tv_sec * 1000000000) + timeout->tv_nsec; + + if (!ns_timeout) + { + errno = ETIMEDOUT; + return -1; + } + return os_sync_wait_on_address_with_timeout( (void *)addr, (uint64_t)val, 4, OS_SYNC_WAIT_ON_ADDRESS_NONE, + OS_CLOCK_MACH_ABSOLUTE_TIME, ns_timeout ); + } + + return os_sync_wait_on_address( (void *)addr, (uint64_t)val, 4, OS_SYNC_WAIT_ON_ADDRESS_NONE ); + } +#endif + + /* 4294 seconds could overflow a uint32_t in microseconds */ + if (timeout && timeout->tv_sec < 4294) + { + uint32_t us_timeout = ((uint32_t)timeout->tv_sec * 1000000) + ((uint32_t)timeout->tv_nsec / 1000); + + if (!us_timeout) + { + errno = ETIMEDOUT; + return -1; + } + return __ulock_wait( UL_COMPARE_AND_WAIT, (void *)addr, (uint64_t)val, us_timeout ); + } + + return __ulock_wait( UL_COMPARE_AND_WAIT, (void *)addr, (uint64_t)val, 0 ); +} + +static inline int futex_wake_one( const LONG *addr ) +{ +#ifdef MAC_OS_VERSION_14_4 + if (__builtin_available( macOS 14.4, * )) + return os_sync_wake_by_address_any( (void *)addr, 4, OS_SYNC_WAKE_BY_ADDRESS_NONE ); +#endif + return __ulock_wake( UL_COMPARE_AND_WAIT, (void *)addr, 0 ); +} + +#endif /* __APPLE__ */
/* create a struct security_descriptor and contained information in one contiguous piece of memory */ unsigned int alloc_object_attributes( const OBJECT_ATTRIBUTES *attr, struct object_attributes **ret,
Sorry for the late approval; I missed that I had been assigned.
As far as I can tell from the documentation this is correct and matches the semantics we need.
There are a couple trailing whitespace errors in the last patch though.
This merge request was approved by Elizabeth Figura.