xref: /llvm-project/libcxx/src/atomic.cpp (revision d0438d2d087e78571a671c98cbb42308e4dcfcec)
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 
9cea42859SHui #include <__thread/timed_backoff_policy.h>
1054fa9ecdSOlivier Giroux #include <atomic>
11df51be85SLouis Dionne #include <climits>
1254fa9ecdSOlivier Giroux #include <functional>
13df51be85SLouis Dionne #include <thread>
1454fa9ecdSOlivier Giroux 
15b5a9f9f6SLouis Dionne #include "include/apple_availability.h"
16b5a9f9f6SLouis Dionne 
1754fa9ecdSOlivier Giroux #ifdef __linux__
1854fa9ecdSOlivier Giroux 
1954fa9ecdSOlivier Giroux #  include <linux/futex.h>
2054fa9ecdSOlivier Giroux #  include <sys/syscall.h>
219783f28cSLouis Dionne #  include <unistd.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
28af7467ceSJohn Ericson #  define _LIBCPP_FUTEX(...) syscall(SYS_futex, __VA_ARGS__)
2985b9c5ccSLouis Dionne 
3083387dbcSKonstantin Belousov #elif defined(__FreeBSD__)
3183387dbcSKonstantin Belousov 
3283387dbcSKonstantin Belousov #  include <sys/types.h>
3383387dbcSKonstantin Belousov #  include <sys/umtx.h>
3483387dbcSKonstantin Belousov 
35af7467ceSJohn Ericson #  define _LIBCPP_FUTEX(...) syscall(SYS_futex, __VA_ARGS__)
36af7467ceSJohn Ericson 
37af7467ceSJohn Ericson #elif defined(__OpenBSD__)
38af7467ceSJohn Ericson 
39af7467ceSJohn Ericson #  include <sys/futex.h>
40af7467ceSJohn Ericson 
41af7467ceSJohn Ericson // OpenBSD has no indirect syscalls
42af7467ceSJohn Ericson #  define _LIBCPP_FUTEX(...) futex(__VA_ARGS__)
43af7467ceSJohn Ericson 
4454fa9ecdSOlivier Giroux #else // <- Add other operating systems here
4554fa9ecdSOlivier Giroux 
4654fa9ecdSOlivier Giroux // Baseline needs no new headers
4754fa9ecdSOlivier Giroux 
48af7467ceSJohn Ericson #  define _LIBCPP_FUTEX(...) syscall(SYS_futex, __VA_ARGS__)
49af7467ceSJohn Ericson 
5054fa9ecdSOlivier Giroux #endif
5154fa9ecdSOlivier Giroux 
5254fa9ecdSOlivier Giroux _LIBCPP_BEGIN_NAMESPACE_STD
5354fa9ecdSOlivier Giroux 
5454fa9ecdSOlivier Giroux #ifdef __linux__
5554fa9ecdSOlivier Giroux 
569783f28cSLouis Dionne static void
579783f28cSLouis Dionne __libcpp_platform_wait_on_address(__cxx_atomic_contention_t const volatile* __ptr, __cxx_contention_t __val) {
5854fa9ecdSOlivier Giroux   static constexpr timespec __timeout = {2, 0};
59af7467ceSJohn Ericson   _LIBCPP_FUTEX(__ptr, FUTEX_WAIT_PRIVATE, __val, &__timeout, 0, 0);
6054fa9ecdSOlivier Giroux }
6154fa9ecdSOlivier Giroux 
629783f28cSLouis Dionne static void __libcpp_platform_wake_by_address(__cxx_atomic_contention_t const volatile* __ptr, bool __notify_one) {
63af7467ceSJohn Ericson   _LIBCPP_FUTEX(__ptr, FUTEX_WAKE_PRIVATE, __notify_one ? 1 : INT_MAX, 0, 0, 0);
6454fa9ecdSOlivier Giroux }
6554fa9ecdSOlivier Giroux 
6654fa9ecdSOlivier Giroux #elif defined(__APPLE__) && defined(_LIBCPP_USE_ULOCK)
6754fa9ecdSOlivier Giroux 
689783f28cSLouis Dionne extern "C" int __ulock_wait(
699783f28cSLouis Dionne     uint32_t operation, void* addr, uint64_t value, uint32_t timeout); /* timeout is specified in microseconds */
7054fa9ecdSOlivier Giroux extern "C" int __ulock_wake(uint32_t operation, void* addr, uint64_t wake_value);
7154fa9ecdSOlivier Giroux 
726e22b538SHui // https://github.com/apple/darwin-xnu/blob/2ff845c2e033bd0ff64b5b6aa6063a1f8f65aa32/bsd/sys/ulock.h#L82
736e22b538SHui #  define UL_COMPARE_AND_WAIT64 5
7454fa9ecdSOlivier Giroux #  define ULF_WAKE_ALL 0x00000100
7554fa9ecdSOlivier Giroux 
769783f28cSLouis Dionne static void
779783f28cSLouis Dionne __libcpp_platform_wait_on_address(__cxx_atomic_contention_t const volatile* __ptr, __cxx_contention_t __val) {
786e22b538SHui   static_assert(sizeof(__cxx_atomic_contention_t) == 8, "Waiting on 8 bytes value");
796e22b538SHui   __ulock_wait(UL_COMPARE_AND_WAIT64, const_cast<__cxx_atomic_contention_t*>(__ptr), __val, 0);
8054fa9ecdSOlivier Giroux }
8154fa9ecdSOlivier Giroux 
829783f28cSLouis Dionne static void __libcpp_platform_wake_by_address(__cxx_atomic_contention_t const volatile* __ptr, bool __notify_one) {
836e22b538SHui   static_assert(sizeof(__cxx_atomic_contention_t) == 8, "Waking up on 8 bytes value");
849783f28cSLouis Dionne   __ulock_wake(
856e22b538SHui       UL_COMPARE_AND_WAIT64 | (__notify_one ? 0 : ULF_WAKE_ALL), const_cast<__cxx_atomic_contention_t*>(__ptr), 0);
8654fa9ecdSOlivier Giroux }
8754fa9ecdSOlivier Giroux 
880fe1012fSEd Maste #elif defined(__FreeBSD__) && __SIZEOF_LONG__ == 8
8917ecbb3eSKonstantin Belousov /*
9017ecbb3eSKonstantin Belousov  * Since __cxx_contention_t is int64_t even on 32bit FreeBSD
9117ecbb3eSKonstantin Belousov  * platforms, we have to use umtx ops that work on the long type, and
9217ecbb3eSKonstantin Belousov  * limit its use to architectures where long and int64_t are synonyms.
9317ecbb3eSKonstantin Belousov  */
9483387dbcSKonstantin Belousov 
959783f28cSLouis Dionne static void
969783f28cSLouis Dionne __libcpp_platform_wait_on_address(__cxx_atomic_contention_t const volatile* __ptr, __cxx_contention_t __val) {
97*d0438d2dSLouis Dionne   _umtx_op(const_cast<__cxx_atomic_contention_t*>(__ptr), UMTX_OP_WAIT, __val, nullptr, nullptr);
9883387dbcSKonstantin Belousov }
9983387dbcSKonstantin Belousov 
1009783f28cSLouis Dionne static void __libcpp_platform_wake_by_address(__cxx_atomic_contention_t const volatile* __ptr, bool __notify_one) {
101*d0438d2dSLouis Dionne   _umtx_op(const_cast<__cxx_atomic_contention_t*>(__ptr), UMTX_OP_WAKE, __notify_one ? 1 : INT_MAX, nullptr, nullptr);
10283387dbcSKonstantin Belousov }
10383387dbcSKonstantin Belousov 
10454fa9ecdSOlivier Giroux #else // <- Add other operating systems here
10554fa9ecdSOlivier Giroux 
10654fa9ecdSOlivier Giroux // Baseline is just a timed backoff
10754fa9ecdSOlivier Giroux 
1089783f28cSLouis Dionne static void
1099783f28cSLouis Dionne __libcpp_platform_wait_on_address(__cxx_atomic_contention_t const volatile* __ptr, __cxx_contention_t __val) {
1109783f28cSLouis Dionne   __libcpp_thread_poll_with_backoff(
1119783f28cSLouis Dionne       [=]() -> bool { return !__cxx_nonatomic_compare_equal(__cxx_atomic_load(__ptr, memory_order_relaxed), __val); },
1129783f28cSLouis Dionne       __libcpp_timed_backoff_policy());
11354fa9ecdSOlivier Giroux }
11454fa9ecdSOlivier Giroux 
11554fa9ecdSOlivier Giroux static void __libcpp_platform_wake_by_address(__cxx_atomic_contention_t const volatile*, bool) {}
11654fa9ecdSOlivier Giroux 
11754fa9ecdSOlivier Giroux #endif // __linux__
11854fa9ecdSOlivier Giroux 
11954fa9ecdSOlivier Giroux static constexpr size_t __libcpp_contention_table_size = (1 << 8); /* < there's no magic in this number */
12054fa9ecdSOlivier Giroux 
1219783f28cSLouis Dionne struct alignas(64) /*  aim to avoid false sharing */ __libcpp_contention_table_entry {
12254fa9ecdSOlivier Giroux   __cxx_atomic_contention_t __contention_state;
12354fa9ecdSOlivier Giroux   __cxx_atomic_contention_t __platform_state;
1249783f28cSLouis Dionne   inline constexpr __libcpp_contention_table_entry() : __contention_state(0), __platform_state(0) {}
12554fa9ecdSOlivier Giroux };
12654fa9ecdSOlivier Giroux 
12754fa9ecdSOlivier Giroux static __libcpp_contention_table_entry __libcpp_contention_table[__libcpp_contention_table_size];
12854fa9ecdSOlivier Giroux 
12954fa9ecdSOlivier Giroux static hash<void const volatile*> __libcpp_contention_hasher;
13054fa9ecdSOlivier Giroux 
1319783f28cSLouis Dionne static __libcpp_contention_table_entry* __libcpp_contention_state(void const volatile* p) {
13254fa9ecdSOlivier Giroux   return &__libcpp_contention_table[__libcpp_contention_hasher(p) & (__libcpp_contention_table_size - 1)];
13354fa9ecdSOlivier Giroux }
13454fa9ecdSOlivier Giroux 
13554fa9ecdSOlivier Giroux /* Given an atomic to track contention and an atomic to actually wait on, which may be
13654fa9ecdSOlivier Giroux    the same atomic, we try to detect contention to avoid spuriously calling the platform. */
13754fa9ecdSOlivier Giroux 
13854fa9ecdSOlivier Giroux static void __libcpp_contention_notify(__cxx_atomic_contention_t volatile* __contention_state,
13954fa9ecdSOlivier Giroux                                        __cxx_atomic_contention_t const volatile* __platform_state,
1409783f28cSLouis Dionne                                        bool __notify_one) {
14154fa9ecdSOlivier Giroux   if (0 != __cxx_atomic_load(__contention_state, memory_order_seq_cst))
14254fa9ecdSOlivier Giroux     // We only call 'wake' if we consumed a contention bit here.
14354fa9ecdSOlivier Giroux     __libcpp_platform_wake_by_address(__platform_state, __notify_one);
14454fa9ecdSOlivier Giroux }
1459783f28cSLouis Dionne static __cxx_contention_t
1469783f28cSLouis Dionne __libcpp_contention_monitor_for_wait(__cxx_atomic_contention_t volatile* /*__contention_state*/,
1479783f28cSLouis Dionne                                      __cxx_atomic_contention_t const volatile* __platform_state) {
14854fa9ecdSOlivier Giroux   // We will monitor this value.
14954fa9ecdSOlivier Giroux   return __cxx_atomic_load(__platform_state, memory_order_acquire);
15054fa9ecdSOlivier Giroux }
15154fa9ecdSOlivier Giroux static void __libcpp_contention_wait(__cxx_atomic_contention_t volatile* __contention_state,
15254fa9ecdSOlivier Giroux                                      __cxx_atomic_contention_t const volatile* __platform_state,
1539783f28cSLouis Dionne                                      __cxx_contention_t __old_value) {
15454fa9ecdSOlivier Giroux   __cxx_atomic_fetch_add(__contention_state, __cxx_contention_t(1), memory_order_seq_cst);
15554fa9ecdSOlivier Giroux   // We sleep as long as the monitored value hasn't changed.
15654fa9ecdSOlivier Giroux   __libcpp_platform_wait_on_address(__platform_state, __old_value);
15754fa9ecdSOlivier Giroux   __cxx_atomic_fetch_sub(__contention_state, __cxx_contention_t(1), memory_order_release);
15854fa9ecdSOlivier Giroux }
15954fa9ecdSOlivier Giroux 
16054fa9ecdSOlivier Giroux /* When the incoming atomic is the wrong size for the platform wait size, need to
16154fa9ecdSOlivier Giroux    launder the value sequence through an atomic from our table. */
16254fa9ecdSOlivier Giroux 
1639783f28cSLouis Dionne static void __libcpp_atomic_notify(void const volatile* __location) {
16454fa9ecdSOlivier Giroux   auto const __entry = __libcpp_contention_state(__location);
16554fa9ecdSOlivier Giroux   // The value sequence laundering happens on the next line below.
16654fa9ecdSOlivier Giroux   __cxx_atomic_fetch_add(&__entry->__platform_state, __cxx_contention_t(1), memory_order_release);
1679783f28cSLouis Dionne   __libcpp_contention_notify(
1689783f28cSLouis Dionne       &__entry->__contention_state,
16954fa9ecdSOlivier Giroux       &__entry->__platform_state,
17054fa9ecdSOlivier Giroux       false /* when laundering, we can't handle notify_one */);
17154fa9ecdSOlivier Giroux }
1724748b494SNikolas Klauser _LIBCPP_EXPORTED_FROM_ABI void __cxx_atomic_notify_one(void const volatile* __location) noexcept {
1739783f28cSLouis Dionne   __libcpp_atomic_notify(__location);
1749783f28cSLouis Dionne }
1754748b494SNikolas Klauser _LIBCPP_EXPORTED_FROM_ABI void __cxx_atomic_notify_all(void const volatile* __location) noexcept {
1769783f28cSLouis Dionne   __libcpp_atomic_notify(__location);
1779783f28cSLouis Dionne }
1784748b494SNikolas Klauser _LIBCPP_EXPORTED_FROM_ABI __cxx_contention_t __libcpp_atomic_monitor(void const volatile* __location) noexcept {
17954fa9ecdSOlivier Giroux   auto const __entry = __libcpp_contention_state(__location);
18054fa9ecdSOlivier Giroux   return __libcpp_contention_monitor_for_wait(&__entry->__contention_state, &__entry->__platform_state);
18154fa9ecdSOlivier Giroux }
1824748b494SNikolas Klauser _LIBCPP_EXPORTED_FROM_ABI void
1834748b494SNikolas Klauser __libcpp_atomic_wait(void const volatile* __location, __cxx_contention_t __old_value) noexcept {
18454fa9ecdSOlivier Giroux   auto const __entry = __libcpp_contention_state(__location);
18554fa9ecdSOlivier Giroux   __libcpp_contention_wait(&__entry->__contention_state, &__entry->__platform_state, __old_value);
18654fa9ecdSOlivier Giroux }
18754fa9ecdSOlivier Giroux 
18854fa9ecdSOlivier Giroux /* When the incoming atomic happens to be the platform wait size, we still need to use the
18954fa9ecdSOlivier Giroux    table for the contention detection, but we can use the atomic directly for the wait. */
19054fa9ecdSOlivier Giroux 
1914748b494SNikolas Klauser _LIBCPP_EXPORTED_FROM_ABI void __cxx_atomic_notify_one(__cxx_atomic_contention_t const volatile* __location) noexcept {
19254fa9ecdSOlivier Giroux   __libcpp_contention_notify(&__libcpp_contention_state(__location)->__contention_state, __location, true);
19354fa9ecdSOlivier Giroux }
1944748b494SNikolas Klauser _LIBCPP_EXPORTED_FROM_ABI void __cxx_atomic_notify_all(__cxx_atomic_contention_t const volatile* __location) noexcept {
19554fa9ecdSOlivier Giroux   __libcpp_contention_notify(&__libcpp_contention_state(__location)->__contention_state, __location, false);
19654fa9ecdSOlivier Giroux }
19795ebf2beSJan Kokemüller // This function is never used, but still exported for ABI compatibility.
1989783f28cSLouis Dionne _LIBCPP_EXPORTED_FROM_ABI __cxx_contention_t
1994748b494SNikolas Klauser __libcpp_atomic_monitor(__cxx_atomic_contention_t const volatile* __location) noexcept {
20054fa9ecdSOlivier Giroux   return __libcpp_contention_monitor_for_wait(&__libcpp_contention_state(__location)->__contention_state, __location);
20154fa9ecdSOlivier Giroux }
2029783f28cSLouis Dionne _LIBCPP_EXPORTED_FROM_ABI void
2034748b494SNikolas Klauser __libcpp_atomic_wait(__cxx_atomic_contention_t const volatile* __location, __cxx_contention_t __old_value) noexcept {
20454fa9ecdSOlivier Giroux   __libcpp_contention_wait(&__libcpp_contention_state(__location)->__contention_state, __location, __old_value);
20554fa9ecdSOlivier Giroux }
20654fa9ecdSOlivier Giroux 
20754fa9ecdSOlivier Giroux _LIBCPP_END_NAMESPACE_STD
208