mirror of
https://github.com/gcc-mirror/gcc.git
synced 2026-05-06 23:25:24 +02:00
This defines __platform_wait_t as unsigned int for OpenBSD and DragonFly. This means that std::semaphore will use unsigned int by default, and so will benefit from more efficient wait/notify ops if we start to use the OpenBSD futex(2) syscall or the DragonFly umtx(2) syscalls. We don't currently use them, but if we start to in future, it would be an ABI break to change __platform_wait_t later. libstdc++-v3/ChangeLog: PR libstdc++/120527 * include/bits/atomic_wait.h [__OpenBSD__ || __DragonFly]: Use unsigned int for __platform_wait_t. Reviewed-by: Tomasz Kamiński <tkaminsk@redhat.com>
406 lines
14 KiB
C++
406 lines
14 KiB
C++
// -*- C++ -*- header.
|
|
|
|
// Copyright (C) 2020-2026 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.
|
|
|
|
// Under Section 7 of GPL version 3, you are granted additional
|
|
// permissions described in the GCC Runtime Library Exception, version
|
|
// 3.1, as published by the Free Software Foundation.
|
|
|
|
// You should have received a copy of the GNU General Public License and
|
|
// a copy of the GCC Runtime Library Exception along with this program;
|
|
// see the files COPYING3 and COPYING.RUNTIME respectively. If not, see
|
|
// <http://www.gnu.org/licenses/>.
|
|
|
|
/** @file bits/atomic_wait.h
|
|
* This is an internal header file, included by other library headers.
|
|
* Do not attempt to use it directly. @headername{atomic}
|
|
*/
|
|
|
|
#ifndef _GLIBCXX_ATOMIC_WAIT_H
|
|
#define _GLIBCXX_ATOMIC_WAIT_H 1
|
|
|
|
#ifdef _GLIBCXX_SYSHDR
|
|
#pragma GCC system_header
|
|
#endif
|
|
|
|
#include <bits/version.h>
|
|
|
|
#if __glibcxx_atomic_wait
|
|
#include <bits/gthr.h>
|
|
#include <ext/numeric_traits.h>
|
|
|
|
#include <bits/stl_pair.h>
|
|
|
|
namespace std _GLIBCXX_VISIBILITY(default)
|
|
{
|
|
_GLIBCXX_BEGIN_NAMESPACE_VERSION
|
|
|
|
namespace __detail
|
|
{
|
|
// TODO: this needs to be false for types with padding, e.g. __int20.
|
|
// TODO: should this be true only for integral, enum, and pointer types?
|
|
template<typename _Tp>
|
|
concept __waitable
|
|
= is_scalar_v<_Tp> && __builtin_popcountg(sizeof(_Tp)) == 1
|
|
&& (sizeof(_Tp) <= sizeof(__UINT64_TYPE__));
|
|
}
|
|
|
|
#if defined _GLIBCXX_HAVE_LINUX_FUTEX
|
|
namespace __detail
|
|
{
|
|
// Use futex syscall on int objects.
|
|
using __platform_wait_t = int;
|
|
inline constexpr size_t __platform_wait_alignment = 4;
|
|
}
|
|
// Defined to true for a subset of __waitable types which are statically
|
|
// known to definitely be able to use futex, not a proxy wait.
|
|
template<typename _Tp>
|
|
inline constexpr bool __platform_wait_uses_type
|
|
= __detail::__waitable<_Tp>
|
|
&& sizeof(_Tp) == sizeof(int) && alignof(_Tp) >= 4;
|
|
#elif defined __APPLE__ \
|
|
&& __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ >= 101200
|
|
namespace __detail
|
|
{
|
|
using __platform_wait_t = __INT32_TYPE__;
|
|
inline constexpr size_t __platform_wait_alignment = 4;
|
|
}
|
|
// Defined to true for a subset of __waitable types which are statically
|
|
// known to definitely be able to use __ulock_wait, not a proxy wait.
|
|
// We know that OS Versions later than 10.15 support 64b wait types even
|
|
// though we must make the __platform_wait_t 32b for compatibility with
|
|
// earlier versions of __ulock_xxxx.
|
|
template<typename _Tp>
|
|
inline constexpr bool __platform_wait_uses_type
|
|
= __detail::__waitable<_Tp>
|
|
# if __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ < 101500
|
|
&& sizeof(_Tp) == 4 && alignof(_Tp) >= 4;
|
|
# else
|
|
&& ((sizeof(_Tp) == 4 && alignof(_Tp) >= 4)
|
|
|| (sizeof(_Tp) == 8 && alignof(_Tp) >= 8));
|
|
# endif
|
|
#elif defined __FreeBSD__ && __SIZEOF_LONG__ == 8
|
|
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
|
|
// by the platform (e.g. __ulock_wait()/__ulock_wake()) which is better than
|
|
// a mutex/condvar based wait.
|
|
namespace __detail
|
|
{
|
|
# if defined __OpenBSD__ || defined __DragonFly__
|
|
// These targets provide 32-bit futex-like syscalls.
|
|
// We don't currently make use of them, but we want to in future.
|
|
using __platform_wait_t = unsigned int;
|
|
# elif ATOMIC_LONG_LOCK_FREE == 2
|
|
using __platform_wait_t = unsigned long;
|
|
# else
|
|
using __platform_wait_t = unsigned int;
|
|
# endif
|
|
inline constexpr size_t __platform_wait_alignment
|
|
= sizeof(__platform_wait_t) < __alignof__(__platform_wait_t)
|
|
? __alignof__(__platform_wait_t) : sizeof(__platform_wait_t);
|
|
} // namespace __detail
|
|
|
|
// This must be false for the general case where we don't know of any
|
|
// futex-like syscall.
|
|
template<typename>
|
|
inline constexpr bool __platform_wait_uses_type = false;
|
|
#endif
|
|
|
|
namespace __detail
|
|
{
|
|
inline void
|
|
__thread_yield() noexcept
|
|
{
|
|
#if defined _GLIBCXX_HAS_GTHREADS && defined _GLIBCXX_USE_SCHED_YIELD
|
|
__gthread_yield();
|
|
#endif
|
|
}
|
|
|
|
inline void
|
|
__thread_relax() noexcept
|
|
{
|
|
#if defined __i386__ || defined __x86_64__
|
|
__builtin_ia32_pause();
|
|
#else
|
|
__thread_yield();
|
|
#endif
|
|
}
|
|
|
|
// return true if equal
|
|
template<typename _Tp>
|
|
inline bool
|
|
__atomic_eq(const _Tp& __a, const _Tp& __b)
|
|
{
|
|
// TODO make this do the correct padding bit ignoring comparison
|
|
return __builtin_memcmp(std::addressof(__a), std::addressof(__b),
|
|
sizeof(_Tp)) == 0;
|
|
}
|
|
|
|
// Storage for up to 64 bits of value, should be considered opaque bits.
|
|
using __wait_value_type = __UINT64_TYPE__;
|
|
|
|
// lightweight std::optional<__wait_value_type>
|
|
struct __wait_result_type
|
|
{
|
|
__wait_value_type _M_val;
|
|
unsigned char _M_has_val : 1; // _M_val value was loaded before return.
|
|
unsigned char _M_timeout : 1; // Waiting function ended with timeout.
|
|
unsigned char _M_unused : 6; // padding
|
|
};
|
|
|
|
enum class __wait_flags : __UINT_LEAST32_TYPE__
|
|
{
|
|
__abi_version = 0x00000000,
|
|
// currently unused = 1,
|
|
__track_contention = 2,
|
|
__do_spin = 4,
|
|
__spin_only = 8, // Ignored unless __do_spin is also set.
|
|
// __abi_version_mask = 0xff000000,
|
|
};
|
|
|
|
[[__gnu__::__always_inline__]]
|
|
constexpr __wait_flags
|
|
operator|(__wait_flags __l, __wait_flags __r) noexcept
|
|
{
|
|
using _Ut = underlying_type_t<__wait_flags>;
|
|
return static_cast<__wait_flags>(static_cast<_Ut>(__l)
|
|
| static_cast<_Ut>(__r));
|
|
}
|
|
|
|
[[__gnu__::__always_inline__]]
|
|
constexpr __wait_flags&
|
|
operator|=(__wait_flags& __l, __wait_flags __r) noexcept
|
|
{ return __l = __l | __r; }
|
|
|
|
// Simple aggregate containing arguments used by implementation details.
|
|
struct __wait_args_base
|
|
{
|
|
__wait_flags _M_flags;
|
|
int _M_order = __ATOMIC_ACQUIRE; // Memory order for loads from _M_obj.
|
|
__wait_value_type _M_old = 0; // Previous value of *_M_obj.
|
|
void* _M_wait_state = nullptr; // For proxy wait and tracking contention.
|
|
const void* _M_obj = nullptr; // The address of the object to wait on.
|
|
unsigned char _M_obj_size = 0; // The size of that object.
|
|
|
|
// Test whether _M_flags & __flags is non-zero.
|
|
bool
|
|
operator&(__wait_flags __flags) const noexcept
|
|
{
|
|
using _Ut = underlying_type_t<__wait_flags>;
|
|
return static_cast<_Ut>(_M_flags) & static_cast<_Ut>(__flags);
|
|
}
|
|
};
|
|
|
|
// Utility for populating a __wait_args_base structure.
|
|
struct __wait_args : __wait_args_base
|
|
{
|
|
template<typename _Tp> requires (!is_same_v<_Tp, __wait_args>)
|
|
explicit
|
|
__wait_args(const _Tp* __addr, bool __bare_wait = false) noexcept
|
|
: __wait_args_base{ _S_flags_for(__addr, __bare_wait) }
|
|
{
|
|
_M_obj = __addr; // Might be replaced by _M_setup_wait
|
|
if constexpr (__waitable<_Tp>)
|
|
// __wait_impl might be able to wait directly on __addr
|
|
// instead of using a proxy, depending on its size.
|
|
_M_obj_size = sizeof(_Tp);
|
|
}
|
|
|
|
__wait_args(const __platform_wait_t* __addr, __platform_wait_t __old,
|
|
int __order, bool __bare_wait = false) noexcept
|
|
: __wait_args(__addr, __bare_wait)
|
|
{
|
|
_M_order = __order;
|
|
_M_old = __old;
|
|
}
|
|
|
|
__wait_args(const __wait_args&) noexcept = default;
|
|
__wait_args& operator=(const __wait_args&) noexcept = default;
|
|
|
|
template<typename _Tp, typename _ValFn>
|
|
_Tp
|
|
_M_setup_wait(const _Tp* __addr, _ValFn __vfn)
|
|
{
|
|
static_assert(is_same_v<_Tp, decay_t<decltype(__vfn())>>);
|
|
|
|
if constexpr (!__platform_wait_uses_type<_Tp>)
|
|
if (_M_setup_proxy_wait(__addr))
|
|
{
|
|
// We will use a proxy wait for this object.
|
|
// The library has set _M_wait_state, _M_obj, _M_obj_size,
|
|
// and _M_old.
|
|
// Call __vfn to load the current value from *__addr
|
|
// (which must happen after the call to _M_setup_proxy_wait).
|
|
return __vfn();
|
|
}
|
|
|
|
// We will use a futex-like operation to wait on this object,
|
|
// so just load the value, store it into _M_old, and return it.
|
|
return _M_store(__vfn());
|
|
}
|
|
|
|
// Called after a wait returns, to prepare to wait again.
|
|
template<typename _Tp, typename _ValFn>
|
|
_Tp
|
|
_M_on_wake(const _Tp* __addr, _ValFn __vfn, __wait_result_type __res)
|
|
{
|
|
if constexpr (!__platform_wait_uses_type<_Tp>) // maybe a proxy wait
|
|
if (_M_obj != __addr) // definitely a proxy wait
|
|
{
|
|
if (__res._M_has_val)
|
|
// Previous wait loaded a recent value from the proxy.
|
|
_M_old = __res._M_val;
|
|
else // Load a new value from the proxy and store in _M_old.
|
|
(void) _M_setup_proxy_wait(nullptr);
|
|
// Read the current value of *__addr
|
|
return __vfn();
|
|
}
|
|
|
|
if (__res._M_has_val) // The previous wait loaded a recent value.
|
|
{
|
|
_M_old = __res._M_val;
|
|
|
|
// Not a proxy wait, so the value in __res._M_val was loaded
|
|
// from *__addr and we don't need to call __vfn().
|
|
if constexpr (sizeof(_Tp) == sizeof(__UINT64_TYPE__))
|
|
return __builtin_bit_cast(_Tp, (__UINT64_TYPE__)_M_old);
|
|
else if constexpr (sizeof(_Tp) == sizeof(__UINT32_TYPE__))
|
|
return __builtin_bit_cast(_Tp, (__UINT32_TYPE__)_M_old);
|
|
else if constexpr (sizeof(_Tp) == sizeof(__UINT16_TYPE__))
|
|
return __builtin_bit_cast(_Tp, (__UINT16_TYPE__)_M_old);
|
|
else if constexpr (sizeof(_Tp) == sizeof(__UINT8_TYPE__))
|
|
return __builtin_bit_cast(_Tp, (__UINT8_TYPE__)_M_old);
|
|
else // Should be a proxy wait for this size!
|
|
__glibcxx_assert(false);
|
|
}
|
|
else
|
|
return _M_store(__vfn());
|
|
}
|
|
|
|
private:
|
|
// Store __val in _M_old.
|
|
// pre: This must be a non-proxy wait.
|
|
template<typename _Tp>
|
|
[[__gnu__::__always_inline__]]
|
|
_Tp
|
|
_M_store(_Tp __val)
|
|
{
|
|
// We have to consider various sizes, because a future libstdc++.so
|
|
// might enable non-proxy waits for additional sizes.
|
|
if constexpr (sizeof(_Tp) == sizeof(__UINT64_TYPE__))
|
|
_M_old = __builtin_bit_cast(__UINT64_TYPE__, __val);
|
|
else if constexpr (sizeof(_Tp) == sizeof(__UINT32_TYPE__))
|
|
_M_old = __builtin_bit_cast(__UINT32_TYPE__, __val);
|
|
else if constexpr (sizeof(_Tp) == sizeof(__UINT16_TYPE__))
|
|
_M_old = __builtin_bit_cast(__UINT16_TYPE__, __val);
|
|
else if constexpr (sizeof(_Tp) == sizeof(__UINT8_TYPE__))
|
|
_M_old = __builtin_bit_cast(__UINT8_TYPE__, __val);
|
|
else // Should be a proxy wait for this size!
|
|
__glibcxx_assert(false);
|
|
return __val;
|
|
}
|
|
|
|
// Prepare `*this` for a call to `__wait_impl` or `__wait_until_impl`.
|
|
// See comments in src/c++20/atomic.cc for more details.
|
|
bool
|
|
_M_setup_proxy_wait(const void* __addr);
|
|
|
|
template<typename _Tp>
|
|
static constexpr __wait_flags
|
|
_S_flags_for(const _Tp*, bool __bare_wait) noexcept
|
|
{
|
|
using enum __wait_flags;
|
|
__wait_flags __res = __abi_version | __do_spin;
|
|
if (!__bare_wait)
|
|
__res |= __track_contention;
|
|
return __res;
|
|
}
|
|
};
|
|
|
|
__wait_result_type
|
|
__wait_impl(const void* __addr, __wait_args_base&);
|
|
|
|
void
|
|
__notify_impl(const void* __addr, bool __all, const __wait_args_base&);
|
|
} // namespace __detail
|
|
|
|
// Wait on __addr while __pred(__vfn()) is false.
|
|
// If __bare_wait is false, increment a counter while waiting.
|
|
// For callers that keep their own count of waiters, use __bare_wait=true.
|
|
// The effect of __vfn() must be an atomic load from __addr and nothing else.
|
|
template<typename _Tp, typename _Pred, typename _ValFn>
|
|
void
|
|
__atomic_wait_address(const _Tp* __addr, _Pred&& __pred, _ValFn&& __vfn,
|
|
bool __bare_wait = false) noexcept
|
|
{
|
|
__detail::__wait_args __args{ __addr, __bare_wait };
|
|
_Tp __val = __args._M_setup_wait(__addr, __vfn);
|
|
while (!__pred(__val))
|
|
{
|
|
auto __res = __detail::__wait_impl(__addr, __args);
|
|
__val = __args._M_on_wake(__addr, __vfn, __res);
|
|
}
|
|
// C++26 will return __val
|
|
}
|
|
|
|
// Wait on __addr while *__addr == __old is true.
|
|
inline void
|
|
__atomic_wait_address_v(const __detail::__platform_wait_t* __addr,
|
|
__detail::__platform_wait_t __old,
|
|
int __order, bool __bare_wait = false)
|
|
{
|
|
// This function must not be used if __wait_impl might use a proxy wait:
|
|
__glibcxx_assert(__platform_wait_uses_type<__detail::__platform_wait_t>);
|
|
|
|
__detail::__wait_args __args{ __addr, __old, __order, __bare_wait };
|
|
// C++26 will not ignore the return value here
|
|
__detail::__wait_impl(__addr, __args);
|
|
}
|
|
|
|
// Wait on __addr while __vfn() == __old is true.
|
|
template<typename _Tp, typename _ValFn>
|
|
void
|
|
__atomic_wait_address_v(const _Tp* __addr, _Tp __old,
|
|
_ValFn __vfn) noexcept
|
|
{
|
|
auto __pfn = [&](const _Tp& __val)
|
|
{ return !__detail::__atomic_eq(__old, __val); };
|
|
std::__atomic_wait_address(__addr, __pfn, forward<_ValFn>(__vfn));
|
|
}
|
|
|
|
template<typename _Tp>
|
|
void
|
|
__atomic_notify_address(const _Tp* __addr, bool __all,
|
|
bool __bare_wait = false) noexcept
|
|
{
|
|
__detail::__wait_args __args{ __addr, __bare_wait };
|
|
__detail::__notify_impl(__addr, __all, __args);
|
|
}
|
|
|
|
_GLIBCXX_END_NAMESPACE_VERSION
|
|
} // namespace std
|
|
#endif // __glibcxx_atomic_wait
|
|
#endif // _GLIBCXX_ATOMIC_WAIT_H
|