libstdc++: Add platform wait functions for Darwin [PR120527]

Darwin has kernel support for this facility from 10.12 (macOS Sierra).

From 10.15 (macOS Catalina) 64bit qualitities are supported.

When the library is built for 10.12+ both 32b and 64b quantities will be
supported by the DSO which means it can be installed on 10.12+ with support
for 64bit available when the instalation is >= 10.15.

The header will only recognise 64b quantities when the deployment version
is >= 10.15.

If the library is built for <= 10.11, the support will be missing and attempts
to use it wlll result in link errors.

The platform wait type is unconditionally set to 32bits, since this is compatible
across supported OS editions.

	PR libstdc++/120527

libstdc++-v3/ChangeLog:

	* include/bits/atomic_wait.h:
	* src/c++20/atomic.cc (__ulock_wait): Enable supported Darwin versions.
	(__ulock_wake): Likewise.
	(UL_COMPARE_AND_WAIT): New.
	(UL_COMPARE_AND_WAIT64): New.
	(ULF_WAKE_ALL): New.
	(_GLIBCXX_HAVE_PLATFORM_WAIT): Enable for suppported Darwin versions.

Co-authored-by: Iain Sandoe <iain@sandoe.co.uk>
Signed-off-by: Iain Sandoe <iain@sandoe.co.uk>
This commit is contained in:
Jonathan Wakely
2025-12-05 15:47:10 +00:00
committed by Jonathan Wakely
parent 6adc49fc08
commit a261719a1f
2 changed files with 95 additions and 0 deletions

View File

@@ -69,6 +69,27 @@ _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__ >= 101200
namespace __detail
{
using __platform_wait_t = __INT32_TYPE__;
inline constexpr size_t __platform_wait_alignment = 4;
}
// Defined to true for a subset of __waitable types which are statically
// known to definitely be able to use __ulock_wait, not a proxy wait.
// We know that OS Versions later than 10.15 support 64b wait types even
// though we must make the __platform_wait_t 32b for compatibility with
// earlier versions of __ulock_xxxx.
template<typename _Tp>
inline constexpr bool __platform_wait_uses_type
= __detail::__waitable<_Tp>
# if __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ < 101500
&& sizeof(_Tp) == 4 && alignof(_Tp) >= 4;
# else
&& ((sizeof(_Tp) == 4 && alignof(_Tp) >= 4)
|| (sizeof(_Tp) == 8 && alignof(_Tp) >= 8));
# endif
#elif defined __FreeBSD__ && __SIZEOF_LONG__ == 8
namespace __detail
{

View File

@@ -27,6 +27,7 @@
#if __glibcxx_atomic_wait
#include <atomic>
#include <bits/atomic_timed_wait.h>
#include <utility> // cmp_less
#include <cstdint> // uint32_t, uint64_t, uintptr_t
#include <climits> // INT_MAX
#include <cerrno> // errno, ETIMEDOUT, etc.
@@ -39,6 +40,19 @@
# include <unistd.h>
# include <sys/time.h> // timespec
# define _GLIBCXX_HAVE_PLATFORM_WAIT 1
#elif defined __APPLE__ \
&& __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ >= 101200
// 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.
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__ && __FreeBSD__ >= 11 && __SIZEOF_LONG__ == 8
# include <sys/types.h>
# include <sys/umtx.h>
@@ -154,6 +168,66 @@ namespace
__platform_load(const int* addr, int order, int /* obj_sz */) noexcept
{ return __atomic_load_n(addr, order); }
#elif defined __APPLE__ \
&& __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ >= 101200
[[gnu::always_inline]]
inline uint32_t
wait_op(int obj_sz) noexcept
{
__glibcxx_assert(obj_sz == 4 || obj_sz == 8);
return obj_sz == 4 ? UL_COMPARE_AND_WAIT : UL_COMPARE_AND_WAIT64;
}
void
__platform_wait(const void* addr, uint64_t val, int obj_sz) noexcept
{
if (0 > __ulock_wait(wait_op(obj_sz), 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 = wait_op (obj_sz);
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>(atime - __wait_clock_t::now());
if (reltime <= reltime.zero())
return false;
uint32_t timeout = numeric_limits<uint32_t>::max();
if (std::cmp_less(reltime.count(), timeout))
timeout = reltime.count();
if (0 > __ulock_wait(wait_op(obj_sz), const_cast<void*>(addr), val,
timeout))
{
if (errno == ETIMEDOUT)
return timeout == numeric_limits<uint32_t>::max();
if (errno != EINTR && errno != EFAULT)
__throw_system_error(errno);
}
return true;
}
// ??? CHECKME: this could likely be more efficient.
[[gnu::always_inline]]
inline __wait_value_type
__platform_load(const __platform_wait_t* addr, int memory_order,
int /* obj_sz */) noexcept
{ return __atomic_load_n(addr, memory_order); }
#elif defined __FreeBSD__ && __SIZEOF_LONG__ == 8
[[gnu::always_inline]]
inline int