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>
21cb14a3feSDimitry 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
283a079333SDimitry Andric # define _LIBCPP_FUTEX(...) syscall(SYS_futex, __VA_ARGS__)
29d409305fSDimitry Andric
309c996882SKonstantin Belousov #elif defined(__FreeBSD__)
319c996882SKonstantin Belousov
329c996882SKonstantin Belousov # include <sys/types.h>
339c996882SKonstantin Belousov # include <sys/umtx.h>
349c996882SKonstantin Belousov
353a079333SDimitry Andric # define _LIBCPP_FUTEX(...) syscall(SYS_futex, __VA_ARGS__)
363a079333SDimitry Andric
373a079333SDimitry Andric #elif defined(__OpenBSD__)
383a079333SDimitry Andric
393a079333SDimitry Andric # include <sys/futex.h>
403a079333SDimitry Andric
413a079333SDimitry Andric // OpenBSD has no indirect syscalls
423a079333SDimitry Andric # define _LIBCPP_FUTEX(...) futex(__VA_ARGS__)
433a079333SDimitry Andric
445ffd83dbSDimitry Andric #else // <- Add other operating systems here
455ffd83dbSDimitry Andric
465ffd83dbSDimitry Andric // Baseline needs no new headers
475ffd83dbSDimitry Andric
483a079333SDimitry Andric # define _LIBCPP_FUTEX(...) syscall(SYS_futex, __VA_ARGS__)
493a079333SDimitry Andric
505ffd83dbSDimitry Andric #endif
515ffd83dbSDimitry Andric
525ffd83dbSDimitry Andric _LIBCPP_BEGIN_NAMESPACE_STD
535ffd83dbSDimitry Andric
545ffd83dbSDimitry Andric #ifdef __linux__
555ffd83dbSDimitry Andric
56cb14a3feSDimitry Andric static void
__libcpp_platform_wait_on_address(__cxx_atomic_contention_t const volatile * __ptr,__cxx_contention_t __val)57cb14a3feSDimitry Andric __libcpp_platform_wait_on_address(__cxx_atomic_contention_t const volatile* __ptr, __cxx_contention_t __val) {
585ffd83dbSDimitry Andric static constexpr timespec __timeout = {2, 0};
593a079333SDimitry Andric _LIBCPP_FUTEX(__ptr, FUTEX_WAIT_PRIVATE, __val, &__timeout, 0, 0);
605ffd83dbSDimitry Andric }
615ffd83dbSDimitry Andric
__libcpp_platform_wake_by_address(__cxx_atomic_contention_t const volatile * __ptr,bool __notify_one)62cb14a3feSDimitry Andric static void __libcpp_platform_wake_by_address(__cxx_atomic_contention_t const volatile* __ptr, bool __notify_one) {
633a079333SDimitry Andric _LIBCPP_FUTEX(__ptr, FUTEX_WAKE_PRIVATE, __notify_one ? 1 : INT_MAX, 0, 0, 0);
645ffd83dbSDimitry Andric }
655ffd83dbSDimitry Andric
665ffd83dbSDimitry Andric #elif defined(__APPLE__) && defined(_LIBCPP_USE_ULOCK)
675ffd83dbSDimitry Andric
68cb14a3feSDimitry Andric extern "C" int __ulock_wait(
69cb14a3feSDimitry Andric uint32_t operation, void* addr, uint64_t value, uint32_t timeout); /* timeout is specified in microseconds */
705ffd83dbSDimitry Andric extern "C" int __ulock_wake(uint32_t operation, void* addr, uint64_t wake_value);
715ffd83dbSDimitry Andric
72*0fca6ea1SDimitry Andric // https://github.com/apple/darwin-xnu/blob/2ff845c2e033bd0ff64b5b6aa6063a1f8f65aa32/bsd/sys/ulock.h#L82
73*0fca6ea1SDimitry Andric # define UL_COMPARE_AND_WAIT64 5
745ffd83dbSDimitry Andric # define ULF_WAKE_ALL 0x00000100
755ffd83dbSDimitry Andric
76cb14a3feSDimitry Andric static void
77cb14a3feSDimitry Andric __libcpp_platform_wait_on_address(__cxx_atomic_contention_t const volatile* __ptr, __cxx_contention_t __val) {
78*0fca6ea1SDimitry Andric static_assert(sizeof(__cxx_atomic_contention_t) == 8, "Waiting on 8 bytes value");
79*0fca6ea1SDimitry Andric __ulock_wait(UL_COMPARE_AND_WAIT64, const_cast<__cxx_atomic_contention_t*>(__ptr), __val, 0);
805ffd83dbSDimitry Andric }
815ffd83dbSDimitry Andric
82cb14a3feSDimitry Andric static void __libcpp_platform_wake_by_address(__cxx_atomic_contention_t const volatile* __ptr, bool __notify_one) {
83*0fca6ea1SDimitry Andric static_assert(sizeof(__cxx_atomic_contention_t) == 8, "Waking up on 8 bytes value");
84cb14a3feSDimitry Andric __ulock_wake(
85*0fca6ea1SDimitry Andric UL_COMPARE_AND_WAIT64 | (__notify_one ? 0 : ULF_WAKE_ALL), const_cast<__cxx_atomic_contention_t*>(__ptr), 0);
865ffd83dbSDimitry Andric }
875ffd83dbSDimitry Andric
8825b18d89SKonstantin Belousov #elif defined(__FreeBSD__) && __SIZEOF_LONG__ == 8
894c4a2926SKonstantin Belousov /*
904c4a2926SKonstantin Belousov * Since __cxx_contention_t is int64_t even on 32bit FreeBSD
914c4a2926SKonstantin Belousov * platforms, we have to use umtx ops that work on the long type, and
924c4a2926SKonstantin Belousov * limit its use to architectures where long and int64_t are synonyms.
934c4a2926SKonstantin Belousov */
949c996882SKonstantin Belousov
95cb14a3feSDimitry Andric static void
96cb14a3feSDimitry Andric __libcpp_platform_wait_on_address(__cxx_atomic_contention_t const volatile* __ptr, __cxx_contention_t __val) {
97cb14a3feSDimitry Andric _umtx_op(const_cast<__cxx_atomic_contention_t*>(__ptr), UMTX_OP_WAIT, __val, NULL, NULL);
989c996882SKonstantin Belousov }
999c996882SKonstantin Belousov
100cb14a3feSDimitry Andric static void __libcpp_platform_wake_by_address(__cxx_atomic_contention_t const volatile* __ptr, bool __notify_one) {
101cb14a3feSDimitry Andric _umtx_op(const_cast<__cxx_atomic_contention_t*>(__ptr), UMTX_OP_WAKE, __notify_one ? 1 : INT_MAX, NULL, NULL);
1029c996882SKonstantin Belousov }
1039c996882SKonstantin Belousov
1045ffd83dbSDimitry Andric #else // <- Add other operating systems here
1055ffd83dbSDimitry Andric
1065ffd83dbSDimitry Andric // Baseline is just a timed backoff
1075ffd83dbSDimitry Andric
108cb14a3feSDimitry Andric static void
109cb14a3feSDimitry Andric __libcpp_platform_wait_on_address(__cxx_atomic_contention_t const volatile* __ptr, __cxx_contention_t __val) {
110cb14a3feSDimitry Andric __libcpp_thread_poll_with_backoff(
111cb14a3feSDimitry Andric [=]() -> bool { return !__cxx_nonatomic_compare_equal(__cxx_atomic_load(__ptr, memory_order_relaxed), __val); },
112cb14a3feSDimitry Andric __libcpp_timed_backoff_policy());
1135ffd83dbSDimitry Andric }
1145ffd83dbSDimitry Andric
1155ffd83dbSDimitry Andric static void __libcpp_platform_wake_by_address(__cxx_atomic_contention_t const volatile*, bool) {}
1165ffd83dbSDimitry Andric
1175ffd83dbSDimitry Andric #endif // __linux__
1185ffd83dbSDimitry Andric
1195ffd83dbSDimitry Andric static constexpr size_t __libcpp_contention_table_size = (1 << 8); /* < there's no magic in this number */
1205ffd83dbSDimitry Andric
121cb14a3feSDimitry Andric struct alignas(64) /* aim to avoid false sharing */ __libcpp_contention_table_entry {
1225ffd83dbSDimitry Andric __cxx_atomic_contention_t __contention_state;
1235ffd83dbSDimitry Andric __cxx_atomic_contention_t __platform_state;
__libcpp_contention_table_entry__libcpp_contention_table_entry124cb14a3feSDimitry Andric inline constexpr __libcpp_contention_table_entry() : __contention_state(0), __platform_state(0) {}
1255ffd83dbSDimitry Andric };
1265ffd83dbSDimitry Andric
1275ffd83dbSDimitry Andric static __libcpp_contention_table_entry __libcpp_contention_table[__libcpp_contention_table_size];
1285ffd83dbSDimitry Andric
1295ffd83dbSDimitry Andric static hash<void const volatile*> __libcpp_contention_hasher;
1305ffd83dbSDimitry Andric
__libcpp_contention_state(void const volatile * p)131cb14a3feSDimitry Andric static __libcpp_contention_table_entry* __libcpp_contention_state(void const volatile* p) {
1325ffd83dbSDimitry Andric return &__libcpp_contention_table[__libcpp_contention_hasher(p) & (__libcpp_contention_table_size - 1)];
1335ffd83dbSDimitry Andric }
1345ffd83dbSDimitry Andric
1355ffd83dbSDimitry Andric /* Given an atomic to track contention and an atomic to actually wait on, which may be
1365ffd83dbSDimitry Andric the same atomic, we try to detect contention to avoid spuriously calling the platform. */
1375ffd83dbSDimitry Andric
__libcpp_contention_notify(__cxx_atomic_contention_t volatile * __contention_state,__cxx_atomic_contention_t const volatile * __platform_state,bool __notify_one)1385ffd83dbSDimitry Andric static void __libcpp_contention_notify(__cxx_atomic_contention_t volatile* __contention_state,
1395ffd83dbSDimitry Andric __cxx_atomic_contention_t const volatile* __platform_state,
140cb14a3feSDimitry Andric bool __notify_one) {
1415ffd83dbSDimitry Andric if (0 != __cxx_atomic_load(__contention_state, memory_order_seq_cst))
1425ffd83dbSDimitry Andric // We only call 'wake' if we consumed a contention bit here.
1435ffd83dbSDimitry Andric __libcpp_platform_wake_by_address(__platform_state, __notify_one);
1445ffd83dbSDimitry Andric }
145cb14a3feSDimitry Andric static __cxx_contention_t
__libcpp_contention_monitor_for_wait(__cxx_atomic_contention_t volatile *,__cxx_atomic_contention_t const volatile * __platform_state)146cb14a3feSDimitry Andric __libcpp_contention_monitor_for_wait(__cxx_atomic_contention_t volatile* /*__contention_state*/,
147cb14a3feSDimitry Andric __cxx_atomic_contention_t const volatile* __platform_state) {
1485ffd83dbSDimitry Andric // We will monitor this value.
1495ffd83dbSDimitry Andric return __cxx_atomic_load(__platform_state, memory_order_acquire);
1505ffd83dbSDimitry Andric }
__libcpp_contention_wait(__cxx_atomic_contention_t volatile * __contention_state,__cxx_atomic_contention_t const volatile * __platform_state,__cxx_contention_t __old_value)1515ffd83dbSDimitry Andric static void __libcpp_contention_wait(__cxx_atomic_contention_t volatile* __contention_state,
1525ffd83dbSDimitry Andric __cxx_atomic_contention_t const volatile* __platform_state,
153cb14a3feSDimitry Andric __cxx_contention_t __old_value) {
1545ffd83dbSDimitry Andric __cxx_atomic_fetch_add(__contention_state, __cxx_contention_t(1), memory_order_seq_cst);
1555ffd83dbSDimitry Andric // We sleep as long as the monitored value hasn't changed.
1565ffd83dbSDimitry Andric __libcpp_platform_wait_on_address(__platform_state, __old_value);
1575ffd83dbSDimitry Andric __cxx_atomic_fetch_sub(__contention_state, __cxx_contention_t(1), memory_order_release);
1585ffd83dbSDimitry Andric }
1595ffd83dbSDimitry Andric
1605ffd83dbSDimitry Andric /* When the incoming atomic is the wrong size for the platform wait size, need to
1615ffd83dbSDimitry Andric launder the value sequence through an atomic from our table. */
1625ffd83dbSDimitry Andric
__libcpp_atomic_notify(void const volatile * __location)163cb14a3feSDimitry Andric static void __libcpp_atomic_notify(void const volatile* __location) {
1645ffd83dbSDimitry Andric auto const __entry = __libcpp_contention_state(__location);
1655ffd83dbSDimitry Andric // The value sequence laundering happens on the next line below.
1665ffd83dbSDimitry Andric __cxx_atomic_fetch_add(&__entry->__platform_state, __cxx_contention_t(1), memory_order_release);
167cb14a3feSDimitry Andric __libcpp_contention_notify(
168cb14a3feSDimitry Andric &__entry->__contention_state,
1695ffd83dbSDimitry Andric &__entry->__platform_state,
1705ffd83dbSDimitry Andric false /* when laundering, we can't handle notify_one */);
1715ffd83dbSDimitry Andric }
__cxx_atomic_notify_one(void const volatile * __location)172*0fca6ea1SDimitry Andric _LIBCPP_EXPORTED_FROM_ABI void __cxx_atomic_notify_one(void const volatile* __location) noexcept {
173cb14a3feSDimitry Andric __libcpp_atomic_notify(__location);
174cb14a3feSDimitry Andric }
__cxx_atomic_notify_all(void const volatile * __location)175*0fca6ea1SDimitry Andric _LIBCPP_EXPORTED_FROM_ABI void __cxx_atomic_notify_all(void const volatile* __location) noexcept {
176cb14a3feSDimitry Andric __libcpp_atomic_notify(__location);
177cb14a3feSDimitry Andric }
__libcpp_atomic_monitor(void const volatile * __location)178*0fca6ea1SDimitry Andric _LIBCPP_EXPORTED_FROM_ABI __cxx_contention_t __libcpp_atomic_monitor(void const volatile* __location) noexcept {
1795ffd83dbSDimitry Andric auto const __entry = __libcpp_contention_state(__location);
1805ffd83dbSDimitry Andric return __libcpp_contention_monitor_for_wait(&__entry->__contention_state, &__entry->__platform_state);
1815ffd83dbSDimitry Andric }
182*0fca6ea1SDimitry Andric _LIBCPP_EXPORTED_FROM_ABI void
__libcpp_atomic_wait(void const volatile * __location,__cxx_contention_t __old_value)183*0fca6ea1SDimitry Andric __libcpp_atomic_wait(void const volatile* __location, __cxx_contention_t __old_value) noexcept {
1845ffd83dbSDimitry Andric auto const __entry = __libcpp_contention_state(__location);
1855ffd83dbSDimitry Andric __libcpp_contention_wait(&__entry->__contention_state, &__entry->__platform_state, __old_value);
1865ffd83dbSDimitry Andric }
1875ffd83dbSDimitry Andric
1885ffd83dbSDimitry Andric /* When the incoming atomic happens to be the platform wait size, we still need to use the
1895ffd83dbSDimitry Andric table for the contention detection, but we can use the atomic directly for the wait. */
1905ffd83dbSDimitry Andric
__cxx_atomic_notify_one(__cxx_atomic_contention_t const volatile * __location)191*0fca6ea1SDimitry Andric _LIBCPP_EXPORTED_FROM_ABI void __cxx_atomic_notify_one(__cxx_atomic_contention_t const volatile* __location) noexcept {
1925ffd83dbSDimitry Andric __libcpp_contention_notify(&__libcpp_contention_state(__location)->__contention_state, __location, true);
1935ffd83dbSDimitry Andric }
__cxx_atomic_notify_all(__cxx_atomic_contention_t const volatile * __location)194*0fca6ea1SDimitry Andric _LIBCPP_EXPORTED_FROM_ABI void __cxx_atomic_notify_all(__cxx_atomic_contention_t const volatile* __location) noexcept {
1955ffd83dbSDimitry Andric __libcpp_contention_notify(&__libcpp_contention_state(__location)->__contention_state, __location, false);
1965ffd83dbSDimitry Andric }
197*0fca6ea1SDimitry Andric // This function is never used, but still exported for ABI compatibility.
198cb14a3feSDimitry Andric _LIBCPP_EXPORTED_FROM_ABI __cxx_contention_t
__libcpp_atomic_monitor(__cxx_atomic_contention_t const volatile * __location)199*0fca6ea1SDimitry Andric __libcpp_atomic_monitor(__cxx_atomic_contention_t const volatile* __location) noexcept {
2005ffd83dbSDimitry Andric return __libcpp_contention_monitor_for_wait(&__libcpp_contention_state(__location)->__contention_state, __location);
2015ffd83dbSDimitry Andric }
202cb14a3feSDimitry Andric _LIBCPP_EXPORTED_FROM_ABI void
__libcpp_atomic_wait(__cxx_atomic_contention_t const volatile * __location,__cxx_contention_t __old_value)203*0fca6ea1SDimitry Andric __libcpp_atomic_wait(__cxx_atomic_contention_t const volatile* __location, __cxx_contention_t __old_value) noexcept {
2045ffd83dbSDimitry Andric __libcpp_contention_wait(&__libcpp_contention_state(__location)->__contention_state, __location, __old_value);
2055ffd83dbSDimitry Andric }
2065ffd83dbSDimitry Andric
2075ffd83dbSDimitry Andric _LIBCPP_END_NAMESPACE_STD
208