public inbox for libstdc++@gcc.gnu.org
 help / color / mirror / Atom feed
* [PATCH] Want feedback for potential implementation of P2643R2 atomic features
@ 2024-04-14 20:15 Teodor Spæren
  0 siblings, 0 replies; only message in thread
From: Teodor Spæren @ 2024-04-14 20:15 UTC (permalink / raw)
  To: libstdc++

[-- Attachment #1: Type: text/plain, Size: 1416 bytes --]

Hello!

Earlier I sent an email about wanting to try to implement the new 
features described in 
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2024/p2643r2.html

I've now gone and done that. I've added the support for all the commands 
for floats, and integer values, as well as atomic_flag. I've added tests 
for atomic_integer. I will of course finish all the types and add tests 
for all the types, but I wanted to know if this is work that is wanted 
or if it's to early to implement, and I also wanted some feedback.

I have a couple of questions:

1. How should I feature test for this to be enabled or not? For the 
`wait` and `notify` functionality #if __glibcxx_atomic_wait is used,
should I guard this behind a new type of flag?


2. How is the formatting? Is there a command to format all the files?


3. What timeout values should I use for tests? I don't want the waiting 
to take 2seconds, but I also don't want the tests to fail 
intermittently. I've tried to use 60ms buffer now, but this seems like a 
lot to me.

4. Why does "__atomic_wait_address_for_v" take the "old" parameter by 
rvalue reference? Immediately we call a function which takes the 
parameter by value. Is this just an oversight or is there some semantics 
here that I'm not aware of?


I've attached the patches in what I hope is a good format, I hope that 
I've not butchered this completely!

Best regards,
Teodor Spæren

[-- Attachment #2: 0004-Add-tests-for-atomic-integral.patch --]
[-- Type: text/x-patch, Size: 17853 bytes --]

From b0bfe970752e84493e2e79c7244372dad32c29cb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Teodor=20Sp=C3=A6ren?= <teodor@sparen.no>
Date: Sun, 14 Apr 2024 22:08:38 +0200
Subject: [PATCH 4/4] Add tests for atomic integral

---
 .../atomic_integral/extended_wait.cc          | 656 ++++++++++++++++++
 1 file changed, 656 insertions(+)
 create mode 100644 libstdc++-v3/testsuite/29_atomics/atomic_integral/extended_wait.cc

diff --git a/libstdc++-v3/testsuite/29_atomics/atomic_integral/extended_wait.cc b/libstdc++-v3/testsuite/29_atomics/atomic_integral/extended_wait.cc
new file mode 100644
index 00000000000..6b91440f8bd
--- /dev/null
+++ b/libstdc++-v3/testsuite/29_atomics/atomic_integral/extended_wait.cc
@@ -0,0 +1,656 @@
+#include <atomic>
+#include <thread>
+#include <chrono>
+
+// { dg-do run { target c++20 } }
+// { dg-require-gthreads "" }
+// { dg-add-options libatomic }
+// { dg-additional-options "-pthread" { target pthread } }
+
+// Copyright (C) 2020-2024 Free Software Foundation, Inc.
+//
+// This file is part of the GNU ISO C++ Library.  This library is free
+// software; you can redistribute it and/or modify it under the
+// terms of the GNU General Public License as published by the
+// Free Software Foundation; either version 3, or (at your option)
+// any later version.
+
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License along
+// with this library; see the file COPYING3.  If not see
+// <http://www.gnu.org/licenses/>.
+
+#include <testsuite_hooks.h>
+
+struct Timer
+{
+  std::chrono::steady_clock::time_point start{
+    std::chrono::steady_clock::now()
+  };
+
+  [[nodiscard]] auto
+  duration() const
+  {
+    return std::chrono::steady_clock::now() - start;
+  }
+};
+
+
+template<typename T>
+void check()
+{
+  using namespace std::chrono_literals;
+
+  inline constexpr auto ms_buffer = 60ms;
+
+  // Wait value should return immidiatly when it has the right value
+  {
+    const Timer t;
+    std::atomic<T> wow = 34;
+    const auto next = wow.wait_value(100);
+    VERIFY(next == 34);
+    VERIFY(t.duration() < 1ms);
+  }
+
+  // "Wait value should wait until notified atleast"
+  {
+    std::atomic<T> wow = 34;
+    const Timer t;
+    std::jthread outThere([&wow]() {
+      std::this_thread::sleep_for(50ms);
+      wow.store(102, std::memory_order::release);
+      wow.notify_all();
+    });
+
+    auto res = wow.wait_value(34);
+    VERIFY(res == 102);
+    VERIFY(50ms <= t.duration());
+    VERIFY(t.duration() < ms_buffer);
+  }
+
+  // "Wait value should not return on unchanged value."
+  {
+    const Timer t;
+    std::atomic<T> wow = 34;
+    std::jthread outThere([&wow]() {
+      std::this_thread::sleep_for(20ms);
+      wow.store(34);
+      wow.notify_all();
+      std::this_thread::sleep_for(40ms);
+      wow.store(102);
+      wow.notify_all();
+    });
+
+    auto res = wow.wait_value(34);
+    const auto dur = t.duration();
+    VERIFY(res == 102);
+    VERIFY(60ms <= dur);
+    VERIFY(dur < ms_buffer);
+  }
+
+  // "try wait should return right away when it's correct
+  {
+    const Timer t;
+    std::atomic<T> wow = 34;
+    auto res = wow.try_wait(100);
+    VERIFY(res);
+    VERIFY(*res == 34);
+    VERIFY(t.duration() < 1ms);
+  }
+
+  // "try wait should return after a little while when it's not correct
+  {
+    const Timer t;
+    std::atomic<T> wow = 34;
+    auto res = wow.try_wait(34);
+    VERIFY(!res);
+    VERIFY(t.duration() < 100ms);
+  }
+
+  // "WaitFor: No waiting if it's good
+  {
+    const Timer t;
+    std::atomic<T> wow = 34;
+    auto res = wow.wait_for(100, 10s);
+    VERIFY(res);
+    VERIFY(*res == 34);
+    VERIFY(t.duration() < 1ms);
+  }
+
+  // "WaitFor: Max waiting if it doesn't change
+  {
+    const Timer t;
+    std::atomic<T> wow = 34;
+    auto res = wow.wait_for(34, 50ms);
+    VERIFY(!res);
+    VERIFY(t.duration() < 60ms);
+    VERIFY(50ms <= t.duration());
+  }
+
+  // "WaitFor: Will wakeup if changed.
+  {
+    const Timer t;
+    std::atomic<T> wow = 34;
+
+    std::jthread outThere([&wow]() {
+      std::this_thread::sleep_for(50ms);
+      wow.store(102);
+      wow.notify_all();
+    });
+
+    auto res = wow.wait_for(34, 5s);
+    VERIFY(res);
+    VERIFY(*res == 102);
+    VERIFY(50ms <= t.duration());
+    VERIFY(t.duration() < 70ms);
+  }
+
+  // "WaitFor: Will not return if value is changed to same value.
+  {
+    const Timer t;
+    std::atomic<T> wow = 34;
+
+    std::jthread outThere([&wow]() {
+      for (int i = 0; i < 2; i++)
+        {
+          std::this_thread::sleep_for(20ms);
+          wow.store(34);
+          wow.notify_all();
+        }
+    });
+
+    auto res = wow.wait_for(34, 50ms);
+    auto taken = t.duration();
+    VERIFY(!res);
+    VERIFY(50ms <= taken);
+    VERIFY(taken < 100ms);
+  }
+
+  // "WaitUntil: Will return right away if the value is the same.
+  {
+    std::atomic<T> wow = 34;
+
+    const auto deadLine = std::chrono::system_clock::now() + 100ms;
+    auto res = wow.wait_until(102, deadLine);
+    const auto finished = std::chrono::system_clock::now();
+    VERIFY(res);
+    VERIFY(*res == 34);
+    VERIFY(finished <= deadLine);
+  }
+
+  // "WaitUntil: Will not return before deadline, if not hit
+  {
+    std::atomic<T> wow = 34;
+    const auto deadLine = std::chrono::system_clock::now() + 100ms;
+    auto res = wow.wait_until(34, deadLine);
+    const auto finished = std::chrono::system_clock::now();
+    VERIFY(!res);
+    VERIFY(deadLine <= finished);
+    VERIFY(finished < deadLine + 40ms);
+  }
+
+  // "WaitUntil: Will return if changed, if not hit
+  {
+    std::atomic<T> wow = 34;
+    const auto deadLine = std::chrono::system_clock::now() + 100ms;
+
+    std::jthread outThere([&wow]() {
+      std::this_thread::sleep_for(20ms);
+      wow.store(102);
+      wow.notify_all();
+    });
+    auto res = wow.wait_until(34, deadLine);
+    const auto finished = std::chrono::system_clock::now();
+    VERIFY(res);
+    VERIFY(*res == 102);
+    VERIFY(finished < deadLine);
+  }
+
+  // "WaitUntil: Will not return if not changed, if not hit
+  {
+    std::atomic<T> wow = 34;
+    const auto deadline = std::chrono::system_clock::now() + 100ms;
+
+    std::jthread outThere([&wow]() {
+      for (int i = 0; i < 2; i++)
+        {
+          std::this_thread::sleep_for(20ms);
+          wow.store(34);
+          wow.notify_all();
+        }
+    });
+
+    auto res = wow.wait_until(34, deadline);
+    const auto finished = std::chrono::system_clock::now();
+    VERIFY(!res);
+    VERIFY(deadline <= finished);
+    VERIFY(finished < deadline + 40ms);
+  }
+
+  // "Wait value should return immidiatly when it has the right value
+  {
+    std::atomic<T> wow = 34;
+    const Timer t;
+    const auto next = wow.wait_value(100);
+    VERIFY(next == 34);
+    VERIFY(t.duration() < 1ms);
+  }
+
+  // "Wait value should wait until notified atleast
+  {
+    std::atomic<T> wow = 34;
+    const Timer t;
+    std::jthread outThere([&wow]() {
+      std::this_thread::sleep_for(50ms);
+      wow.store(102, std::memory_order::release);
+      wow.notify_all();
+    });
+
+    auto res = wow.wait_value(34);
+    VERIFY(res == 102);
+    VERIFY(50ms <= t.duration());
+    VERIFY(t.duration() < 60ms);
+  }
+
+  // "Wait value should not return on unchanged value.
+  {
+    std::atomic<T> wow = 34;
+    const Timer t;
+    std::jthread outThere([&wow]() {
+      std::this_thread::sleep_for(20ms);
+      wow.store(34);
+      wow.notify_all();
+      std::this_thread::sleep_for(40ms);
+      wow.store(102);
+      wow.notify_all();
+    });
+
+    auto res = wow.wait_value(34);
+    const auto dur = t.duration();
+    VERIFY(res == 102);
+    VERIFY(60ms <= dur);
+    VERIFY(dur < 70ms);
+  }
+
+  // "try wait should return right away when it's correct
+  {
+    std::atomic<T> wow = 34;
+    const Timer t;
+    auto res = wow.try_wait(100);
+    VERIFY(res);
+    VERIFY(*res == 34);
+    VERIFY(t.duration() < 1ms);
+  }
+
+  // "try wait should return after a little while when it's not correct
+  {
+    std::atomic<T> wow = 34;
+    const Timer t;
+    auto res = wow.try_wait(34);
+    VERIFY(!res);
+    VERIFY(t.duration() < 100ms);
+  }
+
+  // "WaitFor: No waiting if it's good
+  {
+    std::atomic<T> wow = 34;
+    const Timer t;
+    auto res = wow.wait_for(100, 10s);
+    VERIFY(res);
+    VERIFY(*res == 34);
+    VERIFY(t.duration() < 1ms);
+  }
+
+  // "WaitFor: Max waiting if it doesn't change
+  {
+    std::atomic<T> wow = 34;
+    const Timer t;
+    auto res = wow.wait_for(34, 50ms);
+    VERIFY(!res);
+    VERIFY(t.duration() < 60ms);
+    VERIFY(50ms <= t.duration());
+  }
+
+  // "WaitFor: Will wakeup if changed.
+  {
+    std::atomic<T> wow = 34;
+    const Timer t;
+
+    std::jthread outThere([&wow]() {
+      std::this_thread::sleep_for(50ms);
+      wow.store(102);
+      wow.notify_all();
+    });
+
+    auto res = wow.wait_for(34, 5s);
+    VERIFY(res);
+    VERIFY(*res == 102);
+    VERIFY(50ms <= t.duration());
+    VERIFY(t.duration() < 70ms);
+  }
+
+  // "WaitFor: Will not return if value is changed to same value.
+  {
+    std::atomic<T> wow = 34;
+    const Timer t;
+
+    std::jthread outThere([&wow]() {
+      for (int i = 0; i < 2; i++)
+        {
+          std::this_thread::sleep_for(20ms);
+          wow.store(34);
+          wow.notify_all();
+        }
+    });
+
+    auto res = wow.wait_for(34, 50ms);
+    auto taken = t.duration();
+    VERIFY(!res);
+    VERIFY(50ms <= taken);
+    VERIFY(taken < 100ms);
+  }
+
+  // "WaitUntil: Will return right away if the value is the same.
+  {
+    std::atomic<T> wow = 34;
+    const auto deadLine = std::chrono::system_clock::now() + 100ms;
+    auto res = wow.wait_until(102, deadLine);
+    const auto finished = std::chrono::system_clock::now();
+    VERIFY(res);
+    VERIFY(*res == 34);
+    VERIFY(finished <= deadLine);
+  }
+
+  // "WaitUntil: Will not return before deadline, if not hit
+  {
+    std::atomic<T> wow = 34;
+    const auto deadLine = std::chrono::system_clock::now() + 100ms;
+    auto res = wow.wait_until(34, deadLine);
+    const auto finished = std::chrono::system_clock::now();
+    VERIFY(!res);
+    VERIFY(deadLine <= finished);
+    VERIFY(finished < deadLine + 40ms);
+  }
+
+  // "WaitUntil: Will return if changed, if not hit
+  {
+    std::atomic<T> wow = 34;
+    const auto deadLine = std::chrono::system_clock::now() + 100ms;
+
+    std::jthread outThere([&wow]() {
+      std::this_thread::sleep_for(20ms);
+      wow.store(102);
+      wow.notify_all();
+    });
+    auto res = wow.wait_until(34, deadLine);
+    const auto finished = std::chrono::system_clock::now();
+    VERIFY(res);
+    VERIFY(*res == 102);
+    VERIFY(finished < deadLine);
+  }
+
+  // "WaitUntil: Will not return if not changed, if not hit
+  {
+    std::atomic<T> wow = 34;
+    const auto deadline = std::chrono::system_clock::now() + 100ms;
+
+    std::jthread outThere([&wow]() {
+      for (int i = 0; i < 2; i++)
+        {
+          std::this_thread::sleep_for(20ms);
+          wow.store(34);
+          wow.notify_all();
+        }
+    });
+
+    auto res = wow.wait_until(34, deadline);
+    const auto finished = std::chrono::system_clock::now();
+    VERIFY(!res);
+    VERIFY(deadline <= finished);
+    VERIFY(finished < deadline + 40ms);
+  }
+
+  // Predicate api
+
+
+  // Wait predicate should return immidiatly when it has the right value
+  {
+    std::atomic<T> wow = 34;
+    const Timer t;
+    const auto next
+        = wow.wait_with_predicate([](const auto in) { return in < 40; });
+    VERIFY(next == 34);
+    VERIFY(t.duration() < 1ms);
+  }
+
+  // "Wait predicate should wait until notified atleast
+  {
+    std::atomic<T> wow = 34;
+    const Timer t;
+    std::jthread outThere([&wow]() {
+      std::this_thread::sleep_for(50ms);
+      wow.store(102, std::memory_order::release);
+      wow.notify_all();
+    });
+
+    auto res = wow.wait_with_predicate([](const auto in) { return 100 < in; });
+    VERIFY(res == 102);
+    VERIFY(50ms <= t.duration());
+    VERIFY(t.duration() < 60ms);
+  }
+
+  // "Wait predicate should not return on unchanged value.
+  {
+    std::atomic<T> wow = 34;
+
+    const Timer t;
+    std::jthread outThere([&wow]() {
+      std::this_thread::sleep_for(20ms);
+      wow.store(34);
+      wow.notify_all();
+      std::this_thread::sleep_for(40ms);
+      wow.store(102);
+      wow.notify_all();
+    });
+
+    auto res = wow.wait_with_predicate([](const auto in) { return in != 34; });
+    const auto dur = t.duration();
+    VERIFY(res == 102);
+    VERIFY(60ms <= dur);
+    VERIFY(dur < 70ms);
+  }
+
+  // "try wait predicate should return right away when it's correct
+  {
+    std::atomic<T> wow = 34;
+    const Timer t;
+    auto res
+        = wow.try_wait_with_predicate([](const auto in) { return in != 100; });
+    VERIFY(res);
+    VERIFY(*res == 34);
+    VERIFY(t.duration() < 1ms);
+  }
+
+  // "try wait predicate should return after a little while when it's not correct
+  {
+    std::atomic<T> wow = 34;
+    const Timer t;
+    auto res
+        = wow.try_wait_with_predicate([](const auto in) { return in != 34; });
+    VERIFY(!res);
+    VERIFY(t.duration() < 100ms);
+  }
+
+  // WaitFor predicate : No waiting if it's good
+  {
+    std::atomic<T> wow = 34;
+    const Timer t;
+    auto res = wow.wait_for_with_predicate(
+        [](const auto in) { return in != 100; }, 10s);
+    VERIFY(res);
+    VERIFY(*res == 34);
+    VERIFY(t.duration() < 1ms);
+  }
+
+  // WaitFor predicate : Max waiting if it doesn't change
+  {
+    std::atomic<T> wow = 34;
+    const Timer t;
+    auto res = wow.wait_for_with_predicate(
+        [](const auto in) { return 50 < in; }, 50ms);
+    VERIFY(!res);
+    VERIFY(t.duration() < 60ms);
+    VERIFY(50ms <= t.duration());
+  }
+
+  // WaitFor predicate : Will wakeup if changed.
+  {
+    std::atomic<T> wow = 34;
+    const Timer t;
+
+    std::jthread outThere([&wow]() {
+      std::this_thread::sleep_for(50ms);
+      wow.store(102);
+      wow.notify_all();
+    });
+
+    auto res = wow.wait_for_with_predicate(
+        [](const auto in) { return 100 < in; }, 5s);
+    VERIFY(res);
+    VERIFY(*res == 102);
+    VERIFY(50ms <= t.duration());
+    VERIFY(t.duration() < 70ms);
+  }
+
+  // WaitFor predicate : Will not return if value is changed to same value.
+  {
+    std::atomic<T> wow = 34;
+    const Timer t;
+
+    std::jthread outThere([&wow]() {
+      for (int i = 0; i < 2; i++)
+        {
+          std::this_thread::sleep_for(20ms);
+          wow.store(34);
+          wow.notify_all();
+        }
+    });
+
+    auto res = wow.wait_for_with_predicate(
+        [](const auto in) { return in < 30; }, 50ms);
+    auto taken = t.duration();
+    VERIFY(!res);
+    VERIFY(50ms <= taken);
+    VERIFY(taken < 100ms);
+  }
+
+  // WaitUntil predicate : Will return right away if the value is the same.
+  {
+    std::atomic<T> wow = 34;
+    const auto deadLine = std::chrono::system_clock::now() + 100ms;
+    auto res = wow.wait_until_with_predicate(
+        [](const auto in) { return in != 102; }, deadLine);
+    const auto finished = std::chrono::system_clock::now();
+    VERIFY(res);
+    VERIFY(*res == 34);
+    VERIFY(finished <= deadLine);
+  }
+
+  // WaitUntil predicate : Will not return before deadline, if not hit
+  {
+    std::atomic<T> wow = 34;
+    const auto deadLine = std::chrono::system_clock::now() + 100ms;
+    auto res = wow.wait_until_with_predicate([](const auto in)
+    {
+      return 40 < in;
+    }, deadLine);
+    const auto finished = std::chrono::system_clock::now();
+    VERIFY(!res);
+    VERIFY(deadLine <= finished);
+    VERIFY(finished < deadLine + 40ms);
+  }
+
+  // WaitUntil predicate: Will return if changed, if not hit
+  {
+    std::atomic<T> wow = 34;
+    const auto deadLine = std::chrono::system_clock::now() + 100ms;
+
+    std::jthread outThere([&wow]() {
+      std::this_thread::sleep_for(20ms);
+      wow.store(102);
+      wow.notify_all();
+    });
+
+    auto res = wow.wait_until_with_predicate([](const auto in)
+    {
+      return 100 < in;
+    }, deadLine);
+    const auto finished = std::chrono::system_clock::now();
+    VERIFY(res);
+    VERIFY(*res == 102);
+    VERIFY(finished < deadLine);
+  }
+
+  // WaitUntil predicate: Will not return if not changed, if not hit
+  {
+    std::atomic<T> wow = 34;
+    const auto deadline = std::chrono::system_clock::now() + 100ms;
+
+    std::jthread outThere([&wow]() {
+      for (int i = 0; i < 2; i++)
+        {
+          std::this_thread::sleep_for(20ms);
+          wow.store(34);
+          wow.notify_all();
+        }
+    });
+
+    auto res = wow.wait_until_with_predicate([](const auto in)
+    {
+      return 40 < in;
+    }, deadline);
+    const auto finished = std::chrono::system_clock::now();
+    VERIFY(!res);
+    VERIFY(deadline <= finished);
+    VERIFY(finished < deadline + ms_buffer);
+  }
+
+}
+
+
+int
+main ()
+{
+  // check<bool> bb;
+  check<char>();
+  check<signed char>();
+  check<unsigned char>();
+  check<short>();
+  check<unsigned short>();
+  check<int>();
+  check<unsigned int>();
+  check<long>();
+  check<unsigned long>();
+  check<long long>();
+  check<unsigned long long>();
+
+  check<wchar_t>();
+  check<char8_t>();
+  check<char16_t>();
+  check<char32_t>();
+
+  check<int8_t>();
+  check<int16_t>();
+  check<int32_t>();
+  check<int64_t>();
+
+  check<uint8_t>();
+  check<uint16_t>();
+  check<uint32_t>();
+  check<uint64_t>();
+  return 0;
+}
-- 
2.44.0


[-- Attachment #3: 0003-Add-atomic-wait-for-floating-points.patch --]
[-- Type: text/x-patch, Size: 4955 bytes --]

From 81e2424d093148d7ba05a471d39b51e5f390aafb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Teodor=20Sp=C3=A6ren?= <teodor@sparen.no>
Date: Wed, 10 Apr 2024 17:45:11 +0200
Subject: [PATCH 3/4] Add atomic wait for floating points.

---
 libstdc++-v3/include/bits/atomic_base.h | 107 ++++++++++++++++++++++++
 1 file changed, 107 insertions(+)

diff --git a/libstdc++-v3/include/bits/atomic_base.h b/libstdc++-v3/include/bits/atomic_base.h
index 8a2ed65eaf2..50037a84ed2 100644
--- a/libstdc++-v3/include/bits/atomic_base.h
+++ b/libstdc++-v3/include/bits/atomic_base.h
@@ -1656,6 +1656,113 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       { __atomic_impl::notify_all(&_M_fp); }
 
       // TODO add const volatile overload
+
+  	_GLIBCXX_ALWAYS_INLINE _Fp
+      wait_value(_Fp __old,
+                 memory_order __m = memory_order_seq_cst) const noexcept {
+      	return this->wait_with_predicate([__old](const auto __cur) {
+      		// TODO(rHermes): consider doing atomic comparisons here?
+      		return __old != __cur;
+      	}, __m);
+      }
+
+      template <class __Pred> requires std::predicate<__Pred, _Fp>
+      _GLIBCXX_ALWAYS_INLINE _Fp
+      wait_with_predicate(__Pred &&__stop_waiting,
+                          memory_order __m = memory_order_seq_cst) const {
+        _Fp __ret;
+        std::__atomic_wait_address(&_M_fp, [__m, this, &__stop_waiting, &__ret] {
+          __ret = this->load(__m);
+          return std::invoke(__stop_waiting, __ret);
+        });
+        return __ret;
+      }
+
+      template <class __Rep, class __Period>
+      _GLIBCXX_ALWAYS_INLINE std::optional<_Fp>
+      wait_for(_Fp __old,
+               const std::chrono::duration<__Rep, __Period> &__dur,
+               memory_order __m = memory_order_seq_cst) const {
+      	// the reson for this, is that we need to ensure that the predicate
+      	// is called, so we are sure the value has changed, and we are able to
+      	// return it.
+      	return this->wait_for_with_predicate([__old](_Fp __cur) {
+      		// TODO(rHermes): consider doing atomic comparisons here?
+      		return __old != __cur;
+      	}, __dur, __m);
+      }
+
+      template <class __Clock, class __Duration>
+      _GLIBCXX_ALWAYS_INLINE std::optional<_Fp>
+      wait_until(_Fp __old,
+                 const std::chrono::time_point<__Clock, __Duration> &__deadline,
+                 memory_order __m = memory_order_seq_cst) const {
+      	return this->wait_until_with_predicate([__old](const auto __cur) {
+      		// TODO(rHermes): consider doing atomic comparisons here?
+      		return __old != __cur;
+      	}, __deadline, __m);
+      }
+
+      _GLIBCXX_ALWAYS_INLINE std::optional<_Fp>
+      try_wait(_Fp __old,
+               memory_order __m = memory_order_seq_cst) const noexcept {
+        // TODO(rHermes): This is really not a good default, I need to
+        // discuss withsomeone smarter
+        return wait_for(__old, std::chrono::milliseconds(10), __m);
+      }
+
+      template <class __Pred, class __Rep, class __Period>
+        requires std::predicate<__Pred, _Fp>
+      _GLIBCXX_ALWAYS_INLINE std::optional<_Fp> wait_for_with_predicate(
+          __Pred &&__stop_waiting,
+          const std::chrono::duration<__Rep, __Period> &__dur,
+          memory_order __m = memory_order_seq_cst) const {
+        _Fp __ret;
+        auto __succeeded = std::__atomic_wait_address_for(
+            &_M_fp,
+            [__m, this, &__stop_waiting, &__ret] {
+              __ret = this->load(__m);
+              return std::invoke(__stop_waiting, __ret);
+            },
+            __dur);
+
+        if (!__succeeded)
+          return std::nullopt;
+
+        return __ret;
+      }
+
+      template <class __Pred>
+        requires std::predicate<__Pred, _Fp>
+      _GLIBCXX_ALWAYS_INLINE std::optional<_Fp>
+      try_wait_with_predicate(__Pred &&__stop_waiting,
+                              memory_order __m = memory_order_seq_cst) const {
+        return wait_for_with_predicate(std::forward<__Pred>(__stop_waiting),
+                                       std::chrono::milliseconds(10), __m);
+      }
+
+      template <class __Pred, class __Clock, class __Duration>
+        requires std::predicate<__Pred, _Fp>
+      _GLIBCXX_ALWAYS_INLINE std::optional<_Fp>
+      wait_until_with_predicate(
+          __Pred &&__stop_waiting,
+          const std::chrono::time_point<__Clock, __Duration> &__deadline,
+          memory_order __m = memory_order_seq_cst) const {
+        _Fp __ret;
+        auto __succeeded = std::__atomic_wait_address_until(
+            &_M_fp,
+            [__m, this, &__stop_waiting, &__ret] {
+              __ret = this->load(__m);
+              return std::invoke(__stop_waiting, __ret);
+            },
+            __deadline);
+
+        if (!__succeeded)
+          return std::nullopt;
+
+        return __ret;
+      }
+
 #endif // __glibcxx_atomic_wait
 
       value_type
-- 
2.44.0


[-- Attachment #4: 0002-Add-atomic-waits-to-atomic_flag.patch --]
[-- Type: text/x-patch, Size: 6187 bytes --]

From 184b9839440e63c4074634668fdea1809c6a29a9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Teodor=20Sp=C3=A6ren?= <teodor@sparen.no>
Date: Wed, 10 Apr 2024 12:11:50 +0200
Subject: [PATCH 2/4] Add atomic waits to atomic_flag

---
 libstdc++-v3/include/bits/atomic_base.h | 111 +++++++++++++++++++++---
 1 file changed, 100 insertions(+), 11 deletions(-)

diff --git a/libstdc++-v3/include/bits/atomic_base.h b/libstdc++-v3/include/bits/atomic_base.h
index 3e12cc917b5..8a2ed65eaf2 100644
--- a/libstdc++-v3/include/bits/atomic_base.h
+++ b/libstdc++-v3/include/bits/atomic_base.h
@@ -280,16 +280,104 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
     notify_all() noexcept
     { std::__atomic_notify_address(&_M_i, true); }
 
-    _GLIBCXX_ALWAYS_INLINE bool
-    wait_value(bool __old,
+      template <class __Pred> requires std::predicate<__Pred, bool>
+      _GLIBCXX_ALWAYS_INLINE bool
+      wait_with_predicate(__Pred &&__stop_waiting,
+                          memory_order __m = memory_order_seq_cst) const {
+        bool __ret;
+        std::__atomic_wait_address(&_M_i, [__m, this, &__stop_waiting, &__ret] {
+          __ret = this->test(__m);
+          return std::invoke(__stop_waiting, __ret);
+        });
+        return __ret;
+      }
+
+      template <class __Rep, class __Period>
+      _GLIBCXX_ALWAYS_INLINE std::optional<bool>
+      wait_for(bool __old,
+               const std::chrono::duration<__Rep, __Period> &__dur,
+               memory_order __m = memory_order_seq_cst) const {
+      	// the reson for this, is that we need to ensure that the predicate
+      	// is called, so we are sure the value has changed, and we are able to
+      	// return it.
+      	return this->wait_for_with_predicate([__old](const auto __cur) {
+      		// TODO(rHermes): consider doing atomic comparisons here?
+      		return __old != __cur;
+      	}, __dur, __m);
+      }
+
+      template <class __Clock, class __Duration>
+      _GLIBCXX_ALWAYS_INLINE std::optional<bool>
+      wait_until(bool __old,
+                 const std::chrono::time_point<__Clock, __Duration> &__deadline,
+                 memory_order __m = memory_order_seq_cst) const {
+      	return this->wait_until_with_predicate([__old](const auto __cur) {
+      		// TODO(rHermes): consider doing atomic comparisons here?
+      		return __old != __cur;
+      	}, __deadline, __m);
+      }
+
+      _GLIBCXX_ALWAYS_INLINE std::optional<bool>
+      try_wait(bool __old,
                memory_order __m = memory_order_seq_cst) const noexcept {
-      bool __ret;
-      std::__atomic_wait_address_v(&_M_i, __old, [__m, this, &__ret] {
-        __ret = __atomic_load_n(&_M_i, int(__m));
+        // TODO(rHermes): This is really not a good default, I need to
+        // discuss withsomeone smarter
+        return wait_for(__old, std::chrono::milliseconds(10), __m);
+      }
+
+      template <class __Pred, class __Rep, class __Period>
+        requires std::predicate<__Pred, bool>
+      _GLIBCXX_ALWAYS_INLINE std::optional<bool> wait_for_with_predicate(
+          __Pred &&__stop_waiting,
+          const std::chrono::duration<__Rep, __Period> &__dur,
+          memory_order __m = memory_order_seq_cst) const {
+        bool __ret;
+        auto __succeeded = std::__atomic_wait_address_for(
+            &_M_i,
+            [__m, this, &__stop_waiting, &__ret] {
+              __ret = this->test(__m);
+              return std::invoke(__stop_waiting, __ret);
+            },
+            __dur);
+
+        if (!__succeeded)
+          return std::nullopt;
+
         return __ret;
-      });
-      return __ret;
-    }
+      }
+
+      template <class __Pred>
+        requires std::predicate<__Pred, bool>
+      _GLIBCXX_ALWAYS_INLINE std::optional<bool>
+      try_wait_with_predicate(__Pred &&__stop_waiting,
+                              memory_order __m = memory_order_seq_cst) const {
+
+    	// TODO(rHermes): This needs to be changed out with an actual good value.
+        return wait_for_with_predicate(std::forward<__Pred>(__stop_waiting),
+                                       std::chrono::milliseconds(10), __m);
+      }
+
+      template <class __Pred, class __Clock, class __Duration>
+        requires std::predicate<__Pred, bool>
+      _GLIBCXX_ALWAYS_INLINE std::optional<bool>
+      wait_until_with_predicate(
+          __Pred &&__stop_waiting,
+          const std::chrono::time_point<__Clock, __Duration> &__deadline,
+          memory_order __m = memory_order_seq_cst) const {
+        bool __ret;
+        auto __succeeded = std::__atomic_wait_address_until(
+            &_M_i,
+            [__m, this, &__stop_waiting, &__ret] {
+              __ret = this->test(__m);
+              return std::invoke(__stop_waiting, __ret);
+            },
+            __deadline);
+
+        if (!__succeeded)
+          return std::nullopt;
+
+        return __ret;
+      }
 
     // TODO add const volatile overload
 #endif // __glibcxx_atomic_wait
@@ -694,7 +782,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       }
 
       template <class __Pred, class __Rep, class __Period>
-  	requires std::predicate<__Pred, __int_type>
+        requires std::predicate<__Pred, __int_type>
       _GLIBCXX_ALWAYS_INLINE std::optional<__int_type> wait_for_with_predicate(
           __Pred &&__stop_waiting,
           const std::chrono::duration<__Rep, __Period> &__dur,
@@ -714,7 +802,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
         return __ret;
       }
 
-      template <class __Pred> requires std::predicate<__Pred, __int_type>
+      template <class __Pred>
+        requires std::predicate<__Pred, __int_type>
       _GLIBCXX_ALWAYS_INLINE std::optional<__int_type>
       try_wait_with_predicate(__Pred &&__stop_waiting,
                               memory_order __m = memory_order_seq_cst) const {
@@ -723,7 +812,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       }
 
       template <class __Pred, class __Clock, class __Duration>
-  	requires std::predicate<__Pred, __int_type>
+        requires std::predicate<__Pred, __int_type>
       _GLIBCXX_ALWAYS_INLINE std::optional<__int_type>
       wait_until_with_predicate(
           __Pred &&__stop_waiting,
-- 
2.44.0


[-- Attachment #5: 0001-Add-atomic-options-for-integers.patch --]
[-- Type: text/x-patch, Size: 7508 bytes --]

From 74bc9f06e3de0089a1b726b4f0a0e0fc1725c5e9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Teodor=20Sp=C3=A6ren?= <teodor@sparen.no>
Date: Mon, 8 Apr 2024 23:55:02 +0200
Subject: [PATCH 1/4] Add atomic options for integers

---
 libstdc++-v3/include/bits/atomic_base.h | 133 ++++++++++++++++++++++++
 libstdc++-v3/include/std/atomic         |  10 ++
 2 files changed, 143 insertions(+)

diff --git a/libstdc++-v3/include/bits/atomic_base.h b/libstdc++-v3/include/bits/atomic_base.h
index dd360302f80..3e12cc917b5 100644
--- a/libstdc++-v3/include/bits/atomic_base.h
+++ b/libstdc++-v3/include/bits/atomic_base.h
@@ -40,6 +40,11 @@
 
 #if __cplusplus > 201703L && _GLIBCXX_HOSTED
 #include <bits/atomic_wait.h>
+
+#include <bits/atomic_timed_wait.h>
+#include <concepts>
+#include <functional>
+#include <optional>
 #endif
 
 #ifndef _GLIBCXX_ALWAYS_INLINE
@@ -48,6 +53,7 @@
 
 #include <bits/version.h>
 
+
 namespace std _GLIBCXX_VISIBILITY(default)
 {
 _GLIBCXX_BEGIN_NAMESPACE_VERSION
@@ -274,6 +280,17 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
     notify_all() noexcept
     { std::__atomic_notify_address(&_M_i, true); }
 
+    _GLIBCXX_ALWAYS_INLINE bool
+    wait_value(bool __old,
+               memory_order __m = memory_order_seq_cst) const noexcept {
+      bool __ret;
+      std::__atomic_wait_address_v(&_M_i, __old, [__m, this, &__ret] {
+        __ret = __atomic_load_n(&_M_i, int(__m));
+        return __ret;
+      });
+      return __ret;
+    }
+
     // TODO add const volatile overload
 #endif // __glibcxx_atomic_wait
 
@@ -622,6 +639,111 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       notify_all() noexcept
       { std::__atomic_notify_address(&_M_i, true); }
 
+      _GLIBCXX_ALWAYS_INLINE __int_type
+      wait_value(__int_type __old,
+                 memory_order __m = memory_order_seq_cst) const noexcept {
+      	return this->wait_with_predicate([__old](const auto __cur) {
+      		// TODO(rHermes): consider doing atomic comparisons here?
+      		return __old != __cur;
+      	}, __m);
+      }
+
+      template <class __Pred> requires std::predicate<__Pred, __int_type>
+      _GLIBCXX_ALWAYS_INLINE __int_type
+      wait_with_predicate(__Pred &&__stop_waiting,
+                          memory_order __m = memory_order_seq_cst) const {
+        __int_type __ret;
+        std::__atomic_wait_address(&_M_i, [__m, this, &__stop_waiting, &__ret] {
+          __ret = this->load(__m);
+          return std::invoke(__stop_waiting, __ret);
+        });
+        return __ret;
+      }
+
+      template <class __Rep, class __Period>
+      _GLIBCXX_ALWAYS_INLINE std::optional<__int_type>
+      wait_for(__int_type __old,
+               const std::chrono::duration<__Rep, __Period> &__dur,
+               memory_order __m = memory_order_seq_cst) const {
+      	// the reson for this, is that we need to ensure that the predicate
+      	// is called, so we are sure the value has changed, and we are able to
+      	// return it.
+      	return this->wait_for_with_predicate([__old](__int_type __cur) {
+      		// TODO(rHermes): consider doing atomic comparisons here?
+      		return __old != __cur;
+      	}, __dur, __m);
+      }
+
+      template <class __Clock, class __Duration>
+      _GLIBCXX_ALWAYS_INLINE std::optional<__int_type>
+      wait_until(__int_type __old,
+                 const std::chrono::time_point<__Clock, __Duration> &__deadline,
+                 memory_order __m = memory_order_seq_cst) const {
+      	return this->wait_until_with_predicate([__old](const auto __cur) {
+      		// TODO(rHermes): consider doing atomic comparisons here?
+      		return __old != __cur;
+      	}, __deadline, __m);
+      }
+
+      _GLIBCXX_ALWAYS_INLINE std::optional<__int_type>
+      try_wait(__int_type __old,
+               memory_order __m = memory_order_seq_cst) const noexcept {
+        // TODO(rHermes): This is really not a good default, I need to
+        // discuss withsomeone smarter
+        return wait_for(__old, std::chrono::milliseconds(10), __m);
+      }
+
+      template <class __Pred, class __Rep, class __Period>
+  	requires std::predicate<__Pred, __int_type>
+      _GLIBCXX_ALWAYS_INLINE std::optional<__int_type> wait_for_with_predicate(
+          __Pred &&__stop_waiting,
+          const std::chrono::duration<__Rep, __Period> &__dur,
+          memory_order __m = memory_order_seq_cst) const {
+        __int_type __ret;
+        auto __succeeded = std::__atomic_wait_address_for(
+            &_M_i,
+            [__m, this, &__stop_waiting, &__ret] {
+              __ret = this->load(__m);
+              return std::invoke(__stop_waiting, __ret);
+            },
+            __dur);
+
+        if (!__succeeded)
+          return std::nullopt;
+
+        return __ret;
+      }
+
+      template <class __Pred> requires std::predicate<__Pred, __int_type>
+      _GLIBCXX_ALWAYS_INLINE std::optional<__int_type>
+      try_wait_with_predicate(__Pred &&__stop_waiting,
+                              memory_order __m = memory_order_seq_cst) const {
+        return wait_for_with_predicate(std::forward<__Pred>(__stop_waiting),
+                                       std::chrono::milliseconds(10), __m);
+      }
+
+      template <class __Pred, class __Clock, class __Duration>
+  	requires std::predicate<__Pred, __int_type>
+      _GLIBCXX_ALWAYS_INLINE std::optional<__int_type>
+      wait_until_with_predicate(
+          __Pred &&__stop_waiting,
+          const std::chrono::time_point<__Clock, __Duration> &__deadline,
+          memory_order __m = memory_order_seq_cst) const {
+        __int_type __ret;
+        auto __succeeded = std::__atomic_wait_address_until(
+            &_M_i,
+            [__m, this, &__stop_waiting, &__ret] {
+              __ret = this->load(__m);
+              return std::invoke(__stop_waiting, __ret);
+            },
+            __deadline);
+
+        if (!__succeeded)
+          return std::nullopt;
+
+        return __ret;
+      }
+
       // TODO add const volatile overload
 #endif // __glibcxx_atomic_wait
 
@@ -925,6 +1047,17 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       notify_all() const noexcept
       { std::__atomic_notify_address(&_M_p, true); }
 
+      _GLIBCXX_ALWAYS_INLINE __pointer_type
+      wait_value(__pointer_type __old,
+                 memory_order __m = memory_order_seq_cst) const noexcept {
+        __pointer_type __ret;
+        std::__atomic_wait_address_v(&_M_p, __old, [__m, this, &__ret] {
+          __ret = this->load(__m);
+          return __ret;
+        });
+        return __ret;
+      }
+
       // TODO add const volatile overload
 #endif // __glibcxx_atomic_wait
 
diff --git a/libstdc++-v3/include/std/atomic b/libstdc++-v3/include/std/atomic
index 1462cf5ec23..3efcddc6938 100644
--- a/libstdc++-v3/include/std/atomic
+++ b/libstdc++-v3/include/std/atomic
@@ -183,6 +183,11 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
     void
     notify_all() noexcept
     { _M_base.notify_all(); }
+
+    bool
+    wait_value(bool __old, memory_order __m = memory_order_seq_cst) const noexcept {
+      return _M_base.wait_value(__old, __m);
+    }
 #endif // __cpp_lib_atomic_wait
   };
 
@@ -663,6 +668,11 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
     void
     notify_all() noexcept
     { _M_b.notify_all(); }
+
+    __pointer_type
+    wait_value(__pointer_type __old, memory_order __m = memory_order_seq_cst) const noexcept
+    { return _M_b.wait_value(__old, __m); }
+
 #endif // __cpp_lib_atomic_wait
 
       __pointer_type
-- 
2.44.0


^ permalink raw reply	[flat|nested] only message in thread

only message in thread, other threads:[~2024-04-14 20:15 UTC | newest]

Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2024-04-14 20:15 [PATCH] Want feedback for potential implementation of P2643R2 atomic features Teodor Spæren

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).