On Tue, 25 Nov 2025 at 15:09, Jonathan Wakely <[email protected]> wrote:
>
> 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));

This is wrong for 32-bit, because _umtx_op supports long and u_int,
which are both 32-bit for ILP32 targets. The conditions above should
be in terms of sizeof(int) and sizeof(long), not hardcoded 4 and 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