On Sat, 06 Dec 2025 at 10:52 +0000, Jonathan Wakely wrote:
On Fri, 05 Dec 2025 at 20:08 +0000, Iain Sandoe wrote:
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.
We could define __detail::__platform_wait_t as uint32_t in the header,
as the statically-known-to-work type, and have the library decide at
runtime whether 64b types are also supported. That would mean code
compiled against an older SDK would still use __ulock_wait if it's
deployed on a system where the libstdc++.dylib was built against a
newer SDK and so knows it can use 64b __ulock_wait.
(FAOD the "runtime check" I'm referring to here means the user code
compiled against libstdc++ headers doesn't know the answer so calls a
function in the dylib, which gives an answer - but that answer is
statically determined in the dylib based on which SDK the dylib was
compiled against. I'm not talking about the dylib trying a 64b
__ulock_wait call to see if it works, or checking weak refs or
anything like that.)
Either way, the #ifdef __APPLE__ check in bits/atomic_wait.h needs to
ensure that __platform_wait_uses_type<T> is only true for types that
really are supported by the library code. So if we restrict the use of
__ulock_wait to __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ > 101500
in the library, then we need to also guard the code in the header with
an equivalent check. Or we could not have the __APPLE__ check in the
header at all, so that no types are statically-known-to-work and all
types need to do a runtime check by calling into the library (which
would then say that 32b and 64b types are supported, and we could make
that support 32b types for older OS versions, and restrict 64b support
to >= 101500.
Or the header could use the older check and be conservative and only
define __platform_wait_uses_type<T> to true for 32b types, and the
library runtime check could say yes for 64b based on a check for the
later version.
I can hack that up to show what it would look like if you want to try
and have more support for older systems.
Something like this (on top of your fixes, which I've just pushed to
the atomic-wait-future-proof branch in my fork on the forge).
This changes the __platform_wait_t type in the header to be 32b and
enables that for some older version (substitue a real value for ????).
Then in the library code this defines _GLIBCXX_HAVE_PLATFORM_WAIT
consistently with that check in the header, but if the OS version is
101500 then also define UL_COMPARE_AND_WAIT64 and make the
use_proxy_wait function return true for 64b types as well.
If we did want to do this, we should do it now, because changing the
size of __platform_wait_t in the header does affect ABI. Changing
the use_proxy_wait function to return true for more types later is not
an ABI change, because what users see in the header doesn't change.
diff --git a/libstdc++-v3/include/bits/atomic_wait.h
b/libstdc++-v3/include/bits/atomic_wait.h
index dbdcdcfd04ca..eec97933f07a 100644
--- a/libstdc++-v3/include/bits/atomic_wait.h
+++ b/libstdc++-v3/include/bits/atomic_wait.h
@@ -70,9 +70,18 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
inline constexpr bool __platform_wait_uses_type
= __detail::__waitable<_Tp>
&& sizeof(_Tp) == sizeof(int) && alignof(_Tp) >= 4;
-#elif (defined __APPLE__ && __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ \
- && __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ >= 101500) \
- || (defined __FreeBSD__ && __SIZEOF_LONG__ == 8)
+#elif defined __APPLE__ && __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ \
+ && __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ >= ????
+ namespace __detail
+ {
+ using __platform_wait_t = __UINT32_TYPE__;
+ inline constexpr size_t __platform_wait_alignment = 4;
+ }
+ template<typename _Tp>
+ inline constexpr bool __platform_wait_uses_type
+ = __detail::__waitable<_Tp>
+ && ((sizeof(_Tp) == 4 && alignof(_Tp) >= 4);
+#elif 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 08c32456ce4e..eef97851fa03 100644
--- a/libstdc++-v3/src/c++20/atomic.cc
+++ b/libstdc++-v3/src/c++20/atomic.cc
@@ -41,7 +41,7 @@
# include <sys/time.h> // timespec
# define _GLIBCXX_HAVE_PLATFORM_WAIT 1
#elif defined __APPLE__ && __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ \
- && __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ >= 101500
+ && __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ >= ????
// These are thin wrappers over the underlying syscall, they exist on
// earlier versions of the OS, however those versions do not support the
// UL_COMPARE_AND_WAIT64 operation.
@@ -50,7 +50,9 @@ __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
+# if __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ >= 101500
+# define UL_COMPARE_AND_WAIT64 5
+# endif
# define ULF_WAKE_ALL 0x00000100
# define _GLIBCXX_HAVE_PLATFORM_WAIT 1
#elif defined __FreeBSD__ && __FreeBSD__ >= 11 && __SIZEOF_LONG__ == 8
@@ -162,12 +164,17 @@ namespace
}
return true;
}
-#elif defined __APPLE__ && __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ \
- && __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ >= 101500
+#elif defined __APPLE__
[[gnu::always_inline]]
inline uint32_t
wait_op(int obj_sz) noexcept
- { return obj_sz == 4 ? UL_COMPARE_AND_WAIT : UL_COMPARE_AND_WAIT64; }
+ {
+#ifdef UL_COMPARE_AND_WAIT64
+ return obj_sz == 4 ? UL_COMPARE_AND_WAIT : UL_COMPARE_AND_WAIT64;
+#else
+ return UL_COMPARE_AND_WAIT;
+#endif
+ }
void
__platform_wait(const void* addr, uint64_t val, int obj_sz) noexcept
@@ -486,6 +493,13 @@ namespace
if (args._M_obj_size == sizeof(uint64_t))
return false;
+#if defined __APPLE__ && defined UL_COMPARE_AND_WAIT64
+ // __platform_wait_uses_type<uint64_t> is false in the header,
+ // but we know that this dylib does actually support 64b waits.
+ if (args._M_obj_size == sizeof(uint64_t))
+ return false;
+#endif
+
// __wait_args::_M_old can only hold 64 bits, so larger types
// must always use a proxy wait.
if (args._M_obj_size > sizeof(uint64_t))