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