diff --git a/libcxx/docs/FeatureTestMacroTable.rst b/libcxx/docs/FeatureTestMacroTable.rst index 6bf2f35fb421..00665332a9ed 100644 --- a/libcxx/docs/FeatureTestMacroTable.rst +++ b/libcxx/docs/FeatureTestMacroTable.rst @@ -424,7 +424,7 @@ Status ---------------------------------------------------------- ----------------- ``__cpp_lib_associative_heterogeneous_insertion`` *unimplemented* ---------------------------------------------------------- ----------------- - ``__cpp_lib_atomic_min_max`` *unimplemented* + ``__cpp_lib_atomic_min_max`` ``202403L`` ---------------------------------------------------------- ----------------- ``__cpp_lib_atomic_ref`` ``202603L`` ---------------------------------------------------------- ----------------- diff --git a/libcxx/docs/ReleaseNotes/24.rst b/libcxx/docs/ReleaseNotes/24.rst index bcac37b8a717..70c704ff9d32 100644 --- a/libcxx/docs/ReleaseNotes/24.rst +++ b/libcxx/docs/ReleaseNotes/24.rst @@ -38,6 +38,7 @@ What's New in Libc++ 24.0.0? Implemented Papers ------------------ +- P0493R5: Atomic minimum/maximum (`Github `__) Improvements and New Features ----------------------------- diff --git a/libcxx/docs/Status/Cxx26Papers.csv b/libcxx/docs/Status/Cxx26Papers.csv index 916bb559f254..7fcebd331197 100644 --- a/libcxx/docs/Status/Cxx26Papers.csv +++ b/libcxx/docs/Status/Cxx26Papers.csv @@ -53,7 +53,7 @@ "`P3107R5 `__","Permit an efficient implementation of ``std::print``","2024-03 (Tokyo)","","","`#105414 `__","" "`P3142R0 `__","Printing Blank Lines with ``println``","2024-03 (Tokyo)","|Complete|","19","`#105415 `__","Implemented as a DR against C++23." "`P2845R8 `__","Formatting of ``std::filesystem::path``","2024-03 (Tokyo)","","","`#105416 `__","" -"`P0493R5 `__","Atomic minimum/maximum","2024-03 (Tokyo)","","","`#105418 `__","" +"`P0493R5 `__","Atomic minimum/maximum","2024-03 (Tokyo)","|Complete|","24","`#105418 `__","" "`P2542R8 `__","``views::concat``","2024-03 (Tokyo)","|Complete|","23","`#105419 `__","" "`P2591R5 `__","Concatenation of strings and string views","2024-03 (Tokyo)","|Complete|","19","`#105420 `__","" "`P2248R8 `__","Enabling list-initialization for algorithms","2024-03 (Tokyo)","","","`#105421 `__","" diff --git a/libcxx/include/__atomic/atomic.h b/libcxx/include/__atomic/atomic.h index 3d0ed9cc4739..d21ebf20bf23 100644 --- a/libcxx/include/__atomic/atomic.h +++ b/libcxx/include/__atomic/atomic.h @@ -16,6 +16,8 @@ #include <__atomic/is_always_lock_free.h> #include <__atomic/memory_order.h> #include <__atomic/support.h> +#include <__concepts/arithmetic.h> +#include <__concepts/same_as.h> #include <__config> #include <__cstddef/ptrdiff_t.h> #include <__memory/addressof.h> @@ -24,6 +26,7 @@ #include <__type_traits/is_function.h> #include <__type_traits/is_integral.h> #include <__type_traits/is_nothrow_constructible.h> +#include <__type_traits/is_pointer.h> #include <__type_traits/is_same.h> #include <__type_traits/is_trivially_copyable.h> #include <__type_traits/remove_pointer.h> @@ -178,6 +181,20 @@ struct __atomic_base<_Tp, true> : public __atomic_base<_Tp, false> { _LIBCPP_HIDE_FROM_ABI _Tp fetch_xor(_Tp __op, memory_order __m = memory_order_seq_cst) _NOEXCEPT { return std::__cxx_atomic_fetch_xor(std::addressof(this->__a_), __op, __m); } +#if _LIBCPP_STD_VER >= 26 + _LIBCPP_HIDE_FROM_ABI _Tp fetch_max(_Tp __op, memory_order __m = memory_order_seq_cst) volatile _NOEXCEPT { + return std::__cxx_atomic_fetch_max(std::addressof(this->__a_), __op, __m); + } + _LIBCPP_HIDE_FROM_ABI _Tp fetch_max(_Tp __op, memory_order __m = memory_order_seq_cst) _NOEXCEPT { + return std::__cxx_atomic_fetch_max(std::addressof(this->__a_), __op, __m); + } + _LIBCPP_HIDE_FROM_ABI _Tp fetch_min(_Tp __op, memory_order __m = memory_order_seq_cst) volatile _NOEXCEPT { + return std::__cxx_atomic_fetch_min(std::addressof(this->__a_), __op, __m); + } + _LIBCPP_HIDE_FROM_ABI _Tp fetch_min(_Tp __op, memory_order __m = memory_order_seq_cst) _NOEXCEPT { + return std::__cxx_atomic_fetch_min(std::addressof(this->__a_), __op, __m); + } +#endif _LIBCPP_HIDE_FROM_ABI _Tp operator++(int) volatile _NOEXCEPT { return fetch_add(_Tp(1)); } _LIBCPP_HIDE_FROM_ABI _Tp operator++(int) _NOEXCEPT { return fetch_add(_Tp(1)); } @@ -306,6 +323,28 @@ struct atomic<_Tp*> : public __atomic_base<_Tp*> { return std::__cxx_atomic_fetch_sub(std::addressof(this->__a_), __op, __m); } +#if _LIBCPP_STD_VER >= 26 + _LIBCPP_HIDE_FROM_ABI _Tp* fetch_max(_Tp* __op, memory_order __m = memory_order_seq_cst) volatile _NOEXCEPT { + static_assert(!is_function<__remove_pointer_t<_Tp> >::value, "Pointer to function isn't allowed"); + return std::__cxx_atomic_fetch_max(std::addressof(this->__a_), __op, __m); + } + + _LIBCPP_HIDE_FROM_ABI _Tp* fetch_max(_Tp* __op, memory_order __m = memory_order_seq_cst) _NOEXCEPT { + static_assert(!is_function<__remove_pointer_t<_Tp> >::value, "Pointer to function isn't allowed"); + return std::__cxx_atomic_fetch_max(std::addressof(this->__a_), __op, __m); + } + + _LIBCPP_HIDE_FROM_ABI _Tp* fetch_min(_Tp* __op, memory_order __m = memory_order_seq_cst) volatile _NOEXCEPT { + static_assert(!is_function<__remove_pointer_t<_Tp> >::value, "Pointer to function isn't allowed"); + return std::__cxx_atomic_fetch_min(std::addressof(this->__a_), __op, __m); + } + + _LIBCPP_HIDE_FROM_ABI _Tp* fetch_min(_Tp* __op, memory_order __m = memory_order_seq_cst) _NOEXCEPT { + static_assert(!is_function<__remove_pointer_t<_Tp> >::value, "Pointer to function isn't allowed"); + return std::__cxx_atomic_fetch_min(std::addressof(this->__a_), __op, __m); + } +#endif + _LIBCPP_HIDE_FROM_ABI _Tp* operator++(int) volatile _NOEXCEPT { return fetch_add(1); } _LIBCPP_HIDE_FROM_ABI _Tp* operator++(int) _NOEXCEPT { return fetch_add(1); } _LIBCPP_HIDE_FROM_ABI _Tp* operator--(int) volatile _NOEXCEPT { return fetch_sub(1); } @@ -799,6 +838,68 @@ atomic_fetch_xor_explicit(atomic<_Tp>* __o, typename atomic<_Tp>::value_type __o return __o->fetch_xor(__op, __m); } +#if _LIBCPP_STD_VER >= 26 +// atomic_fetch_max + +template + requires((integral<_Tp> && !same_as<_Tp, bool>) || is_pointer_v<_Tp>) +_LIBCPP_HIDE_FROM_ABI _Tp atomic_fetch_max(volatile atomic<_Tp>* __o, typename atomic<_Tp>::value_type __op) _NOEXCEPT { + return __o->fetch_max(__op); +} + +template + requires((integral<_Tp> && !same_as<_Tp, bool>) || is_pointer_v<_Tp>) +_LIBCPP_HIDE_FROM_ABI _Tp atomic_fetch_max(atomic<_Tp>* __o, typename atomic<_Tp>::value_type __op) _NOEXCEPT { + return __o->fetch_max(__op); +} + +// atomic_fetch_max_explicit + +template + requires((integral<_Tp> && !same_as<_Tp, bool>) || is_pointer_v<_Tp>) +_LIBCPP_HIDE_FROM_ABI _Tp atomic_fetch_max_explicit( + volatile atomic<_Tp>* __o, typename atomic<_Tp>::value_type __op, memory_order __m) _NOEXCEPT { + return __o->fetch_max(__op, __m); +} + +template + requires((integral<_Tp> && !same_as<_Tp, bool>) || is_pointer_v<_Tp>) +_LIBCPP_HIDE_FROM_ABI _Tp +atomic_fetch_max_explicit(atomic<_Tp>* __o, typename atomic<_Tp>::value_type __op, memory_order __m) _NOEXCEPT { + return __o->fetch_max(__op, __m); +} + +// atomic_fetch_min + +template + requires((integral<_Tp> && !same_as<_Tp, bool>) || is_pointer_v<_Tp>) +_LIBCPP_HIDE_FROM_ABI _Tp atomic_fetch_min(volatile atomic<_Tp>* __o, typename atomic<_Tp>::value_type __op) _NOEXCEPT { + return __o->fetch_min(__op); +} + +template + requires((integral<_Tp> && !same_as<_Tp, bool>) || is_pointer_v<_Tp>) +_LIBCPP_HIDE_FROM_ABI _Tp atomic_fetch_min(atomic<_Tp>* __o, typename atomic<_Tp>::value_type __op) _NOEXCEPT { + return __o->fetch_min(__op); +} + +// atomic_fetch_min_explicit + +template + requires((integral<_Tp> && !same_as<_Tp, bool>) || is_pointer_v<_Tp>) +_LIBCPP_HIDE_FROM_ABI _Tp atomic_fetch_min_explicit( + volatile atomic<_Tp>* __o, typename atomic<_Tp>::value_type __op, memory_order __m) _NOEXCEPT { + return __o->fetch_min(__op, __m); +} + +template + requires((integral<_Tp> && !same_as<_Tp, bool>) || is_pointer_v<_Tp>) +_LIBCPP_HIDE_FROM_ABI _Tp +atomic_fetch_min_explicit(atomic<_Tp>* __o, typename atomic<_Tp>::value_type __op, memory_order __m) _NOEXCEPT { + return __o->fetch_min(__op, __m); +} +#endif + _LIBCPP_END_NAMESPACE_STD #endif // _LIBCPP___ATOMIC_ATOMIC_H diff --git a/libcxx/include/__atomic/atomic_ref.h b/libcxx/include/__atomic/atomic_ref.h index 93f19a740ba3..78f1f24f4c7a 100644 --- a/libcxx/include/__atomic/atomic_ref.h +++ b/libcxx/include/__atomic/atomic_ref.h @@ -304,6 +304,32 @@ struct atomic_ref<_Tp> : public __atomic_ref_base<_Tp> { _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)); } +# if _LIBCPP_STD_VER >= 26 + _LIBCPP_HIDE_FROM_ABI _Tp fetch_max(_Tp __arg, memory_order __order = memory_order_seq_cst) const noexcept { +# if __has_builtin(__atomic_fetch_max) + return __atomic_fetch_max(this->__ptr_, __arg, std::__to_gcc_order(__order)); +# else + _Tp __old = this->load(memory_order_relaxed); + _Tp __new; + do { + __new = __old > __arg ? __old : __arg; + } while (!this->compare_exchange_weak(__old, __new, __order, memory_order_relaxed)); + return __old; +# endif + } + _LIBCPP_HIDE_FROM_ABI _Tp fetch_min(_Tp __arg, memory_order __order = memory_order_seq_cst) const noexcept { +# if __has_builtin(__atomic_fetch_min) + return __atomic_fetch_min(this->__ptr_, __arg, std::__to_gcc_order(__order)); +# else + _Tp __old = this->load(memory_order_relaxed); + _Tp __new; + do { + __new = __old < __arg ? __old : __arg; + } while (!this->compare_exchange_weak(__old, __new, __order, memory_order_relaxed)); + return __old; +# endif + } +# endif _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)); } @@ -386,6 +412,26 @@ struct atomic_ref<_Tp*> : public __atomic_ref_base<_Tp*> { _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)); } +# if _LIBCPP_STD_VER >= 26 + _LIBCPP_HIDE_FROM_ABI _Tp* fetch_max(_Tp* __arg, memory_order __order = memory_order_seq_cst) const noexcept { + // TODO: Use __atomic_fetch_max once it accepts pointer arguments. + _Tp* __old = this->load(memory_order_relaxed); + _Tp* __new; + do { + __new = __old > __arg ? __old : __arg; + } while (!this->compare_exchange_weak(__old, __new, __order, memory_order_relaxed)); + return __old; + } + _LIBCPP_HIDE_FROM_ABI _Tp* fetch_min(_Tp* __arg, memory_order __order = memory_order_seq_cst) const noexcept { + // TODO: Use __atomic_fetch_min once it accepts pointer arguments. + _Tp* __old = this->load(memory_order_relaxed); + _Tp* __new; + do { + __new = __old < __arg ? __old : __arg; + } while (!this->compare_exchange_weak(__old, __new, __order, memory_order_relaxed)); + return __old; + } +# endif _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); } diff --git a/libcxx/include/__atomic/support.h b/libcxx/include/__atomic/support.h index 053ad54aa6ce..55a93face9d8 100644 --- a/libcxx/include/__atomic/support.h +++ b/libcxx/include/__atomic/support.h @@ -99,6 +99,15 @@ // template // _Tp __cxx_atomic_fetch_xor(_Atmc<_Tp>* __a, _Tp __pattern, memory_order __order) noexcept; // +// template +// _Tp __cxx_atomic_fetch_max(_Atmc<_Tp> volatile* __a, _Tp __pattern, memory_order __order) noexcept; +// template +// _Tp __cxx_atomic_fetch_max(_Atmc<_Tp>* __a, _Tp __pattern, memory_order __order) noexcept; +// template +// _Tp __cxx_atomic_fetch_min(_Atmc<_Tp> volatile* __a, _Tp __pattern, memory_order __order) noexcept; +// template +// _Tp __cxx_atomic_fetch_min(_Atmc<_Tp>* __a, _Tp __pattern, memory_order __order) noexcept; +// // clang-format on // diff --git a/libcxx/include/__atomic/support/c11.h b/libcxx/include/__atomic/support/c11.h index 1ad299882a12..899502422570 100644 --- a/libcxx/include/__atomic/support/c11.h +++ b/libcxx/include/__atomic/support/c11.h @@ -13,6 +13,8 @@ #include <__config> #include <__cstddef/ptrdiff_t.h> #include <__memory/addressof.h> +#include <__type_traits/enable_if.h> +#include <__type_traits/is_pointer.h> #include <__type_traits/remove_const.h> #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) @@ -259,6 +261,81 @@ __cxx_atomic_fetch_xor(__cxx_atomic_base_impl<_Tp>* __a, _Tp __pattern, memory_o std::addressof(__a->__a_value), __pattern, static_cast<__memory_order_underlying_t>(__order)); } +// Clang's __c11_atomic_fetch_max/min builtins do not accept pointer +// arguments, so dispatch to a CAS loop for pointer types. +// TODO: Use the builtin for pointer types once Clang accepts them. +template ::value, int> = 0> +_LIBCPP_HIDE_FROM_ABI _Tp +__cxx_atomic_fetch_max(__cxx_atomic_base_impl<_Tp> volatile* __a, _Tp __val, memory_order __order) _NOEXCEPT { + return __c11_atomic_fetch_max( + std::addressof(__a->__a_value), __val, static_cast<__memory_order_underlying_t>(__order)); +} +template ::value, int> = 0> +_LIBCPP_HIDE_FROM_ABI _Tp +__cxx_atomic_fetch_max(__cxx_atomic_base_impl<_Tp> volatile* __a, _Tp __val, memory_order __order) _NOEXCEPT { + _Tp __ret = std::__cxx_atomic_load(__a, memory_order_relaxed); + _Tp __value; + do { + __value = __ret > __val ? __ret : __val; + } while ( + !std::__cxx_atomic_compare_exchange_weak(__a, std::addressof(__ret), __value, __order, memory_order_relaxed)); + return __ret; +} + +template ::value, int> = 0> +_LIBCPP_HIDE_FROM_ABI _Tp +__cxx_atomic_fetch_max(__cxx_atomic_base_impl<_Tp>* __a, _Tp __val, memory_order __order) _NOEXCEPT { + return __c11_atomic_fetch_max( + std::addressof(__a->__a_value), __val, static_cast<__memory_order_underlying_t>(__order)); +} +template ::value, int> = 0> +_LIBCPP_HIDE_FROM_ABI _Tp +__cxx_atomic_fetch_max(__cxx_atomic_base_impl<_Tp>* __a, _Tp __val, memory_order __order) _NOEXCEPT { + _Tp __ret = std::__cxx_atomic_load(__a, memory_order_relaxed); + _Tp __value; + do { + __value = __ret > __val ? __ret : __val; + } while ( + !std::__cxx_atomic_compare_exchange_weak(__a, std::addressof(__ret), __value, __order, memory_order_relaxed)); + return __ret; +} + +template ::value, int> = 0> +_LIBCPP_HIDE_FROM_ABI _Tp +__cxx_atomic_fetch_min(__cxx_atomic_base_impl<_Tp> volatile* __a, _Tp __val, memory_order __order) _NOEXCEPT { + return __c11_atomic_fetch_min( + std::addressof(__a->__a_value), __val, static_cast<__memory_order_underlying_t>(__order)); +} +template ::value, int> = 0> +_LIBCPP_HIDE_FROM_ABI _Tp +__cxx_atomic_fetch_min(__cxx_atomic_base_impl<_Tp> volatile* __a, _Tp __val, memory_order __order) _NOEXCEPT { + _Tp __ret = std::__cxx_atomic_load(__a, memory_order_relaxed); + _Tp __value; + do { + __value = __ret < __val ? __ret : __val; + } while ( + !std::__cxx_atomic_compare_exchange_weak(__a, std::addressof(__ret), __value, __order, memory_order_relaxed)); + return __ret; +} + +template ::value, int> = 0> +_LIBCPP_HIDE_FROM_ABI _Tp +__cxx_atomic_fetch_min(__cxx_atomic_base_impl<_Tp>* __a, _Tp __val, memory_order __order) _NOEXCEPT { + return __c11_atomic_fetch_min( + std::addressof(__a->__a_value), __val, static_cast<__memory_order_underlying_t>(__order)); +} +template ::value, int> = 0> +_LIBCPP_HIDE_FROM_ABI _Tp +__cxx_atomic_fetch_min(__cxx_atomic_base_impl<_Tp>* __a, _Tp __val, memory_order __order) _NOEXCEPT { + _Tp __ret = std::__cxx_atomic_load(__a, memory_order_relaxed); + _Tp __value; + do { + __value = __ret < __val ? __ret : __val; + } while ( + !std::__cxx_atomic_compare_exchange_weak(__a, std::addressof(__ret), __value, __order, memory_order_relaxed)); + return __ret; +} + _LIBCPP_END_NAMESPACE_STD #endif // _LIBCPP___ATOMIC_SUPPORT_C11_H diff --git a/libcxx/include/__atomic/support/gcc.h b/libcxx/include/__atomic/support/gcc.h index 73c1b1c8070a..37c38d25012c 100644 --- a/libcxx/include/__atomic/support/gcc.h +++ b/libcxx/include/__atomic/support/gcc.h @@ -258,6 +258,64 @@ __cxx_atomic_fetch_xor(__cxx_atomic_base_impl<_Tp>* __a, _Tp __pattern, memory_o return __atomic_fetch_xor(std::addressof(__a->__a_value), __pattern, __to_gcc_order(__order)); } +template +_LIBCPP_HIDE_FROM_ABI _Tp +__cxx_atomic_fetch_max(volatile __cxx_atomic_base_impl<_Tp>* __a, _Tp __val, memory_order __order) { +#if __has_builtin(__atomic_fetch_max) + return __atomic_fetch_max(std::addressof(__a->__a_value), __val, __to_gcc_order(__order)); +#else + _Tp __ret = __cxx_atomic_load(__a, memory_order_relaxed); + _Tp __value; + do { + __value = __ret > __val ? __ret : __val; + } while (!__cxx_atomic_compare_exchange_weak(__a, std::addressof(__ret), __value, __order, memory_order_relaxed)); + return __ret; +#endif +} + +template +_LIBCPP_HIDE_FROM_ABI _Tp __cxx_atomic_fetch_max(__cxx_atomic_base_impl<_Tp>* __a, _Tp __val, memory_order __order) { +#if __has_builtin(__atomic_fetch_max) + return __atomic_fetch_max(std::addressof(__a->__a_value), __val, __to_gcc_order(__order)); +#else + _Tp __ret = __cxx_atomic_load(__a, memory_order_relaxed); + _Tp __value; + do { + __value = __ret > __val ? __ret : __val; + } while (!__cxx_atomic_compare_exchange_weak(__a, std::addressof(__ret), __value, __order, memory_order_relaxed)); + return __ret; +#endif +} + +template +_LIBCPP_HIDE_FROM_ABI _Tp +__cxx_atomic_fetch_min(volatile __cxx_atomic_base_impl<_Tp>* __a, _Tp __val, memory_order __order) { +#if __has_builtin(__atomic_fetch_min) + return __atomic_fetch_min(std::addressof(__a->__a_value), __val, __to_gcc_order(__order)); +#else + _Tp __ret = __cxx_atomic_load(__a, memory_order_relaxed); + _Tp __value; + do { + __value = __ret < __val ? __ret : __val; + } while (!__cxx_atomic_compare_exchange_weak(__a, std::addressof(__ret), __value, __order, memory_order_relaxed)); + return __ret; +#endif +} + +template +_LIBCPP_HIDE_FROM_ABI _Tp __cxx_atomic_fetch_min(__cxx_atomic_base_impl<_Tp>* __a, _Tp __val, memory_order __order) { +#if __has_builtin(__atomic_fetch_min) + return __atomic_fetch_min(std::addressof(__a->__a_value), __val, __to_gcc_order(__order)); +#else + _Tp __ret = __cxx_atomic_load(__a, memory_order_relaxed); + _Tp __value; + do { + __value = __ret < __val ? __ret : __val; + } while (!__cxx_atomic_compare_exchange_weak(__a, std::addressof(__ret), __value, __order, memory_order_relaxed)); + return __ret; +#endif +} + #define __cxx_atomic_is_lock_free(__s) __atomic_is_lock_free(__s, 0) _LIBCPP_END_NAMESPACE_STD diff --git a/libcxx/include/atomic b/libcxx/include/atomic index a6a910c2f380..a89987ebd36e 100644 --- a/libcxx/include/atomic +++ b/libcxx/include/atomic @@ -164,6 +164,10 @@ struct atomic integral fetch_or(integral op, memory_order m = memory_order_seq_cst) noexcept; integral fetch_xor(integral op, memory_order m = memory_order_seq_cst) volatile noexcept; integral fetch_xor(integral op, memory_order m = memory_order_seq_cst) noexcept; + integral fetch_max(integral op, memory_order m = memory_order_seq_cst) volatile noexcept; + integral fetch_max(integral op, memory_order m = memory_order_seq_cst) noexcept; + integral fetch_min(integral op, memory_order m = memory_order_seq_cst) volatile noexcept; + integral fetch_min(integral op, memory_order m = memory_order_seq_cst) noexcept; integral operator++(int) volatile noexcept; integral operator++(int) noexcept; @@ -240,6 +244,10 @@ struct atomic T* fetch_add(ptrdiff_t op, memory_order m = memory_order_seq_cst) noexcept; T* fetch_sub(ptrdiff_t op, memory_order m = memory_order_seq_cst) volatile noexcept; T* fetch_sub(ptrdiff_t op, memory_order m = memory_order_seq_cst) noexcept; + T* fetch_max(T*, memory_order = memory_order_seq_cst) volatile noexcept; + T* fetch_max(T*, memory_order = memory_order_seq_cst) noexcept; + T* fetch_min(T*, memory_order = memory_order_seq_cst) volatile noexcept; + T* fetch_min(T*, memory_order = memory_order_seq_cst) noexcept; T* operator++(int) volatile noexcept; T* operator++(int) noexcept; @@ -441,6 +449,26 @@ template template T atomic_fetch_xor_explicit(atomic*, atomic::value_type, memory_order) noexcept; +template + T atomic_fetch_max(volatile atomic*, atomic::value_type) noexcept; +template + T atomic_fetch_max(atomic*, atomic::value_type) noexcept; +template + T atomic_fetch_max_explicit(volatile atomic*, atomic::value_type, + memory_order) noexcept; +template + T atomic_fetch_max_explicit(atomic*, atomic::value_type, + memory_order) noexcept; +template + T atomic_fetch_min(volatile atomic*, atomic::value_type) noexcept; +template + T atomic_fetch_min(atomic*, atomic::value_type) noexcept; +template + T atomic_fetch_min_explicit(volatile atomic*, atomic::value_type, + memory_order) noexcept; +template + T atomic_fetch_min_explicit(atomic*, atomic::value_type, + memory_order) noexcept; template void atomic_wait(const volatile atomic*, atomic::value_type) noexcept; // since C++20 diff --git a/libcxx/include/version b/libcxx/include/version index 4a6b01093641..7545d187ca7f 100644 --- a/libcxx/include/version +++ b/libcxx/include/version @@ -558,7 +558,7 @@ __cpp_lib_void_t 201411L #if _LIBCPP_STD_VER >= 26 # define __cpp_lib_aligned_accessor 202411L // # define __cpp_lib_associative_heterogeneous_insertion 202306L -// # define __cpp_lib_atomic_min_max 202403L +# define __cpp_lib_atomic_min_max 202403L # undef __cpp_lib_atomic_ref # define __cpp_lib_atomic_ref 202603L # undef __cpp_lib_bind_front diff --git a/libcxx/modules/std/atomic.inc b/libcxx/modules/std/atomic.inc index 9a30fb7affe7..38321e1a1bb6 100644 --- a/libcxx/modules/std/atomic.inc +++ b/libcxx/modules/std/atomic.inc @@ -50,6 +50,12 @@ export namespace std { using std::atomic_fetch_sub_explicit _LIBCPP_USING_IF_EXISTS; using std::atomic_fetch_xor _LIBCPP_USING_IF_EXISTS; using std::atomic_fetch_xor_explicit _LIBCPP_USING_IF_EXISTS; +#if _LIBCPP_STD_VER >= 26 + using std::atomic_fetch_max _LIBCPP_USING_IF_EXISTS; + using std::atomic_fetch_max_explicit _LIBCPP_USING_IF_EXISTS; + using std::atomic_fetch_min _LIBCPP_USING_IF_EXISTS; + using std::atomic_fetch_min_explicit _LIBCPP_USING_IF_EXISTS; +#endif using std::atomic_notify_all _LIBCPP_USING_IF_EXISTS; using std::atomic_notify_one _LIBCPP_USING_IF_EXISTS; using std::atomic_wait _LIBCPP_USING_IF_EXISTS; diff --git a/libcxx/test/std/atomics/atomics.ref/fetch_max.pass.cpp b/libcxx/test/std/atomics/atomics.ref/fetch_max.pass.cpp new file mode 100644 index 000000000000..7302eaea570f --- /dev/null +++ b/libcxx/test/std/atomics/atomics.ref/fetch_max.pass.cpp @@ -0,0 +1,78 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++26 +// XFAIL: !has-64-bit-atomics + +// integral-type fetch_max(integral-type, memory_order = memory_order::seq_cst) const noexcept; +// T* fetch_max(T*, memory_order = memory_order::seq_cst) const noexcept; + +#include +#include +#include +#include + +#include "atomic_fetch_max_helper.h" +#include "atomic_helpers.h" +#include "test_macros.h" + +template +concept has_fetch_max = requires(std::atomic_ref const& a, T v) { + { a.fetch_max(v) } -> std::same_as; + { a.fetch_max(v, std::memory_order::relaxed) } -> std::same_as; +}; + +template +struct TestDoesNotHaveFetchMax { + void operator()() const { static_assert(!has_fetch_max); } +}; + +template +struct TestFetchMax { + void operator()() const { + static_assert((std::is_integral_v || std::is_pointer_v) && has_fetch_max); + + if constexpr (std::is_integral_v) { + alignas(std::atomic_ref::required_alignment) T x{}; + std::atomic_ref const a(x); + + auto load = [&]() { return x; }; + auto store = [&](T val) { x = val; }; + auto max = [&](T val, auto order) { return a.fetch_max(val, order); }; + + ASSERT_NOEXCEPT(a.fetch_max(T(0), std::memory_order_seq_cst)); + test_fetch_max_integral(load, store, max); + + } else if constexpr (std::is_pointer_v) { + using U = std::remove_pointer_t; + U arr[5]{}; + alignas(std::atomic_ref::required_alignment) T p{}; + std::atomic_ref const a(p); + + auto load = [&]() { return p; }; + auto store = [&](T val) { p = val; }; + auto max = [&](T val, auto order) { return a.fetch_max(val, order); }; + + ASSERT_NOEXCEPT(a.fetch_max(&arr[0], std::memory_order_seq_cst)); + test_fetch_max_pointer(&arr[0], &arr[2], &arr[4], load, store, max); + } + } +}; + +int main(int, char**) { + TestEachIntegralType()(); + TestEachPointerType()(); + + TestEachFloatingPointType()(); + + TestDoesNotHaveFetchMax()(); + TestDoesNotHaveFetchMax()(); + TestDoesNotHaveFetchMax()(); + + return 0; +} diff --git a/libcxx/test/std/atomics/atomics.ref/fetch_min.pass.cpp b/libcxx/test/std/atomics/atomics.ref/fetch_min.pass.cpp new file mode 100644 index 000000000000..e7f36363ae3a --- /dev/null +++ b/libcxx/test/std/atomics/atomics.ref/fetch_min.pass.cpp @@ -0,0 +1,78 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++26 +// XFAIL: !has-64-bit-atomics + +// integral-type fetch_min(integral-type, memory_order = memory_order::seq_cst) const noexcept; +// T* fetch_min(T*, memory_order = memory_order::seq_cst) const noexcept; + +#include +#include +#include +#include + +#include "atomic_fetch_min_helper.h" +#include "atomic_helpers.h" +#include "test_macros.h" + +template +concept has_fetch_min = requires(std::atomic_ref const& a, T v) { + { a.fetch_min(v) } -> std::same_as; + { a.fetch_min(v, std::memory_order::relaxed) } -> std::same_as; +}; + +template +struct TestDoesNotHaveFetchMin { + void operator()() const { static_assert(!has_fetch_min); } +}; + +template +struct TestFetchMin { + void operator()() const { + static_assert((std::is_integral_v || std::is_pointer_v) && has_fetch_min); + + if constexpr (std::is_integral_v) { + alignas(std::atomic_ref::required_alignment) T x{}; + std::atomic_ref const a(x); + + auto load = [&]() { return x; }; + auto store = [&](T val) { x = val; }; + auto min = [&](T val, auto order) { return a.fetch_min(val, order); }; + + ASSERT_NOEXCEPT(a.fetch_min(T(0), std::memory_order_seq_cst)); + test_fetch_min_integral(load, store, min); + + } else if constexpr (std::is_pointer_v) { + using U = std::remove_pointer_t; + U arr[5]{}; + alignas(std::atomic_ref::required_alignment) T p{}; + std::atomic_ref const a(p); + + auto load = [&]() { return p; }; + auto store = [&](T val) { p = val; }; + auto min = [&](T val, auto order) { return a.fetch_min(val, order); }; + + ASSERT_NOEXCEPT(a.fetch_min(&arr[0], std::memory_order_seq_cst)); + test_fetch_min_pointer(&arr[0], &arr[2], &arr[4], load, store, min); + } + } +}; + +int main(int, char**) { + TestEachIntegralType()(); + TestEachPointerType()(); + + TestEachFloatingPointType()(); + + TestDoesNotHaveFetchMin()(); + TestDoesNotHaveFetchMin()(); + TestDoesNotHaveFetchMin()(); + + return 0; +} diff --git a/libcxx/test/std/atomics/atomics.types.operations/atomics.types.operations.req/atomic_fetch_max.pass.cpp b/libcxx/test/std/atomics/atomics.types.operations/atomics.types.operations.req/atomic_fetch_max.pass.cpp new file mode 100644 index 000000000000..7b2a5715dddd --- /dev/null +++ b/libcxx/test/std/atomics/atomics.types.operations/atomics.types.operations.req/atomic_fetch_max.pass.cpp @@ -0,0 +1,121 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++26 +// XFAIL: !has-64-bit-atomics + +// + +// template +// T atomic_fetch_max(volatile atomic*, typename atomic::value_type) noexcept; +// template +// constexpr T atomic_fetch_max(atomic*, typename atomic::value_type) noexcept; + +#include +#include +#include +#include +#include + +#include "atomic_fetch_max_helper.h" +#include "atomic_helpers.h" +#include "test_macros.h" + +#ifndef TEST_HAS_NO_THREADS +# include + +# include "make_test_thread.h" +#endif + +template class MaybeVolatile = std::type_identity_t> +void test_impl() { + { + MaybeVolatile> a; + auto load = [&]() { return a.load(); }; + auto store = [&](T val) { a.store(val); }; + auto max = [&](T val, auto) { return std::atomic_fetch_max(&a, val); }; + ASSERT_NOEXCEPT(std::atomic_fetch_max(&a, T(0))); + test_fetch_max_integral(load, store, max); + } + +#ifndef TEST_HAS_NO_THREADS + // Concurrent stress test: many threads calling atomic_fetch_max should leave + // the atomic at the global maximum value seen. + { + constexpr auto number_of_threads = 4; + constexpr auto loop = 30; + + MaybeVolatile> at(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) { + std::atomic_fetch_max(&at, T(i * loop + j)); + } + })); + } + + for (auto& thread : threads) { + thread.join(); + } + + const auto times = [](int n) { + T res(0); + for (auto i = 0; i < n; ++i) { + res = std::max(res, i); + } + return res; + }; + + assert(at.load() == times(number_of_threads * loop)); + } +#endif +} + +template +struct TestFn { + void operator()() const { + test_impl(); + if constexpr (std::atomic::is_always_lock_free) { + test_impl(); + } + } +}; + +template class MaybeVolatile = std::type_identity_t> +void test_pointer_impl() { + T arr[5]; + T* p0 = &arr[0]; + T* p2 = &arr[2]; + T* p4 = &arr[4]; + + MaybeVolatile> a; + auto load = [&]() { return a.load(); }; + auto store = [&](T* val) { a.store(val); }; + auto max = [&](T* val, auto) { return std::atomic_fetch_max(&a, val); }; + ASSERT_NOEXCEPT(std::atomic_fetch_max(&a, p0)); + test_fetch_max_pointer(p0, p2, p4, load, store, max); +} + +template +void test_pointer() { + test_pointer_impl(); + if constexpr (std::atomic::is_always_lock_free) { + test_pointer_impl(); + } +} + +int main(int, char**) { + TestEachIntegralType()(); + test_pointer(); + test_pointer(); + + return 0; +} diff --git a/libcxx/test/std/atomics/atomics.types.operations/atomics.types.operations.req/atomic_fetch_max_explicit.pass.cpp b/libcxx/test/std/atomics/atomics.types.operations/atomics.types.operations.req/atomic_fetch_max_explicit.pass.cpp new file mode 100644 index 000000000000..e811f114708c --- /dev/null +++ b/libcxx/test/std/atomics/atomics.types.operations/atomics.types.operations.req/atomic_fetch_max_explicit.pass.cpp @@ -0,0 +1,123 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++26 +// XFAIL: !has-64-bit-atomics + +// + +// template +// T atomic_fetch_max_explicit(volatile atomic*, typename atomic::value_type, +// memory_order) noexcept; +// template +// constexpr T atomic_fetch_max_explicit(atomic*, typename atomic::value_type, +// memory_order) noexcept; + +#include +#include +#include +#include +#include + +#include "atomic_fetch_max_helper.h" +#include "atomic_helpers.h" +#include "test_macros.h" + +#ifndef TEST_HAS_NO_THREADS +# include + +# include "make_test_thread.h" +#endif + +template class MaybeVolatile = std::type_identity_t> +void test_impl() { + { + MaybeVolatile> a; + auto load = [&]() { return a.load(); }; + auto store = [&](T val) { a.store(val); }; + auto max = [&](T val, auto order) { return std::atomic_fetch_max_explicit(&a, val, order); }; + ASSERT_NOEXCEPT(std::atomic_fetch_max_explicit(&a, T(0), std::memory_order_seq_cst)); + test_fetch_max_integral(load, store, max); + } + +#ifndef TEST_HAS_NO_THREADS + // Concurrent stress test: many threads calling atomic_fetch_max_explicit should + // leave the atomic at the global maximum value seen. + { + constexpr auto number_of_threads = 4; + constexpr auto loop = 30; + + MaybeVolatile> at(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) { + std::atomic_fetch_max_explicit(&at, T(i * loop + j), std::memory_order_relaxed); + } + })); + } + + for (auto& thread : threads) { + thread.join(); + } + + const auto times = [](int n) { + T res(0); + for (auto i = 0; i < n; ++i) { + res = std::max(res, i); + } + return res; + }; + + assert(at.load() == times(number_of_threads * loop)); + } +#endif +} + +template +struct TestFn { + void operator()() const { + test_impl(); + if constexpr (std::atomic::is_always_lock_free) { + test_impl(); + } + } +}; + +template class MaybeVolatile = std::type_identity_t> +void test_pointer_impl() { + T arr[5]; + T* p0 = &arr[0]; + T* p2 = &arr[2]; + T* p4 = &arr[4]; + + MaybeVolatile> a; + auto load = [&]() { return a.load(); }; + auto store = [&](T* val) { a.store(val); }; + auto max = [&](T* val, auto order) { return std::atomic_fetch_max_explicit(&a, val, order); }; + ASSERT_NOEXCEPT(std::atomic_fetch_max_explicit(&a, p0, std::memory_order_seq_cst)); + test_fetch_max_pointer(p0, p2, p4, load, store, max); +} + +template +void test_pointer() { + test_pointer_impl(); + if constexpr (std::atomic::is_always_lock_free) { + test_pointer_impl(); + } +} + +int main(int, char**) { + TestEachIntegralType()(); + test_pointer(); + test_pointer(); + + return 0; +} diff --git a/libcxx/test/std/atomics/atomics.types.operations/atomics.types.operations.req/atomic_fetch_min.pass.cpp b/libcxx/test/std/atomics/atomics.types.operations/atomics.types.operations.req/atomic_fetch_min.pass.cpp new file mode 100644 index 000000000000..df40cc7a92c7 --- /dev/null +++ b/libcxx/test/std/atomics/atomics.types.operations/atomics.types.operations.req/atomic_fetch_min.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 +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++26 +// XFAIL: !has-64-bit-atomics + +// + +// template +// T atomic_fetch_min(volatile atomic*, typename atomic::value_type) noexcept; +// template +// constexpr T atomic_fetch_min(atomic*, typename atomic::value_type) noexcept; + +#include +#include +#include +#include +#include +#include + +#include "atomic_fetch_min_helper.h" +#include "atomic_helpers.h" +#include "test_macros.h" + +#ifndef TEST_HAS_NO_THREADS +# include + +# include "make_test_thread.h" +#endif + +template class MaybeVolatile = std::type_identity_t> +void test_impl() { + { + MaybeVolatile> a; + auto load = [&]() { return a.load(); }; + auto store = [&](T val) { a.store(val); }; + auto min = [&](T val, auto) { return std::atomic_fetch_min(&a, val); }; + ASSERT_NOEXCEPT(std::atomic_fetch_min(&a, T(0))); + test_fetch_min_integral(load, store, min); + } + +#ifndef TEST_HAS_NO_THREADS + // Concurrent stress test: many threads calling atomic_fetch_min should leave + // the atomic at the global minimum value seen. + { + constexpr auto number_of_threads = 4; + constexpr auto loop = 30; + + MaybeVolatile> at(std::numeric_limits::max()); + + 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) { + std::atomic_fetch_min(&at, T(i * loop + j)); + } + })); + } + + for (auto& thread : threads) { + thread.join(); + } + + assert(at.load() == T(0)); + } +#endif +} + +template +struct TestFn { + void operator()() const { + test_impl(); + if constexpr (std::atomic::is_always_lock_free) { + test_impl(); + } + } +}; + +template class MaybeVolatile = std::type_identity_t> +void test_pointer_impl() { + T arr[5]; + T* p0 = &arr[0]; + T* p2 = &arr[2]; + T* p4 = &arr[4]; + + MaybeVolatile> a; + auto load = [&]() { return a.load(); }; + auto store = [&](T* val) { a.store(val); }; + auto min = [&](T* val, auto) { return std::atomic_fetch_min(&a, val); }; + ASSERT_NOEXCEPT(std::atomic_fetch_min(&a, p0)); + test_fetch_min_pointer(p0, p2, p4, load, store, min); +} + +template +void test_pointer() { + test_pointer_impl(); + if constexpr (std::atomic::is_always_lock_free) { + test_pointer_impl(); + } +} + +int main(int, char**) { + TestEachIntegralType()(); + test_pointer(); + test_pointer(); + + return 0; +} diff --git a/libcxx/test/std/atomics/atomics.types.operations/atomics.types.operations.req/atomic_fetch_min_explicit.pass.cpp b/libcxx/test/std/atomics/atomics.types.operations/atomics.types.operations.req/atomic_fetch_min_explicit.pass.cpp new file mode 100644 index 000000000000..2295b942b86d --- /dev/null +++ b/libcxx/test/std/atomics/atomics.types.operations/atomics.types.operations.req/atomic_fetch_min_explicit.pass.cpp @@ -0,0 +1,116 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++26 +// XFAIL: !has-64-bit-atomics + +// + +// template +// T atomic_fetch_min_explicit(volatile atomic*, typename atomic::value_type, +// memory_order) noexcept; +// template +// constexpr T atomic_fetch_min_explicit(atomic*, typename atomic::value_type, +// memory_order) noexcept; + +#include +#include +#include +#include +#include +#include + +#include "atomic_fetch_min_helper.h" +#include "atomic_helpers.h" +#include "test_macros.h" + +#ifndef TEST_HAS_NO_THREADS +# include + +# include "make_test_thread.h" +#endif + +template class MaybeVolatile = std::type_identity_t> +void test_impl() { + { + MaybeVolatile> a; + auto load = [&]() { return a.load(); }; + auto store = [&](T val) { a.store(val); }; + auto min = [&](T val, auto order) { return std::atomic_fetch_min_explicit(&a, val, order); }; + ASSERT_NOEXCEPT(std::atomic_fetch_min_explicit(&a, T(0), std::memory_order_seq_cst)); + test_fetch_min_integral(load, store, min); + } + +#ifndef TEST_HAS_NO_THREADS + // Concurrent stress test: many threads calling atomic_fetch_min_explicit should + // leave the atomic at the global minimum value seen. + { + constexpr auto number_of_threads = 4; + constexpr auto loop = 30; + + MaybeVolatile> at(std::numeric_limits::max()); + + 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) { + std::atomic_fetch_min_explicit(&at, T(i * loop + j), std::memory_order_relaxed); + } + })); + } + + for (auto& thread : threads) { + thread.join(); + } + + assert(at.load() == T(0)); + } +#endif +} + +template +struct TestFn { + void operator()() const { + test_impl(); + if constexpr (std::atomic::is_always_lock_free) { + test_impl(); + } + } +}; + +template class MaybeVolatile = std::type_identity_t> +void test_pointer_impl() { + T arr[5]; + T* p0 = &arr[0]; + T* p2 = &arr[2]; + T* p4 = &arr[4]; + + MaybeVolatile> a; + auto load = [&]() { return a.load(); }; + auto store = [&](T* val) { a.store(val); }; + auto min = [&](T* val, auto order) { return std::atomic_fetch_min_explicit(&a, val, order); }; + ASSERT_NOEXCEPT(std::atomic_fetch_min_explicit(&a, p0, std::memory_order_seq_cst)); + test_fetch_min_pointer(p0, p2, p4, load, store, min); +} + +template +void test_pointer() { + test_pointer_impl(); + if constexpr (std::atomic::is_always_lock_free) { + test_pointer_impl(); + } +} + +int main(int, char**) { + TestEachIntegralType()(); + test_pointer(); + test_pointer(); + + return 0; +} diff --git a/libcxx/test/std/language.support/support.limits/support.limits.general/atomic.version.compile.pass.cpp b/libcxx/test/std/language.support/support.limits/support.limits.general/atomic.version.compile.pass.cpp index e62416257a69..aece31d25c24 100644 --- a/libcxx/test/std/language.support/support.limits/support.limits.general/atomic.version.compile.pass.cpp +++ b/libcxx/test/std/language.support/support.limits/support.limits.general/atomic.version.compile.pass.cpp @@ -339,17 +339,11 @@ # error "__cpp_lib_atomic_lock_free_type_aliases should have the value 201907L in c++26" # endif -# if !defined(_LIBCPP_VERSION) -# ifndef __cpp_lib_atomic_min_max -# error "__cpp_lib_atomic_min_max should be defined in c++26" -# endif -# if __cpp_lib_atomic_min_max != 202403L -# error "__cpp_lib_atomic_min_max should have the value 202403L in c++26" -# endif -# else -# ifdef __cpp_lib_atomic_min_max -# error "__cpp_lib_atomic_min_max should not be defined because it is unimplemented in libc++!" -# endif +# ifndef __cpp_lib_atomic_min_max +# error "__cpp_lib_atomic_min_max should be defined in c++26" +# endif +# if __cpp_lib_atomic_min_max != 202403L +# error "__cpp_lib_atomic_min_max should have the value 202403L in c++26" # endif # ifndef __cpp_lib_atomic_ref diff --git a/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp b/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp index f2d74cc99d86..9e987346c599 100644 --- a/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp +++ b/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp @@ -6434,17 +6434,11 @@ # error "__cpp_lib_atomic_lock_free_type_aliases should have the value 201907L in c++26" # endif -# if !defined(_LIBCPP_VERSION) -# ifndef __cpp_lib_atomic_min_max -# error "__cpp_lib_atomic_min_max should be defined in c++26" -# endif -# if __cpp_lib_atomic_min_max != 202403L -# error "__cpp_lib_atomic_min_max should have the value 202403L in c++26" -# endif -# else -# ifdef __cpp_lib_atomic_min_max -# error "__cpp_lib_atomic_min_max should not be defined because it is unimplemented in libc++!" -# endif +# ifndef __cpp_lib_atomic_min_max +# error "__cpp_lib_atomic_min_max should be defined in c++26" +# endif +# if __cpp_lib_atomic_min_max != 202403L +# error "__cpp_lib_atomic_min_max should have the value 202403L in c++26" # endif # ifndef __cpp_lib_atomic_ref diff --git a/libcxx/test/support/atomic_fetch_max_helper.h b/libcxx/test/support/atomic_fetch_max_helper.h new file mode 100644 index 000000000000..ff4bd7aa3891 --- /dev/null +++ b/libcxx/test/support/atomic_fetch_max_helper.h @@ -0,0 +1,147 @@ +//===----------------------------------------------------------------------===// +// +// 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_ATOMIC_FETCH_MAX_HELPER_H +#define TEST_STD_ATOMICS_ATOMIC_FETCH_MAX_HELPER_H + +#include +#include + +#include "test_macros.h" + +// Test fetch_max for integral types +// LoadOp: () -> T - reads current value +// StoreOp: (T) -> void - stores a value +// MaxOp: (T, memory_order) -> T - performs fetch_max operation +template +void test_fetch_max_integral(LoadOp load, StoreOp store, MaxOp max) { + // Test basic fetch_max (update to larger value) + { + store(T(10)); + T old = max(T(20), std::memory_order_seq_cst); + assert(old == T(10)); + assert(load() == T(20)); + } + + // Test with smaller value (no change) + { + store(T(10)); + T old = max(T(5), std::memory_order_seq_cst); + assert(old == T(10)); + assert(load() == T(10)); + } + + // Test different memory orderings + { + store(T(15)); + T old = max(T(25), std::memory_order_seq_cst); + assert(old == T(15)); + assert(load() == T(25)); + } + + { + store(T(10)); + T old = max(T(25), std::memory_order_relaxed); + assert(old == T(10)); + assert(load() == T(25)); + } + + { + store(T(10)); + T old = max(T(30), std::memory_order_acquire); + assert(old == T(10)); + assert(load() == T(30)); + } + + { + store(T(10)); + T old = max(T(15), std::memory_order_release); + assert(old == T(10)); + assert(load() == T(15)); + } + + { + store(T(10)); + T old = max(T(22), std::memory_order_acq_rel); + assert(old == T(10)); + assert(load() == T(22)); + } + + { + store(T(15)); + T old = max(T(35), std::memory_order_relaxed); + assert(old == T(15)); + assert(load() == T(35)); + } + + // Test return type + { + store(T(10)); + static_assert(std::is_same_v); + } +} + +// Test fetch_max for pointer types +// LoadOp: () -> T* - reads current pointer value +// StoreOp: (T*) -> void - stores a pointer value +// MaxOp: (T*, memory_order) -> T* - performs fetch_max operation +template +void test_fetch_max_pointer(T* p0, T* p2, T* p4, LoadOp load, StoreOp store, MaxOp max) { + // Test basic fetch_max (update to larger pointer) + { + store(p2); + T* old = max(p4, std::memory_order_seq_cst); + assert(old == p2); + assert(load() == p4); + } + + // Test with smaller pointer (no change) + { + store(p2); + T* old = max(p0, std::memory_order_seq_cst); + assert(old == p2); + assert(load() == p2); + } + + // Test different memory orderings + { + store(p0); + T* old = max(p2, std::memory_order_seq_cst); + assert(old == p0); + assert(load() == p2); + } + + { + store(p0); + T* old = max(p2, std::memory_order_relaxed); + assert(old == p0); + assert(load() == p2); + } + + { + store(p0); + T* old = max(p4, std::memory_order_acquire); + assert(old == p0); + assert(load() == p4); + } + + { + store(p0); + T* old = max(p4, std::memory_order_release); + assert(old == p0); + assert(load() == p4); + } + + // Test return type + { + store(p2); + static_assert(std::is_same_v); + } +} + +#endif // TEST_STD_ATOMICS_ATOMIC_FETCH_MAX_HELPER_H diff --git a/libcxx/test/support/atomic_fetch_min_helper.h b/libcxx/test/support/atomic_fetch_min_helper.h new file mode 100644 index 000000000000..20a355829611 --- /dev/null +++ b/libcxx/test/support/atomic_fetch_min_helper.h @@ -0,0 +1,147 @@ +//===----------------------------------------------------------------------===// +// +// 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_ATOMIC_FETCH_MIN_HELPER_H +#define TEST_STD_ATOMICS_ATOMIC_FETCH_MIN_HELPER_H + +#include +#include + +#include "test_macros.h" + +// Test fetch_min for integral types +// LoadOp: () -> T - reads current value +// StoreOp: (T) -> void - stores a value +// MinOp: (T, memory_order) -> T - performs fetch_min operation +template +void test_fetch_min_integral(LoadOp load, StoreOp store, MinOp min) { + // Test basic fetch_min (update to smaller value) + { + store(T(10)); + T old = min(T(5), std::memory_order_seq_cst); + assert(old == T(10)); + assert(load() == T(5)); + } + + // Test with larger value (no change) + { + store(T(10)); + T old = min(T(20), std::memory_order_seq_cst); + assert(old == T(10)); + assert(load() == T(10)); + } + + // Test different memory orderings + { + store(T(15)); + T old = min(T(8), std::memory_order_seq_cst); + assert(old == T(15)); + assert(load() == T(8)); + } + + { + store(T(10)); + T old = min(T(3), std::memory_order_relaxed); + assert(old == T(10)); + assert(load() == T(3)); + } + + { + store(T(10)); + T old = min(T(2), std::memory_order_acquire); + assert(old == T(10)); + assert(load() == T(2)); + } + + { + store(T(10)); + T old = min(T(7), std::memory_order_release); + assert(old == T(10)); + assert(load() == T(7)); + } + + { + store(T(10)); + T old = min(T(4), std::memory_order_acq_rel); + assert(old == T(10)); + assert(load() == T(4)); + } + + { + store(T(15)); + T old = min(T(6), std::memory_order_relaxed); + assert(old == T(15)); + assert(load() == T(6)); + } + + // Test return type + { + store(T(10)); + static_assert(std::is_same_v); + } +} + +// Test fetch_min for pointer types +// LoadOp: () -> T* - reads current pointer value +// StoreOp: (T*) -> void - stores a pointer value +// MinOp: (T*, memory_order) -> T* - performs fetch_min operation +template +void test_fetch_min_pointer(T* p0, T* p2, T* p4, LoadOp load, StoreOp store, MinOp min) { + // Test basic fetch_min (update to smaller pointer) + { + store(p2); + T* old = min(p0, std::memory_order_seq_cst); + assert(old == p2); + assert(load() == p0); + } + + // Test with larger pointer (no change) + { + store(p2); + T* old = min(p4, std::memory_order_seq_cst); + assert(old == p2); + assert(load() == p2); + } + + // Test different memory orderings + { + store(p4); + T* old = min(p2, std::memory_order_seq_cst); + assert(old == p4); + assert(load() == p2); + } + + { + store(p4); + T* old = min(p2, std::memory_order_relaxed); + assert(old == p4); + assert(load() == p2); + } + + { + store(p4); + T* old = min(p0, std::memory_order_acquire); + assert(old == p4); + assert(load() == p0); + } + + { + store(p4); + T* old = min(p0, std::memory_order_release); + assert(old == p4); + assert(load() == p0); + } + + // Test return type + { + store(p2); + static_assert(std::is_same_v); + } +} + +#endif // TEST_STD_ATOMICS_ATOMIC_FETCH_MIN_HELPER_H diff --git a/libcxx/utils/generate_feature_test_macro_components.py b/libcxx/utils/generate_feature_test_macro_components.py index f4e16078d5af..13a5dab01b74 100644 --- a/libcxx/utils/generate_feature_test_macro_components.py +++ b/libcxx/utils/generate_feature_test_macro_components.py @@ -191,7 +191,6 @@ feature_test_macros = [ "name": "__cpp_lib_atomic_min_max", "values": {"c++26": 202403}, # P0493R5: Atomic minimum/maximum "headers": ["atomic"], - "unimplemented": True, }, { "name": "__cpp_lib_atomic_ref",