This defines __platform_wait, __platform_notify, and
__platform_wait_until for FreeBSD, making use of the _umtx_op syscall.

The Linux versions of those functions only support 32-bit integers, but
the FreeBSD versions use the syscall for both 32-bit and 64-bit types,
as the _umtx_op supports both.

libstdc++-v3/ChangeLog:

        PR libstdc++/120527
        * include/bits/atomic_wait.h [__FreeBSD__] (__platform_wait_t):
        Define typedef.
        [__FreeBSD__] (__platform_wait_uses_type): Define variable
        template.
        * src/c++20/atomic.cc [__FreeBSD__] (_GLIBCXX_HAVE_PLATFORM_WAIT)
        (__platform_wait, __platform_notify, __platform_wait_until):
        Define.
---

This demonstrates how to extend the atomic wait/notify code to support
more platforms. This is for FreeBSD, but similar changes should be
possible for macOS (which also supports both 32-bit and 64-bit waits),
and DragonFly and OpenBSD (which only support 32-bit ints, like Linux).

This shows that the refactored API is actually future-proof and can be
extended. 

Tested x86_64-linux and x86_64-freebsd14.0

I need to do some benchmarking to see that this actually improves
performance compared to the std::mutex and std::condition_variable
implementation.

 libstdc++-v3/include/bits/atomic_wait.h | 11 ++++++
 libstdc++-v3/src/c++20/atomic.cc        | 50 +++++++++++++++++++++++++
 2 files changed, 61 insertions(+)

diff --git a/libstdc++-v3/include/bits/atomic_wait.h 
b/libstdc++-v3/include/bits/atomic_wait.h
index a280d3534f46..5bf00a389e03 100644
--- a/libstdc++-v3/include/bits/atomic_wait.h
+++ b/libstdc++-v3/include/bits/atomic_wait.h
@@ -68,6 +68,17 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
     inline constexpr bool __platform_wait_uses_type
       = __detail::__waitable<_Tp>
          && sizeof(_Tp) == sizeof(int) && alignof(_Tp) >= 4;
+#elif defined __FreeBSD__
+  namespace __detail
+  {
+    using __platform_wait_t = __UINT64_TYPE__;
+    inline constexpr size_t __platform_wait_alignment = 8;
+  }
+  template<typename _Tp>
+    inline constexpr bool __platform_wait_uses_type
+      = __detail::__waitable<_Tp>
+         && ((sizeof(_Tp) == 4 && alignof(_Tp) >= 4)
+               || (sizeof(_Tp) == 8 && alignof(_Tp) >= 8));
 #else
 // define _GLIBCX_HAVE_PLATFORM_WAIT and implement __platform_wait()
 // and __platform_notify() if there is a more efficient primitive supported
diff --git a/libstdc++-v3/src/c++20/atomic.cc b/libstdc++-v3/src/c++20/atomic.cc
index aeb4ea3e2466..57881305d643 100644
--- a/libstdc++-v3/src/c++20/atomic.cc
+++ b/libstdc++-v3/src/c++20/atomic.cc
@@ -39,6 +39,11 @@
 # include <syscall.h>
 # include <sys/time.h> // timespec
 # define _GLIBCXX_HAVE_PLATFORM_WAIT 1
+#elif defined __FreeBSD__
+# include <sys/types.h>
+# include <sys/umtx.h>
+# include <sys/time.h>
+# define _GLIBCXX_HAVE_PLATFORM_WAIT 1
 #endif
 
 #pragma GCC diagnostic ignored "-Wmissing-field-initializers"
@@ -88,6 +93,24 @@ namespace
             static_cast<int>(__futex_wait_flags::__wake_private),
             __all ? INT_MAX : 1);
   }
+#elif defined __FreeBSD__
+  void
+  __platform_wait(const void* addr, uint64_t val, int obj_sz) noexcept
+  {
+    const int op = obj_sz == sizeof(int) ? UMTX_OP_WAIT_UINT : UMTX_OP_WAIT;
+    auto __e = _umtx_op(const_cast<void*>(addr), op, val, nullptr, nullptr);
+    if (!__e || errno == EAGAIN)
+      return;
+    if (errno != EINTR)
+      __throw_system_error(errno);
+  }
+
+  void
+  __platform_notify(const void* addr, bool all) noexcept
+  {
+    const int count = all ? INT_MAX : 1;
+    _umtx_op(const_cast<void*>(addr), UMTX_OP_WAKE, count, nullptr, nullptr);
+  }
 #endif
 
   // The state used by atomic waiting and notifying functions.
@@ -401,6 +424,33 @@ __platform_wait_until(const __platform_wait_t* __addr,
     }
   return true;
 }
+#elif defined __FreeBSD__
+// returns true if wait ended before timeout
+bool
+__platform_wait_until(const void* addr, uint64_t old,
+                     const __wait_clock_t::time_point& atime,
+                     int obj_sz) noexcept
+{
+  struct _umtx_time timeout = {
+    ._timeout = chrono::__to_timeout_timespec(atime),
+    ._flags = UMTX_ABSTIME,
+    ._clockid = CLOCK_MONOTONIC
+  };
+  // _umtx_op hangs if timeout._timeout is {0, 0}
+  if (atime.time_since_epoch() < chrono::nanoseconds(1))
+    timeout._timeout.tv_nsec = 1;
+  constexpr uintptr_t timeout_sz = sizeof(timeout);
+  const int op = obj_sz == sizeof(int) ? UMTX_OP_WAIT_UINT : UMTX_OP_WAIT;
+
+  if (_umtx_op(const_cast<void*>(addr), op, old, (void*)timeout_sz, &timeout))
+    {
+      if (errno == ETIMEDOUT)
+       return false;
+      if (errno != EINTR && errno != EAGAIN)
+       __throw_system_error(errno);
+    }
+  return true;
+}
 #endif // HAVE_LINUX_FUTEX
 
 #ifndef _GLIBCXX_HAVE_PLATFORM_WAIT
-- 
2.51.1

Reply via email to