Skip to content

Commit ade218b

Browse files
committed
Fix support for older macOS without __ulock functions
1 parent 6551dde commit ade218b

1 file changed

Lines changed: 79 additions & 17 deletions

File tree

common/atomic.h

Lines changed: 79 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,29 @@
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
@@ -19,8 +42,10 @@
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

5277
template<typename T>
5378
auto 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,
72111
template<typename T>
73112
auto 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
91145
template<typename T>
92146
auto 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

Comments
 (0)