This is to address #146145 The issue before was that, for `std::atomic::wait/notify`, we only support `uint64_t` to go through the native `ulock_wait` directly. Any other types will go through the global contention table's `atomic`, increasing the chances of spurious wakeup. This PR tries to allow any types that are of size 4 or 8 to directly go to the `ulock_wait`. This PR is just proof of concept. If we like this idea, I can go further to update the Linux/FreeBSD branch and add ABI macros so the existing behaviours are reserved under the stable ABI Here are some benchmark results ``` Benchmark Time CPU Time Old Time New CPU Old CPU New ---------------------------------------------------------------------------------------------------------------------------------------------------- BM_stop_token_single_thread_reg_unreg_callback/1024 -0.1113 -0.1165 51519 45785 51397 45408 BM_stop_token_single_thread_reg_unreg_callback/4096 -0.2727 -0.1447 249685 181608 211865 181203 BM_stop_token_single_thread_reg_unreg_callback/65536 -0.1241 -0.1237 3308930 2898396 3300986 2892608 BM_stop_token_single_thread_reg_unreg_callback/262144 +0.0335 -0.1920 13237682 13681632 13208849 10673254 OVERALL_GEOMEAN -0.1254 -0.1447 0 0 0 0 ``` ``` Benchmark Time CPU Time Old Time New CPU Old CPU New ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- BM_1_atomic_1_waiter_1_notifier<KeepNotifying, NumHighPrioTasks<0>>/65536 -0.3344 -0.2424 5960741 3967212 5232250 3964085 BM_1_atomic_1_waiter_1_notifier<KeepNotifying, NumHighPrioTasks<0>>/131072 -0.1474 -0.1475 9144356 7796745 9137547 7790193 BM_1_atomic_1_waiter_1_notifier<KeepNotifying, NumHighPrioTasks<0>>/262144 -0.1336 -0.1340 18333441 15883805 18323711 15868500 OVERALL_GEOMEAN -0.2107 -0.1761 0 0 0 0 ``` ``` Benchmark Time CPU Time Old Time New CPU Old CPU New -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- BM_1_atomic_multi_waiter_1_notifier<KeepNotifying, NumWaitingThreads<2>, NumHighPrioTasks<0>>/16384 +0.2321 -0.0081 836618 1030772 833197 826476 BM_1_atomic_multi_waiter_1_notifier<KeepNotifying, NumWaitingThreads<2>, NumHighPrioTasks<0>>/32768 -0.3034 -0.1329 2182721 1520569 1747211 1515028 BM_1_atomic_multi_waiter_1_notifier<KeepNotifying, NumWaitingThreads<2>, NumHighPrioTasks<0>>/65536 -0.0924 -0.0924 3389098 3075897 3378486 3066448 BM_1_atomic_multi_waiter_1_notifier<KeepNotifying, NumWaitingThreads<8>, NumHighPrioTasks<0>>/4096 +0.0464 +0.0474 664233 695080 657736 688892 BM_1_atomic_multi_waiter_1_notifier<KeepNotifying, NumWaitingThreads<8>, NumHighPrioTasks<0>>/8192 -0.0279 -0.0267 1336041 1298794 1324270 1288953 BM_1_atomic_multi_waiter_1_notifier<KeepNotifying, NumWaitingThreads<8>, NumHighPrioTasks<0>>/16384 +0.0270 +0.0304 2543004 2611786 2517471 2593975 BM_1_atomic_multi_waiter_1_notifier<KeepNotifying, NumWaitingThreads<32>, NumHighPrioTasks<0>>/1024 +0.0423 +0.0941 473621 493657 325604 356245 BM_1_atomic_multi_waiter_1_notifier<KeepNotifying, NumWaitingThreads<32>, NumHighPrioTasks<0>>/2048 +0.0420 +0.0675 906266 944349 636253 679169 BM_1_atomic_multi_waiter_1_notifier<KeepNotifying, NumWaitingThreads<32>, NumHighPrioTasks<0>>/4096 +0.0359 +0.0378 1761584 1824783 1015092 1053439 OVERALL_GEOMEAN -0.0097 -0.0007 0 0 0 0 ``` ``` Benchmark Time CPU Time Old Time New CPU Old CPU New --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- BM_N_atomics_N_waiter_N_notifier<KeepNotifying, NumberOfAtomics<2>, NumHighPrioTasks<0>>/4096 -0.0990 -0.1001 371100 334370 369984 332955 BM_N_atomics_N_waiter_N_notifier<KeepNotifying, NumberOfAtomics<2>, NumHighPrioTasks<0>>/8192 -0.0305 -0.0314 698228 676908 696418 674585 BM_N_atomics_N_waiter_N_notifier<KeepNotifying, NumberOfAtomics<2>, NumHighPrioTasks<0>>/16384 -0.0258 -0.0268 1383530 1347894 1380665 1343680 BM_N_atomics_N_waiter_N_notifier<KeepNotifying, NumberOfAtomics<8>, NumHighPrioTasks<0>>/1024 +0.0465 +0.4702 937821 981388 472087 694082 BM_N_atomics_N_waiter_N_notifier<KeepNotifying, NumberOfAtomics<8>, NumHighPrioTasks<0>>/2048 +0.1596 +0.9140 1704819 1976899 616419 1179852 BM_N_atomics_N_waiter_N_notifier<KeepNotifying, NumberOfAtomics<8>, NumHighPrioTasks<0>>/4096 -0.1018 -0.2316 3793976 3407609 1912209 1469331 BM_N_atomics_N_waiter_N_notifier<KeepNotifying, NumberOfAtomics<32>, NumHighPrioTasks<0>>/256 +0.0395 +0.5818 30102662 31292982 174650 276270 BM_N_atomics_N_waiter_N_notifier<KeepNotifying, NumberOfAtomics<32>, NumHighPrioTasks<0>>/512 -0.0065 +1.2860 33079634 32863968 162150 370680 BM_N_atomics_N_waiter_N_notifier<KeepNotifying, NumberOfAtomics<32>, NumHighPrioTasks<0>>/1024 -0.0325 +0.4683 36581740 35392385 282320 414520 OVERALL_GEOMEAN -0.0084 +0.2878 0 0 0 0 ``` --------- Co-authored-by: Louis Dionne <ldionne.2@gmail.com>
395 lines
17 KiB
C++
395 lines
17 KiB
C++
// -*- C++ -*-
|
|
//===----------------------------------------------------------------------===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
// Kokkos v. 4.0
|
|
// Copyright (2022) National Technology & Engineering
|
|
// Solutions of Sandia, LLC (NTESS).
|
|
//
|
|
// Under the terms of Contract DE-NA0003525 with NTESS,
|
|
// the U.S. Government retains certain rights in this software.
|
|
//
|
|
//===---------------------------------------------------------------------===//
|
|
|
|
#ifndef _LIBCPP___ATOMIC_ATOMIC_REF_H
|
|
#define _LIBCPP___ATOMIC_ATOMIC_REF_H
|
|
|
|
#include <__assert>
|
|
#include <__atomic/atomic_sync.h>
|
|
#include <__atomic/check_memory_order.h>
|
|
#include <__atomic/floating_point_helper.h>
|
|
#include <__atomic/memory_order.h>
|
|
#include <__atomic/to_gcc_order.h>
|
|
#include <__concepts/arithmetic.h>
|
|
#include <__concepts/same_as.h>
|
|
#include <__config>
|
|
#include <__cstddef/byte.h>
|
|
#include <__cstddef/ptrdiff_t.h>
|
|
#include <__memory/addressof.h>
|
|
#include <__type_traits/has_unique_object_representation.h>
|
|
#include <__type_traits/is_trivially_copyable.h>
|
|
#include <cstdint>
|
|
#include <cstring>
|
|
|
|
#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
|
|
# pragma GCC system_header
|
|
#endif
|
|
|
|
_LIBCPP_PUSH_MACROS
|
|
#include <__undef_macros>
|
|
|
|
_LIBCPP_BEGIN_NAMESPACE_STD
|
|
|
|
#if _LIBCPP_STD_VER >= 20
|
|
|
|
// These types are required to make __atomic_is_always_lock_free work across GCC and Clang.
|
|
// The purpose of this trick is to make sure that we provide an object with the correct alignment
|
|
// to __atomic_is_always_lock_free, since that answer depends on the alignment.
|
|
template <size_t _Alignment>
|
|
struct __alignment_checker_type {
|
|
alignas(_Alignment) char __data;
|
|
};
|
|
|
|
template <size_t _Alignment>
|
|
struct __get_aligner_instance {
|
|
static constexpr __alignment_checker_type<_Alignment> __instance{};
|
|
};
|
|
|
|
template <class _Tp>
|
|
struct __atomic_ref_base {
|
|
private:
|
|
_LIBCPP_HIDE_FROM_ABI static _Tp* __clear_padding(_Tp& __val) noexcept {
|
|
_Tp* __ptr = std::addressof(__val);
|
|
# if __has_builtin(__builtin_clear_padding)
|
|
__builtin_clear_padding(__ptr);
|
|
# endif
|
|
return __ptr;
|
|
}
|
|
|
|
_LIBCPP_HIDE_FROM_ABI static bool __compare_exchange(
|
|
_Tp* __ptr, _Tp* __expected, _Tp* __desired, bool __is_weak, int __success, int __failure) noexcept {
|
|
if constexpr (
|
|
# if __has_builtin(__builtin_clear_padding)
|
|
has_unique_object_representations_v<_Tp> || floating_point<_Tp>
|
|
# else
|
|
true // NOLINT(readability-simplify-boolean-expr)
|
|
# endif
|
|
) {
|
|
return __atomic_compare_exchange(__ptr, __expected, __desired, __is_weak, __success, __failure);
|
|
} else { // _Tp has padding bits and __builtin_clear_padding is available
|
|
__clear_padding(*__desired);
|
|
_Tp __copy = *__expected;
|
|
__clear_padding(__copy);
|
|
// The algorithm we use here is basically to perform `__atomic_compare_exchange` on the
|
|
// values until it has either succeeded, or failed because the value representation of the
|
|
// objects involved was different. This is why we loop around __atomic_compare_exchange:
|
|
// we basically loop until its failure is caused by the value representation of the objects
|
|
// being different, not only their object representation.
|
|
while (true) {
|
|
_Tp __prev = __copy;
|
|
if (__atomic_compare_exchange(__ptr, std::addressof(__copy), __desired, __is_weak, __success, __failure)) {
|
|
return true;
|
|
}
|
|
_Tp __curr = __copy;
|
|
if (std::memcmp(__clear_padding(__prev), __clear_padding(__curr), sizeof(_Tp)) != 0) {
|
|
// Value representation without padding bits do not compare equal ->
|
|
// write the current content of *ptr into *expected
|
|
std::memcpy(__expected, std::addressof(__copy), sizeof(_Tp));
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
friend struct __atomic_waitable_traits<__atomic_ref_base<_Tp>>;
|
|
|
|
// require types that are 1, 2, 4, 8, or 16 bytes in length to be aligned to at least their size to be potentially
|
|
// used lock-free
|
|
static constexpr size_t __min_alignment = (sizeof(_Tp) & (sizeof(_Tp) - 1)) || (sizeof(_Tp) > 16) ? 0 : sizeof(_Tp);
|
|
|
|
public:
|
|
using value_type = _Tp;
|
|
|
|
static constexpr size_t required_alignment = alignof(_Tp) > __min_alignment ? alignof(_Tp) : __min_alignment;
|
|
|
|
// The __atomic_always_lock_free builtin takes into account the alignment of the pointer if provided,
|
|
// so we create a fake pointer with a suitable alignment when querying it. Note that we are guaranteed
|
|
// that the pointer is going to be aligned properly at runtime because that is a (checked) precondition
|
|
// of atomic_ref's constructor.
|
|
static constexpr bool is_always_lock_free =
|
|
__atomic_always_lock_free(sizeof(_Tp), std::addressof(__get_aligner_instance<required_alignment>::__instance));
|
|
|
|
_LIBCPP_HIDE_FROM_ABI bool is_lock_free() const noexcept { return __atomic_is_lock_free(sizeof(_Tp), __ptr_); }
|
|
|
|
_LIBCPP_HIDE_FROM_ABI void store(_Tp __desired, memory_order __order = memory_order::seq_cst) const noexcept
|
|
_LIBCPP_CHECK_STORE_MEMORY_ORDER(__order) {
|
|
_LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN(
|
|
__order == memory_order::relaxed || __order == memory_order::release || __order == memory_order::seq_cst,
|
|
"atomic_ref: memory order argument to atomic store operation is invalid");
|
|
__atomic_store(__ptr_, __clear_padding(__desired), std::__to_gcc_order(__order));
|
|
}
|
|
|
|
_LIBCPP_HIDE_FROM_ABI _Tp operator=(_Tp __desired) const noexcept {
|
|
store(__desired);
|
|
return __desired;
|
|
}
|
|
|
|
_LIBCPP_HIDE_FROM_ABI _Tp load(memory_order __order = memory_order::seq_cst) const noexcept
|
|
_LIBCPP_CHECK_LOAD_MEMORY_ORDER(__order) {
|
|
_LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN(
|
|
__order == memory_order::relaxed || __order == memory_order::consume || __order == memory_order::acquire ||
|
|
__order == memory_order::seq_cst,
|
|
"atomic_ref: memory order argument to atomic load operation is invalid");
|
|
alignas(_Tp) byte __mem[sizeof(_Tp)];
|
|
auto* __ret = reinterpret_cast<_Tp*>(__mem);
|
|
__atomic_load(__ptr_, __ret, std::__to_gcc_order(__order));
|
|
return *__ret;
|
|
}
|
|
|
|
_LIBCPP_HIDE_FROM_ABI operator _Tp() const noexcept { return load(); }
|
|
|
|
_LIBCPP_HIDE_FROM_ABI _Tp exchange(_Tp __desired, memory_order __order = memory_order::seq_cst) const noexcept {
|
|
alignas(_Tp) byte __mem[sizeof(_Tp)];
|
|
auto* __ret = reinterpret_cast<_Tp*>(__mem);
|
|
__atomic_exchange(__ptr_, __clear_padding(__desired), __ret, std::__to_gcc_order(__order));
|
|
return *__ret;
|
|
}
|
|
|
|
_LIBCPP_HIDE_FROM_ABI bool
|
|
compare_exchange_weak(_Tp& __expected, _Tp __desired, memory_order __success, memory_order __failure) const noexcept
|
|
_LIBCPP_CHECK_EXCHANGE_MEMORY_ORDER(__success, __failure) {
|
|
_LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN(
|
|
__failure == memory_order::relaxed || __failure == memory_order::consume ||
|
|
__failure == memory_order::acquire || __failure == memory_order::seq_cst,
|
|
"atomic_ref: failure memory order argument to weak atomic compare-and-exchange operation is invalid");
|
|
return __compare_exchange(
|
|
__ptr_,
|
|
std::addressof(__expected),
|
|
std::addressof(__desired),
|
|
true,
|
|
std::__to_gcc_order(__success),
|
|
std::__to_gcc_order(__failure));
|
|
}
|
|
_LIBCPP_HIDE_FROM_ABI bool
|
|
compare_exchange_strong(_Tp& __expected, _Tp __desired, memory_order __success, memory_order __failure) const noexcept
|
|
_LIBCPP_CHECK_EXCHANGE_MEMORY_ORDER(__success, __failure) {
|
|
_LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN(
|
|
__failure == memory_order::relaxed || __failure == memory_order::consume ||
|
|
__failure == memory_order::acquire || __failure == memory_order::seq_cst,
|
|
"atomic_ref: failure memory order argument to strong atomic compare-and-exchange operation is invalid");
|
|
return __compare_exchange(
|
|
__ptr_,
|
|
std::addressof(__expected),
|
|
std::addressof(__desired),
|
|
false,
|
|
std::__to_gcc_order(__success),
|
|
std::__to_gcc_order(__failure));
|
|
}
|
|
|
|
_LIBCPP_HIDE_FROM_ABI bool
|
|
compare_exchange_weak(_Tp& __expected, _Tp __desired, memory_order __order = memory_order::seq_cst) const noexcept {
|
|
return __compare_exchange(
|
|
__ptr_,
|
|
std::addressof(__expected),
|
|
std::addressof(__desired),
|
|
true,
|
|
std::__to_gcc_order(__order),
|
|
std::__to_gcc_failure_order(__order));
|
|
}
|
|
_LIBCPP_HIDE_FROM_ABI bool
|
|
compare_exchange_strong(_Tp& __expected, _Tp __desired, memory_order __order = memory_order::seq_cst) const noexcept {
|
|
return __compare_exchange(
|
|
__ptr_,
|
|
std::addressof(__expected),
|
|
std::addressof(__desired),
|
|
false,
|
|
std::__to_gcc_order(__order),
|
|
std::__to_gcc_failure_order(__order));
|
|
}
|
|
|
|
_LIBCPP_HIDE_FROM_ABI void wait(_Tp __old, memory_order __order = memory_order::seq_cst) const noexcept
|
|
_LIBCPP_CHECK_WAIT_MEMORY_ORDER(__order) {
|
|
_LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN(
|
|
__order == memory_order::relaxed || __order == memory_order::consume || __order == memory_order::acquire ||
|
|
__order == memory_order::seq_cst,
|
|
"atomic_ref: memory order argument to atomic wait operation is invalid");
|
|
std::__atomic_wait(*this, __old, __order);
|
|
}
|
|
_LIBCPP_HIDE_FROM_ABI void notify_one() const noexcept { std::__atomic_notify_one(*this); }
|
|
_LIBCPP_HIDE_FROM_ABI void notify_all() const noexcept { std::__atomic_notify_all(*this); }
|
|
# if _LIBCPP_STD_VER >= 26
|
|
[[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr _Tp* address() const noexcept { return __ptr_; }
|
|
# endif
|
|
|
|
protected:
|
|
using _Aligned_Tp [[__gnu__::__aligned__(required_alignment), __gnu__::__nodebug__]] = _Tp;
|
|
_Aligned_Tp* __ptr_;
|
|
|
|
_LIBCPP_HIDE_FROM_ABI __atomic_ref_base(_Tp& __obj) : __ptr_(std::addressof(__obj)) {}
|
|
};
|
|
|
|
template <class _Tp>
|
|
struct __atomic_waitable_traits<__atomic_ref_base<_Tp>> {
|
|
using __value_type _LIBCPP_NODEBUG = _Tp;
|
|
|
|
static _LIBCPP_HIDE_FROM_ABI _Tp __atomic_load(const __atomic_ref_base<_Tp>& __a, memory_order __order) {
|
|
return __a.load(__order);
|
|
}
|
|
static _LIBCPP_HIDE_FROM_ABI const _Tp* __atomic_contention_address(const __atomic_ref_base<_Tp>& __a) {
|
|
return __a.__ptr_;
|
|
}
|
|
};
|
|
|
|
template <class _Tp>
|
|
struct atomic_ref : public __atomic_ref_base<_Tp> {
|
|
static_assert(is_trivially_copyable_v<_Tp>, "std::atomic_ref<T> requires that 'T' be a trivially copyable type");
|
|
|
|
using __base _LIBCPP_NODEBUG = __atomic_ref_base<_Tp>;
|
|
|
|
_LIBCPP_HIDE_FROM_ABI explicit atomic_ref(_Tp& __obj) : __base(__obj) {
|
|
_LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN(
|
|
reinterpret_cast<uintptr_t>(std::addressof(__obj)) % __base::required_alignment == 0,
|
|
"atomic_ref ctor: referenced object must be aligned to required_alignment");
|
|
}
|
|
|
|
_LIBCPP_HIDE_FROM_ABI atomic_ref(const atomic_ref&) noexcept = default;
|
|
|
|
_LIBCPP_HIDE_FROM_ABI _Tp operator=(_Tp __desired) const noexcept { return __base::operator=(__desired); }
|
|
|
|
atomic_ref& operator=(const atomic_ref&) = delete;
|
|
};
|
|
|
|
template <class _Tp>
|
|
requires(std::integral<_Tp> && !std::same_as<bool, _Tp>)
|
|
struct atomic_ref<_Tp> : public __atomic_ref_base<_Tp> {
|
|
using __base _LIBCPP_NODEBUG = __atomic_ref_base<_Tp>;
|
|
|
|
using difference_type = __base::value_type;
|
|
|
|
_LIBCPP_HIDE_FROM_ABI explicit atomic_ref(_Tp& __obj) : __base(__obj) {
|
|
_LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN(
|
|
reinterpret_cast<uintptr_t>(std::addressof(__obj)) % __base::required_alignment == 0,
|
|
"atomic_ref ctor: referenced object must be aligned to required_alignment");
|
|
}
|
|
|
|
_LIBCPP_HIDE_FROM_ABI atomic_ref(const atomic_ref&) noexcept = default;
|
|
|
|
_LIBCPP_HIDE_FROM_ABI _Tp operator=(_Tp __desired) const noexcept { return __base::operator=(__desired); }
|
|
|
|
atomic_ref& operator=(const atomic_ref&) = delete;
|
|
|
|
_LIBCPP_HIDE_FROM_ABI _Tp fetch_add(_Tp __arg, memory_order __order = memory_order_seq_cst) const noexcept {
|
|
return __atomic_fetch_add(this->__ptr_, __arg, std::__to_gcc_order(__order));
|
|
}
|
|
_LIBCPP_HIDE_FROM_ABI _Tp fetch_sub(_Tp __arg, memory_order __order = memory_order_seq_cst) const noexcept {
|
|
return __atomic_fetch_sub(this->__ptr_, __arg, std::__to_gcc_order(__order));
|
|
}
|
|
_LIBCPP_HIDE_FROM_ABI _Tp fetch_and(_Tp __arg, memory_order __order = memory_order_seq_cst) const noexcept {
|
|
return __atomic_fetch_and(this->__ptr_, __arg, std::__to_gcc_order(__order));
|
|
}
|
|
_LIBCPP_HIDE_FROM_ABI _Tp fetch_or(_Tp __arg, memory_order __order = memory_order_seq_cst) const noexcept {
|
|
return __atomic_fetch_or(this->__ptr_, __arg, std::__to_gcc_order(__order));
|
|
}
|
|
_LIBCPP_HIDE_FROM_ABI _Tp fetch_xor(_Tp __arg, memory_order __order = memory_order_seq_cst) const noexcept {
|
|
return __atomic_fetch_xor(this->__ptr_, __arg, std::__to_gcc_order(__order));
|
|
}
|
|
|
|
_LIBCPP_HIDE_FROM_ABI _Tp operator++(int) const noexcept { return fetch_add(_Tp(1)); }
|
|
_LIBCPP_HIDE_FROM_ABI _Tp operator--(int) const noexcept { return fetch_sub(_Tp(1)); }
|
|
_LIBCPP_HIDE_FROM_ABI _Tp operator++() const noexcept { return fetch_add(_Tp(1)) + _Tp(1); }
|
|
_LIBCPP_HIDE_FROM_ABI _Tp operator--() const noexcept { return fetch_sub(_Tp(1)) - _Tp(1); }
|
|
_LIBCPP_HIDE_FROM_ABI _Tp operator+=(_Tp __arg) const noexcept { return fetch_add(__arg) + __arg; }
|
|
_LIBCPP_HIDE_FROM_ABI _Tp operator-=(_Tp __arg) const noexcept { return fetch_sub(__arg) - __arg; }
|
|
_LIBCPP_HIDE_FROM_ABI _Tp operator&=(_Tp __arg) const noexcept { return fetch_and(__arg) & __arg; }
|
|
_LIBCPP_HIDE_FROM_ABI _Tp operator|=(_Tp __arg) const noexcept { return fetch_or(__arg) | __arg; }
|
|
_LIBCPP_HIDE_FROM_ABI _Tp operator^=(_Tp __arg) const noexcept { return fetch_xor(__arg) ^ __arg; }
|
|
};
|
|
|
|
template <class _Tp>
|
|
requires std::floating_point<_Tp>
|
|
struct atomic_ref<_Tp> : public __atomic_ref_base<_Tp> {
|
|
using __base _LIBCPP_NODEBUG = __atomic_ref_base<_Tp>;
|
|
|
|
using difference_type = __base::value_type;
|
|
|
|
_LIBCPP_HIDE_FROM_ABI explicit atomic_ref(_Tp& __obj) : __base(__obj) {
|
|
_LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN(
|
|
reinterpret_cast<uintptr_t>(std::addressof(__obj)) % __base::required_alignment == 0,
|
|
"atomic_ref ctor: referenced object must be aligned to required_alignment");
|
|
}
|
|
|
|
_LIBCPP_HIDE_FROM_ABI atomic_ref(const atomic_ref&) noexcept = default;
|
|
|
|
_LIBCPP_HIDE_FROM_ABI _Tp operator=(_Tp __desired) const noexcept { return __base::operator=(__desired); }
|
|
|
|
atomic_ref& operator=(const atomic_ref&) = delete;
|
|
|
|
_LIBCPP_HIDE_FROM_ABI _Tp fetch_add(_Tp __arg, memory_order __order = memory_order_seq_cst) const noexcept {
|
|
if constexpr (std::__has_rmw_builtin<_Tp>()) {
|
|
return __atomic_fetch_add(this->__ptr_, __arg, std::__to_gcc_order(__order));
|
|
} else {
|
|
_Tp __old = this->load(memory_order_relaxed);
|
|
_Tp __new = __old + __arg;
|
|
while (!this->compare_exchange_weak(__old, __new, __order, memory_order_relaxed)) {
|
|
__new = __old + __arg;
|
|
}
|
|
return __old;
|
|
}
|
|
}
|
|
_LIBCPP_HIDE_FROM_ABI _Tp fetch_sub(_Tp __arg, memory_order __order = memory_order_seq_cst) const noexcept {
|
|
if constexpr (std::__has_rmw_builtin<_Tp>()) {
|
|
return __atomic_fetch_sub(this->__ptr_, __arg, std::__to_gcc_order(__order));
|
|
} else {
|
|
_Tp __old = this->load(memory_order_relaxed);
|
|
_Tp __new = __old - __arg;
|
|
while (!this->compare_exchange_weak(__old, __new, __order, memory_order_relaxed)) {
|
|
__new = __old - __arg;
|
|
}
|
|
return __old;
|
|
}
|
|
}
|
|
|
|
_LIBCPP_HIDE_FROM_ABI _Tp operator+=(_Tp __arg) const noexcept { return fetch_add(__arg) + __arg; }
|
|
_LIBCPP_HIDE_FROM_ABI _Tp operator-=(_Tp __arg) const noexcept { return fetch_sub(__arg) - __arg; }
|
|
};
|
|
|
|
template <class _Tp>
|
|
struct atomic_ref<_Tp*> : public __atomic_ref_base<_Tp*> {
|
|
using __base _LIBCPP_NODEBUG = __atomic_ref_base<_Tp*>;
|
|
|
|
using difference_type = ptrdiff_t;
|
|
|
|
_LIBCPP_HIDE_FROM_ABI explicit atomic_ref(_Tp*& __ptr) : __base(__ptr) {}
|
|
|
|
_LIBCPP_HIDE_FROM_ABI _Tp* operator=(_Tp* __desired) const noexcept { return __base::operator=(__desired); }
|
|
|
|
atomic_ref& operator=(const atomic_ref&) = delete;
|
|
|
|
_LIBCPP_HIDE_FROM_ABI _Tp* fetch_add(ptrdiff_t __arg, memory_order __order = memory_order_seq_cst) const noexcept {
|
|
return __atomic_fetch_add(this->__ptr_, __arg * sizeof(_Tp), std::__to_gcc_order(__order));
|
|
}
|
|
_LIBCPP_HIDE_FROM_ABI _Tp* fetch_sub(ptrdiff_t __arg, memory_order __order = memory_order_seq_cst) const noexcept {
|
|
return __atomic_fetch_sub(this->__ptr_, __arg * sizeof(_Tp), std::__to_gcc_order(__order));
|
|
}
|
|
|
|
_LIBCPP_HIDE_FROM_ABI _Tp* operator++(int) const noexcept { return fetch_add(1); }
|
|
_LIBCPP_HIDE_FROM_ABI _Tp* operator--(int) const noexcept { return fetch_sub(1); }
|
|
_LIBCPP_HIDE_FROM_ABI _Tp* operator++() const noexcept { return fetch_add(1) + 1; }
|
|
_LIBCPP_HIDE_FROM_ABI _Tp* operator--() const noexcept { return fetch_sub(1) - 1; }
|
|
_LIBCPP_HIDE_FROM_ABI _Tp* operator+=(ptrdiff_t __arg) const noexcept { return fetch_add(__arg) + __arg; }
|
|
_LIBCPP_HIDE_FROM_ABI _Tp* operator-=(ptrdiff_t __arg) const noexcept { return fetch_sub(__arg) - __arg; }
|
|
};
|
|
|
|
_LIBCPP_CTAD_SUPPORTED_FOR_TYPE(atomic_ref);
|
|
|
|
#endif // _LIBCPP_STD_VER >= 20
|
|
|
|
_LIBCPP_END_NAMESPACE_STD
|
|
|
|
_LIBCPP_POP_MACROS
|
|
|
|
#endif // _LIBCPP__ATOMIC_ATOMIC_REF_H
|