diff --git a/libcxx/docs/ReleaseNotes/18.rst b/libcxx/docs/ReleaseNotes/18.rst index 5f5ff83ca5512..c28a5b73f7ec6 100644 --- a/libcxx/docs/ReleaseNotes/18.rst +++ b/libcxx/docs/ReleaseNotes/18.rst @@ -49,6 +49,7 @@ Implemented Papers - P2614R2 - Deprecate ``numeric_limits::has_denorm`` - P0053R7 - C++ Synchronized Buffered Ostream (in the experimental library) - P2467R1 - Support exclusive mode for fstreams +- P0020R6 - Floating Point Atomic Improvements and New Features diff --git a/libcxx/docs/Status/Cxx20Papers.csv b/libcxx/docs/Status/Cxx20Papers.csv index 07f53ee995ea1..7aff860c68cf9 100644 --- a/libcxx/docs/Status/Cxx20Papers.csv +++ b/libcxx/docs/Status/Cxx20Papers.csv @@ -2,7 +2,7 @@ "`P0463R1 `__","LWG","Endian just Endian","Toronto","|Complete|","7.0" "`P0674R1 `__","LWG","Extending make_shared to Support Arrays","Toronto","|Complete|","15.0" "","","","","","","" -"`P0020R6 `__","LWG","Floating Point Atomic","Albuquerque","","" +"`P0020R6 `__","LWG","Floating Point Atomic","Albuquerque","|Complete|","18.0" "`P0053R7 `__","LWG","C++ Synchronized Buffered Ostream","Albuquerque","|Complete|","18.0" "`P0202R3 `__","LWG","Add constexpr modifiers to functions in and Headers","Albuquerque","|Complete|","12.0" "`P0415R1 `__","LWG","Constexpr for ``std::complex``\ ","Albuquerque","|Complete|","16.0" diff --git a/libcxx/include/__atomic/atomic.h b/libcxx/include/__atomic/atomic.h index 47de6b958a96c..449802a2e3040 100644 --- a/libcxx/include/__atomic/atomic.h +++ b/libcxx/include/__atomic/atomic.h @@ -14,11 +14,17 @@ #include <__atomic/cxx_atomic_impl.h> #include <__atomic/memory_order.h> #include <__config> +#include <__functional/operations.h> #include <__memory/addressof.h> +#include <__type_traits/is_floating_point.h> #include <__type_traits/is_function.h> #include <__type_traits/is_same.h> +#include <__type_traits/remove_const.h> #include <__type_traits/remove_pointer.h> +#include <__type_traits/remove_volatile.h> +#include <__utility/forward.h> #include +#include #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) # pragma GCC system_header @@ -136,6 +142,136 @@ struct atomic<_Tp*> atomic& operator=(const atomic&) volatile = delete; }; +#if _LIBCPP_STD_VER >= 20 +template + requires is_floating_point_v<_Tp> +struct atomic<_Tp> : __atomic_base<_Tp> { + private: + _LIBCPP_HIDE_FROM_ABI static constexpr bool __is_fp80_long_double() { + // Only x87-fp80 long double has 64-bit mantissa + return __LDBL_MANT_DIG__ == 64 && std::is_same_v<_Tp, long double>; + } + + _LIBCPP_HIDE_FROM_ABI static constexpr bool __has_rmw_builtin() { +# ifndef _LIBCPP_COMPILER_CLANG_BASED + return false; +# else + // The builtin __cxx_atomic_fetch_add errors during compilation for + // long double on platforms with fp80 format. + // For more details, see + // lib/Sema/SemaChecking.cpp function IsAllowedValueType + // LLVM Parser does not allow atomicrmw with x86_fp80 type. + // if (ValType->isSpecificBuiltinType(BuiltinType::LongDouble) && + // &Context.getTargetInfo().getLongDoubleFormat() == + // &llvm::APFloat::x87DoubleExtended()) + // For more info + // https://github.com/llvm/llvm-project/issues/68602 + // https://reviews.llvm.org/D53965 + return !__is_fp80_long_double(); +# endif + } + + template + _LIBCPP_HIDE_FROM_ABI static _Tp + __rmw_op(_This&& __self, _Tp __operand, memory_order __m, _Operation __operation, _BuiltinOp __builtin_op) { + if constexpr (__has_rmw_builtin()) { + return __builtin_op(std::addressof(std::forward<_This>(__self).__a_), __operand, __m); + } else { + _Tp __old = __self.load(memory_order_relaxed); + _Tp __new = __operation(__old, __operand); + while (!__self.compare_exchange_weak(__old, __new, __m, memory_order_relaxed)) { +# ifdef _LIBCPP_COMPILER_CLANG_BASED + if constexpr (__is_fp80_long_double()) { + // https://github.com/llvm/llvm-project/issues/47978 + // clang bug: __old is not updated on failure for atomic::compare_exchange_weak + // Note __old = __self.load(memory_order_relaxed) will not work + std::__cxx_atomic_load_inplace(std::addressof(__self.__a_), &__old, memory_order_relaxed); + } +# endif + __new = __operation(__old, __operand); + } + return __old; + } + } + + template + _LIBCPP_HIDE_FROM_ABI static _Tp __fetch_add(_This&& __self, _Tp __operand, memory_order __m) { + auto __builtin_op = [](auto __a, auto __builtin_operand, auto __order) { + return std::__cxx_atomic_fetch_add(__a, __builtin_operand, __order); + }; + return __rmw_op(std::forward<_This>(__self), __operand, __m, std::plus<>{}, __builtin_op); + } + + template + _LIBCPP_HIDE_FROM_ABI static _Tp __fetch_sub(_This&& __self, _Tp __operand, memory_order __m) { + auto __builtin_op = [](auto __a, auto __builtin_operand, auto __order) { + return std::__cxx_atomic_fetch_sub(__a, __builtin_operand, __order); + }; + return __rmw_op(std::forward<_This>(__self), __operand, __m, std::minus<>{}, __builtin_op); + } + + public: + using __base = __atomic_base<_Tp>; + using value_type = _Tp; + using difference_type = value_type; + + _LIBCPP_HIDE_FROM_ABI constexpr atomic() noexcept = default; + _LIBCPP_HIDE_FROM_ABI constexpr atomic(_Tp __d) noexcept : __base(__d) {} + + atomic(const atomic&) = delete; + atomic& operator=(const atomic&) = delete; + atomic& operator=(const atomic&) volatile = delete; + + _LIBCPP_HIDE_FROM_ABI _Tp operator=(_Tp __d) volatile noexcept + requires __base::is_always_lock_free + { + __base::store(__d); + return __d; + } + _LIBCPP_HIDE_FROM_ABI _Tp operator=(_Tp __d) noexcept { + __base::store(__d); + return __d; + } + + _LIBCPP_HIDE_FROM_ABI _Tp fetch_add(_Tp __op, memory_order __m = memory_order_seq_cst) volatile noexcept + requires __base::is_always_lock_free + { + return __fetch_add(*this, __op, __m); + } + + _LIBCPP_HIDE_FROM_ABI _Tp fetch_add(_Tp __op, memory_order __m = memory_order_seq_cst) noexcept { + return __fetch_add(*this, __op, __m); + } + + _LIBCPP_HIDE_FROM_ABI _Tp fetch_sub(_Tp __op, memory_order __m = memory_order_seq_cst) volatile noexcept + requires __base::is_always_lock_free + { + return __fetch_sub(*this, __op, __m); + } + + _LIBCPP_HIDE_FROM_ABI _Tp fetch_sub(_Tp __op, memory_order __m = memory_order_seq_cst) noexcept { + return __fetch_sub(*this, __op, __m); + } + + _LIBCPP_HIDE_FROM_ABI _Tp operator+=(_Tp __op) volatile noexcept + requires __base::is_always_lock_free + { + return fetch_add(__op) + __op; + } + + _LIBCPP_HIDE_FROM_ABI _Tp operator+=(_Tp __op) noexcept { return fetch_add(__op) + __op; } + + _LIBCPP_HIDE_FROM_ABI _Tp operator-=(_Tp __op) volatile noexcept + requires __base::is_always_lock_free + { + return fetch_sub(__op) - __op; + } + + _LIBCPP_HIDE_FROM_ABI _Tp operator-=(_Tp __op) noexcept { return fetch_sub(__op) - __op; } +}; + +#endif // _LIBCPP_STD_VER >= 20 + // atomic_is_lock_free template diff --git a/libcxx/include/__atomic/cxx_atomic_impl.h b/libcxx/include/__atomic/cxx_atomic_impl.h index d670fddc3934c..5d724669fee87 100644 --- a/libcxx/include/__atomic/cxx_atomic_impl.h +++ b/libcxx/include/__atomic/cxx_atomic_impl.h @@ -128,6 +128,18 @@ _Tp __cxx_atomic_load(const volatile __cxx_atomic_base_impl<_Tp>* __a, return __ret; } +template +_LIBCPP_HIDE_FROM_ABI void +__cxx_atomic_load_inplace(const volatile __cxx_atomic_base_impl<_Tp>* __a, _Tp* __dst, memory_order __order) { + __atomic_load(std::addressof(__a->__a_value), __dst, __to_gcc_order(__order)); +} + +template +_LIBCPP_HIDE_FROM_ABI void +__cxx_atomic_load_inplace(const __cxx_atomic_base_impl<_Tp>* __a, _Tp* __dst, memory_order __order) { + __atomic_load(std::addressof(__a->__a_value), __dst, __to_gcc_order(__order)); +} + template _LIBCPP_HIDE_FROM_ABI _Tp __cxx_atomic_load(const __cxx_atomic_base_impl<_Tp>* __a, memory_order __order) { @@ -362,6 +374,21 @@ _Tp __cxx_atomic_load(__cxx_atomic_base_impl<_Tp> const* __a, memory_order __ord const_cast<__ptr_type>(std::addressof(__a->__a_value)), static_cast<__memory_order_underlying_t>(__order)); } +template +_LIBCPP_HIDE_FROM_ABI void +__cxx_atomic_load_inplace(__cxx_atomic_base_impl<_Tp> const volatile* __a, _Tp* __dst, memory_order __order) _NOEXCEPT { + using __ptr_type = __remove_const_t__a_value)>*; + *__dst = __c11_atomic_load( + const_cast<__ptr_type>(std::addressof(__a->__a_value)), static_cast<__memory_order_underlying_t>(__order)); +} +template +_LIBCPP_HIDE_FROM_ABI void +__cxx_atomic_load_inplace(__cxx_atomic_base_impl<_Tp> const* __a, _Tp* __dst, memory_order __order) _NOEXCEPT { + using __ptr_type = __remove_const_t__a_value)>*; + *__dst = __c11_atomic_load( + const_cast<__ptr_type>(std::addressof(__a->__a_value)), static_cast<__memory_order_underlying_t>(__order)); +} + template _LIBCPP_HIDE_FROM_ABI _Tp __cxx_atomic_exchange(__cxx_atomic_base_impl<_Tp> volatile* __a, _Tp __value, memory_order __order) _NOEXCEPT { @@ -558,6 +585,16 @@ struct __cxx_atomic_lock_impl { __unlock(); return __old; } + _LIBCPP_HIDE_FROM_ABI void __read_inplace(_Tp* __dst) const volatile { + __lock(); + __cxx_atomic_assign_volatile(*__dst, __a_value); + __unlock(); + } + _LIBCPP_HIDE_FROM_ABI void __read_inplace(_Tp* __dst) const { + __lock(); + *__dst = __a_value; + __unlock(); + } }; template @@ -597,6 +634,16 @@ _Tp __cxx_atomic_load(const __cxx_atomic_lock_impl<_Tp>* __a, memory_order) { return __a->__read(); } +template +_LIBCPP_HIDE_FROM_ABI void +__cxx_atomic_load(const volatile __cxx_atomic_lock_impl<_Tp>* __a, _Tp* __dst, memory_order) { + __a->__read_inplace(__dst); +} +template +_LIBCPP_HIDE_FROM_ABI void __cxx_atomic_load(const __cxx_atomic_lock_impl<_Tp>* __a, _Tp* __dst, memory_order) { + __a->__read_inplace(__dst); +} + template _LIBCPP_HIDE_FROM_ABI _Tp __cxx_atomic_exchange(volatile __cxx_atomic_lock_impl<_Tp>* __a, _Tp __value, memory_order) { diff --git a/libcxx/include/atomic b/libcxx/include/atomic index 2f122a707bdc3..7bed8fd8bacfc 100644 --- a/libcxx/include/atomic +++ b/libcxx/include/atomic @@ -262,6 +262,72 @@ struct atomic void notify_all() noexcept; }; +template<> +struct atomic { // since C++20 + using value_type = floating-point-type; + using difference_type = value_type; + + static constexpr bool is_always_lock_free = implementation-defined; + bool is_lock_free() const volatile noexcept; + bool is_lock_free() const noexcept; + + constexpr atomic() noexcept; + constexpr atomic(floating-point-type) noexcept; + atomic(const atomic&) = delete; + atomic& operator=(const atomic&) = delete; + atomic& operator=(const atomic&) volatile = delete; + + void store(floating-point-type, memory_order = memory_order::seq_cst) volatile noexcept; + void store(floating-point-type, memory_order = memory_order::seq_cst) noexcept; + floating-point-type operator=(floating-point-type) volatile noexcept; + floating-point-type operator=(floating-point-type) noexcept; + floating-point-type load(memory_order = memory_order::seq_cst) volatile noexcept; + floating-point-type load(memory_order = memory_order::seq_cst) noexcept; + operator floating-point-type() volatile noexcept; + operator floating-point-type() noexcept; + + floating-point-type exchange(floating-point-type, + memory_order = memory_order::seq_cst) volatile noexcept; + floating-point-type exchange(floating-point-type, + memory_order = memory_order::seq_cst) noexcept; + bool compare_exchange_weak(floating-point-type&, floating-point-type, + memory_order, memory_order) volatile noexcept; + bool compare_exchange_weak(floating-point-type&, floating-point-type, + memory_order, memory_order) noexcept; + bool compare_exchange_strong(floating-point-type&, floating-point-type, + memory_order, memory_order) volatile noexcept; + bool compare_exchange_strong(floating-point-type&, floating-point-type, + memory_order, memory_order) noexcept; + bool compare_exchange_weak(floating-point-type&, floating-point-type, + memory_order = memory_order::seq_cst) volatile noexcept; + bool compare_exchange_weak(floating-point-type&, floating-point-type, + memory_order = memory_order::seq_cst) noexcept; + bool compare_exchange_strong(floating-point-type&, floating-point-type, + memory_order = memory_order::seq_cst) volatile noexcept; + bool compare_exchange_strong(floating-point-type&, floating-point-type, + memory_order = memory_order::seq_cst) noexcept; + + floating-point-type fetch_add(floating-point-type, + memory_order = memory_order::seq_cst) volatile noexcept; + floating-point-type fetch_add(floating-point-type, + memory_order = memory_order::seq_cst) noexcept; + floating-point-type fetch_sub(floating-point-type, + memory_order = memory_order::seq_cst) volatile noexcept; + floating-point-type fetch_sub(floating-point-type, + memory_order = memory_order::seq_cst) noexcept; + + floating-point-type operator+=(floating-point-type) volatile noexcept; + floating-point-type operator+=(floating-point-type) noexcept; + floating-point-type operator-=(floating-point-type) volatile noexcept; + floating-point-type operator-=(floating-point-type) noexcept; + + void wait(floating-point-type, memory_order = memory_order::seq_cst) const volatile noexcept; + void wait(floating-point-type, memory_order = memory_order::seq_cst) const noexcept; + void notify_one() volatile noexcept; + void notify_one() noexcept; + void notify_all() volatile noexcept; + void notify_all() noexcept; +}; // [atomics.nonmembers], non-member functions template diff --git a/libcxx/test/libcxx/atomics/atomics.types.generic/atomics.types.float/lockfree.pass.cpp b/libcxx/test/libcxx/atomics/atomics.types.generic/atomics.types.float/lockfree.pass.cpp new file mode 100644 index 0000000000000..46511530c7c75 --- /dev/null +++ b/libcxx/test/libcxx/atomics/atomics.types.generic/atomics.types.float/lockfree.pass.cpp @@ -0,0 +1,51 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// UNSUPPORTED: c++03, c++11, c++14, c++17 +// UNSUPPORTED: target={{.+}}-windows-gnu +// ADDITIONAL_COMPILE_FLAGS(has-latomic): -latomic + +// static constexpr bool is_always_lock_free = implementation-defined; +// bool is_lock_free() const volatile noexcept; +// bool is_lock_free() const noexcept; + +#include +#include +#include + +#include "test_macros.h" + +template +void test() { + // static constexpr bool is_always_lock_free = implementation-defined; + { + bool r = std::atomic::is_always_lock_free; + assert(r == __atomic_always_lock_free(sizeof(std::__cxx_atomic_impl), 0)); + } + + // bool is_lock_free() const volatile noexcept; + { + const volatile std::atomic a; + bool r = a.is_lock_free(); + assert(r == __cxx_atomic_is_lock_free(sizeof(std::__cxx_atomic_impl))); + } + + // bool is_lock_free() const noexcept; + { + const std::atomic a; + bool r = a.is_lock_free(); + assert(r == __cxx_atomic_is_lock_free(sizeof(std::__cxx_atomic_impl))); + } +} + +int main(int, char**) { + test(); + test(); + test(); + + return 0; +} diff --git a/libcxx/test/std/atomics/atomics.types.generic/atomics.types.float/assign.pass.cpp b/libcxx/test/std/atomics/atomics.types.generic/atomics.types.float/assign.pass.cpp new file mode 100644 index 0000000000000..8efb556cb5d99 --- /dev/null +++ b/libcxx/test/std/atomics/atomics.types.generic/atomics.types.float/assign.pass.cpp @@ -0,0 +1,62 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// UNSUPPORTED: c++03, c++11, c++14, c++17 +// UNSUPPORTED: target={{.+}}-windows-gnu +// ADDITIONAL_COMPILE_FLAGS(has-latomic): -latomic + +// floating-point-type operator=(floating-point-type) volatile noexcept; +// floating-point-type operator=(floating-point-type) noexcept; + +#include +#include +#include +#include + +#include "test_helper.h" +#include "test_macros.h" + +template +concept HasVolatileAssign = requires(volatile std::atomic& a, T t) { a = t; }; + +template class MaybeVolatile = std::type_identity_t> +void test_impl() { + static_assert(HasVolatileAssign == std::atomic::is_always_lock_free); + + static_assert(noexcept(std::declval>&>() = (T(0)))); + + // assignment + { + MaybeVolatile> a(3.1); + std::same_as decltype(auto) r = (a = T(1.2)); + assert(a.load() == T(1.2)); + assert(r == T(1.2)); + } + + // memory_order::seq_cst + { + auto assign = [](MaybeVolatile>& x, T, T new_val) { x = new_val; }; + auto load = [](MaybeVolatile>& x) { return x.load(); }; + test_seq_cst(assign, load); + } +} + +template +void test() { + test_impl(); + if constexpr (std::atomic::is_always_lock_free) { + test_impl(); + } +} + +int main(int, char**) { + test(); + test(); + test(); + + return 0; +} diff --git a/libcxx/test/std/atomics/atomics.types.generic/atomics.types.float/compare_exchange_strong.pass.cpp b/libcxx/test/std/atomics/atomics.types.generic/atomics.types.float/compare_exchange_strong.pass.cpp new file mode 100644 index 0000000000000..839d79d3a4110 --- /dev/null +++ b/libcxx/test/std/atomics/atomics.types.generic/atomics.types.float/compare_exchange_strong.pass.cpp @@ -0,0 +1,226 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// UNSUPPORTED: c++03, c++11, c++14, c++17 +// ADDITIONAL_COMPILE_FLAGS(has-latomic): -latomic + +// bool compare_exchange_strong(T& expected, T desired, +// memory_order success, memory_order failure) volatile noexcept; +// bool compare_exchange_strong(T& expected, T desired, +// memory_order success, memory_order failure) noexcept; +// bool compare_exchange_strong(T& expected, T desired, +// memory_order order = memory_order::seq_cst) volatile noexcept; +// bool compare_exchange_strong(T& expected, T desired, +// memory_order order = memory_order::seq_cst) noexcept; + +#include +#include +#include +#include + +#include "test_helper.h" +#include "test_macros.h" + +template +concept HasVolatileCompareExchangeStrong = + requires(volatile std::atomic& a, T t, Args... args) { a.compare_exchange_strong(t, t, args...); }; + +template class MaybeVolatile, class... Args> +concept HasNoexceptCompareExchangeStrong = requires(MaybeVolatile>& a, T t, Args... args) { + { a.compare_exchange_strong(t, t, args...) } noexcept; +}; + +template class MaybeVolatile = std::type_identity_t, class... MemoryOrder> +void testBasic(MemoryOrder... memory_order) { + // Uncomment the test after P1831R1 is implemented + // static_assert(HasVolatileCompareExchangeStrong == std::atomic::is_always_lock_free); + static_assert(HasNoexceptCompareExchangeStrong); + + // compare pass + { + MaybeVolatile> a(T(1.2)); + T expected(1.2); + const T desired(2.3); + std::same_as decltype(auto) r = a.compare_exchange_strong(expected, desired, memory_order...); + + assert(r); + assert(a.load() == desired); + assert(expected == T(1.2)); + } + + // compare fail + { + MaybeVolatile> a(T(1.2)); + T expected(1.5); + const T desired(2.3); + std::same_as decltype(auto) r = a.compare_exchange_strong(expected, desired, memory_order...); + + assert(!r); + assert(a.load() == T(1.2)); + assert(expected == T(1.2)); + } +} + +template class MaybeVolatile = std::type_identity_t> +void test_impl() { + testBasic(); + testBasic(std::memory_order::relaxed); + testBasic(std::memory_order::relaxed, std::memory_order_relaxed); + + // test success memory order release + { + auto store = [](MaybeVolatile>& x, T old_val, T new_val) { + auto r = x.compare_exchange_strong(old_val, new_val, std::memory_order::release, std::memory_order_relaxed); + assert(r); + }; + + auto load = [](MaybeVolatile>& x) { return x.load(std::memory_order::acquire); }; + test_acquire_release(store, load); + + auto store_one_arg = [](MaybeVolatile>& x, T old_val, T new_val) { + auto r = x.compare_exchange_strong(old_val, new_val, std::memory_order::release); + assert(r); + }; + test_acquire_release(store_one_arg, load); + } + + // test success memory order acquire + { + auto store = [](MaybeVolatile>& x, T, T new_val) { x.store(new_val, std::memory_order::release); }; + auto load = [](MaybeVolatile>& x) { + auto val = x.load(std::memory_order::relaxed); + while (!x.compare_exchange_strong(val, val, std::memory_order::acquire, std::memory_order_relaxed)) { + } + return val; + }; + test_acquire_release(store, load); + + auto load_one_arg = [](MaybeVolatile>& x) { + auto val = x.load(std::memory_order::relaxed); + while (!x.compare_exchange_strong(val, val, std::memory_order::acquire)) { + } + return val; + }; + test_acquire_release(store, load_one_arg); + } + + // test success memory order acq_rel + { + auto store = [](MaybeVolatile>& x, T old_val, T new_val) { + auto r = x.compare_exchange_strong(old_val, new_val, std::memory_order::acq_rel, std::memory_order_relaxed); + assert(r); + }; + auto load = [](MaybeVolatile>& x) { + auto val = x.load(std::memory_order::relaxed); + while (!x.compare_exchange_strong(val, val, std::memory_order::acq_rel, std::memory_order_relaxed)) { + } + return val; + }; + test_acquire_release(store, load); + + auto store_one_arg = [](MaybeVolatile>& x, T old_val, T new_val) { + auto r = x.compare_exchange_strong(old_val, new_val, std::memory_order::acq_rel); + assert(r); + }; + auto load_one_arg = [](MaybeVolatile>& x) { + auto val = x.load(std::memory_order::relaxed); + while (!x.compare_exchange_strong(val, val, std::memory_order::acq_rel)) { + } + return val; + }; + test_acquire_release(store_one_arg, load_one_arg); + } + + // test success memory seq_cst + { + auto store = [](MaybeVolatile>& x, T old_val, T new_val) { + auto r = x.compare_exchange_strong(old_val, new_val, std::memory_order::seq_cst, std::memory_order_relaxed); + assert(r); + }; + auto load = [](MaybeVolatile>& x) { + auto val = x.load(std::memory_order::relaxed); + while (!x.compare_exchange_strong(val, val, std::memory_order::seq_cst, std::memory_order_relaxed)) { + } + return val; + }; + test_seq_cst(store, load); + + auto store_one_arg = [](MaybeVolatile>& x, T old_val, T new_val) { + auto r = x.compare_exchange_strong(old_val, new_val, std::memory_order::seq_cst, std::memory_order_relaxed); + assert(r); + }; + auto load_one_arg = [](MaybeVolatile>& x) { + auto val = x.load(std::memory_order::relaxed); + while (!x.compare_exchange_strong(val, val, std::memory_order::seq_cst, std::memory_order_relaxed)) { + } + return val; + }; + test_seq_cst(store_one_arg, load_one_arg); + } + + // test fail memory order acquire + { + auto store = [](MaybeVolatile>& x, T, T new_val) { x.store(new_val, std::memory_order::release); }; + auto load = [](MaybeVolatile>& x) { + auto result = x.load(std::memory_order::relaxed); + T unexpected(-9999.99); + bool r = x.compare_exchange_strong(unexpected, unexpected, std::memory_order_relaxed, std::memory_order_acquire); + assert(!r); + return result; + }; + test_acquire_release(store, load); + + auto load_one_arg = [](MaybeVolatile>& x) { + auto result = x.load(std::memory_order::relaxed); + T unexpected(-9999.99); + bool r = x.compare_exchange_strong(unexpected, unexpected, std::memory_order_acquire); + assert(!r); + return result; + }; + test_acquire_release(store, load_one_arg); + + // acq_rel replaced by acquire + auto load_one_arg_acq_rel = [](MaybeVolatile>& x) { + auto result = x.load(std::memory_order::relaxed); + T unexpected(-9999.99); + bool r = x.compare_exchange_strong(unexpected, unexpected, std::memory_order_acq_rel); + assert(!r); + return result; + }; + test_acquire_release(store, load_one_arg_acq_rel); + } + + // test fail memory order seq_cst + { + auto store = [](MaybeVolatile>& x, T, T new_val) { x.store(new_val, std::memory_order::seq_cst); }; + auto load = [](MaybeVolatile>& x) { + auto result = x.load(std::memory_order::relaxed); + T unexpected(-9999.99); + bool r = x.compare_exchange_strong(unexpected, unexpected, std::memory_order_relaxed, std::memory_order::seq_cst); + assert(!r); + return result; + }; + test_seq_cst(store, load); + } +} + +template +void test() { + test_impl(); + if constexpr (std::atomic::is_always_lock_free) { + test_impl(); + } +} + +int main(int, char**) { + test(); + test(); + // https://github.com/llvm/llvm-project/issues/47978 + // test(); + + return 0; +} diff --git a/libcxx/test/std/atomics/atomics.types.generic/atomics.types.float/compare_exchange_weak.pass.cpp b/libcxx/test/std/atomics/atomics.types.generic/atomics.types.float/compare_exchange_weak.pass.cpp new file mode 100644 index 0000000000000..097210cc4a9db --- /dev/null +++ b/libcxx/test/std/atomics/atomics.types.generic/atomics.types.float/compare_exchange_weak.pass.cpp @@ -0,0 +1,244 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// UNSUPPORTED: c++03, c++11, c++14, c++17 +// ADDITIONAL_COMPILE_FLAGS(has-latomic): -latomic + +// bool compare_exchange_weak(T& expected, T desired, +// memory_order success, memory_order failure) volatile noexcept; +// bool compare_exchange_weak(T& expected, T desired, +// memory_order success, memory_order failure) noexcept; +// bool compare_exchange_weak(T& expected, T desired, +// memory_order order = memory_order::seq_cst) volatile noexcept; +// bool compare_exchange_weak(T& expected, T desired, +// memory_order order = memory_order::seq_cst) noexcept; + +#include +#include +#include +#include + +#include "test_helper.h" +#include "test_macros.h" + +template +concept HasVolatileCompareExchangeWeak = + requires(volatile std::atomic& a, T t, Args... args) { a.compare_exchange_weak(t, t, args...); }; + +template class MaybeVolatile, class... Args> +concept HasNoexceptCompareExchangeWeak = requires(MaybeVolatile>& a, T t, Args... args) { + { a.compare_exchange_weak(t, t, args...) } noexcept; +}; + +template class MaybeVolatile = std::type_identity_t, class... MemoryOrder> +void testBasic(MemoryOrder... memory_order) { + // Uncomment the test after P1831R1 is implemented + // static_assert(HasVolatileCompareExchangeWeak == std::atomic::is_always_lock_free); + static_assert(HasNoexceptCompareExchangeWeak); + + // compare pass + { + MaybeVolatile> a(T(1.2)); + T expected(1.2); + const T desired(2.3); + std::same_as decltype(auto) r = a.compare_exchange_weak(expected, desired, memory_order...); + + // could be false spuriously + if (r) { + assert(a.load() == desired); + } + // if r is true, expected should be unmodified (1.2) + // if r is false, the original value of a (1.2) is written to expected + assert(expected == T(1.2)); + } + + // compare fail + { + MaybeVolatile> a(T(1.2)); + T expected(1.5); + const T desired(2.3); + std::same_as decltype(auto) r = a.compare_exchange_weak(expected, desired, memory_order...); + + assert(!r); + assert(a.load() == T(1.2)); + + // bug + // https://github.com/llvm/llvm-project/issues/47978 + if constexpr (!std::same_as) { + assert(expected == T(1.2)); + } + } +} + +template class MaybeVolatile = std::type_identity_t> +void test_impl() { + testBasic(); + testBasic(std::memory_order::relaxed); + testBasic(std::memory_order::relaxed, std::memory_order_relaxed); + + // test success memory order release + { + auto store = [](MaybeVolatile>& x, T old_val, T new_val) { + // could fail spuriously, so put it in a loop + while (!x.compare_exchange_weak(old_val, new_val, std::memory_order::release, std::memory_order_relaxed)) { + } + }; + + auto load = [](MaybeVolatile>& x) { return x.load(std::memory_order::acquire); }; + test_acquire_release(store, load); + + auto store_one_arg = [](MaybeVolatile>& x, T old_val, T new_val) { + // could fail spuriously, so put it in a loop + while (!x.compare_exchange_weak(old_val, new_val, std::memory_order::release)) { + } + }; + test_acquire_release(store_one_arg, load); + } + + // test success memory order acquire + { + auto store = [](MaybeVolatile>& x, T, T new_val) { x.store(new_val, std::memory_order::release); }; + auto load = [](MaybeVolatile>& x) { + auto val = x.load(std::memory_order::relaxed); + while (!x.compare_exchange_weak(val, val, std::memory_order::acquire, std::memory_order_relaxed)) { + } + return val; + }; + test_acquire_release(store, load); + + auto load_one_arg = [](MaybeVolatile>& x) { + auto val = x.load(std::memory_order::relaxed); + while (!x.compare_exchange_weak(val, val, std::memory_order::acquire)) { + } + return val; + }; + test_acquire_release(store, load_one_arg); + } + + // test success memory order acq_rel + { + auto store = [](MaybeVolatile>& x, T old_val, T new_val) { + // could fail spuriously, so put it in a loop + while (!x.compare_exchange_weak(old_val, new_val, std::memory_order::acq_rel, std::memory_order_relaxed)) { + } + }; + auto load = [](MaybeVolatile>& x) { + auto val = x.load(std::memory_order::relaxed); + while (!x.compare_exchange_weak(val, val, std::memory_order::acq_rel, std::memory_order_relaxed)) { + } + return val; + }; + test_acquire_release(store, load); + + auto store_one_arg = [](MaybeVolatile>& x, T old_val, T new_val) { + // could fail spuriously, so put it in a loop + while (!x.compare_exchange_weak(old_val, new_val, std::memory_order::acq_rel)) { + } + }; + auto load_one_arg = [](MaybeVolatile>& x) { + auto val = x.load(std::memory_order::relaxed); + while (!x.compare_exchange_weak(val, val, std::memory_order::acq_rel)) { + } + return val; + }; + test_acquire_release(store_one_arg, load_one_arg); + } + + // test success memory seq_cst + { + auto store = [](MaybeVolatile>& x, T old_val, T new_val) { + // could fail spuriously, so put it in a loop + while (!x.compare_exchange_weak(old_val, new_val, std::memory_order::seq_cst, std::memory_order_relaxed)) { + } + }; + auto load = [](MaybeVolatile>& x) { + auto val = x.load(std::memory_order::relaxed); + while (!x.compare_exchange_weak(val, val, std::memory_order::seq_cst, std::memory_order_relaxed)) { + } + return val; + }; + test_seq_cst(store, load); + + auto store_one_arg = [](MaybeVolatile>& x, T old_val, T new_val) { + // could fail spuriously, so put it in a loop + while (!x.compare_exchange_weak(old_val, new_val, std::memory_order::seq_cst, std::memory_order_relaxed)) { + } + }; + auto load_one_arg = [](MaybeVolatile>& x) { + auto val = x.load(std::memory_order::relaxed); + while (!x.compare_exchange_weak(val, val, std::memory_order::seq_cst, std::memory_order_relaxed)) { + } + return val; + }; + test_seq_cst(store_one_arg, load_one_arg); + } + + // test fail memory order acquire + { + auto store = [](MaybeVolatile>& x, T, T new_val) { x.store(new_val, std::memory_order::release); }; + auto load = [](MaybeVolatile>& x) { + auto result = x.load(std::memory_order::relaxed); + T unexpected(-9999.99); + bool r = x.compare_exchange_weak(unexpected, unexpected, std::memory_order_relaxed, std::memory_order_acquire); + assert(!r); + return result; + }; + test_acquire_release(store, load); + + auto load_one_arg = [](MaybeVolatile>& x) { + auto result = x.load(std::memory_order::relaxed); + T unexpected(-9999.99); + bool r = x.compare_exchange_weak(unexpected, unexpected, std::memory_order_acquire); + assert(!r); + return result; + }; + test_acquire_release(store, load_one_arg); + + // acq_rel replaced by acquire + auto load_one_arg_acq_rel = [](MaybeVolatile>& x) { + auto result = x.load(std::memory_order::relaxed); + T unexpected(-9999.99); + bool r = x.compare_exchange_weak(unexpected, unexpected, std::memory_order_acq_rel); + assert(!r); + return result; + }; + test_acquire_release(store, load_one_arg_acq_rel); + } + + // test fail memory order seq_cst + { + auto store = [](MaybeVolatile>& x, T, T new_val) { x.store(new_val, std::memory_order::seq_cst); }; + auto load = [](MaybeVolatile>& x) { + auto result = x.load(std::memory_order::relaxed); + T unexpected(-9999.99); + bool r = x.compare_exchange_weak(unexpected, unexpected, std::memory_order_relaxed, std::memory_order::seq_cst); + assert(!r); + return result; + }; + test_seq_cst(store, load); + } +} + +template +void test() { + test_impl(); + if constexpr (std::atomic::is_always_lock_free) { + test_impl(); + } +} + +int main(int, char**) { + test(); + test(); + +// https://github.com/llvm/llvm-project/issues/47978 +#ifndef TEST_COMPILER_CLANG + test(); +#endif + + return 0; +} diff --git a/libcxx/test/std/atomics/atomics.types.generic/atomics.types.float/copy.compile.pass.cpp b/libcxx/test/std/atomics/atomics.types.generic/atomics.types.float/copy.compile.pass.cpp new file mode 100644 index 0000000000000..3984d621ad3ab --- /dev/null +++ b/libcxx/test/std/atomics/atomics.types.generic/atomics.types.float/copy.compile.pass.cpp @@ -0,0 +1,29 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// UNSUPPORTED: c++03, c++11, c++14, c++17 + +// atomic(const atomic&) = delete; +// atomic& operator=(const atomic&) = delete; +// atomic& operator=(const atomic&) volatile = delete; + +#include +#include + +template +void test() { + static_assert(!std::is_copy_assignable_v>); + static_assert(!std::is_copy_constructible_v>); + static_assert(!std::is_move_constructible_v>); + static_assert(!std::is_move_assignable_v>); + static_assert(!std::is_assignable_v&, const std::atomic&>); +} + +template void test(); +template void test(); +template void test(); diff --git a/libcxx/test/std/atomics/atomics.types.generic/atomics.types.float/ctor.pass.cpp b/libcxx/test/std/atomics/atomics.types.generic/atomics.types.float/ctor.pass.cpp new file mode 100644 index 0000000000000..febabb4f26782 --- /dev/null +++ b/libcxx/test/std/atomics/atomics.types.generic/atomics.types.float/ctor.pass.cpp @@ -0,0 +1,68 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// UNSUPPORTED: c++03, c++11, c++14, c++17 +// UNSUPPORTED: target={{.+}}-windows-gnu +// ADDITIONAL_COMPILE_FLAGS(has-latomic): -latomic + +// constexpr atomic() noexcept; +// constexpr atomic(floating-point-type) noexcept; + +#include +#include +#include +#include + +#include "test_macros.h" + +template +constinit std::atomic a1 = T(); + +template +constinit std::atomic a2 = T(5.2); + +template +constexpr void testOne() { + static_assert(std::is_nothrow_constructible_v>); + static_assert(std::is_nothrow_constructible_v, T>); + + // constexpr atomic() noexcept; + { + std::atomic a = {}; + if (!TEST_IS_CONSTANT_EVALUATED) { + assert(a.load() == T(0)); + } + } + + // constexpr atomic(floating-point-type) noexcept; + { + std::atomic a = T(5.2); + if (!TEST_IS_CONSTANT_EVALUATED) { + assert(a.load() == T(5.2)); + } + } + + // test constinit + if (!TEST_IS_CONSTANT_EVALUATED) { + assert(a1 == T(0.0)); + assert(a2 == T(5.2)); + } +} + +constexpr bool test() { + testOne(); + testOne(); + testOne(); + return true; +} + +int main(int, char**) { + test(); + static_assert(test()); + + return 0; +} diff --git a/libcxx/test/std/atomics/atomics.types.generic/atomics.types.float/exchange.pass.cpp b/libcxx/test/std/atomics/atomics.types.generic/atomics.types.float/exchange.pass.cpp new file mode 100644 index 0000000000000..4bd50396213d8 --- /dev/null +++ b/libcxx/test/std/atomics/atomics.types.generic/atomics.types.float/exchange.pass.cpp @@ -0,0 +1,77 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// UNSUPPORTED: c++03, c++11, c++14, c++17 +// UNSUPPORTED: target={{.+}}-windows-gnu +// Clang's support for atomic operations on long double is broken. See https://github.com/llvm/llvm-project/issues/72893 +// XFAIL: tsan, msan +// ADDITIONAL_COMPILE_FLAGS(has-latomic): -latomic + +// T exchange(T, memory_order = memory_order::seq_cst) volatile noexcept; +// T exchange(T, memory_order = memory_order::seq_cst) noexcept; + +#include +#include +#include +#include + +#include "test_helper.h" +#include "test_macros.h" + +template +concept HasVolatileExchange = requires(volatile std::atomic& a, T t) { a.exchange(t); }; + +template class MaybeVolatile = std::type_identity_t> +void test_impl() { + // Uncomment the test after P1831R1 is implemented + // static_assert(HasVolatileExchange == std::atomic::is_always_lock_free); + static_assert(noexcept(std::declval>&>() = (T(0)))); + + // exchange + { + MaybeVolatile> a(3.1); + std::same_as decltype(auto) r = a.exchange(T(1.2), std::memory_order::relaxed); + assert(a.load() == T(1.2)); + assert(r == T(3.1)); + } + + // memory_order::release + { + auto exchange = [](MaybeVolatile>& x, T, T new_val) { + x.exchange(new_val, std::memory_order::release); + }; + auto load = [](MaybeVolatile>& x) { return x.load(std::memory_order::acquire); }; + test_acquire_release(exchange, load); + } + + // memory_order::seq_cst + { + auto exchange_no_arg = [](MaybeVolatile>& x, T, T new_val) { x.exchange(new_val); }; + auto exchange_with_order = [](MaybeVolatile>& x, T, T new_val) { + x.exchange(new_val, std::memory_order::seq_cst); + }; + auto load = [](MaybeVolatile>& x) { return x.load(); }; + test_seq_cst(exchange_no_arg, load); + test_seq_cst(exchange_with_order, load); + } +} + +template +void test() { + test_impl(); + if constexpr (std::atomic::is_always_lock_free) { + test_impl(); + } +} + +int main(int, char**) { + test(); + test(); + test(); + + return 0; +} diff --git a/libcxx/test/std/atomics/atomics.types.generic/atomics.types.float/fetch_add.pass.cpp b/libcxx/test/std/atomics/atomics.types.generic/atomics.types.float/fetch_add.pass.cpp new file mode 100644 index 0000000000000..bb0b7c5b58472 --- /dev/null +++ b/libcxx/test/std/atomics/atomics.types.generic/atomics.types.float/fetch_add.pass.cpp @@ -0,0 +1,118 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// UNSUPPORTED: c++03, c++11, c++14, c++17 +// UNSUPPORTED: target={{.+}}-windows-gnu +// XFAIL: LIBCXX-AIX-FIXME +// Clang's support for atomic operations on long double is broken. See https://github.com/llvm/llvm-project/issues/72893 +// XFAIL: tsan +// ADDITIONAL_COMPILE_FLAGS(has-latomic): -latomic + +// floating-point-type fetch_add(floating-point-type, +// memory_order = memory_order::seq_cst) volatile noexcept; +// floating-point-type fetch_add(floating-point-type, +// memory_order = memory_order::seq_cst) noexcept; + +#include +#include +#include +#include +#include + +#include "test_helper.h" +#include "test_macros.h" + +#ifndef TEST_HAS_NO_THREADS +# include "make_test_thread.h" +# include +#endif +template +concept HasVolatileFetchAdd = requires(volatile std::atomic& a, T t) { a.fetch_add(t); }; + +template class MaybeVolatile = std::type_identity_t> +void test_impl() { + static_assert(HasVolatileFetchAdd == std::atomic::is_always_lock_free); + static_assert(noexcept(std::declval>&>().fetch_add(T(0)))); + + // fetch_add + { + MaybeVolatile> a(3.1); + std::same_as decltype(auto) r = a.fetch_add(T(1.2), std::memory_order::relaxed); + assert(r == T(3.1)); + assert(a.load() == T(3.1) + T(1.2)); + } + +#ifndef TEST_HAS_NO_THREADS + // fetch_add concurrent + { + constexpr auto number_of_threads = 4; + constexpr auto loop = 1000; + + MaybeVolatile> at; + + std::vector threads; + threads.reserve(number_of_threads); + for (auto i = 0; i < number_of_threads; ++i) { + threads.push_back(support::make_test_thread([&at]() { + for (auto j = 0; j < loop; ++j) { + at.fetch_add(T(1.234), std::memory_order::relaxed); + } + })); + } + + for (auto& thread : threads) { + thread.join(); + } + + const auto times = [](T t, int n) { + T res(0); + for (auto i = 0; i < n; ++i) { + res += t; + } + return res; + }; + + assert(at.load() == times(1.234, number_of_threads * loop)); + } +#endif + + // memory_order::release + { + auto store = [](MaybeVolatile>& x, T old_val, T new_val) { + x.fetch_add(new_val - old_val, std::memory_order::release); + }; + auto load = [](MaybeVolatile>& x) { return x.load(std::memory_order::acquire); }; + test_acquire_release(store, load); + } + + // memory_order::seq_cst + { + auto fetch_add = [](MaybeVolatile>& x, T old_value, T new_val) { x.fetch_add(new_val - old_value); }; + auto fetch_add_with_order = [](MaybeVolatile>& x, T old_value, T new_val) { + x.fetch_add(new_val - old_value, std::memory_order::seq_cst); + }; + auto load = [](MaybeVolatile>& x) { return x.load(); }; + test_seq_cst(fetch_add, load); + test_seq_cst(fetch_add_with_order, load); + } +} + +template +void test() { + test_impl(); + if constexpr (std::atomic::is_always_lock_free) { + test_impl(); + } +} + +int main(int, char**) { + test(); + test(); + test(); + + return 0; +} diff --git a/libcxx/test/std/atomics/atomics.types.generic/atomics.types.float/fetch_sub.pass.cpp b/libcxx/test/std/atomics/atomics.types.generic/atomics.types.float/fetch_sub.pass.cpp new file mode 100644 index 0000000000000..b847cedf404a1 --- /dev/null +++ b/libcxx/test/std/atomics/atomics.types.generic/atomics.types.float/fetch_sub.pass.cpp @@ -0,0 +1,119 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// UNSUPPORTED: c++03, c++11, c++14, c++17 +// UNSUPPORTED: target={{.+}}-windows-gnu +// XFAIL: LIBCXX-AIX-FIXME +// Clang's support for atomic operations on long double is broken. See https://github.com/llvm/llvm-project/issues/72893 +// XFAIL: tsan +// ADDITIONAL_COMPILE_FLAGS(has-latomic): -latomic + +// floating-point-type fetch_sub(floating-point-type, +// memory_order = memory_order::seq_cst) volatile noexcept; +// floating-point-type fetch_sub(floating-point-type, +// memory_order = memory_order::seq_cst) noexcept; + +#include +#include +#include +#include +#include + +#include "test_helper.h" +#include "test_macros.h" + +#ifndef TEST_HAS_NO_THREADS +# include "make_test_thread.h" +# include +#endif + +template +concept HasVolatileFetchSub = requires(volatile std::atomic& a, T t) { a.fetch_sub(t); }; + +template class MaybeVolatile = std::type_identity_t> +void test_impl() { + static_assert(HasVolatileFetchSub == std::atomic::is_always_lock_free); + static_assert(noexcept(std::declval>&>().fetch_sub(T(0)))); + + // fetch_sub + { + MaybeVolatile> a(3.1); + std::same_as decltype(auto) r = a.fetch_sub(T(1.2), std::memory_order::relaxed); + assert(r == T(3.1)); + assert(a.load() == T(3.1) - T(1.2)); + } + +#ifndef TEST_HAS_NO_THREADS + // fetch_sub concurrent + { + constexpr auto number_of_threads = 4; + constexpr auto loop = 1000; + + MaybeVolatile> at; + + std::vector threads; + threads.reserve(number_of_threads); + for (auto i = 0; i < number_of_threads; ++i) { + threads.push_back(support::make_test_thread([&at]() { + for (auto j = 0; j < loop; ++j) { + at.fetch_sub(T(1.234), std::memory_order::relaxed); + } + })); + } + + for (auto& thread : threads) { + thread.join(); + } + + const auto accu_neg = [](T t, int n) { + T res(0); + for (auto i = 0; i < n; ++i) { + res -= t; + } + return res; + }; + + assert(at.load() == accu_neg(1.234, number_of_threads * loop)); + } +#endif + + // memory_order::release + { + auto store = [](MaybeVolatile>& x, T old_val, T new_val) { + x.fetch_sub(old_val - new_val, std::memory_order::release); + }; + auto load = [](MaybeVolatile>& x) { return x.load(std::memory_order::acquire); }; + test_acquire_release(store, load); + } + + // memory_order::seq_cst + { + auto fetch_sub = [](MaybeVolatile>& x, T old_value, T new_val) { x.fetch_sub(old_value - new_val); }; + auto fetch_sub_with_order = [](MaybeVolatile>& x, T old_value, T new_val) { + x.fetch_sub(old_value - new_val, std::memory_order::seq_cst); + }; + auto load = [](auto& x) { return x.load(); }; + test_seq_cst(fetch_sub, load); + test_seq_cst(fetch_sub_with_order, load); + } +} + +template +void test() { + test_impl(); + if constexpr (std::atomic::is_always_lock_free) { + test_impl(); + } +} + +int main(int, char**) { + test(); + test(); + test(); + + return 0; +} diff --git a/libcxx/test/std/atomics/atomics.types.generic/atomics.types.float/load.pass.cpp b/libcxx/test/std/atomics/atomics.types.generic/atomics.types.float/load.pass.cpp new file mode 100644 index 0000000000000..fa085275de299 --- /dev/null +++ b/libcxx/test/std/atomics/atomics.types.generic/atomics.types.float/load.pass.cpp @@ -0,0 +1,140 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// UNSUPPORTED: c++03, c++11, c++14, c++17 +// UNSUPPORTED: target={{.+}}-windows-gnu +// Clang's support for atomic operations on long double is broken. See https://github.com/llvm/llvm-project/issues/72893 +// XFAIL: tsan +// ADDITIONAL_COMPILE_FLAGS(has-latomic): -latomic + +// floating-point-type load(memory_order = memory_order::seq_cst) volatile noexcept; +// floating-point-type load(memory_order = memory_order::seq_cst) noexcept; + +#include +#include +#include +#include +#include +#include +#include + +#include "test_helper.h" +#include "test_macros.h" + +#ifndef TEST_HAS_NO_THREADS +# include "make_test_thread.h" +# include +#endif + +template +concept HasVolatileLoad = requires(volatile std::atomic& a, T t) { a.load(); }; + +template class MaybeVolatile = std::type_identity_t> +void test_impl() { + // Uncomment the test after P1831R1 is implemented + // static_assert(HasVolatileLoad == std::atomic::is_always_lock_free); + static_assert(noexcept(std::declval>&>().load())); + + // load + { + MaybeVolatile> a(3.1); + a.store(T(1.2)); + std::same_as decltype(auto) r = a.load(std::memory_order::relaxed); + assert(r == T(1.2)); + } + +#ifndef TEST_HAS_NO_THREADS + // memory_order::relaxed + { + constexpr auto number_of_threads = 4; + constexpr auto loop = 1000; + + MaybeVolatile> at(T(-1.0)); + + std::vector threads; + threads.reserve(number_of_threads); + for (auto i = 0; i < number_of_threads; ++i) { + threads.push_back(support::make_test_thread([&at, i]() { + for (auto j = 0; j < loop; ++j) { + at.store(T(i)); + } + })); + } + + while (at.load(std::memory_order::relaxed) == T(-1.0)) { + std::this_thread::yield(); + } + + for (auto i = 0; i < loop; ++i) { + auto r = at.load(std::memory_order_relaxed); + assert(std::ranges::any_of(std::views::iota(0, number_of_threads), [r](auto j) { return r == T(j); })); + } + + for (auto& thread : threads) { + thread.join(); + } + } + + // memory_order::comsume + { + std::unique_ptr p = std::make_unique(T(0.0)); + MaybeVolatile> at(T(0.0)); + + constexpr auto number_of_threads = 8; + std::vector threads; + threads.reserve(number_of_threads); + + for (auto i = 0; i < number_of_threads; ++i) { + threads.push_back(support::make_test_thread([&at, &p] { + while (at.load(std::memory_order::consume) == T(0.0)) { + std::this_thread::yield(); + } + assert(*p == T(1.0)); // the write from other thread should be visible + })); + } + + *p = T(1.0); + at.store(*p, std::memory_order_release); + + for (auto& thread : threads) { + thread.join(); + } + } +#endif + + // memory_order::acquire + { + auto store = [](MaybeVolatile>& x, T, T new_val) { x.store(new_val, std::memory_order::release); }; + auto load = [](MaybeVolatile>& x) { return x.load(std::memory_order::acquire); }; + test_acquire_release(store, load); + } + + // memory_order::seq_cst + { + auto store = [](MaybeVolatile>& x, T, T new_val) { x.store(new_val); }; + auto load_no_arg = [](MaybeVolatile>& x) { return x.load(); }; + auto load_with_order = [](MaybeVolatile>& x) { return x.load(std::memory_order::seq_cst); }; + test_seq_cst(store, load_no_arg); + test_seq_cst(store, load_with_order); + } +} + +template +void test() { + test_impl(); + if constexpr (std::atomic::is_always_lock_free) { + test_impl(); + } +} + +int main(int, char**) { + test(); + test(); + test(); + + return 0; +} diff --git a/libcxx/test/std/atomics/atomics.types.generic/atomics.types.float/lockfree.pass.cpp b/libcxx/test/std/atomics/atomics.types.generic/atomics.types.float/lockfree.pass.cpp new file mode 100644 index 0000000000000..447e0f8650027 --- /dev/null +++ b/libcxx/test/std/atomics/atomics.types.generic/atomics.types.float/lockfree.pass.cpp @@ -0,0 +1,61 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// UNSUPPORTED: c++03, c++11, c++14, c++17 +// UNSUPPORTED: target={{.+}}-windows-gnu +// ADDITIONAL_COMPILE_FLAGS(has-latomic): -latomic + +// static constexpr bool is_always_lock_free = implementation-defined; +// bool is_lock_free() const volatile noexcept; +// bool is_lock_free() const noexcept; + +#include +#include +#include +#include + +#include "test_macros.h" + +template +concept isLockFreeNoexcept = requires(T t) { + { t.is_lock_free() } noexcept; +}; + +template +void test() { + static_assert(isLockFreeNoexcept&>); + static_assert(isLockFreeNoexcept&>); + + // static constexpr bool is_always_lock_free = implementation-defined; + { [[maybe_unused]] constexpr std::same_as decltype(auto) r = std::atomic::is_always_lock_free; } + + // bool is_lock_free() const volatile noexcept; + { + const volatile std::atomic a; + std::same_as decltype(auto) r = a.is_lock_free(); + if (std::atomic::is_always_lock_free) { + assert(r); + } + } + + // bool is_lock_free() const noexcept; + { + const std::atomic a; + std::same_as decltype(auto) r = a.is_lock_free(); + if (std::atomic::is_always_lock_free) { + assert(r); + } + } +} + +int main(int, char**) { + test(); + test(); + test(); + + return 0; +} diff --git a/libcxx/test/std/atomics/atomics.types.generic/atomics.types.float/notify_all.pass.cpp b/libcxx/test/std/atomics/atomics.types.generic/atomics.types.float/notify_all.pass.cpp new file mode 100644 index 0000000000000..e2320ed0b70ed --- /dev/null +++ b/libcxx/test/std/atomics/atomics.types.generic/atomics.types.float/notify_all.pass.cpp @@ -0,0 +1,98 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// UNSUPPORTED: no-threads +// XFAIL: availability-synchronization_library-missing +// UNSUPPORTED: c++03, c++11, c++14, c++17 +// ADDITIONAL_COMPILE_FLAGS(has-latomic): -latomic + +// void notify_all() volatile noexcept; +// void notify_all() noexcept; + +#include +#include +#include +#include +#include +#include + +#include "test_helper.h" +#include "test_macros.h" + +template +concept HasVolatileNotifyAll = requires(volatile std::atomic& a, T t) { a.notify_all(); }; + +template class MaybeVolatile = std::type_identity_t> +void test_impl() { + // Uncomment the test after P1831R1 is implemented + // static_assert(HasVolatileNotifyAll == std::atomic::is_always_lock_free); + static_assert(noexcept(std::declval>&>().notify_all())); + + // bug?? wait can also fail for long double ?? + // should x87 80bit long double work at all? + if constexpr (!std::same_as) { + for (auto i = 0; i < 100; ++i) { + const T old = 3.1; + MaybeVolatile> a(old); + + bool done = false; + std::atomic started_num = 0; + std::atomic wait_done_num = 0; + + constexpr auto number_of_threads = 8; + std::vector threads; + threads.reserve(number_of_threads); + + for (auto j = 0; j < number_of_threads; ++j) { + threads.push_back(support::make_test_thread([&a, &started_num, old, &done, &wait_done_num] { + started_num.fetch_add(1, std::memory_order::relaxed); + + a.wait(old); + wait_done_num.fetch_add(1, std::memory_order::relaxed); + + // likely to fail if wait did not block + assert(done); + })); + } + + while (started_num.load(std::memory_order::relaxed) != number_of_threads) { + std::this_thread::yield(); + } + + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + + done = true; + a.store(T(9.9)); + a.notify_all(); + + // notify_all should unblock all the threads so that the loop below won't stuck + while (wait_done_num.load(std::memory_order::relaxed) != number_of_threads) { + std::this_thread::yield(); + } + + for (auto& thread : threads) { + thread.join(); + } + } + } +} + +template +void test() { + test_impl(); + if constexpr (std::atomic::is_always_lock_free) { + test_impl(); + } +} + +int main(int, char**) { + test(); + test(); + test(); + + return 0; +} diff --git a/libcxx/test/std/atomics/atomics.types.generic/atomics.types.float/notify_one.pass.cpp b/libcxx/test/std/atomics/atomics.types.generic/atomics.types.float/notify_one.pass.cpp new file mode 100644 index 0000000000000..058424f5cee05 --- /dev/null +++ b/libcxx/test/std/atomics/atomics.types.generic/atomics.types.float/notify_one.pass.cpp @@ -0,0 +1,82 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// UNSUPPORTED: no-threads +// XFAIL: availability-synchronization_library-missing +// UNSUPPORTED: c++03, c++11, c++14, c++17 +// ADDITIONAL_COMPILE_FLAGS(has-latomic): -latomic + +// void notify_one() volatile noexcept; +// void notify_one() noexcept; + +#include +#include +#include +#include +#include +#include + +#include "test_helper.h" +#include "test_macros.h" + +template +concept HasVolatileNotifyOne = requires(volatile std::atomic& a, T t) { a.notify_one(); }; + +template class MaybeVolatile = std::type_identity_t> +void test_impl() { + // Uncomment the test after P1831R1 is implemented + // static_assert(HasVolatileNotifyOne == std::atomic::is_always_lock_free); + static_assert(noexcept(std::declval>&>().notify_one())); + + // bug?? wait can also fail for long double ?? + // should x87 80bit long double work at all? + if constexpr (!std::same_as) { + for (auto i = 0; i < 100; ++i) { + const T old = 3.1; + MaybeVolatile> a(old); + + std::atomic_bool started = false; + bool done = false; + + auto t = support::make_test_thread([&a, &started, old, &done] { + started.store(true, std::memory_order::relaxed); + + a.wait(old); + + // likely to fail if wait did not block + assert(done); + }); + + while (!started.load(std::memory_order::relaxed)) { + std::this_thread::yield(); + } + + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + + done = true; + a.store(T(9.9)); + a.notify_one(); + t.join(); + } + } +} + +template +void test() { + test_impl(); + if constexpr (std::atomic::is_always_lock_free) { + test_impl(); + } +} + +int main(int, char**) { + test(); + test(); + test(); + + return 0; +} diff --git a/libcxx/test/std/atomics/atomics.types.generic/atomics.types.float/operator.float.pass.cpp b/libcxx/test/std/atomics/atomics.types.generic/atomics.types.float/operator.float.pass.cpp new file mode 100644 index 0000000000000..54c62ba8d0918 --- /dev/null +++ b/libcxx/test/std/atomics/atomics.types.generic/atomics.types.float/operator.float.pass.cpp @@ -0,0 +1,58 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// UNSUPPORTED: c++03, c++11, c++14, c++17 +// UNSUPPORTED: target={{.+}}-windows-gnu +// ADDITIONAL_COMPILE_FLAGS(has-latomic): -latomic + +// operator floating-point-type() volatile noexcept; +// operator floating-point-type() noexcept; + +#include +#include +#include +#include + +#include "test_helper.h" +#include "test_macros.h" + +template class MaybeVolatile = std::type_identity_t> +void test_impl() { + // Uncomment the test after P1831R1 is implemented + // static_assert(std::is_convertible_v&, T> == std::atomic::is_always_lock_free); + static_assert(noexcept(T(std::declval>&>()))); + + // operator float + { + MaybeVolatile> a(3.1); + T r = a; + assert(r == T(3.1)); + } + + // memory_order::seq_cst + { + auto store = [](MaybeVolatile>& x, T, T new_val) { x.store(new_val); }; + auto op_float = [](MaybeVolatile>& x) -> T { return x; }; + test_seq_cst(store, op_float); + } +} + +template +void test() { + test_impl(); + if constexpr (std::atomic::is_always_lock_free) { + test_impl(); + } +} + +int main(int, char**) { + test(); + test(); + test(); + + return 0; +} diff --git a/libcxx/test/std/atomics/atomics.types.generic/atomics.types.float/operator.minus_equals.pass.cpp b/libcxx/test/std/atomics/atomics.types.generic/atomics.types.float/operator.minus_equals.pass.cpp new file mode 100644 index 0000000000000..18c42d7b9bb13 --- /dev/null +++ b/libcxx/test/std/atomics/atomics.types.generic/atomics.types.float/operator.minus_equals.pass.cpp @@ -0,0 +1,102 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// UNSUPPORTED: c++03, c++11, c++14, c++17 +// UNSUPPORTED: target={{.+}}-windows-gnu +// XFAIL: LIBCXX-AIX-FIXME +// ADDITIONAL_COMPILE_FLAGS(has-latomic): -latomic + +// floating-point-type operator-=(floating-point-type) volatile noexcept; +// floating-point-type operator-=(floating-point-type) noexcept; + +#include +#include +#include +#include +#include + +#include "test_helper.h" +#include "test_macros.h" + +#ifndef TEST_HAS_NO_THREADS +# include "make_test_thread.h" +# include +#endif + +template +concept HasVolatileMinusEquals = requires(volatile std::atomic& a, T t) { a -= t; }; + +template class MaybeVolatile = std::type_identity_t> +void test_impl() { + static_assert(HasVolatileMinusEquals == std::atomic::is_always_lock_free); + static_assert(noexcept(std::declval>&>() -= T(0))); + + // -= + { + MaybeVolatile> a(3.1); + std::same_as decltype(auto) r = a -= T(1.2); + assert(r == T(3.1) - T(1.2)); + assert(a.load() == T(3.1) - T(1.2)); + } + +#ifndef TEST_HAS_NO_THREADS + // -= concurrent + { + constexpr auto number_of_threads = 4; + constexpr auto loop = 1000; + + MaybeVolatile> at; + + std::vector threads; + threads.reserve(number_of_threads); + for (auto i = 0; i < number_of_threads; ++i) { + threads.push_back(support::make_test_thread([&at]() { + for (auto j = 0; j < loop; ++j) { + at -= T(1.234); + } + })); + } + + for (auto& thread : threads) { + thread.join(); + } + + const auto accu_neg = [](T t, int n) { + T res(0); + for (auto i = 0; i < n; ++i) { + res -= t; + } + return res; + }; + + assert(at.load() == accu_neg(1.234, number_of_threads * loop)); + } +#endif + + // memory_order::seq_cst + { + auto minus_equals = [](MaybeVolatile>& x, T old_value, T new_val) { x -= (old_value - new_val); }; + auto load = [](MaybeVolatile>& x) { return x.load(); }; + test_seq_cst(minus_equals, load); + } +} + +template +void test() { + test_impl(); + if constexpr (std::atomic::is_always_lock_free) { + test_impl(); + } +} + +int main(int, char**) { + test(); + test(); + test(); + + return 0; +} diff --git a/libcxx/test/std/atomics/atomics.types.generic/atomics.types.float/operator.plus_equals.pass.cpp b/libcxx/test/std/atomics/atomics.types.generic/atomics.types.float/operator.plus_equals.pass.cpp new file mode 100644 index 0000000000000..6026d2392d937 --- /dev/null +++ b/libcxx/test/std/atomics/atomics.types.generic/atomics.types.float/operator.plus_equals.pass.cpp @@ -0,0 +1,102 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// UNSUPPORTED: c++03, c++11, c++14, c++17 +// UNSUPPORTED: target={{.+}}-windows-gnu +// XFAIL: LIBCXX-AIX-FIXME +// ADDITIONAL_COMPILE_FLAGS(has-latomic): -latomic + +// floating-point-type operator+=(floating-point-type) volatile noexcept; +// floating-point-type operator+=(floating-point-type) noexcept; + +#include +#include +#include +#include +#include + +#include "test_helper.h" +#include "test_macros.h" + +#ifndef TEST_HAS_NO_THREADS +# include "make_test_thread.h" +# include +#endif + +template +concept HasVolatilePlusEquals = requires(volatile std::atomic& a, T t) { a += t; }; + +template class MaybeVolatile = std::type_identity_t> +void test_impl() { + static_assert(HasVolatilePlusEquals == std::atomic::is_always_lock_free); + static_assert(noexcept(std::declval>&>() += T(0))); + + // += + { + MaybeVolatile> a(3.1); + std::same_as decltype(auto) r = a += T(1.2); + assert(r == T(3.1) + T(1.2)); + assert(a.load() == T(3.1) + T(1.2)); + } + +#ifndef TEST_HAS_NO_THREADS + // += concurrent + { + constexpr auto number_of_threads = 4; + constexpr auto loop = 1000; + + MaybeVolatile> at; + + std::vector threads; + threads.reserve(number_of_threads); + for (auto i = 0; i < number_of_threads; ++i) { + threads.push_back(support::make_test_thread([&at]() { + for (auto j = 0; j < loop; ++j) { + at += T(1.234); + } + })); + } + + for (auto& thread : threads) { + thread.join(); + } + + const auto times = [](T t, int n) { + T res(0); + for (auto i = 0; i < n; ++i) { + res += t; + } + return res; + }; + + assert(at.load() == times(1.234, number_of_threads * loop)); + } + + // memory_order::seq_cst + { + auto plus_equals = [](MaybeVolatile>& x, T old_value, T new_val) { x += (new_val - old_value); }; + auto load = [](MaybeVolatile>& x) { return x.load(); }; + test_seq_cst(plus_equals, load); + } +#endif +} + +template +void test() { + test_impl(); + if constexpr (std::atomic::is_always_lock_free) { + test_impl(); + } +} + +int main(int, char**) { + test(); + test(); + test(); + + return 0; +} diff --git a/libcxx/test/std/atomics/atomics.types.generic/atomics.types.float/store.pass.cpp b/libcxx/test/std/atomics/atomics.types.generic/atomics.types.float/store.pass.cpp new file mode 100644 index 0000000000000..0bb1f00840a89 --- /dev/null +++ b/libcxx/test/std/atomics/atomics.types.generic/atomics.types.float/store.pass.cpp @@ -0,0 +1,114 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// UNSUPPORTED: c++03, c++11, c++14, c++17 +// Clang's support for atomic operations on long double is broken. See https://github.com/llvm/llvm-project/issues/72893 +// XFAIL: tsan +// ADDITIONAL_COMPILE_FLAGS(has-latomic): -latomic + +// void store(floating-point-type, memory_order = memory_order::seq_cst) volatile noexcept; +// void store(floating-point-type, memory_order = memory_order::seq_cst) noexcept; + +#include +#include +#include +#include +#include +#include +#include + +#include "test_helper.h" +#include "test_macros.h" + +#ifndef TEST_HAS_NO_THREADS +# include "make_test_thread.h" +# include +#endif + +template +concept HasVolatileStore = requires(volatile std::atomic& a, T t) { a.store(t); }; + +template class MaybeVolatile = std::type_identity_t> +void test_impl() { + // Uncomment the test after P1831R1 is implemented + // static_assert(HasVolatileStore == std::atomic::is_always_lock_free); + static_assert(noexcept(std::declval>&>().store(T(0)))); + + // store + { + MaybeVolatile> a(3.1); + a.store(T(1.2), std::memory_order::relaxed); + assert(a.load() == T(1.2)); + } + +#ifndef TEST_HAS_NO_THREADS + // memory_order::relaxed + { + constexpr auto number_of_threads = 4; + constexpr auto loop = 1000; + + MaybeVolatile> at(T(-1.0)); + + std::vector threads; + threads.reserve(number_of_threads); + for (auto i = 0; i < number_of_threads; ++i) { + threads.push_back(support::make_test_thread([&at, i]() { + for (auto j = 0; j < loop; ++j) { + at.store(T(i), std::memory_order_relaxed); + } + })); + } + + while (at.load() == T(-1.0)) { + std::this_thread::yield(); + } + + for (auto i = 0; i < loop; ++i) { + auto r = at.load(); + assert(std::ranges::any_of(std::views::iota(0, number_of_threads), [r](auto j) { return r == T(j); })); + } + + for (auto& thread : threads) { + thread.join(); + } + } +#endif + + // memory_order::release + { + auto store = [](MaybeVolatile>& x, T, T new_val) { x.store(new_val, std::memory_order::release); }; + auto load = [](MaybeVolatile>& x) { return x.load(std::memory_order::acquire); }; + test_acquire_release(store, load); + } + + // memory_order::seq_cst + { + auto store_no_arg = [](MaybeVolatile>& x, T, T new_val) { x.store(new_val); }; + auto store_with_order = [](MaybeVolatile>& x, T, T new_val) { + x.store(new_val, std::memory_order::seq_cst); + }; + auto load = [](MaybeVolatile>& x) { return x.load(); }; + test_seq_cst(store_no_arg, load); + test_seq_cst(store_with_order, load); + } +} + +template +void test() { + test_impl(); + if constexpr (std::atomic::is_always_lock_free) { + test_impl(); + } +} + +int main(int, char**) { + test(); + test(); + test(); + + return 0; +} diff --git a/libcxx/test/std/atomics/atomics.types.generic/atomics.types.float/test_helper.h b/libcxx/test/std/atomics/atomics.types.generic/atomics.types.float/test_helper.h new file mode 100644 index 0000000000000..b831b774fbd30 --- /dev/null +++ b/libcxx/test/std/atomics/atomics.types.generic/atomics.types.float/test_helper.h @@ -0,0 +1,122 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef TEST_STD_ATOMICS_ATOMICS_TYPES_FLOAT_TEST_HELPER_H +#define TEST_STD_ATOMICS_ATOMICS_TYPES_FLOAT_TEST_HELPER_H + +#include +#include +#include +#include + +#include "test_macros.h" + +#ifndef TEST_HAS_NO_THREADS +# include "make_test_thread.h" +# include +#endif + +template +bool approximately_equals(T x, T y) { + T epsilon = 0.001; + return std::abs(x - y) < epsilon; +} + +// Test that all threads see the exact same sequence of events +// Test will pass 100% if store_op and load_op are correctly +// affecting the memory with seq_cst order +template class MaybeVolatile, class StoreOp, class LoadOp> +void test_seq_cst(StoreOp store_op, LoadOp load_op) { +#ifndef TEST_HAS_NO_THREADS + for (int i = 0; i < 100; ++i) { + T old_value = 0.0; + T new_value = 1.0; + + MaybeVolatile> x(old_value); + MaybeVolatile> y(old_value); + + std::atomic_bool x_updated_first(false); + std::atomic_bool y_updated_first(false); + + auto t1 = support::make_test_thread([&] { store_op(x, old_value, new_value); }); + + auto t2 = support::make_test_thread([&] { store_op(y, old_value, new_value); }); + + auto t3 = support::make_test_thread([&] { + while (!approximately_equals(load_op(x), new_value)) { + std::this_thread::yield(); + } + if (!approximately_equals(load_op(y), new_value)) { + x_updated_first.store(true, std::memory_order_relaxed); + } + }); + + auto t4 = support::make_test_thread([&] { + while (!approximately_equals(load_op(y), new_value)) { + std::this_thread::yield(); + } + if (!approximately_equals(load_op(x), new_value)) { + y_updated_first.store(true, std::memory_order_relaxed); + } + }); + + t1.join(); + t2.join(); + t3.join(); + t4.join(); + // thread 3 and thread 4 cannot see different orders of storing x and y + assert(!(x_updated_first && y_updated_first)); + } +#else + (void)store_op; + (void)load_op; +#endif +} + +// Test that all writes before the store are seen by other threads after the load +// Test will pass 100% if store_op and load_op are correctly +// affecting the memory with acquire-release order +template class MaybeVolatile, class StoreOp, class LoadOp> +void test_acquire_release(StoreOp store_op, LoadOp load_op) { +#ifndef TEST_HAS_NO_THREADS + for (auto i = 0; i < 100; ++i) { + T old_value = 0.0; + T new_value = 1.0; + + MaybeVolatile> at(old_value); + int non_atomic = 5; + + constexpr auto number_of_threads = 8; + std::vector threads; + threads.reserve(number_of_threads); + + for (auto j = 0; j < number_of_threads; ++j) { + threads.push_back(support::make_test_thread([&at, &non_atomic, load_op, new_value] { + while (!approximately_equals(load_op(at), new_value)) { + std::this_thread::yield(); + } + // Other thread's writes before the release store are visible + // in this thread's read after the acquire load + assert(non_atomic == 6); + })); + } + + non_atomic = 6; + store_op(at, old_value, new_value); + + for (auto& thread : threads) { + thread.join(); + } + } +#else + (void)store_op; + (void)load_op; +#endif +} + +#endif // TEST_STD_ATOMICS_ATOMICS_TYPES_FLOAT_TEST_HELPER_H diff --git a/libcxx/test/std/atomics/atomics.types.generic/atomics.types.float/types.compile.pass.cpp b/libcxx/test/std/atomics/atomics.types.generic/atomics.types.float/types.compile.pass.cpp new file mode 100644 index 0000000000000..1a4e6dfe0b315 --- /dev/null +++ b/libcxx/test/std/atomics/atomics.types.generic/atomics.types.float/types.compile.pass.cpp @@ -0,0 +1,28 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// UNSUPPORTED: c++03, c++11, c++14, c++17 + +// using value_type = floating-point-type; +// using difference_type = value_type; +// The atomic floating-point specializations are standard-layout structs. They each have a trivial destructor. + +#include +#include + +template +void test() { + static_assert(std::is_same_v::value_type, T>); + static_assert(std::is_same_v::difference_type, T>); + static_assert(std::is_standard_layout_v>); + static_assert(std::is_trivially_destructible_v>); +} + +template void test(); +template void test(); +template void test(); diff --git a/libcxx/test/std/atomics/atomics.types.generic/atomics.types.float/wait.pass.cpp b/libcxx/test/std/atomics/atomics.types.generic/atomics.types.float/wait.pass.cpp new file mode 100644 index 0000000000000..ce730b792a21e --- /dev/null +++ b/libcxx/test/std/atomics/atomics.types.generic/atomics.types.float/wait.pass.cpp @@ -0,0 +1,124 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// UNSUPPORTED: c++03, c++11, c++14, c++17 +// XFAIL: availability-synchronization_library-missing +// Clang's support for atomic operations on long double is broken. See https://github.com/llvm/llvm-project/issues/72893 +// XFAIL: tsan, msan +// ADDITIONAL_COMPILE_FLAGS(has-latomic): -latomic + +// void wait(T old, memory_order order = memory_order::seq_cst) const volatile noexcept; +// void wait(T old, memory_order order = memory_order::seq_cst) const noexcept; + +#include +#include +#include +#include +#include + +#include "test_helper.h" +#include "test_macros.h" + +#ifndef TEST_HAS_NO_THREADS +# include "make_test_thread.h" +# include +#endif + +template +concept HasVolatileWait = requires(volatile std::atomic& a, T t) { a.wait(T()); }; + +template class MaybeVolatile = std::type_identity_t> +void test_impl() { + // Uncomment the test after P1831R1 is implemented + // static_assert(HasVolatileWait == std::atomic::is_always_lock_free); + static_assert(noexcept(std::declval>&>().wait(T()))); + + // wait with different value + { + MaybeVolatile> a(T(3.1)); + a.wait(T(1.1), std::memory_order::relaxed); + } + +#ifndef TEST_HAS_NO_THREADS + // equal at the beginning and changed later + // bug?? wait can also fail for long double ?? + // should x87 80bit long double work at all? + if constexpr (!std::same_as) { + for (auto i = 0; i < 100; ++i) { + const T old = 3.1; + MaybeVolatile> a(old); + + std::atomic_bool started = false; + bool done = false; + + auto t = support::make_test_thread([&a, &started, old, &done] { + started.store(true, std::memory_order::relaxed); + + a.wait(old); + + // likely to fail if wait did not block + assert(done); + }); + + while (!started.load(std::memory_order::relaxed)) { + std::this_thread::yield(); + } + + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + + done = true; + a.store(T(9.9)); + a.notify_all(); + t.join(); + } + } +#endif + + // memory_order::acquire + { + auto store = [](MaybeVolatile>& x, T, T new_val) { x.store(new_val, std::memory_order::release); }; + auto load = [](MaybeVolatile>& x) { + auto result = x.load(std::memory_order::relaxed); + x.wait(T(9999.999), std::memory_order::acquire); + return result; + }; + test_acquire_release(store, load); + } + + // memory_order::seq_cst + { + auto store = [](MaybeVolatile>& x, T, T new_val) { x.store(new_val); }; + auto load_no_arg = [](MaybeVolatile>& x) { + auto result = x.load(std::memory_order::relaxed); + x.wait(T(9999.999)); + return result; + }; + auto load_with_order = [](MaybeVolatile>& x) { + auto result = x.load(std::memory_order::relaxed); + x.wait(T(9999.999), std::memory_order::seq_cst); + return result; + }; + test_seq_cst(store, load_no_arg); + test_seq_cst(store, load_with_order); + } +} + +template +void test() { + test_impl(); + if constexpr (std::atomic::is_always_lock_free) { + test_impl(); + } +} + +int main(int, char**) { + test(); + test(); + test(); + + return 0; +} diff --git a/libcxx/utils/libcxx/test/features.py b/libcxx/utils/libcxx/test/features.py index 29822f5552136..e854aed665138 100644 --- a/libcxx/utils/libcxx/test/features.py +++ b/libcxx/utils/libcxx/test/features.py @@ -110,6 +110,16 @@ def _getAndroidDeviceApi(cfg): name="verify-support", when=lambda cfg: hasCompileFlag(cfg, "-Xclang -verify-ignore-unexpected"), ), + Feature( + name="has-latomic", + when=lambda cfg: sourceBuilds( + cfg, + """ + int main(int, char**) { return 0; } + """, + ["-latomic"], + ), + ), Feature( name="non-lockfree-atomics", when=lambda cfg: sourceBuilds(