99#include " gsl/gsl"
1010
1111#ifdef __APPLE__
12+ #include < AvailabilityMacros.h>
13+ #endif
14+
15+ #if defined(MAC_OS_X_VERSION_MIN_REQUIRED) && MAC_OS_X_VERSION_MIN_REQUIRED < 110000
16+ /* For macOS versions before 11, we can't rely on atomic::wait and friends, and
17+ * need to use custom methods.
18+ */
19+
20+ #include < mutex>
21+ #include < condition_variable>
22+
23+ /* These could be put into a table where, rather than one mutex/condvar/counter
24+ * set that all atomic waiters use, individual waiters will use one of a number
25+ * of different sets dependent on their address, reducing the number of waiters
26+ * on any given set. However, we use this as a fallback for much older macOS
27+ * systems and only expect one atomic waiter per device, and one device per
28+ * process, which itself won't wake the atomic terribly often, so the extra
29+ * complexity and resources may be best avoided if not necessary.
30+ */
31+ inline auto gAtomicWaitMutex = std::mutex{};
32+ inline auto gAtomicWaitCondVar = std::condition_variable{};
33+ inline auto gAtomicWaitCounter = 0_u32;
34+
1235/* See: https://outerproduct.net/futex-dictionary.html */
1336#define UL_COMPARE_AND_WAIT 1
1437#define UL_UNFAIR_LOCK 2
1942
2043#define ULF_WAKE_ALL 0x00000100
2144
22- extern " C" auto __ulock_wait (u32 op, void *addr, u64 value, u32 timeout) -> int;
23- extern " C" auto __ulock_wake (u32 op, void *addr, u64 wake_value) -> int;
45+ extern " C" {
46+ auto __attribute__ ((weak_import)) __ulock_wait(u32 op, void *addr, u64 value, u32 timeout) -> int;
47+ auto __attribute__ ((weak_import)) __ulock_wake(u32 op, void *addr, u64 wake_value) -> int;
48+ } /* extern "C" */
2449#endif
2550
2651
@@ -51,17 +76,31 @@ namespace al {
5176
5277template <typename T>
5378auto atomic_wait (std::atomic<T> &aval, T const value,
54- std::memory_order const order [[maybe_unused]] = std::memory_order_seq_cst) noexcept -> void
79+ std::memory_order const order = std::memory_order_seq_cst) noexcept -> void
5580{
56- #ifdef __APPLE__
81+ #if defined(MAC_OS_X_VERSION_MIN_REQUIRED) && MAC_OS_X_VERSION_MIN_REQUIRED < 110000
5782 static_assert (sizeof (aval) == sizeof (T));
5883
59- if constexpr (sizeof (T) == sizeof (u32 ))
60- __ulock_wait (UL_COMPARE_AND_WAIT, &aval, value, 0 );
61- else if constexpr (sizeof (T) == sizeof (u64 ))
62- __ulock_wait (UL_COMPARE_AND_WAIT64, &aval, value, 0 );
84+ if (sizeof (T) == sizeof (u32 ) && __ulock_wait != nullptr )
85+ {
86+ while (aval.load (order) != value)
87+ __ulock_wait (UL_COMPARE_AND_WAIT, &aval, value, 0 );
88+ }
89+ #if MAC_OS_X_VERSION_MIN_REQUIRED >= 101500
90+ else if (sizeof (T) == sizeof (u64 ) && __ulock_wait != nullptr )
91+ {
92+ while (aval.load (order) != value)
93+ __ulock_wait (UL_COMPARE_AND_WAIT64, &aval, value, 0 );
94+ }
95+ #endif
6396 else
64- static_assert (sizeof (T) == sizeof (u32 ) || sizeof (T) == sizeof (u64 ));
97+ {
98+ auto lock = std::unique_lock{gAtomicWaitMutex };
99+ ++gAtomicWaitCounter ;
100+ while (aval.load (order) != value)
101+ gAtomicWaitCondVar .wait (lock);
102+ --gAtomicWaitCounter ;
103+ }
65104
66105#else
67106
@@ -72,15 +111,30 @@ auto atomic_wait(std::atomic<T> &aval, T const value,
72111template <typename T>
73112auto atomic_notify_one (std::atomic<T> &aval) noexcept -> void
74113{
75- #ifdef __APPLE__
114+ #if defined(MAC_OS_X_VERSION_MIN_REQUIRED) && MAC_OS_X_VERSION_MIN_REQUIRED < 110000
76115 static_assert (sizeof (aval) == sizeof (T));
77116
78- if constexpr (sizeof (T) == sizeof (u32 ))
117+ if (sizeof (T) == sizeof (u32 ) && __ulock_wake != nullptr )
79118 __ulock_wake (UL_COMPARE_AND_WAIT, &aval, 0 );
80- else if constexpr (sizeof (T) == sizeof (u64 ))
119+ #if MAC_OS_X_VERSION_MIN_REQUIRED >= 101500
120+ else if (sizeof (T) == sizeof (u64 ) && __ulock_wake != nullptr )
81121 __ulock_wake (UL_COMPARE_AND_WAIT64, &aval, 0 );
122+ #endif
82123 else
83- static_assert (sizeof (T) == sizeof (u32 ) || sizeof (T) == sizeof (u64 ));
124+ {
125+ auto lock = std::unique_lock{gAtomicWaitMutex };
126+ auto const numwaits = gAtomicWaitCounter ;
127+ lock.unlock ();
128+ if (numwaits > 0 )
129+ {
130+ /* notify_all since we can't guarantee notify_one will wake a
131+ * waiter waiting on this particular object. With notify_all, we
132+ * just act as all if waiters were spuriously woken up and they'll
133+ * recheck.
134+ */
135+ gAtomicWaitCondVar .notify_all ();
136+ }
137+ }
84138
85139#else
86140
@@ -91,15 +145,23 @@ auto atomic_notify_one(std::atomic<T> &aval) noexcept -> void
91145template <typename T>
92146auto atomic_notify_all (std::atomic<T> &aval) noexcept -> void
93147{
94- #ifdef __APPLE__
148+ #if defined(MAC_OS_X_VERSION_MIN_REQUIRED) && MAC_OS_X_VERSION_MIN_REQUIRED < 110000
95149 static_assert (sizeof (aval) == sizeof (T));
96150
97- if constexpr (sizeof (T) == sizeof (u32 ))
151+ if (sizeof (T) == sizeof (u32 ) && __ulock_wake != nullptr )
98152 __ulock_wake (UL_COMPARE_AND_WAIT | ULF_WAKE_ALL, &aval, 0 );
99- else if constexpr (sizeof (T) == sizeof (u64 ))
153+ #if defined(MAC_OS_X_VERSION_MIN_REQUIRED) && MAC_OS_X_VERSION_MIN_REQUIRED >= 101500
154+ else if (sizeof (T) == sizeof (u64 ) && __ulock_wake != nullptr )
100155 __ulock_wake (UL_COMPARE_AND_WAIT64 | ULF_WAKE_ALL, &aval, 0 );
156+ #endif
101157 else
102- static_assert (sizeof (T) == sizeof (u32 ) || sizeof (T) == sizeof (u64 ));
158+ {
159+ auto lock = std::unique_lock{gAtomicWaitMutex };
160+ auto const numwaits = gAtomicWaitCounter ;
161+ lock.unlock ();
162+ if (numwaits > 0 )
163+ gAtomicWaitCondVar .notify_all ();
164+ }
103165
104166#else
105167
0 commit comments