[libcxx-commits] [libcxx] 3c33c36 - [libc++] Use public os_sync API instead of private __ulock on newer Apple platforms (#202519)
via libcxx-commits
libcxx-commits at lists.llvm.org
Thu Jun 18 06:09:39 PDT 2026
Author: Louis Dionne
Date: 2026-06-18T09:09:35-04:00
New Revision: 3c33c36d925f5d278501fea55ef5a2e1f8f331ca
URL: https://github.com/llvm/llvm-project/commit/3c33c36d925f5d278501fea55ef5a2e1f8f331ca
DIFF: https://github.com/llvm/llvm-project/commit/3c33c36d925f5d278501fea55ef5a2e1f8f331ca.diff
LOG: [libc++] Use public os_sync API instead of private __ulock on newer Apple platforms (#202519)
The atomic wait and wake implementation on Apple platforms currently
relies on `__ulock_wait` and `__ulock_wake`, which are private kernel
APIs. This is a problem for anyone shipping apps through the App Store
since Apple flags private symbol usage during review.
Starting with macOS 14.4 and iOS 17.4, Apple ships public replacements
through `os_sync_wait_on_address` and `os_sync_wake_by_address_any/all`
in `<os/os_sync_wait_on_address.h>`. These cover the same functionality
and are documented, stable, and safe for App Store submissions.
This patch makes use of the public APIs instead of the private ones
whenever the underlying OS permits it.
This takes over #182947.
Fixes #182908
Fixes #146142
Co-authored-by: Bbn08 <atrancendentbeing at gmail.com>
Added:
Modified:
libcxx/src/atomic.cpp
Removed:
################################################################################
diff --git a/libcxx/src/atomic.cpp b/libcxx/src/atomic.cpp
index b661714cff6ec..ae79ac7f87965 100644
--- a/libcxx/src/atomic.cpp
+++ b/libcxx/src/atomic.cpp
@@ -17,6 +17,20 @@
#include <thread>
#include <type_traits>
+// Determine whether to use the public os_sync API on Apple platforms based on the deployment target.
+// Otherwise we fall back to the private __ulock API.
+#if defined(__APPLE__)
+# if (defined(__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__) && \
+ __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ >= 140400) || \
+ (defined(__ENVIRONMENT_IPHONE_OS_VERSION_MIN_REQUIRED__) && \
+ __ENVIRONMENT_IPHONE_OS_VERSION_MIN_REQUIRED__ >= 170400) || \
+ (defined(__ENVIRONMENT_TV_OS_VERSION_MIN_REQUIRED__) && __ENVIRONMENT_TV_OS_VERSION_MIN_REQUIRED__ >= 170400) || \
+ (defined(__ENVIRONMENT_WATCH_OS_VERSION_MIN_REQUIRED__) && \
+ __ENVIRONMENT_WATCH_OS_VERSION_MIN_REQUIRED__ >= 100400)
+# define _LIBCPP_USE_OS_SYNC
+# endif
+#endif
+
#ifdef __linux__
# include <linux/futex.h>
@@ -49,6 +63,12 @@
# include <memory>
# include <windows.h>
+#elif defined(__APPLE__)
+
+# if defined(_LIBCPP_USE_OS_SYNC)
+# include <os/os_sync_wait_on_address.h>
+# endif
+
#elif defined(__Fuchsia__)
# include <zircon/syscalls.h>
@@ -95,18 +115,62 @@ static void __platform_wake_by_address(void const* __ptr, bool __notify_one) {
#elif defined(__APPLE__)
+# if defined(_LIBCPP_USE_OS_SYNC)
+
+template <std::size_t _Size, class MaybeTimeout>
+static void __platform_wait_on_address(void const* __ptr, void const* __val, MaybeTimeout maybe_timeout_ns) {
+ std::uint64_t __value = [__val]() -> std::uint64_t {
+ if constexpr (_Size == 4) {
+ std::uint32_t __result;
+ std::memcpy(&__result, __val, _Size);
+ return __result;
+ } else if constexpr (_Size == 8) {
+ std::uint64_t __result;
+ std::memcpy(&__result, __val, _Size);
+ return __result;
+ } else {
+ static_assert(false, "Can only wait on 8 bytes or 4 bytes value");
+ }
+ }();
+
+ if constexpr (is_same_v<MaybeTimeout, NoTimeout>) {
+ os_sync_wait_on_address(const_cast<void*>(__ptr), __value, _Size, OS_SYNC_WAIT_ON_ADDRESS_NONE);
+ } else {
+ // os_sync_wait_on_address_with_timeout returns EINVAL on a 0-ns timeout, so clamp to 1ns
+ // when an already-expired deadline produced a zero-duration timeout.
+ uint64_t __timeout_ns = std::max<uint64_t>(maybe_timeout_ns, 1);
+ os_sync_wait_on_address_with_timeout(
+ const_cast<void*>(__ptr),
+ __value,
+ _Size,
+ OS_SYNC_WAIT_ON_ADDRESS_NONE,
+ OS_CLOCK_MACH_ABSOLUTE_TIME,
+ __timeout_ns);
+ }
+}
+
+template <std::size_t _Size>
+static void __platform_wake_by_address(void const* __ptr, bool __notify_one) {
+ static_assert(_Size == 8 || _Size == 4, "Can only wake up on 8 bytes or 4 bytes value");
+ if (__notify_one)
+ os_sync_wake_by_address_any(const_cast<void*>(__ptr), _Size, OS_SYNC_WAKE_BY_ADDRESS_NONE);
+ else
+ os_sync_wake_by_address_all(const_cast<void*>(__ptr), _Size, OS_SYNC_WAKE_BY_ADDRESS_NONE);
+}
+
+# else // !_LIBCPP_USE_OS_SYNC -- fall back to the private __ulock API
+
extern "C" int __ulock_wait(
uint32_t operation, void* addr, uint64_t value, uint32_t timeout); /* timeout is specified in microseconds */
extern "C" int __ulock_wake(uint32_t operation, void* addr, uint64_t wake_value);
// https://github.com/apple/darwin-xnu/blob/2ff845c2e033bd0ff64b5b6aa6063a1f8f65aa32/bsd/sys/ulock.h#L82
-# define UL_COMPARE_AND_WAIT 1
-# define UL_COMPARE_AND_WAIT64 5
-# define ULF_WAKE_ALL 0x00000100
+# define UL_COMPARE_AND_WAIT 1
+# define UL_COMPARE_AND_WAIT64 5
+# define ULF_WAKE_ALL 0x00000100
template <std::size_t _Size, class MaybeTimeout>
static void __platform_wait_on_address(void const* __ptr, void const* __val, MaybeTimeout maybe_timeout_ns) {
- static_assert(_Size == 8 || _Size == 4, "Can only wait on 8 bytes or 4 bytes value");
auto __timeout_us = [&] {
if constexpr (is_same_v<MaybeTimeout, NoTimeout>) {
return uint32_t(0);
@@ -114,17 +178,23 @@ static void __platform_wait_on_address(void const* __ptr, void const* __val, May
return std::max(static_cast<uint32_t>(maybe_timeout_ns / 1000), uint32_t(1));
}
}();
- if constexpr (_Size == 4) {
- alignas(uint32_t) char buffer[_Size];
- std::memcpy(&buffer, const_cast<const void*>(__val), _Size);
- __ulock_wait(
- UL_COMPARE_AND_WAIT, const_cast<void*>(__ptr), *reinterpret_cast<uint32_t const*>(&buffer), __timeout_us);
- } else {
- alignas(uint64_t) char buffer[_Size];
- std::memcpy(&buffer, const_cast<const void*>(__val), _Size);
- __ulock_wait(
- UL_COMPARE_AND_WAIT64, const_cast<void*>(__ptr), *reinterpret_cast<uint64_t const*>(&buffer), __timeout_us);
- }
+
+ std::uint64_t __value = [__val]() -> std::uint64_t {
+ if constexpr (_Size == 4) {
+ std::uint32_t __result;
+ std::memcpy(&__result, __val, _Size);
+ return __result;
+ } else if constexpr (_Size == 8) {
+ std::uint64_t __result;
+ std::memcpy(&__result, __val, _Size);
+ return __result;
+ } else {
+ static_assert(false, "Can only wait on 8 bytes or 4 bytes value");
+ }
+ }();
+
+ __ulock_wait(
+ _Size == 4 ? UL_COMPARE_AND_WAIT : UL_COMPARE_AND_WAIT64, const_cast<void*>(__ptr), __value, __timeout_us);
}
template <std::size_t _Size>
@@ -137,6 +207,8 @@ static void __platform_wake_by_address(void const* __ptr, bool __notify_one) {
__ulock_wake(UL_COMPARE_AND_WAIT64 | (__notify_one ? 0 : ULF_WAKE_ALL), const_cast<void*>(__ptr), 0);
}
+# endif // _LIBCPP_USE_OS_SYNC
+
#elif defined(__FreeBSD__) && __SIZEOF_LONG__ == 8
/*
* Since __cxx_contention_t is int64_t even on 32bit FreeBSD
More information about the libcxx-commits
mailing list