xref: /freebsd/contrib/llvm-project/libcxx/src/atomic.cpp (revision cb14a3fe5122c879eae1fb480ed7ce82a699ddb6)
1349cc55cSDimitry Andric //===----------------------------------------------------------------------===//
25ffd83dbSDimitry Andric //
35ffd83dbSDimitry Andric // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
45ffd83dbSDimitry Andric // See https://llvm.org/LICENSE.txt for license information.
55ffd83dbSDimitry Andric // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
65ffd83dbSDimitry Andric //
75ffd83dbSDimitry Andric //===----------------------------------------------------------------------===//
85ffd83dbSDimitry Andric 
906c3fb27SDimitry Andric #include <__thread/timed_backoff_policy.h>
105ffd83dbSDimitry Andric #include <atomic>
1104eeddc0SDimitry Andric #include <climits>
125ffd83dbSDimitry Andric #include <functional>
1304eeddc0SDimitry Andric #include <thread>
145ffd83dbSDimitry Andric 
1506c3fb27SDimitry Andric #include "include/apple_availability.h"
1606c3fb27SDimitry Andric 
175ffd83dbSDimitry Andric #ifdef __linux__
185ffd83dbSDimitry Andric 
195ffd83dbSDimitry Andric #  include <linux/futex.h>
205ffd83dbSDimitry Andric #  include <sys/syscall.h>
21*cb14a3feSDimitry Andric #  include <unistd.h>
225ffd83dbSDimitry Andric 
23d409305fSDimitry Andric // libc++ uses SYS_futex as a universal syscall name. However, on 32 bit architectures
24d409305fSDimitry Andric // with a 64 bit time_t, we need to specify SYS_futex_time64.
25d409305fSDimitry Andric #  if !defined(SYS_futex) && defined(SYS_futex_time64)
26d409305fSDimitry Andric #    define SYS_futex SYS_futex_time64
27d409305fSDimitry Andric #  endif
28d409305fSDimitry Andric 
299c996882SKonstantin Belousov #elif defined(__FreeBSD__)
309c996882SKonstantin Belousov 
319c996882SKonstantin Belousov #  include <sys/types.h>
329c996882SKonstantin Belousov #  include <sys/umtx.h>
339c996882SKonstantin Belousov 
345ffd83dbSDimitry Andric #else // <- Add other operating systems here
355ffd83dbSDimitry Andric 
365ffd83dbSDimitry Andric // Baseline needs no new headers
375ffd83dbSDimitry Andric 
385ffd83dbSDimitry Andric #endif
395ffd83dbSDimitry Andric 
405ffd83dbSDimitry Andric _LIBCPP_BEGIN_NAMESPACE_STD
415ffd83dbSDimitry Andric 
425ffd83dbSDimitry Andric #ifdef __linux__
435ffd83dbSDimitry Andric 
44*cb14a3feSDimitry Andric static void
45*cb14a3feSDimitry Andric __libcpp_platform_wait_on_address(__cxx_atomic_contention_t const volatile* __ptr, __cxx_contention_t __val) {
465ffd83dbSDimitry Andric   static constexpr timespec __timeout = {2, 0};
475ffd83dbSDimitry Andric   syscall(SYS_futex, __ptr, FUTEX_WAIT_PRIVATE, __val, &__timeout, 0, 0);
485ffd83dbSDimitry Andric }
495ffd83dbSDimitry Andric 
50*cb14a3feSDimitry Andric static void __libcpp_platform_wake_by_address(__cxx_atomic_contention_t const volatile* __ptr, bool __notify_one) {
515ffd83dbSDimitry Andric   syscall(SYS_futex, __ptr, FUTEX_WAKE_PRIVATE, __notify_one ? 1 : INT_MAX, 0, 0, 0);
525ffd83dbSDimitry Andric }
535ffd83dbSDimitry Andric 
545ffd83dbSDimitry Andric #elif defined(__APPLE__) && defined(_LIBCPP_USE_ULOCK)
555ffd83dbSDimitry Andric 
56*cb14a3feSDimitry Andric extern "C" int __ulock_wait(
57*cb14a3feSDimitry Andric     uint32_t operation, void* addr, uint64_t value, uint32_t timeout); /* timeout is specified in microseconds */
585ffd83dbSDimitry Andric extern "C" int __ulock_wake(uint32_t operation, void* addr, uint64_t wake_value);
595ffd83dbSDimitry Andric 
605ffd83dbSDimitry Andric #  define UL_COMPARE_AND_WAIT 1
615ffd83dbSDimitry Andric #  define ULF_WAKE_ALL 0x00000100
625ffd83dbSDimitry Andric 
63*cb14a3feSDimitry Andric static void
64*cb14a3feSDimitry Andric __libcpp_platform_wait_on_address(__cxx_atomic_contention_t const volatile* __ptr, __cxx_contention_t __val) {
65*cb14a3feSDimitry Andric   __ulock_wait(UL_COMPARE_AND_WAIT, const_cast<__cxx_atomic_contention_t*>(__ptr), __val, 0);
665ffd83dbSDimitry Andric }
675ffd83dbSDimitry Andric 
68*cb14a3feSDimitry Andric static void __libcpp_platform_wake_by_address(__cxx_atomic_contention_t const volatile* __ptr, bool __notify_one) {
69*cb14a3feSDimitry Andric   __ulock_wake(
70*cb14a3feSDimitry Andric       UL_COMPARE_AND_WAIT | (__notify_one ? 0 : ULF_WAKE_ALL), const_cast<__cxx_atomic_contention_t*>(__ptr), 0);
715ffd83dbSDimitry Andric }
725ffd83dbSDimitry Andric 
7325b18d89SKonstantin Belousov #elif defined(__FreeBSD__) && __SIZEOF_LONG__ == 8
744c4a2926SKonstantin Belousov /*
754c4a2926SKonstantin Belousov  * Since __cxx_contention_t is int64_t even on 32bit FreeBSD
764c4a2926SKonstantin Belousov  * platforms, we have to use umtx ops that work on the long type, and
774c4a2926SKonstantin Belousov  * limit its use to architectures where long and int64_t are synonyms.
784c4a2926SKonstantin Belousov  */
799c996882SKonstantin Belousov 
80*cb14a3feSDimitry Andric static void
81*cb14a3feSDimitry Andric __libcpp_platform_wait_on_address(__cxx_atomic_contention_t const volatile* __ptr, __cxx_contention_t __val) {
82*cb14a3feSDimitry Andric   _umtx_op(const_cast<__cxx_atomic_contention_t*>(__ptr), UMTX_OP_WAIT, __val, NULL, NULL);
839c996882SKonstantin Belousov }
849c996882SKonstantin Belousov 
85*cb14a3feSDimitry Andric static void __libcpp_platform_wake_by_address(__cxx_atomic_contention_t const volatile* __ptr, bool __notify_one) {
86*cb14a3feSDimitry Andric   _umtx_op(const_cast<__cxx_atomic_contention_t*>(__ptr), UMTX_OP_WAKE, __notify_one ? 1 : INT_MAX, NULL, NULL);
879c996882SKonstantin Belousov }
889c996882SKonstantin Belousov 
895ffd83dbSDimitry Andric #else // <- Add other operating systems here
905ffd83dbSDimitry Andric 
915ffd83dbSDimitry Andric // Baseline is just a timed backoff
925ffd83dbSDimitry Andric 
93*cb14a3feSDimitry Andric static void
94*cb14a3feSDimitry Andric __libcpp_platform_wait_on_address(__cxx_atomic_contention_t const volatile* __ptr, __cxx_contention_t __val) {
95*cb14a3feSDimitry Andric   __libcpp_thread_poll_with_backoff(
96*cb14a3feSDimitry Andric       [=]() -> bool { return !__cxx_nonatomic_compare_equal(__cxx_atomic_load(__ptr, memory_order_relaxed), __val); },
97*cb14a3feSDimitry Andric       __libcpp_timed_backoff_policy());
985ffd83dbSDimitry Andric }
995ffd83dbSDimitry Andric 
1005ffd83dbSDimitry Andric static void __libcpp_platform_wake_by_address(__cxx_atomic_contention_t const volatile*, bool) {}
1015ffd83dbSDimitry Andric 
1025ffd83dbSDimitry Andric #endif // __linux__
1035ffd83dbSDimitry Andric 
1045ffd83dbSDimitry Andric static constexpr size_t __libcpp_contention_table_size = (1 << 8); /* < there's no magic in this number */
1055ffd83dbSDimitry Andric 
106*cb14a3feSDimitry Andric struct alignas(64) /*  aim to avoid false sharing */ __libcpp_contention_table_entry {
1075ffd83dbSDimitry Andric   __cxx_atomic_contention_t __contention_state;
1085ffd83dbSDimitry Andric   __cxx_atomic_contention_t __platform_state;
109*cb14a3feSDimitry Andric   inline constexpr __libcpp_contention_table_entry() : __contention_state(0), __platform_state(0) {}
1105ffd83dbSDimitry Andric };
1115ffd83dbSDimitry Andric 
1125ffd83dbSDimitry Andric static __libcpp_contention_table_entry __libcpp_contention_table[__libcpp_contention_table_size];
1135ffd83dbSDimitry Andric 
1145ffd83dbSDimitry Andric static hash<void const volatile*> __libcpp_contention_hasher;
1155ffd83dbSDimitry Andric 
116*cb14a3feSDimitry Andric static __libcpp_contention_table_entry* __libcpp_contention_state(void const volatile* p) {
1175ffd83dbSDimitry Andric   return &__libcpp_contention_table[__libcpp_contention_hasher(p) & (__libcpp_contention_table_size - 1)];
1185ffd83dbSDimitry Andric }
1195ffd83dbSDimitry Andric 
1205ffd83dbSDimitry Andric /* Given an atomic to track contention and an atomic to actually wait on, which may be
1215ffd83dbSDimitry Andric    the same atomic, we try to detect contention to avoid spuriously calling the platform. */
1225ffd83dbSDimitry Andric 
1235ffd83dbSDimitry Andric static void __libcpp_contention_notify(__cxx_atomic_contention_t volatile* __contention_state,
1245ffd83dbSDimitry Andric                                        __cxx_atomic_contention_t const volatile* __platform_state,
125*cb14a3feSDimitry Andric                                        bool __notify_one) {
1265ffd83dbSDimitry Andric   if (0 != __cxx_atomic_load(__contention_state, memory_order_seq_cst))
1275ffd83dbSDimitry Andric     // We only call 'wake' if we consumed a contention bit here.
1285ffd83dbSDimitry Andric     __libcpp_platform_wake_by_address(__platform_state, __notify_one);
1295ffd83dbSDimitry Andric }
130*cb14a3feSDimitry Andric static __cxx_contention_t
131*cb14a3feSDimitry Andric __libcpp_contention_monitor_for_wait(__cxx_atomic_contention_t volatile* /*__contention_state*/,
132*cb14a3feSDimitry Andric                                      __cxx_atomic_contention_t const volatile* __platform_state) {
1335ffd83dbSDimitry Andric   // We will monitor this value.
1345ffd83dbSDimitry Andric   return __cxx_atomic_load(__platform_state, memory_order_acquire);
1355ffd83dbSDimitry Andric }
1365ffd83dbSDimitry Andric static void __libcpp_contention_wait(__cxx_atomic_contention_t volatile* __contention_state,
1375ffd83dbSDimitry Andric                                      __cxx_atomic_contention_t const volatile* __platform_state,
138*cb14a3feSDimitry Andric                                      __cxx_contention_t __old_value) {
1395ffd83dbSDimitry Andric   __cxx_atomic_fetch_add(__contention_state, __cxx_contention_t(1), memory_order_seq_cst);
1405ffd83dbSDimitry Andric   // We sleep as long as the monitored value hasn't changed.
1415ffd83dbSDimitry Andric   __libcpp_platform_wait_on_address(__platform_state, __old_value);
1425ffd83dbSDimitry Andric   __cxx_atomic_fetch_sub(__contention_state, __cxx_contention_t(1), memory_order_release);
1435ffd83dbSDimitry Andric }
1445ffd83dbSDimitry Andric 
1455ffd83dbSDimitry Andric /* When the incoming atomic is the wrong size for the platform wait size, need to
1465ffd83dbSDimitry Andric    launder the value sequence through an atomic from our table. */
1475ffd83dbSDimitry Andric 
148*cb14a3feSDimitry Andric static void __libcpp_atomic_notify(void const volatile* __location) {
1495ffd83dbSDimitry Andric   auto const __entry = __libcpp_contention_state(__location);
1505ffd83dbSDimitry Andric   // The value sequence laundering happens on the next line below.
1515ffd83dbSDimitry Andric   __cxx_atomic_fetch_add(&__entry->__platform_state, __cxx_contention_t(1), memory_order_release);
152*cb14a3feSDimitry Andric   __libcpp_contention_notify(
153*cb14a3feSDimitry Andric       &__entry->__contention_state,
1545ffd83dbSDimitry Andric       &__entry->__platform_state,
1555ffd83dbSDimitry Andric       false /* when laundering, we can't handle notify_one */);
1565ffd83dbSDimitry Andric }
157*cb14a3feSDimitry Andric _LIBCPP_EXPORTED_FROM_ABI void __cxx_atomic_notify_one(void const volatile* __location) {
158*cb14a3feSDimitry Andric   __libcpp_atomic_notify(__location);
159*cb14a3feSDimitry Andric }
160*cb14a3feSDimitry Andric _LIBCPP_EXPORTED_FROM_ABI void __cxx_atomic_notify_all(void const volatile* __location) {
161*cb14a3feSDimitry Andric   __libcpp_atomic_notify(__location);
162*cb14a3feSDimitry Andric }
163*cb14a3feSDimitry Andric _LIBCPP_EXPORTED_FROM_ABI __cxx_contention_t __libcpp_atomic_monitor(void const volatile* __location) {
1645ffd83dbSDimitry Andric   auto const __entry = __libcpp_contention_state(__location);
1655ffd83dbSDimitry Andric   return __libcpp_contention_monitor_for_wait(&__entry->__contention_state, &__entry->__platform_state);
1665ffd83dbSDimitry Andric }
167*cb14a3feSDimitry Andric _LIBCPP_EXPORTED_FROM_ABI void __libcpp_atomic_wait(void const volatile* __location, __cxx_contention_t __old_value) {
1685ffd83dbSDimitry Andric   auto const __entry = __libcpp_contention_state(__location);
1695ffd83dbSDimitry Andric   __libcpp_contention_wait(&__entry->__contention_state, &__entry->__platform_state, __old_value);
1705ffd83dbSDimitry Andric }
1715ffd83dbSDimitry Andric 
1725ffd83dbSDimitry Andric /* When the incoming atomic happens to be the platform wait size, we still need to use the
1735ffd83dbSDimitry Andric    table for the contention detection, but we can use the atomic directly for the wait. */
1745ffd83dbSDimitry Andric 
175*cb14a3feSDimitry Andric _LIBCPP_EXPORTED_FROM_ABI void __cxx_atomic_notify_one(__cxx_atomic_contention_t const volatile* __location) {
1765ffd83dbSDimitry Andric   __libcpp_contention_notify(&__libcpp_contention_state(__location)->__contention_state, __location, true);
1775ffd83dbSDimitry Andric }
178*cb14a3feSDimitry Andric _LIBCPP_EXPORTED_FROM_ABI void __cxx_atomic_notify_all(__cxx_atomic_contention_t const volatile* __location) {
1795ffd83dbSDimitry Andric   __libcpp_contention_notify(&__libcpp_contention_state(__location)->__contention_state, __location, false);
1805ffd83dbSDimitry Andric }
181*cb14a3feSDimitry Andric _LIBCPP_EXPORTED_FROM_ABI __cxx_contention_t
182*cb14a3feSDimitry Andric __libcpp_atomic_monitor(__cxx_atomic_contention_t const volatile* __location) {
1835ffd83dbSDimitry Andric   return __libcpp_contention_monitor_for_wait(&__libcpp_contention_state(__location)->__contention_state, __location);
1845ffd83dbSDimitry Andric }
185*cb14a3feSDimitry Andric _LIBCPP_EXPORTED_FROM_ABI void
186*cb14a3feSDimitry Andric __libcpp_atomic_wait(__cxx_atomic_contention_t const volatile* __location, __cxx_contention_t __old_value) {
1875ffd83dbSDimitry Andric   __libcpp_contention_wait(&__libcpp_contention_state(__location)->__contention_state, __location, __old_value);
1885ffd83dbSDimitry Andric }
1895ffd83dbSDimitry Andric 
1905ffd83dbSDimitry Andric _LIBCPP_END_NAMESPACE_STD
191