xref: /freebsd/contrib/llvm-project/libcxx/src/atomic.cpp (revision 5ffd83dbcc34f10e07f6d3e968ae6365869615f4)
1*5ffd83dbSDimitry Andric //===------------------------- atomic.cpp ---------------------------------===//
2*5ffd83dbSDimitry Andric //
3*5ffd83dbSDimitry Andric // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4*5ffd83dbSDimitry Andric // See https://llvm.org/LICENSE.txt for license information.
5*5ffd83dbSDimitry Andric // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6*5ffd83dbSDimitry Andric //
7*5ffd83dbSDimitry Andric //===----------------------------------------------------------------------===//
8*5ffd83dbSDimitry Andric 
9*5ffd83dbSDimitry Andric #include <__config>
10*5ffd83dbSDimitry Andric #ifndef _LIBCPP_HAS_NO_THREADS
11*5ffd83dbSDimitry Andric 
12*5ffd83dbSDimitry Andric #include <climits>
13*5ffd83dbSDimitry Andric #include <atomic>
14*5ffd83dbSDimitry Andric #include <functional>
15*5ffd83dbSDimitry Andric 
16*5ffd83dbSDimitry Andric #include <iostream>
17*5ffd83dbSDimitry Andric 
18*5ffd83dbSDimitry Andric #ifdef __linux__
19*5ffd83dbSDimitry Andric 
20*5ffd83dbSDimitry Andric #include <unistd.h>
21*5ffd83dbSDimitry Andric #include <linux/futex.h>
22*5ffd83dbSDimitry Andric #include <sys/syscall.h>
23*5ffd83dbSDimitry Andric 
24*5ffd83dbSDimitry Andric #else // <- Add other operating systems here
25*5ffd83dbSDimitry Andric 
26*5ffd83dbSDimitry Andric // Baseline needs no new headers
27*5ffd83dbSDimitry Andric 
28*5ffd83dbSDimitry Andric #endif
29*5ffd83dbSDimitry Andric 
30*5ffd83dbSDimitry Andric _LIBCPP_BEGIN_NAMESPACE_STD
31*5ffd83dbSDimitry Andric 
32*5ffd83dbSDimitry Andric #ifdef __linux__
33*5ffd83dbSDimitry Andric 
34*5ffd83dbSDimitry Andric static void __libcpp_platform_wait_on_address(__cxx_atomic_contention_t const volatile* __ptr,
35*5ffd83dbSDimitry Andric                                               __cxx_contention_t __val)
36*5ffd83dbSDimitry Andric {
37*5ffd83dbSDimitry Andric     static constexpr timespec __timeout = { 2, 0 };
38*5ffd83dbSDimitry Andric     syscall(SYS_futex, __ptr, FUTEX_WAIT_PRIVATE, __val, &__timeout, 0, 0);
39*5ffd83dbSDimitry Andric }
40*5ffd83dbSDimitry Andric 
41*5ffd83dbSDimitry Andric static void __libcpp_platform_wake_by_address(__cxx_atomic_contention_t const volatile* __ptr,
42*5ffd83dbSDimitry Andric                                               bool __notify_one)
43*5ffd83dbSDimitry Andric {
44*5ffd83dbSDimitry Andric     syscall(SYS_futex, __ptr, FUTEX_WAKE_PRIVATE, __notify_one ? 1 : INT_MAX, 0, 0, 0);
45*5ffd83dbSDimitry Andric }
46*5ffd83dbSDimitry Andric 
47*5ffd83dbSDimitry Andric #elif defined(__APPLE__) && defined(_LIBCPP_USE_ULOCK)
48*5ffd83dbSDimitry Andric 
49*5ffd83dbSDimitry Andric extern "C" int __ulock_wait(uint32_t operation, void *addr, uint64_t value,
50*5ffd83dbSDimitry Andric 		uint32_t timeout); /* timeout is specified in microseconds */
51*5ffd83dbSDimitry Andric extern "C" int __ulock_wake(uint32_t operation, void *addr, uint64_t wake_value);
52*5ffd83dbSDimitry Andric 
53*5ffd83dbSDimitry Andric #define UL_COMPARE_AND_WAIT				1
54*5ffd83dbSDimitry Andric #define ULF_WAKE_ALL					0x00000100
55*5ffd83dbSDimitry Andric 
56*5ffd83dbSDimitry Andric static void __libcpp_platform_wait_on_address(__cxx_atomic_contention_t const volatile* __ptr,
57*5ffd83dbSDimitry Andric                                               __cxx_contention_t __val)
58*5ffd83dbSDimitry Andric {
59*5ffd83dbSDimitry Andric     __ulock_wait(UL_COMPARE_AND_WAIT,
60*5ffd83dbSDimitry Andric                  const_cast<__cxx_atomic_contention_t*>(__ptr), __val, 0);
61*5ffd83dbSDimitry Andric }
62*5ffd83dbSDimitry Andric 
63*5ffd83dbSDimitry Andric static void __libcpp_platform_wake_by_address(__cxx_atomic_contention_t const volatile* __ptr,
64*5ffd83dbSDimitry Andric                                               bool __notify_one)
65*5ffd83dbSDimitry Andric {
66*5ffd83dbSDimitry Andric     __ulock_wake(UL_COMPARE_AND_WAIT | (__notify_one ? 0 : ULF_WAKE_ALL),
67*5ffd83dbSDimitry Andric                  const_cast<__cxx_atomic_contention_t*>(__ptr), 0);
68*5ffd83dbSDimitry Andric }
69*5ffd83dbSDimitry Andric 
70*5ffd83dbSDimitry Andric #else // <- Add other operating systems here
71*5ffd83dbSDimitry Andric 
72*5ffd83dbSDimitry Andric // Baseline is just a timed backoff
73*5ffd83dbSDimitry Andric 
74*5ffd83dbSDimitry Andric static void __libcpp_platform_wait_on_address(__cxx_atomic_contention_t const volatile* __ptr,
75*5ffd83dbSDimitry Andric                                               __cxx_contention_t __val)
76*5ffd83dbSDimitry Andric {
77*5ffd83dbSDimitry Andric     __libcpp_thread_poll_with_backoff([=]() -> bool {
78*5ffd83dbSDimitry Andric         return !__cxx_nonatomic_compare_equal(__cxx_atomic_load(__ptr, memory_order_relaxed), __val);
79*5ffd83dbSDimitry Andric     }, __libcpp_timed_backoff_policy());
80*5ffd83dbSDimitry Andric }
81*5ffd83dbSDimitry Andric 
82*5ffd83dbSDimitry Andric static void __libcpp_platform_wake_by_address(__cxx_atomic_contention_t const volatile*, bool) { }
83*5ffd83dbSDimitry Andric 
84*5ffd83dbSDimitry Andric #endif // __linux__
85*5ffd83dbSDimitry Andric 
86*5ffd83dbSDimitry Andric static constexpr size_t __libcpp_contention_table_size = (1 << 8);  /* < there's no magic in this number */
87*5ffd83dbSDimitry Andric 
88*5ffd83dbSDimitry Andric struct alignas(64) /*  aim to avoid false sharing */ __libcpp_contention_table_entry
89*5ffd83dbSDimitry Andric {
90*5ffd83dbSDimitry Andric     __cxx_atomic_contention_t __contention_state;
91*5ffd83dbSDimitry Andric     __cxx_atomic_contention_t __platform_state;
92*5ffd83dbSDimitry Andric     inline constexpr __libcpp_contention_table_entry() :
93*5ffd83dbSDimitry Andric         __contention_state(0), __platform_state(0) { }
94*5ffd83dbSDimitry Andric };
95*5ffd83dbSDimitry Andric 
96*5ffd83dbSDimitry Andric static __libcpp_contention_table_entry __libcpp_contention_table[ __libcpp_contention_table_size ];
97*5ffd83dbSDimitry Andric 
98*5ffd83dbSDimitry Andric static hash<void const volatile*> __libcpp_contention_hasher;
99*5ffd83dbSDimitry Andric 
100*5ffd83dbSDimitry Andric static __libcpp_contention_table_entry* __libcpp_contention_state(void const volatile * p)
101*5ffd83dbSDimitry Andric {
102*5ffd83dbSDimitry Andric     return &__libcpp_contention_table[__libcpp_contention_hasher(p) & (__libcpp_contention_table_size - 1)];
103*5ffd83dbSDimitry Andric }
104*5ffd83dbSDimitry Andric 
105*5ffd83dbSDimitry Andric /* Given an atomic to track contention and an atomic to actually wait on, which may be
106*5ffd83dbSDimitry Andric    the same atomic, we try to detect contention to avoid spuriously calling the platform. */
107*5ffd83dbSDimitry Andric 
108*5ffd83dbSDimitry Andric static void __libcpp_contention_notify(__cxx_atomic_contention_t volatile* __contention_state,
109*5ffd83dbSDimitry Andric                                        __cxx_atomic_contention_t const volatile* __platform_state,
110*5ffd83dbSDimitry Andric                                        bool __notify_one)
111*5ffd83dbSDimitry Andric {
112*5ffd83dbSDimitry Andric     if(0 != __cxx_atomic_load(__contention_state, memory_order_seq_cst))
113*5ffd83dbSDimitry Andric         // We only call 'wake' if we consumed a contention bit here.
114*5ffd83dbSDimitry Andric         __libcpp_platform_wake_by_address(__platform_state, __notify_one);
115*5ffd83dbSDimitry Andric }
116*5ffd83dbSDimitry Andric static __cxx_contention_t __libcpp_contention_monitor_for_wait(__cxx_atomic_contention_t volatile* __contention_state,
117*5ffd83dbSDimitry Andric                                                                __cxx_atomic_contention_t const volatile* __platform_state)
118*5ffd83dbSDimitry Andric {
119*5ffd83dbSDimitry Andric     // We will monitor this value.
120*5ffd83dbSDimitry Andric     return __cxx_atomic_load(__platform_state, memory_order_acquire);
121*5ffd83dbSDimitry Andric }
122*5ffd83dbSDimitry Andric static void __libcpp_contention_wait(__cxx_atomic_contention_t volatile* __contention_state,
123*5ffd83dbSDimitry Andric                                      __cxx_atomic_contention_t const volatile* __platform_state,
124*5ffd83dbSDimitry Andric                                      __cxx_contention_t __old_value)
125*5ffd83dbSDimitry Andric {
126*5ffd83dbSDimitry Andric     __cxx_atomic_fetch_add(__contention_state, __cxx_contention_t(1), memory_order_seq_cst);
127*5ffd83dbSDimitry Andric     // We sleep as long as the monitored value hasn't changed.
128*5ffd83dbSDimitry Andric     __libcpp_platform_wait_on_address(__platform_state, __old_value);
129*5ffd83dbSDimitry Andric     __cxx_atomic_fetch_sub(__contention_state, __cxx_contention_t(1), memory_order_release);
130*5ffd83dbSDimitry Andric }
131*5ffd83dbSDimitry Andric 
132*5ffd83dbSDimitry Andric /* When the incoming atomic is the wrong size for the platform wait size, need to
133*5ffd83dbSDimitry Andric    launder the value sequence through an atomic from our table. */
134*5ffd83dbSDimitry Andric 
135*5ffd83dbSDimitry Andric static void __libcpp_atomic_notify(void const volatile* __location)
136*5ffd83dbSDimitry Andric {
137*5ffd83dbSDimitry Andric     auto const __entry = __libcpp_contention_state(__location);
138*5ffd83dbSDimitry Andric     // The value sequence laundering happens on the next line below.
139*5ffd83dbSDimitry Andric     __cxx_atomic_fetch_add(&__entry->__platform_state, __cxx_contention_t(1), memory_order_release);
140*5ffd83dbSDimitry Andric     __libcpp_contention_notify(&__entry->__contention_state,
141*5ffd83dbSDimitry Andric                                &__entry->__platform_state,
142*5ffd83dbSDimitry Andric                                false /* when laundering, we can't handle notify_one */);
143*5ffd83dbSDimitry Andric }
144*5ffd83dbSDimitry Andric _LIBCPP_EXPORTED_FROM_ABI
145*5ffd83dbSDimitry Andric void __cxx_atomic_notify_one(void const volatile* __location)
146*5ffd83dbSDimitry Andric     { __libcpp_atomic_notify(__location); }
147*5ffd83dbSDimitry Andric _LIBCPP_EXPORTED_FROM_ABI
148*5ffd83dbSDimitry Andric void __cxx_atomic_notify_all(void const volatile* __location)
149*5ffd83dbSDimitry Andric     { __libcpp_atomic_notify(__location); }
150*5ffd83dbSDimitry Andric _LIBCPP_EXPORTED_FROM_ABI
151*5ffd83dbSDimitry Andric __cxx_contention_t __libcpp_atomic_monitor(void const volatile* __location)
152*5ffd83dbSDimitry Andric {
153*5ffd83dbSDimitry Andric     auto const __entry = __libcpp_contention_state(__location);
154*5ffd83dbSDimitry Andric     return __libcpp_contention_monitor_for_wait(&__entry->__contention_state, &__entry->__platform_state);
155*5ffd83dbSDimitry Andric }
156*5ffd83dbSDimitry Andric _LIBCPP_EXPORTED_FROM_ABI
157*5ffd83dbSDimitry Andric void __libcpp_atomic_wait(void const volatile* __location, __cxx_contention_t __old_value)
158*5ffd83dbSDimitry Andric {
159*5ffd83dbSDimitry Andric     auto const __entry = __libcpp_contention_state(__location);
160*5ffd83dbSDimitry Andric     __libcpp_contention_wait(&__entry->__contention_state, &__entry->__platform_state, __old_value);
161*5ffd83dbSDimitry Andric }
162*5ffd83dbSDimitry Andric 
163*5ffd83dbSDimitry Andric /* When the incoming atomic happens to be the platform wait size, we still need to use the
164*5ffd83dbSDimitry Andric    table for the contention detection, but we can use the atomic directly for the wait. */
165*5ffd83dbSDimitry Andric 
166*5ffd83dbSDimitry Andric _LIBCPP_EXPORTED_FROM_ABI
167*5ffd83dbSDimitry Andric void __cxx_atomic_notify_one(__cxx_atomic_contention_t const volatile* __location)
168*5ffd83dbSDimitry Andric {
169*5ffd83dbSDimitry Andric     __libcpp_contention_notify(&__libcpp_contention_state(__location)->__contention_state, __location, true);
170*5ffd83dbSDimitry Andric }
171*5ffd83dbSDimitry Andric _LIBCPP_EXPORTED_FROM_ABI
172*5ffd83dbSDimitry Andric void __cxx_atomic_notify_all(__cxx_atomic_contention_t const volatile* __location)
173*5ffd83dbSDimitry Andric {
174*5ffd83dbSDimitry Andric     __libcpp_contention_notify(&__libcpp_contention_state(__location)->__contention_state, __location, false);
175*5ffd83dbSDimitry Andric }
176*5ffd83dbSDimitry Andric _LIBCPP_EXPORTED_FROM_ABI
177*5ffd83dbSDimitry Andric __cxx_contention_t __libcpp_atomic_monitor(__cxx_atomic_contention_t const volatile* __location)
178*5ffd83dbSDimitry Andric {
179*5ffd83dbSDimitry Andric     return __libcpp_contention_monitor_for_wait(&__libcpp_contention_state(__location)->__contention_state, __location);
180*5ffd83dbSDimitry Andric }
181*5ffd83dbSDimitry Andric _LIBCPP_EXPORTED_FROM_ABI
182*5ffd83dbSDimitry Andric void __libcpp_atomic_wait(__cxx_atomic_contention_t const volatile* __location, __cxx_contention_t __old_value)
183*5ffd83dbSDimitry Andric {
184*5ffd83dbSDimitry Andric     __libcpp_contention_wait(&__libcpp_contention_state(__location)->__contention_state, __location, __old_value);
185*5ffd83dbSDimitry Andric }
186*5ffd83dbSDimitry Andric 
187*5ffd83dbSDimitry Andric _LIBCPP_END_NAMESPACE_STD
188*5ffd83dbSDimitry Andric 
189*5ffd83dbSDimitry Andric #endif //_LIBCPP_HAS_NO_THREADS
190