xref: /llvm-project-15.0.7/libcxx/src/atomic.cpp (revision df51be85)
1eb8650a7SLouis Dionne //===----------------------------------------------------------------------===//
254fa9ecdSOlivier Giroux //
354fa9ecdSOlivier Giroux // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
454fa9ecdSOlivier Giroux // See https://llvm.org/LICENSE.txt for license information.
554fa9ecdSOlivier Giroux // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
654fa9ecdSOlivier Giroux //
754fa9ecdSOlivier Giroux //===----------------------------------------------------------------------===//
854fa9ecdSOlivier Giroux 
954fa9ecdSOlivier Giroux #include <__config>
1054fa9ecdSOlivier Giroux #ifndef _LIBCPP_HAS_NO_THREADS
1154fa9ecdSOlivier Giroux 
1254fa9ecdSOlivier Giroux #include <atomic>
13*df51be85SLouis Dionne #include <climits>
1454fa9ecdSOlivier Giroux #include <functional>
15*df51be85SLouis Dionne #include <thread>
1654fa9ecdSOlivier Giroux 
1754fa9ecdSOlivier Giroux #ifdef __linux__
1854fa9ecdSOlivier Giroux 
1954fa9ecdSOlivier Giroux #include <unistd.h>
2054fa9ecdSOlivier Giroux #include <linux/futex.h>
2154fa9ecdSOlivier Giroux #include <sys/syscall.h>
2254fa9ecdSOlivier Giroux 
2385b9c5ccSLouis Dionne // libc++ uses SYS_futex as a universal syscall name. However, on 32 bit architectures
2485b9c5ccSLouis Dionne // with a 64 bit time_t, we need to specify SYS_futex_time64.
2585b9c5ccSLouis Dionne #if !defined(SYS_futex) && defined(SYS_futex_time64)
2685b9c5ccSLouis Dionne # define SYS_futex SYS_futex_time64
2785b9c5ccSLouis Dionne #endif
2885b9c5ccSLouis Dionne 
2954fa9ecdSOlivier Giroux #else // <- Add other operating systems here
3054fa9ecdSOlivier Giroux 
3154fa9ecdSOlivier Giroux // Baseline needs no new headers
3254fa9ecdSOlivier Giroux 
3354fa9ecdSOlivier Giroux #endif
3454fa9ecdSOlivier Giroux 
3554fa9ecdSOlivier Giroux _LIBCPP_BEGIN_NAMESPACE_STD
3654fa9ecdSOlivier Giroux 
3754fa9ecdSOlivier Giroux #ifdef __linux__
3854fa9ecdSOlivier Giroux 
__libcpp_platform_wait_on_address(__cxx_atomic_contention_t const volatile * __ptr,__cxx_contention_t __val)3954fa9ecdSOlivier Giroux static void __libcpp_platform_wait_on_address(__cxx_atomic_contention_t const volatile* __ptr,
4054fa9ecdSOlivier Giroux                                               __cxx_contention_t __val)
4154fa9ecdSOlivier Giroux {
4254fa9ecdSOlivier Giroux     static constexpr timespec __timeout = { 2, 0 };
4354fa9ecdSOlivier Giroux     syscall(SYS_futex, __ptr, FUTEX_WAIT_PRIVATE, __val, &__timeout, 0, 0);
4454fa9ecdSOlivier Giroux }
4554fa9ecdSOlivier Giroux 
__libcpp_platform_wake_by_address(__cxx_atomic_contention_t const volatile * __ptr,bool __notify_one)4654fa9ecdSOlivier Giroux static void __libcpp_platform_wake_by_address(__cxx_atomic_contention_t const volatile* __ptr,
4754fa9ecdSOlivier Giroux                                               bool __notify_one)
4854fa9ecdSOlivier Giroux {
4954fa9ecdSOlivier Giroux     syscall(SYS_futex, __ptr, FUTEX_WAKE_PRIVATE, __notify_one ? 1 : INT_MAX, 0, 0, 0);
5054fa9ecdSOlivier Giroux }
5154fa9ecdSOlivier Giroux 
5254fa9ecdSOlivier Giroux #elif defined(__APPLE__) && defined(_LIBCPP_USE_ULOCK)
5354fa9ecdSOlivier Giroux 
5454fa9ecdSOlivier Giroux extern "C" int __ulock_wait(uint32_t operation, void *addr, uint64_t value,
5554fa9ecdSOlivier Giroux 		uint32_t timeout); /* timeout is specified in microseconds */
5654fa9ecdSOlivier Giroux extern "C" int __ulock_wake(uint32_t operation, void *addr, uint64_t wake_value);
5754fa9ecdSOlivier Giroux 
5854fa9ecdSOlivier Giroux #define UL_COMPARE_AND_WAIT				1
5954fa9ecdSOlivier Giroux #define ULF_WAKE_ALL					0x00000100
6054fa9ecdSOlivier Giroux 
6154fa9ecdSOlivier Giroux static void __libcpp_platform_wait_on_address(__cxx_atomic_contention_t const volatile* __ptr,
6254fa9ecdSOlivier Giroux                                               __cxx_contention_t __val)
6354fa9ecdSOlivier Giroux {
6454fa9ecdSOlivier Giroux     __ulock_wait(UL_COMPARE_AND_WAIT,
6554fa9ecdSOlivier Giroux                  const_cast<__cxx_atomic_contention_t*>(__ptr), __val, 0);
6654fa9ecdSOlivier Giroux }
6754fa9ecdSOlivier Giroux 
6854fa9ecdSOlivier Giroux static void __libcpp_platform_wake_by_address(__cxx_atomic_contention_t const volatile* __ptr,
6954fa9ecdSOlivier Giroux                                               bool __notify_one)
7054fa9ecdSOlivier Giroux {
7154fa9ecdSOlivier Giroux     __ulock_wake(UL_COMPARE_AND_WAIT | (__notify_one ? 0 : ULF_WAKE_ALL),
7254fa9ecdSOlivier Giroux                  const_cast<__cxx_atomic_contention_t*>(__ptr), 0);
7354fa9ecdSOlivier Giroux }
7454fa9ecdSOlivier Giroux 
7554fa9ecdSOlivier Giroux #else // <- Add other operating systems here
7654fa9ecdSOlivier Giroux 
7754fa9ecdSOlivier Giroux // Baseline is just a timed backoff
7854fa9ecdSOlivier Giroux 
7954fa9ecdSOlivier Giroux static void __libcpp_platform_wait_on_address(__cxx_atomic_contention_t const volatile* __ptr,
8054fa9ecdSOlivier Giroux                                               __cxx_contention_t __val)
8154fa9ecdSOlivier Giroux {
8254fa9ecdSOlivier Giroux     __libcpp_thread_poll_with_backoff([=]() -> bool {
8354fa9ecdSOlivier Giroux         return !__cxx_nonatomic_compare_equal(__cxx_atomic_load(__ptr, memory_order_relaxed), __val);
8454fa9ecdSOlivier Giroux     }, __libcpp_timed_backoff_policy());
8554fa9ecdSOlivier Giroux }
8654fa9ecdSOlivier Giroux 
8754fa9ecdSOlivier Giroux static void __libcpp_platform_wake_by_address(__cxx_atomic_contention_t const volatile*, bool) { }
8854fa9ecdSOlivier Giroux 
8954fa9ecdSOlivier Giroux #endif // __linux__
9054fa9ecdSOlivier Giroux 
9154fa9ecdSOlivier Giroux static constexpr size_t __libcpp_contention_table_size = (1 << 8);  /* < there's no magic in this number */
9254fa9ecdSOlivier Giroux 
9354fa9ecdSOlivier Giroux struct alignas(64) /*  aim to avoid false sharing */ __libcpp_contention_table_entry
9454fa9ecdSOlivier Giroux {
9554fa9ecdSOlivier Giroux     __cxx_atomic_contention_t __contention_state;
9654fa9ecdSOlivier Giroux     __cxx_atomic_contention_t __platform_state;
__libcpp_contention_table_entry__libcpp_contention_table_entry9754fa9ecdSOlivier Giroux     inline constexpr __libcpp_contention_table_entry() :
9854fa9ecdSOlivier Giroux         __contention_state(0), __platform_state(0) { }
9954fa9ecdSOlivier Giroux };
10054fa9ecdSOlivier Giroux 
10154fa9ecdSOlivier Giroux static __libcpp_contention_table_entry __libcpp_contention_table[ __libcpp_contention_table_size ];
10254fa9ecdSOlivier Giroux 
10354fa9ecdSOlivier Giroux static hash<void const volatile*> __libcpp_contention_hasher;
10454fa9ecdSOlivier Giroux 
__libcpp_contention_state(void const volatile * p)10554fa9ecdSOlivier Giroux static __libcpp_contention_table_entry* __libcpp_contention_state(void const volatile * p)
10654fa9ecdSOlivier Giroux {
10754fa9ecdSOlivier Giroux     return &__libcpp_contention_table[__libcpp_contention_hasher(p) & (__libcpp_contention_table_size - 1)];
10854fa9ecdSOlivier Giroux }
10954fa9ecdSOlivier Giroux 
11054fa9ecdSOlivier Giroux /* Given an atomic to track contention and an atomic to actually wait on, which may be
11154fa9ecdSOlivier Giroux    the same atomic, we try to detect contention to avoid spuriously calling the platform. */
11254fa9ecdSOlivier Giroux 
__libcpp_contention_notify(__cxx_atomic_contention_t volatile * __contention_state,__cxx_atomic_contention_t const volatile * __platform_state,bool __notify_one)11354fa9ecdSOlivier Giroux static void __libcpp_contention_notify(__cxx_atomic_contention_t volatile* __contention_state,
11454fa9ecdSOlivier Giroux                                        __cxx_atomic_contention_t const volatile* __platform_state,
11554fa9ecdSOlivier Giroux                                        bool __notify_one)
11654fa9ecdSOlivier Giroux {
11754fa9ecdSOlivier Giroux     if(0 != __cxx_atomic_load(__contention_state, memory_order_seq_cst))
11854fa9ecdSOlivier Giroux         // We only call 'wake' if we consumed a contention bit here.
11954fa9ecdSOlivier Giroux         __libcpp_platform_wake_by_address(__platform_state, __notify_one);
12054fa9ecdSOlivier Giroux }
__libcpp_contention_monitor_for_wait(__cxx_atomic_contention_t volatile * __contention_state,__cxx_atomic_contention_t const volatile * __platform_state)12154fa9ecdSOlivier Giroux static __cxx_contention_t __libcpp_contention_monitor_for_wait(__cxx_atomic_contention_t volatile* __contention_state,
12254fa9ecdSOlivier Giroux                                                                __cxx_atomic_contention_t const volatile* __platform_state)
12354fa9ecdSOlivier Giroux {
12454fa9ecdSOlivier Giroux     // We will monitor this value.
12554fa9ecdSOlivier Giroux     return __cxx_atomic_load(__platform_state, memory_order_acquire);
12654fa9ecdSOlivier Giroux }
__libcpp_contention_wait(__cxx_atomic_contention_t volatile * __contention_state,__cxx_atomic_contention_t const volatile * __platform_state,__cxx_contention_t __old_value)12754fa9ecdSOlivier Giroux static void __libcpp_contention_wait(__cxx_atomic_contention_t volatile* __contention_state,
12854fa9ecdSOlivier Giroux                                      __cxx_atomic_contention_t const volatile* __platform_state,
12954fa9ecdSOlivier Giroux                                      __cxx_contention_t __old_value)
13054fa9ecdSOlivier Giroux {
13154fa9ecdSOlivier Giroux     __cxx_atomic_fetch_add(__contention_state, __cxx_contention_t(1), memory_order_seq_cst);
13254fa9ecdSOlivier Giroux     // We sleep as long as the monitored value hasn't changed.
13354fa9ecdSOlivier Giroux     __libcpp_platform_wait_on_address(__platform_state, __old_value);
13454fa9ecdSOlivier Giroux     __cxx_atomic_fetch_sub(__contention_state, __cxx_contention_t(1), memory_order_release);
13554fa9ecdSOlivier Giroux }
13654fa9ecdSOlivier Giroux 
13754fa9ecdSOlivier Giroux /* When the incoming atomic is the wrong size for the platform wait size, need to
13854fa9ecdSOlivier Giroux    launder the value sequence through an atomic from our table. */
13954fa9ecdSOlivier Giroux 
__libcpp_atomic_notify(void const volatile * __location)14054fa9ecdSOlivier Giroux static void __libcpp_atomic_notify(void const volatile* __location)
14154fa9ecdSOlivier Giroux {
14254fa9ecdSOlivier Giroux     auto const __entry = __libcpp_contention_state(__location);
14354fa9ecdSOlivier Giroux     // The value sequence laundering happens on the next line below.
14454fa9ecdSOlivier Giroux     __cxx_atomic_fetch_add(&__entry->__platform_state, __cxx_contention_t(1), memory_order_release);
14554fa9ecdSOlivier Giroux     __libcpp_contention_notify(&__entry->__contention_state,
14654fa9ecdSOlivier Giroux                                &__entry->__platform_state,
14754fa9ecdSOlivier Giroux                                false /* when laundering, we can't handle notify_one */);
14854fa9ecdSOlivier Giroux }
14954fa9ecdSOlivier Giroux _LIBCPP_EXPORTED_FROM_ABI
__cxx_atomic_notify_one(void const volatile * __location)15054fa9ecdSOlivier Giroux void __cxx_atomic_notify_one(void const volatile* __location)
15154fa9ecdSOlivier Giroux     { __libcpp_atomic_notify(__location); }
15254fa9ecdSOlivier Giroux _LIBCPP_EXPORTED_FROM_ABI
__cxx_atomic_notify_all(void const volatile * __location)15354fa9ecdSOlivier Giroux void __cxx_atomic_notify_all(void const volatile* __location)
15454fa9ecdSOlivier Giroux     { __libcpp_atomic_notify(__location); }
15554fa9ecdSOlivier Giroux _LIBCPP_EXPORTED_FROM_ABI
__libcpp_atomic_monitor(void const volatile * __location)15654fa9ecdSOlivier Giroux __cxx_contention_t __libcpp_atomic_monitor(void const volatile* __location)
15754fa9ecdSOlivier Giroux {
15854fa9ecdSOlivier Giroux     auto const __entry = __libcpp_contention_state(__location);
15954fa9ecdSOlivier Giroux     return __libcpp_contention_monitor_for_wait(&__entry->__contention_state, &__entry->__platform_state);
16054fa9ecdSOlivier Giroux }
16154fa9ecdSOlivier Giroux _LIBCPP_EXPORTED_FROM_ABI
__libcpp_atomic_wait(void const volatile * __location,__cxx_contention_t __old_value)16254fa9ecdSOlivier Giroux void __libcpp_atomic_wait(void const volatile* __location, __cxx_contention_t __old_value)
16354fa9ecdSOlivier Giroux {
16454fa9ecdSOlivier Giroux     auto const __entry = __libcpp_contention_state(__location);
16554fa9ecdSOlivier Giroux     __libcpp_contention_wait(&__entry->__contention_state, &__entry->__platform_state, __old_value);
16654fa9ecdSOlivier Giroux }
16754fa9ecdSOlivier Giroux 
16854fa9ecdSOlivier Giroux /* When the incoming atomic happens to be the platform wait size, we still need to use the
16954fa9ecdSOlivier Giroux    table for the contention detection, but we can use the atomic directly for the wait. */
17054fa9ecdSOlivier Giroux 
17154fa9ecdSOlivier Giroux _LIBCPP_EXPORTED_FROM_ABI
__cxx_atomic_notify_one(__cxx_atomic_contention_t const volatile * __location)17254fa9ecdSOlivier Giroux void __cxx_atomic_notify_one(__cxx_atomic_contention_t const volatile* __location)
17354fa9ecdSOlivier Giroux {
17454fa9ecdSOlivier Giroux     __libcpp_contention_notify(&__libcpp_contention_state(__location)->__contention_state, __location, true);
17554fa9ecdSOlivier Giroux }
17654fa9ecdSOlivier Giroux _LIBCPP_EXPORTED_FROM_ABI
__cxx_atomic_notify_all(__cxx_atomic_contention_t const volatile * __location)17754fa9ecdSOlivier Giroux void __cxx_atomic_notify_all(__cxx_atomic_contention_t const volatile* __location)
17854fa9ecdSOlivier Giroux {
17954fa9ecdSOlivier Giroux     __libcpp_contention_notify(&__libcpp_contention_state(__location)->__contention_state, __location, false);
18054fa9ecdSOlivier Giroux }
18154fa9ecdSOlivier Giroux _LIBCPP_EXPORTED_FROM_ABI
__libcpp_atomic_monitor(__cxx_atomic_contention_t const volatile * __location)18254fa9ecdSOlivier Giroux __cxx_contention_t __libcpp_atomic_monitor(__cxx_atomic_contention_t const volatile* __location)
18354fa9ecdSOlivier Giroux {
18454fa9ecdSOlivier Giroux     return __libcpp_contention_monitor_for_wait(&__libcpp_contention_state(__location)->__contention_state, __location);
18554fa9ecdSOlivier Giroux }
18654fa9ecdSOlivier Giroux _LIBCPP_EXPORTED_FROM_ABI
__libcpp_atomic_wait(__cxx_atomic_contention_t const volatile * __location,__cxx_contention_t __old_value)18754fa9ecdSOlivier Giroux void __libcpp_atomic_wait(__cxx_atomic_contention_t const volatile* __location, __cxx_contention_t __old_value)
18854fa9ecdSOlivier Giroux {
18954fa9ecdSOlivier Giroux     __libcpp_contention_wait(&__libcpp_contention_state(__location)->__contention_state, __location, __old_value);
19054fa9ecdSOlivier Giroux }
19154fa9ecdSOlivier Giroux 
19254fa9ecdSOlivier Giroux _LIBCPP_END_NAMESPACE_STD
19354fa9ecdSOlivier Giroux 
19454fa9ecdSOlivier Giroux #endif //_LIBCPP_HAS_NO_THREADS
195