Hi Jonathan,

As discussed off-list, here are my findings about implementing this on
Darwin (presented here for the search engine record).

> On 26 Nov 2025, at 09:48, Jonathan Wakely <[email protected]> wrote:
> 
> On Tue, 25 Nov 2025 at 21:08, Jonathan Wakely <[email protected]> wrote:
>> 
>> This defines __platform_wait, __platform_notify, and
>> __platform_wait_until for darwin, making use of the __ulock_wait and
>> __ulock_wake functions.
>> 
>> As with the FreeBSD _mutx_op syscall, these functions support both
>> 32-bit and 64-bit types.
>> 
>> libstdc++-v3/ChangeLog:
>> 
>>        PR libstdc++/120527
>>        * include/bits/atomic_wait.h [__APPLE__]: Reuse FreeBSD
>>        definitions of __platform_wait_t and __platform_wait_uses_type.
>>        * src/c++20/atomic.cc [__APPLE__] (_GLIBCXX_HAVE_PLATFORM_WAIT)
>>        (__platform_wait, __platform_notify, __platform_wait_until):
>>        Define.
>> ---
>> 
>> Untested, so I don't plan to commit this, but maybe Iain will want to
>> test it some time.
> 
> N.B. libc++ uses __ulock_wait based on this check:
> 
> #    if __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ >= 101500
> 
> so we might want a similar check,

1/

The __ulock_{wait,wake} functions are undocumented, but are very thin wrappers
over the corresponding syscall (the source code for the syscall impl. is 
available
but the source code for the wrappers is not).

The issue is less about whether the function is available (it is on much of the
supported os verison range), but more that the functionality of the syscall has
expanded over time - and therefore the presence of the function cannot be
taken to represent this (in this case whether UL_COMPARE_AND_WAIT64 is
supported).

NOTE: 32b locks are much more widely supported over OS versions if that is
useful.

2/

The functions appear to return the number of remaining waiters on the object
for OK and -1 + errno for failure.  Optionally, it seems that one can pass a 
flag
to arrange for the result to be -ERRNO-VALUE and avoid using errno.  Since
the use is in the cold path, there does not seem to be too much motivation to
avoid errno in this case.  However, we must detect fail as res < 0 rather than  
!= 0.

> and might want to not define the
> __platform_wait_uses_type variable template to true in
> bits/atomic_wait.h and instead only do a runtime check in
> use_proxy_wait, based on whether a weak symbol is resolved at runtime.
> So it would depend on the system where the code executes, instead of
> being a compile-time constant.

Because the presence of the function does not indicate it’s capability and
because we also build the runtimes to support a minimum OS version it
seems reasonable to use
 __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ as is
done elsewhere.   I’ve done that in the changes I made.

Using a weak ref is not enough here.

If we were to make a runtime check, it would have to be a dummy call that
used UL_COMPARE_AND_WAIT64 and then set some flag (I did not try
to implement this)

I’m attaching two incremental patchlets to make sure it’s clear what I
tested (they are really quite trivial - consider them review feedback).

thanks
Iain

Attachment: 0001-libstdc-Darwin-Incremental-patch-for-platform_wait.patch
Description: Binary data

Attachment: 0002-libstdc-Dragonfly-Fix-computation-of-time-offset-in-.patch
Description: Binary data


>> libstdc++-v3/include/bits/atomic_wait.h |  2 +-
>> libstdc++-v3/src/c++20/atomic.cc        | 49 +++++++++++++++++++++++++
>> 2 files changed, 50 insertions(+), 1 deletion(-)
>> 
>> diff --git a/libstdc++-v3/include/bits/atomic_wait.h 
>> b/libstdc++-v3/include/bits/atomic_wait.h
>> index 0205df40d68c..752f0e1e1baa 100644
>> --- a/libstdc++-v3/include/bits/atomic_wait.h
>> +++ b/libstdc++-v3/include/bits/atomic_wait.h
>> @@ -68,7 +68,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>>     inline constexpr bool __platform_wait_uses_type
>>       = __detail::__waitable<_Tp>
>>          && sizeof(_Tp) == sizeof(int) && alignof(_Tp) >= 4;
>> -#elif defined __FreeBSD__ && __SIZEOF_LONG__ == 8
>> +#elif defined __APPLE__ || (defined __FreeBSD__ && __SIZEOF_LONG__ == 8)
>>   namespace __detail
>>   {
>>     using __platform_wait_t = __UINT64_TYPE__;
>> diff --git a/libstdc++-v3/src/c++20/atomic.cc 
>> b/libstdc++-v3/src/c++20/atomic.cc
>> index af3b074a52c9..57509caca87a 100644
>> --- a/libstdc++-v3/src/c++20/atomic.cc
>> +++ b/libstdc++-v3/src/c++20/atomic.cc
>> @@ -39,6 +39,15 @@
>> # include <syscall.h>
>> # include <sys/time.h> // timespec
>> # define _GLIBCXX_HAVE_PLATFORM_WAIT 1
>> +#elif defined __APPLE__
>> +extern "C" int
>> +__ulock_wait(uint32_t operation, void* addr, uint64_t value, uint32_t 
>> timeout);
>> +extern "C" int
>> +__ulock_wake(uint32_t operation, void* addr, uint64_t wake_value);
>> +# define UL_COMPARE_AND_WAIT             1
>> +# define UL_COMPARE_AND_WAIT64           5
>> +# define ULF_WAKE_ALL                    0x00000100
>> +# define _GLIBCXX_HAVE_PLATFORM_WAIT 1
>> #elif defined __FreeBSD__ && __SIZEOF_LONG__ == 8
>> # include <sys/types.h>
>> # include <sys/umtx.h>
>> @@ -113,6 +122,46 @@ namespace
>>       }
>>     return true;
>>   }
>> +#elif defined __APPLE__
>> +  void
>> +  __platform_wait(const void* addr, uint64_t val, int obj_sz) noexcept
>> +  {
>> +    const uint32_t op = obj_sz == 4 ? UL_COMPARE_AND_WAIT : 
>> UL_COMPARE_AND_WAIT64;
>> +    if (__ulock_wait(op, const_cast<void*>(addr), val, 0))
>> +      if (errno != EINTR && errno != EFAULT)
>> +       __throw_system_error(errno);
>> +  }
>> +
>> +  void
>> +  __platform_notify(const void* addr, bool all, int obj_sz) noexcept
>> +  {
>> +    uint32_t op = obj_sz == 4 ? UL_COMPARE_AND_WAIT : UL_COMPARE_AND_WAIT64;
>> +    if (all)
>> +      op |= ULF_WAKE_ALL;
>> +    __ulock_wake(op, const_cast<void*>(addr), 0);
>> +  }
>> +
>> +  // returns true if wait ended before timeout
>> +  bool
>> +  __platform_wait_until(const void* addr, uint64_t val,
>> +                       const __wait_clock_t::time_point& atime,
>> +                       int obj_sz) noexcept
>> +  {
>> +    auto reltime
>> +      = chrono::ceil<chrono::microseconds>(__wait_clock_t::now() - atime);
>> +    if (reltime <= reltime.zero())
>> +      return false;
>> +    uint32_t op = obj_sz == 4 ? UL_COMPARE_AND_WAIT : UL_COMPARE_AND_WAIT64;
>> +
>> +    if (__ulock_wait(op, const_cast<void*>(addr), val, reltime.count()))
>> +      {
>> +       if (errno == ETIMEDOUT)
>> +         return false;
>> +       if (errno != EINTR && errno != EFAULT)
>> +         __throw_system_error(errno);
>> +      }
>> +    return true;
>> +  }
>> #elif defined __FreeBSD__ && __SIZEOF_LONG__ == 8
>>   void
>>   __platform_wait(const void* addr, uint64_t val, int obj_sz) noexcept
>> --
>> 2.51.1


Reply via email to