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