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.
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 cde6a0f8483..917c281b6a5 100644 --- a/dlls/ntdll/unix/sync.c +++ b/dlls/ntdll/unix/sync.c @@ -2334,10 +2334,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 @@ -2375,7 +2375,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(); @@ -2405,8 +2407,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) { @@ -2434,7 +2434,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 = { @@ -2449,13 +2456,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 @@ -2485,59 +2485,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.@) */ @@ -2581,6 +2528,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 ); @@ -2590,7 +2577,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 917c281b6a5..3ad6e4d9759 100644 --- a/dlls/ntdll/unix/sync.c +++ b/dlls/ntdll/unix/sync.c @@ -100,9 +100,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 HAVE_FUTEX + #include <linux/futex.h>
static inline int futex_wait( const LONG *addr, int val, struct timespec *timeout ) @@ -126,8 +127,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, @@ -2334,7 +2334,7 @@ NTSTATUS WINAPI NtQueryInformationAtom( RTL_ATOM atom, ATOM_INFORMATION_CLASS cl
union tid_alert_entry { -#ifdef __linux__ +#ifdef HAVE_FUTEX LONG futex; #elif defined(HAVE_KQUEUE) int kq; @@ -2375,7 +2375,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 HAVE_FUTEX return entry; #elif defined(HAVE_KQUEUE) if (!entry->kq) @@ -2434,7 +2434,7 @@ NTSTATUS WINAPI NtAlertThreadByThreadId( HANDLE tid )
if (!entry) return STATUS_INVALID_CID;
-#ifdef __linux__ +#ifdef HAVE_FUTEX { LONG *futex = &entry->futex; if (!InterlockedExchange( futex, 1 )) @@ -2462,7 +2462,7 @@ NTSTATUS WINAPI NtAlertThreadByThreadId( HANDLE tid ) }
-#if defined(__linux__) || defined(HAVE_KQUEUE) +#if defined(HAVE_FUTEX) || defined(HAVE_KQUEUE) static LONGLONG get_absolute_timeout( const LARGE_INTEGER *timeout ) { LARGE_INTEGER now; @@ -2496,7 +2496,7 @@ NTSTATUS WINAPI NtWaitForAlertByThreadId( const void *address, const LARGE_INTEG
if (!entry) return STATUS_INVALID_CID;
-#ifdef __linux__ +#ifdef HAVE_FUTEX { LONG *futex = &entry->futex; ULONGLONG end;
From: Marc-Aurel Zent mzent@codeweavers.com
--- README.md | 2 +- dlls/ntdll/unix/sync.c | 35 ++++++++++++++++++++++++++++++++++- 2 files changed, 35 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 3ad6e4d9759..140a0c4ab19 100644 --- a/dlls/ntdll/unix/sync.c +++ b/dlls/ntdll/unix/sync.c @@ -127,7 +127,40 @@ static inline int futex_wake( const LONG *addr, int val ) return syscall( __NR_futex, addr, FUTEX_WAKE_PRIVATE, val, NULL, 0, 0 ); }
-#endif /* __linux__ */ +#elif defined(__APPLE__) + +#define HAVE_FUTEX + +#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 ) +{ + /* 4294 seconds could overflow a uint32_t in microseconds */ + if (timeout && timeout->tv_sec < 4294) + { + uint32_t us_timeout = (timeout->tv_sec * 1000000) + (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( const LONG *addr, int val ) +{ + return __ulock_wake( UL_COMPARE_AND_WAIT, (void *)addr, (uint64_t)val ); +} + +#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,
Brendan Shanks (@bshanks) commented about dlls/ntdll/unix/sync.c:
return ticks_from_time_t( now.tv_sec ) + now.tv_usec * 10 - server_start_time;
}
#ifdef __linux__
+#define HAVE_FUTEX
This seems like it could be confusing, since `HAVE_` defines are usually set by configure. Maybe `USE_FUTEX`?
Brendan Shanks (@bshanks) commented about dlls/ntdll/unix/sync.c:
{
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( const LONG *addr, int val ) +{
- return __ulock_wake( UL_COMPARE_AND_WAIT, (void *)addr, (uint64_t)val );
+}
+#endif /* __APPLE__ */
These APIs are being publicly added to macOS 14.4 (`/usr/include/os/os_sync_wait_on_address.h` in Xcode 15.3, set for release any day now). Could this be implemented using that API, but use a fallback implementation (implemented with the private ulock APIs) when not building against/running on 14.4?