[libcxx-commits] [libcxx] [libc++][In progress] Floating Point Atomic (PR #67799)

via libcxx-commits libcxx-commits at lists.llvm.org
Mon Oct 2 08:16:15 PDT 2023


https://github.com/huixie90 updated https://github.com/llvm/llvm-project/pull/67799

>From 317d84b88baa18ae535d8ded053dc8bb7d4f8599 Mon Sep 17 00:00:00 2001
From: Hui <hui.xie0621 at gmail.com>
Date: Wed, 28 Jun 2023 15:47:39 +0100
Subject: [PATCH 1/2] [libc++][In progress] Floating Point Atomic

- implement P0020R6 Floating Point Atomic

Differential Revision: https://reviews.llvm.org/D153981
---
 libcxx/include/__atomic/atomic.h              | 118 ++++++++++++++++++
 .../atomics.types.float/copy.compile.pass.cpp |  29 +++++
 .../atomics.types.float/ctor.pass.cpp         |  39 ++++++
 .../atomics.types.float/fetch_add.pass.cpp    |  81 ++++++++++++
 .../atomics.types.float/fetch_sub.pass.cpp    |  50 ++++++++
 .../atomics.types.float/lockfree.pass.cpp     |  52 ++++++++
 .../typedef.compile.pass.cpp                  |  25 ++++
 7 files changed, 394 insertions(+)
 create mode 100644 libcxx/test/std/atomics/atomics.types.generic/atomics.types.float/copy.compile.pass.cpp
 create mode 100644 libcxx/test/std/atomics/atomics.types.generic/atomics.types.float/ctor.pass.cpp
 create mode 100644 libcxx/test/std/atomics/atomics.types.generic/atomics.types.float/fetch_add.pass.cpp
 create mode 100644 libcxx/test/std/atomics/atomics.types.generic/atomics.types.float/fetch_sub.pass.cpp
 create mode 100644 libcxx/test/std/atomics/atomics.types.generic/atomics.types.float/lockfree.pass.cpp
 create mode 100644 libcxx/test/std/atomics/atomics.types.generic/atomics.types.float/typedef.compile.pass.cpp

diff --git a/libcxx/include/__atomic/atomic.h b/libcxx/include/__atomic/atomic.h
index 47de6b958a96c1b..7b17c1cbbd98640 100644
--- a/libcxx/include/__atomic/atomic.h
+++ b/libcxx/include/__atomic/atomic.h
@@ -14,11 +14,15 @@
 #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_pointer.h>
+#include <__utility/forward.h>
 #include <cstddef>
+#include <cstdio>
 
 #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
 #  pragma GCC system_header
@@ -136,6 +140,120 @@ struct atomic<_Tp*>
     atomic& operator=(const atomic&) volatile = delete;
 };
 
+#if _LIBCPP_STD_VER >= 20
+template <class _Tp> requires is_floating_point_v<_Tp>
+struct atomic<_Tp>
+    : public __atomic_base<_Tp>
+{
+  private:
+    // see lib/Sema/SemaChecking.cpp approx. line 6748, function IsAllowedValueType
+    // // LLVM Parser does not allow atomicrmw with x86_fp80 type.
+    //if (ValType->isSpecificBuiltinType(BuiltinType::LongDouble) &&
+    //    &Context.getTargetInfo().getLongDoubleFormat() ==
+    //        &llvm::APFloat::x87DoubleExtended())
+
+    // this is platform dependent. How can we get the correct answer?
+    _LIBCPP_HIDE_FROM_ABI static constexpr bool __has_rmw_builtin = !std::is_same_v<_Tp, long double>;
+
+    template <class _This, class _BuiltinOp, class _Operation>
+    _LIBCPP_HIDE_FROM_ABI static _Tp __rmw_op(_This&& __self, _Tp __operand, memory_order __m, _BuiltinOp __builtin_op, _Operation __operation) {
+        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)) {
+                if constexpr (std::is_same_v<_Tp, long double>){
+                    // https://reviews.llvm.org/D53965
+                    // https://bugs.llvm.org/show_bug.cgi?id=48634
+                    // clang bug: __old is not updated on failure for atomic<long double>
+                    __old = __self.load(memory_order_relaxed);
+                }
+                __new = __operation(__old, __operand);
+            }
+            return __old;
+        }
+    }
+
+    template <class _This>
+    _LIBCPP_HIDE_FROM_ABI static _Tp __fetch_add(_This&& __self, _Tp __operand, memory_order __m) {
+        auto __builtin_op = [](auto __a, auto __operand, auto __order){
+            return std::__cxx_atomic_fetch_add(__a, __operand, __order);
+        };
+        return __rmw_op(std::forward<_This>(__self), __operand, __m, __builtin_op, std::plus<>{});
+    }
+
+    template <class _This>
+    _LIBCPP_HIDE_FROM_ABI static _Tp __fetch_sub(_This&& __self, _Tp __operand, memory_order __m) {
+        auto __builtin_op = [](auto __a, auto __operand, auto __order){
+            return std::__cxx_atomic_fetch_sub(__a, __operand, __order);
+        };
+        return __rmw_op(std::forward<_This>(__self), __operand, __m, __builtin_op, std::minus<>{});
+    }
+
+  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
+        {__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 <class _Tp>
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 000000000000000..3984d621ad3ab04
--- /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 <atomic>
+#include <type_traits>
+
+template <class T>
+void test() {
+  static_assert(!std::is_copy_assignable_v<std::atomic<T>>);
+  static_assert(!std::is_copy_constructible_v<std::atomic<T>>);
+  static_assert(!std::is_move_constructible_v<std::atomic<T>>);
+  static_assert(!std::is_move_assignable_v<std::atomic<T>>);
+  static_assert(!std::is_assignable_v<volatile std::atomic<T>&, const std::atomic<T>&>);
+}
+
+template void test<float>();
+template void test<double>();
+template void test<long double>();
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 000000000000000..8fc6ac0f34cf1ed
--- /dev/null
+++ b/libcxx/test/std/atomics/atomics.types.generic/atomics.types.float/ctor.pass.cpp
@@ -0,0 +1,39 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+
+// constexpr atomic() noexcept;
+// constexpr atomic(floating-point-type) noexcept;
+
+#include <atomic>
+#include <cassert>
+#include <concepts>
+
+#include "test_macros.h"
+
+template <class T>
+void test() {
+  // constexpr atomic() noexcept;
+  {
+    constexpr std::atomic<T> a = {};
+    assert(a.load() == T(0));
+  }
+
+  // constexpr atomic(floating-point-type) noexcept;
+  {
+    constexpr std::atomic<T> a = T(5.2);
+    assert(a.load() == T(5.2));
+  }
+}
+
+int main(int, char**) {
+  test<float>();
+  test<double>();
+  test<long double>();
+  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 000000000000000..32f5f4097165ae4
--- /dev/null
+++ b/libcxx/test/std/atomics/atomics.types.generic/atomics.types.float/fetch_add.pass.cpp
@@ -0,0 +1,81 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+
+// 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 <atomic>
+#include <cassert>
+#include <concepts>
+#include <thread>
+#include <vector>
+
+#include "test_macros.h"
+#include "make_test_thread.h"
+
+template <class T>
+void test() {
+  // fetch_add
+  {
+    std::atomic<T> a(3.1);
+    std::same_as<T> decltype(auto) r = a.fetch_add(1.2);
+    assert(r == T(3.1));
+    assert(a.load() == T(3.1) + T(1.2));
+  }
+
+  // fetch_add volatile
+  {
+    volatile std::atomic<T> a(3.1);
+    std::same_as<T> decltype(auto) r = a.fetch_add(1.2);
+    assert(r == T(3.1));
+    assert(a.load() == T(3.1) + T(1.2));
+  }
+
+  // fetch_add concurrent
+  {
+    constexpr auto numberOfThreads = 4;
+    constexpr auto loop            = 1000;
+
+    std::atomic<T> at;
+
+    std::vector<std::thread> threads;
+    threads.reserve(numberOfThreads);
+    for (auto i = 0; i < numberOfThreads; ++i) {
+      threads.emplace_back([&at]() {
+        for (auto j = 0; j < loop; ++j) {
+          at.fetch_add(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, numberOfThreads * loop));
+  }
+}
+
+int main(int, char**) {
+  test<float>();
+  test<double>();
+  test<long double>();
+
+  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 000000000000000..ce3aa57e506acfd
--- /dev/null
+++ b/libcxx/test/std/atomics/atomics.types.generic/atomics.types.float/fetch_sub.pass.cpp
@@ -0,0 +1,50 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+
+// 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;
+
+#include <atomic>
+#include <cassert>
+#include <concepts>
+
+#include "test_macros.h"
+
+template <class T>
+void test() {
+  // fetch_add
+  {
+    std::atomic<T> a(3.1);
+    std::same_as<T> decltype(auto) r = a.fetch_add(1.2);
+    assert(r == T(3.1));
+    assert(a.load() == T(3.1) + T(1.2));
+  }
+
+  // fetch_sub
+  {
+    std::atomic<T> a(3.1);
+    std::same_as<T> decltype(auto) r = a.fetch_sub(1.2);
+    assert(r == T(3.1));
+    assert(a.load() == T(3.1) - T(1.2));
+  }
+}
+
+int main(int, char**) {
+  test<float>();
+  test<double>();
+  test<long double>();
+
+  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 000000000000000..b632df79c330486
--- /dev/null
+++ b/libcxx/test/std/atomics/atomics.types.generic/atomics.types.float/lockfree.pass.cpp
@@ -0,0 +1,52 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+
+//   static constexpr bool is_always_lock_free = implementation-defined;
+//   bool is_lock_free() const volatile noexcept;
+//   bool is_lock_free() const noexcept;
+
+#include <atomic>
+#include <cassert>
+#include <concepts>
+
+#include "test_macros.h"
+
+template <class T>
+concept isLockFreeNoexcept = requires(T t) {
+  { t.is_lock_free() } noexcept;
+};
+
+template <class T>
+void test() {
+  static_assert(isLockFreeNoexcept<const std::atomic<T>>);
+  static_assert(isLockFreeNoexcept<const volatile std::atomic<T>>);
+
+  //   static constexpr bool is_always_lock_free = implementation-defined;
+  { [[maybe_unused]] constexpr std::same_as<const bool> decltype(auto) r = std::atomic<T>::is_always_lock_free; }
+
+  //   bool is_lock_free() const volatile noexcept;
+  {
+    const volatile std::atomic<T> a;
+    [[maybe_unused]] std::same_as<bool> decltype(auto) r = a.is_lock_free();
+  }
+
+  //   bool is_lock_free() const noexcept;
+  {
+    const std::atomic<T> a;
+    [[maybe_unused]] std::same_as<bool> decltype(auto) r = a.is_lock_free();
+  }
+}
+
+int main(int, char**) {
+  test<float>();
+  test<double>();
+  test<long double>();
+
+  return 0;
+}
diff --git a/libcxx/test/std/atomics/atomics.types.generic/atomics.types.float/typedef.compile.pass.cpp b/libcxx/test/std/atomics/atomics.types.generic/atomics.types.float/typedef.compile.pass.cpp
new file mode 100644
index 000000000000000..1046665f1c8fa26
--- /dev/null
+++ b/libcxx/test/std/atomics/atomics.types.generic/atomics.types.float/typedef.compile.pass.cpp
@@ -0,0 +1,25 @@
+//===----------------------------------------------------------------------===//
+//
+// 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;
+
+#include <atomic>
+#include <type_traits>
+
+template <class T>
+void test() {
+  static_assert(std::is_same_v<typename std::atomic<T>::value_type, T>);
+  static_assert(std::is_same_v<typename std::atomic<T>::difference_type, T>);
+}
+
+template void test<float>();
+template void test<double>();
+template void test<long double>();

>From ce4c4f6b4d87dd3e8fe5e7b2fd622c970baaccdf Mon Sep 17 00:00:00 2001
From: Hui <hui.xie0621 at gmail.com>
Date: Mon, 2 Oct 2023 16:16:02 +0100
Subject: [PATCH 2/2] more tests

---
 libcxx/include/__atomic/atomic.h              | 56 ++++++------
 .../atomics.types.float/fetch_add.pass.cpp    | 22 +++--
 .../atomics.types.float/fetch_sub.pass.cpp    | 59 ++++++++++---
 .../operator.minus_equals.pass.cpp            | 85 +++++++++++++++++++
 .../operator.plus_equals.pass.cpp             | 85 +++++++++++++++++++
 ...ompile.pass.cpp => types.compile.pass.cpp} |  3 +
 6 files changed, 260 insertions(+), 50 deletions(-)
 create mode 100644 libcxx/test/std/atomics/atomics.types.generic/atomics.types.float/operator.minus_equals.pass.cpp
 create mode 100644 libcxx/test/std/atomics/atomics.types.generic/atomics.types.float/operator.plus_equals.pass.cpp
 rename libcxx/test/std/atomics/atomics.types.generic/atomics.types.float/{typedef.compile.pass.cpp => types.compile.pass.cpp} (77%)

diff --git a/libcxx/include/__atomic/atomic.h b/libcxx/include/__atomic/atomic.h
index 7b17c1cbbd98640..36cfc1c9bbd0a39 100644
--- a/libcxx/include/__atomic/atomic.h
+++ b/libcxx/include/__atomic/atomic.h
@@ -146,49 +146,43 @@ struct atomic<_Tp>
     : public __atomic_base<_Tp>
 {
   private:
-    // see lib/Sema/SemaChecking.cpp approx. line 6748, function IsAllowedValueType
-    // // LLVM Parser does not allow atomicrmw with x86_fp80 type.
-    //if (ValType->isSpecificBuiltinType(BuiltinType::LongDouble) &&
+    // The builtin __cxx_atomic_fetch_add does not work for
+    // long double on some platforms with fp80 type
+    // There is no way on the libc++ side to test whether it is
+    // ok to use the builtin for a certain type.
+    // Therefore, we do not use the builtin here
+    // 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())
-
-    // this is platform dependent. How can we get the correct answer?
-    _LIBCPP_HIDE_FROM_ABI static constexpr bool __has_rmw_builtin = !std::is_same_v<_Tp, long double>;
-
-    template <class _This, class _BuiltinOp, class _Operation>
-    _LIBCPP_HIDE_FROM_ABI static _Tp __rmw_op(_This&& __self, _Tp __operand, memory_order __m, _BuiltinOp __builtin_op, _Operation __operation) {
-        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)) {
-                if constexpr (std::is_same_v<_Tp, long double>){
-                    // https://reviews.llvm.org/D53965
-                    // https://bugs.llvm.org/show_bug.cgi?id=48634
-                    // clang bug: __old is not updated on failure for atomic<long double>
-                    __old = __self.load(memory_order_relaxed);
-                }
-                __new = __operation(__old, __operand);
+    // For more info
+    // https://reviews.llvm.org/D53965
+
+    template <class _This, class _Operation>
+    _LIBCPP_HIDE_FROM_ABI static _Tp __rmw_op(_This&& __self, _Tp __operand, memory_order __m, _Operation __operation) {
+        _Tp __old = __self.load(memory_order_relaxed);
+        _Tp __new = __operation(__old, __operand);
+        while(!__self.compare_exchange_weak(__old, __new, __m, memory_order_relaxed)) {
+            if constexpr (std::is_same_v<_Tp, long double>){
+                // https://bugs.llvm.org/show_bug.cgi?id=48634
+                // clang bug: __old is not updated on failure for atomic<long double>
+                __old = __self.load(memory_order_relaxed);
             }
-            return __old;
+            __new = __operation(__old, __operand);
         }
+        return __old;
     }
 
     template <class _This>
     _LIBCPP_HIDE_FROM_ABI static _Tp __fetch_add(_This&& __self, _Tp __operand, memory_order __m) {
-        auto __builtin_op = [](auto __a, auto __operand, auto __order){
-            return std::__cxx_atomic_fetch_add(__a, __operand, __order);
-        };
-        return __rmw_op(std::forward<_This>(__self), __operand, __m, __builtin_op, std::plus<>{});
+        return __rmw_op(std::forward<_This>(__self), __operand, __m, std::plus<>{});
     }
 
     template <class _This>
     _LIBCPP_HIDE_FROM_ABI static _Tp __fetch_sub(_This&& __self, _Tp __operand, memory_order __m) {
-        auto __builtin_op = [](auto __a, auto __operand, auto __order){
-            return std::__cxx_atomic_fetch_sub(__a, __operand, __order);
-        };
-        return __rmw_op(std::forward<_This>(__self), __operand, __m, __builtin_op, std::minus<>{});
+        return __rmw_op(std::forward<_This>(__self), __operand, __m, std::minus<>{});
     }
 
   public:
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
index 32f5f4097165ae4..a156364af93e373 100644
--- 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
@@ -21,34 +21,40 @@
 #include "test_macros.h"
 #include "make_test_thread.h"
 
+template <class T>
+concept HasVolatileFetchAdd = requires(volatile std::atomic<T> a, T t) { a.fetch_add(t); };
+
 template <class T>
 void test() {
+  static_assert(noexcept(std::declval<std::atomic<T>&>().fetch_add(T(0))));
+  static_assert(HasVolatileFetchAdd<T> == std::atomic<T>::is_always_lock_free);
+
   // fetch_add
   {
     std::atomic<T> a(3.1);
-    std::same_as<T> decltype(auto) r = a.fetch_add(1.2);
+    std::same_as<T> decltype(auto) r = a.fetch_add(T(1.2));
     assert(r == T(3.1));
     assert(a.load() == T(3.1) + T(1.2));
   }
 
   // fetch_add volatile
-  {
+  if constexpr (std::atomic<T>::is_always_lock_free) {
     volatile std::atomic<T> a(3.1);
-    std::same_as<T> decltype(auto) r = a.fetch_add(1.2);
+    std::same_as<T> decltype(auto) r = a.fetch_add(T(1.2));
     assert(r == T(3.1));
     assert(a.load() == T(3.1) + T(1.2));
   }
 
   // fetch_add concurrent
   {
-    constexpr auto numberOfThreads = 4;
-    constexpr auto loop            = 1000;
+    constexpr auto number_of_threads = 4;
+    constexpr auto loop              = 1000;
 
     std::atomic<T> at;
 
     std::vector<std::thread> threads;
-    threads.reserve(numberOfThreads);
-    for (auto i = 0; i < numberOfThreads; ++i) {
+    threads.reserve(number_of_threads);
+    for (auto i = 0; i < number_of_threads; ++i) {
       threads.emplace_back([&at]() {
         for (auto j = 0; j < loop; ++j) {
           at.fetch_add(T(1.234));
@@ -68,7 +74,7 @@ void test() {
       return res;
     };
 
-    assert(at.load() == times(1.234, numberOfThreads * loop));
+    assert(at.load() == times(1.234, number_of_threads * loop));
   }
 }
 
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
index ce3aa57e506acfd..b37383d0dec5294 100644
--- 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
@@ -7,10 +7,6 @@
 //===----------------------------------------------------------------------===//
 // UNSUPPORTED: c++03, c++11, c++14, c++17
 
-// 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,
@@ -19,26 +15,67 @@
 #include <atomic>
 #include <cassert>
 #include <concepts>
+#include <thread>
+#include <vector>
 
 #include "test_macros.h"
+#include "make_test_thread.h"
+
+template <class T>
+concept HasVolatileFetchSub = requires(volatile std::atomic<T> a, T t) { a.fetch_sub(t); };
 
 template <class T>
 void test() {
-  // fetch_add
+  static_assert(noexcept(std::declval<std::atomic<T>&>().fetch_sub(T(0))));
+  static_assert(HasVolatileFetchSub<T> == std::atomic<T>::is_always_lock_free);
+
+  // fetch_sub
   {
     std::atomic<T> a(3.1);
-    std::same_as<T> decltype(auto) r = a.fetch_add(1.2);
+    std::same_as<T> decltype(auto) r = a.fetch_sub(T(1.2));
     assert(r == T(3.1));
-    assert(a.load() == T(3.1) + T(1.2));
+    assert(a.load() == T(3.1) - T(1.2));
   }
 
-  // fetch_sub
-  {
-    std::atomic<T> a(3.1);
-    std::same_as<T> decltype(auto) r = a.fetch_sub(1.2);
+  // fetch_sub volatile
+  if constexpr (std::atomic<T>::is_always_lock_free) {
+    volatile std::atomic<T> a(3.1);
+    std::same_as<T> decltype(auto) r = a.fetch_sub(T(1.2));
     assert(r == T(3.1));
     assert(a.load() == T(3.1) - T(1.2));
   }
+
+  // fetch_sub concurrent
+  {
+    constexpr auto number_of_threads = 4;
+    constexpr auto loop              = 1000;
+
+    std::atomic<T> at;
+
+    std::vector<std::thread> threads;
+    threads.reserve(number_of_threads);
+    for (auto i = 0; i < number_of_threads; ++i) {
+      threads.emplace_back([&at]() {
+        for (auto j = 0; j < loop; ++j) {
+          at.fetch_sub(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));
+  }
 }
 
 int main(int, char**) {
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 000000000000000..5a04e9e94027d4a
--- /dev/null
+++ b/libcxx/test/std/atomics/atomics.types.generic/atomics.types.float/operator.minus_equals.pass.cpp
@@ -0,0 +1,85 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+
+// floating-point-type operator-=(floating-point-type) volatile noexcept;
+// floating-point-type operator-=(floating-point-type) noexcept;
+
+#include <atomic>
+#include <cassert>
+#include <concepts>
+#include <thread>
+#include <vector>
+
+#include "test_macros.h"
+#include "make_test_thread.h"
+
+template <class T>
+concept HasVolatileMinusEquals = requires(volatile std::atomic<T> a, T t) { a -= t; };
+
+template <class T>
+void test() {
+  static_assert(noexcept(std::declval<std::atomic<T>&>() -= T(0)));
+  static_assert(HasVolatileMinusEquals<T> == std::atomic<T>::is_always_lock_free);
+
+  // -=
+  {
+    std::atomic<T> a(3.1);
+    std::same_as<T> decltype(auto) r = a -= T(1.2);
+    assert(r == T(3.1) - T(1.2));
+    assert(a.load() == T(3.1) - T(1.2));
+  }
+
+  // -= volatile
+  if constexpr (std::atomic<T>::is_always_lock_free) {
+    volatile std::atomic<T> a(3.1);
+    std::same_as<T> decltype(auto) r = a -= T(1.2);
+    assert(r == T(3.1) - T(1.2));
+    assert(a.load() == T(3.1) - T(1.2));
+  }
+
+  // -= concurrent
+  {
+    constexpr auto number_of_threads = 4;
+    constexpr auto loop              = 1000;
+
+    std::atomic<T> at;
+
+    std::vector<std::thread> threads;
+    threads.reserve(number_of_threads);
+    for (auto i = 0; i < number_of_threads; ++i) {
+      threads.emplace_back([&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));
+  }
+}
+
+int main(int, char**) {
+  test<float>();
+  test<double>();
+  test<long double>();
+
+  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 000000000000000..9cace80331736b8
--- /dev/null
+++ b/libcxx/test/std/atomics/atomics.types.generic/atomics.types.float/operator.plus_equals.pass.cpp
@@ -0,0 +1,85 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+
+// floating-point-type operator+=(floating-point-type) volatile noexcept;
+// floating-point-type operator+=(floating-point-type) noexcept;
+
+#include <atomic>
+#include <cassert>
+#include <concepts>
+#include <thread>
+#include <vector>
+
+#include "test_macros.h"
+#include "make_test_thread.h"
+
+template <class T>
+concept HasVolatilePlusEquals = requires(volatile std::atomic<T> a, T t) { a += t; };
+
+template <class T>
+void test() {
+  static_assert(noexcept(std::declval<std::atomic<T>&>() += T(0)));
+  static_assert(HasVolatilePlusEquals<T> == std::atomic<T>::is_always_lock_free);
+
+  // +=
+  {
+    std::atomic<T> a(3.1);
+    std::same_as<T> decltype(auto) r = a += T(1.2);
+    assert(r == T(3.1) + T(1.2));
+    assert(a.load() == T(3.1) + T(1.2));
+  }
+
+  // += volatile
+  if constexpr (std::atomic<T>::is_always_lock_free) {
+    volatile std::atomic<T> a(3.1);
+    std::same_as<T> decltype(auto) r = a += T(1.2);
+    assert(r == T(3.1) + T(1.2));
+    assert(a.load() == T(3.1) + T(1.2));
+  }
+
+  // += concurrent
+  {
+    constexpr auto number_of_threads = 4;
+    constexpr auto loop              = 1000;
+
+    std::atomic<T> at;
+
+    std::vector<std::thread> threads;
+    threads.reserve(number_of_threads);
+    for (auto i = 0; i < number_of_threads; ++i) {
+      threads.emplace_back([&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));
+  }
+}
+
+int main(int, char**) {
+  test<float>();
+  test<double>();
+  test<long double>();
+
+  return 0;
+}
diff --git a/libcxx/test/std/atomics/atomics.types.generic/atomics.types.float/typedef.compile.pass.cpp b/libcxx/test/std/atomics/atomics.types.generic/atomics.types.float/types.compile.pass.cpp
similarity index 77%
rename from libcxx/test/std/atomics/atomics.types.generic/atomics.types.float/typedef.compile.pass.cpp
rename to libcxx/test/std/atomics/atomics.types.generic/atomics.types.float/types.compile.pass.cpp
index 1046665f1c8fa26..1a4e6dfe0b31553 100644
--- a/libcxx/test/std/atomics/atomics.types.generic/atomics.types.float/typedef.compile.pass.cpp
+++ b/libcxx/test/std/atomics/atomics.types.generic/atomics.types.float/types.compile.pass.cpp
@@ -10,6 +10,7 @@
 
 //    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 <atomic>
 #include <type_traits>
@@ -18,6 +19,8 @@ template <class T>
 void test() {
   static_assert(std::is_same_v<typename std::atomic<T>::value_type, T>);
   static_assert(std::is_same_v<typename std::atomic<T>::difference_type, T>);
+  static_assert(std::is_standard_layout_v<std::atomic<T>>);
+  static_assert(std::is_trivially_destructible_v<std::atomic<T>>);
 }
 
 template void test<float>();



More information about the libcxx-commits mailing list