mirror of
https://forge.sourceware.org/marek/gcc.git
synced 2026-02-22 03:47:02 -05:00
Jakub's fix for PR c++/91388 means that we don't need an unreachable return statement after static_assert(false). libstdc++-v3/ChangeLog: * include/bits/atomic_wait.h (__wait_args::_M_setup_wait): Remove unreachable return statement. Reviewed-by: Tomasz Kamiński <tkaminsk@redhat.com>
343 lines
11 KiB
C++
343 lines
11 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;
|
|
#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 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;
|
|
__wait_value_type _M_old = 0;
|
|
void* _M_wait_state = nullptr;
|
|
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,
|
|
__wait_result_type __res = {})
|
|
{
|
|
static_assert(is_same_v<_Tp, decay_t<decltype(__vfn())>>);
|
|
|
|
if (__res._M_has_val) // A previous wait loaded a recent value.
|
|
{
|
|
_M_old = __res._M_val;
|
|
if constexpr (!__platform_wait_uses_type<_Tp>)
|
|
{
|
|
// __res._M_val might be the value of a proxy wait object,
|
|
// not the value of *__addr. Call __vfn() to get new value.
|
|
return __vfn();
|
|
}
|
|
// Not a proxy wait, so the value in __res._M_val was loaded
|
|
// from *__addr and we don't need to call __vfn().
|
|
else if constexpr (sizeof(_Tp) == sizeof(__UINT32_TYPE__))
|
|
return __builtin_bit_cast(_Tp, (__UINT32_TYPE__)_M_old);
|
|
else if constexpr (sizeof(_Tp) == sizeof(__UINT64_TYPE__))
|
|
return __builtin_bit_cast(_Tp, (__UINT64_TYPE__)_M_old);
|
|
else
|
|
static_assert(false); // Unsupported size
|
|
}
|
|
|
|
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_obj and _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,
|
|
// and so can just load the value and store it into _M_old.
|
|
auto __val = __vfn();
|
|
// 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 // _M_setup_proxy_wait should have returned true for this type!
|
|
__glibcxx_assert(false);
|
|
return __val;
|
|
}
|
|
|
|
private:
|
|
// Returns true if a proxy wait will be used for __addr, false otherwise.
|
|
// If true, _M_wait_state, _M_obj, _M_obj_size, and _M_old are set.
|
|
// If false, data members are unchanged.
|
|
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_setup_wait(__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
|