Previously, atomic<shared_ptr<T>>::wait (and the weak_ptr version) was equivalent to waiting directly on _M_val, which corresponds to the pointer to the control block (_M_pi). Consequently, wakeups were not triggered if the stored pointer value was changed to a pointer that uses the same control block but stores pointer to a different object. Such a pointer can be constructed using an aliasing constructor.
To address this, wait now uses a generic proxy wait std::__atomic_wait_address function, which supports waiting until any predicate is satisfied. The provided predicate now compares both the control block (_M_pi) and the stored pointer (_M_ptr). Comparing the latter requires locking the pointer. Since this function operates on raw pointers, the type of _M_val was changed from __atomic_base<uintptr_t> to uninptr. Invocations of the corresponding member functions are now replaced with direct use of __atomic builtins. PR libstdc++/118757 libstdc++-v3/ChangeLog: * include/bits/shared_ptr_atomic.h (_Atomic_count::_M_wait_unlock): Add parameter capturing reference to _M_ptr. Reimplement in terms of __atomic_wait_address. (_Atomic_count::~_Atomic_count, _Atomic_count::lock) (_Atomic_count::unlock, _Atomic_count::_M_swap_unlock): Replace invocation of atomic member funcitons with __atomic builtins. (_Atomic_count::notify_one, _Atomic_count::notify_all): Use __atomic_notify_address. (_Sp_atomic::element_type): Define. (_Sp_atomic::_M_val): Change type to uintptr_t. (_Sp_atomic::wait): Pass _M_ptr to _M_wait_unlock. * python/libstdcxx/v6/printers.py: * testsuite/20_util/shared_ptr/atomic/pr118757.cc: New test. * testsuite/20_util/weak_ptr/pr118757.cc: New test. Signed-off-by: Tomasz Kamiński <tkami...@redhat.com> --- Changes in v2: * update pretty printers * use __atomic_sub_fetch in _M_wait_unlock OK for trunk? As atomic wait fixes are in v16, not planning to backport this one. libstdc++-v3/include/bits/shared_ptr_atomic.h | 60 +++++++++++++------ libstdc++-v3/python/libstdcxx/v6/printers.py | 2 +- .../20_util/shared_ptr/atomic/pr118757.cc | 46 ++++++++++++++ .../testsuite/20_util/weak_ptr/pr118757.cc | 47 +++++++++++++++ 4 files changed, 137 insertions(+), 18 deletions(-) create mode 100644 libstdc++-v3/testsuite/20_util/shared_ptr/atomic/pr118757.cc create mode 100644 libstdc++-v3/testsuite/20_util/weak_ptr/pr118757.cc diff --git a/libstdc++-v3/include/bits/shared_ptr_atomic.h b/libstdc++-v3/include/bits/shared_ptr_atomic.h index cc7841a8775..5f1e1b50514 100644 --- a/libstdc++-v3/include/bits/shared_ptr_atomic.h +++ b/libstdc++-v3/include/bits/shared_ptr_atomic.h @@ -392,6 +392,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION class _Sp_atomic { using value_type = _Tp; + using element_type = typename _Tp::element_type; friend struct atomic<_Tp>; @@ -420,7 +421,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION ~_Atomic_count() { - auto __val = _M_val.load(memory_order_relaxed); + auto __val = __atomic_load_n(&_M_val, int(memory_order_relaxed)); _GLIBCXX_TSAN_MUTEX_DESTROY(&_M_val); __glibcxx_assert(!(__val & _S_lock_bit)); if (auto __pi = reinterpret_cast<pointer>(__val)) @@ -442,21 +443,20 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION { // To acquire the lock we flip the LSB from 0 to 1. - auto __current = _M_val.load(memory_order_relaxed); + auto __current = __atomic_load_n(&_M_val, int(memory_order_relaxed)); while (__current & _S_lock_bit) { #if __glibcxx_atomic_wait __detail::__thread_relax(); #endif - __current = _M_val.load(memory_order_relaxed); + __current = __atomic_load_n(&_M_val, int(memory_order_relaxed)); } _GLIBCXX_TSAN_MUTEX_TRY_LOCK(&_M_val); - while (!_M_val.compare_exchange_strong(__current, - __current | _S_lock_bit, - __o, - memory_order_relaxed)) + while (!__atomic_compare_exchange_n( + &_M_val, &__current, __current | _S_lock_bit, 0, + int(__o), int(memory_order_relaxed))) { _GLIBCXX_TSAN_MUTEX_TRY_LOCK_FAILED(&_M_val); #if __glibcxx_atomic_wait @@ -474,7 +474,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION unlock(memory_order __o) const noexcept { _GLIBCXX_TSAN_MUTEX_PRE_UNLOCK(&_M_val); - _M_val.fetch_sub(1, __o); + __atomic_fetch_sub(&_M_val, 1, int(__o)); _GLIBCXX_TSAN_MUTEX_POST_UNLOCK(&_M_val); } @@ -487,7 +487,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION __o = memory_order_release; auto __x = reinterpret_cast<uintptr_t>(__c._M_pi); _GLIBCXX_TSAN_MUTEX_PRE_UNLOCK(&_M_val); - __x = _M_val.exchange(__x, __o); + __x = __atomic_exchange_n(&_M_val, __x, int(__o)); _GLIBCXX_TSAN_MUTEX_POST_UNLOCK(&_M_val); __c._M_pi = reinterpret_cast<pointer>(__x & ~_S_lock_bit); } @@ -495,19 +495,45 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION #if __glibcxx_atomic_wait // Precondition: caller holds lock! void - _M_wait_unlock(memory_order __o) const noexcept + _M_wait_unlock(const element_type* const& __ptr, memory_order __o) const noexcept { + auto __old_ptr = __ptr; _GLIBCXX_TSAN_MUTEX_PRE_UNLOCK(&_M_val); - auto __v = _M_val.fetch_sub(1, memory_order_relaxed); + auto __old_pi + = __atomic_sub_fetch(&_M_val, 1, int(memory_order_relaxed)); _GLIBCXX_TSAN_MUTEX_POST_UNLOCK(&_M_val); - _M_val.wait(__v & ~_S_lock_bit, __o); + + // Ensure that the correct value of _M_ptr is visible after locking, + // by upgrading relaxed or consume to acquire. + auto __lo = __o; + if (__o != memory_order_seq_cst) + __lo = memory_order_acquire; + + std::__atomic_wait_address( + &_M_val, + [=, &__ptr, this](uintptr_t __new_pi) + { + if (__old_pi != (__new_pi & ~_S_lock_bit)) + // control block changed, we can wake up + return true; + + // control block is same, we need to check if ptr changed, + // the lock needs to be taken first, the value of pi may have + // also been updated in meantime, so reload it + __new_pi = reinterpret_cast<uintptr_t>(this->lock(__lo)); + auto __new_ptr = __ptr; + this->unlock(memory_order_relaxed); + // wake up if either of the values changed + return __new_pi != __old_pi || __new_ptr != __old_ptr; + }, + [__o, this] { return __atomic_load_n(&_M_val, int(__o)); }); } void notify_one() noexcept { _GLIBCXX_TSAN_MUTEX_PRE_SIGNAL(&_M_val); - _M_val.notify_one(); + std::__atomic_notify_address(&_M_val, false); _GLIBCXX_TSAN_MUTEX_POST_SIGNAL(&_M_val); } @@ -515,17 +541,17 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION notify_all() noexcept { _GLIBCXX_TSAN_MUTEX_PRE_SIGNAL(&_M_val); - _M_val.notify_all(); + std::__atomic_notify_address(&_M_val, true); _GLIBCXX_TSAN_MUTEX_POST_SIGNAL(&_M_val); } #endif private: - mutable __atomic_base<uintptr_t> _M_val{0}; + mutable uintptr_t _M_val{0}; static constexpr uintptr_t _S_lock_bit{1}; }; - typename _Tp::element_type* _M_ptr = nullptr; + element_type* _M_ptr = nullptr; _Atomic_count _M_refcount; static typename _Atomic_count::pointer @@ -608,7 +634,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION { auto __pi = _M_refcount.lock(memory_order_acquire); if (_M_ptr == __old._M_ptr && __pi == __old._M_refcount._M_pi) - _M_refcount._M_wait_unlock(__o); + _M_refcount._M_wait_unlock(_M_ptr, __o); else _M_refcount.unlock(memory_order_relaxed); } diff --git a/libstdc++-v3/python/libstdcxx/v6/printers.py b/libstdc++-v3/python/libstdcxx/v6/printers.py index 5f5963cb595..51defbce78b 100644 --- a/libstdc++-v3/python/libstdcxx/v6/printers.py +++ b/libstdc++-v3/python/libstdcxx/v6/printers.py @@ -287,7 +287,7 @@ class SharedPointerPrinter(printer_base): def _get_refcounts(self): if self._typename == 'std::atomic': # A tagged pointer is stored as uintptr_t. - ptr_val = self._val['_M_refcount']['_M_val']['_M_i'] + ptr_val = self._val['_M_refcount']['_M_val'] ptr_val = ptr_val - (ptr_val % 2) # clear lock bit ptr_type = find_type(self._val['_M_refcount'].type, 'pointer') return ptr_val.cast(ptr_type) diff --git a/libstdc++-v3/testsuite/20_util/shared_ptr/atomic/pr118757.cc b/libstdc++-v3/testsuite/20_util/shared_ptr/atomic/pr118757.cc new file mode 100644 index 00000000000..9c3dc01255c --- /dev/null +++ b/libstdc++-v3/testsuite/20_util/shared_ptr/atomic/pr118757.cc @@ -0,0 +1,46 @@ +// Copyright (C) 2014-2025 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/>. + +// { dg-do run { target c++20 } } +// { dg-require-gthreads "" } +// { dg-require-effective-target hosted } + +#include <memory> +#include <chrono> +#include <thread> +#include <barrier> + +std::shared_ptr<int> q = std::make_shared<int>(42); +std::atomic<std::shared_ptr<int>> p = q; + +std::barrier bar(2); + +void signaler() +{ + std::this_thread::sleep_for(std::chrono::seconds(1)); + p.store(std::shared_ptr<int>(q, nullptr)); + p.notify_one(); + bar.arrive_and_wait(); +} + +int main(int, char**) +{ + std::thread thr(signaler); + p.wait(q); + bar.arrive_and_wait(); + thr.join(); +} diff --git a/libstdc++-v3/testsuite/20_util/weak_ptr/pr118757.cc b/libstdc++-v3/testsuite/20_util/weak_ptr/pr118757.cc new file mode 100644 index 00000000000..c1110241f09 --- /dev/null +++ b/libstdc++-v3/testsuite/20_util/weak_ptr/pr118757.cc @@ -0,0 +1,47 @@ +// Copyright (C) 2014-2025 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/>. + +// { dg-do run { target c++20 } } +// { dg-require-gthreads "" } +// { dg-require-effective-target hosted } + +#include <memory> +#include <chrono> +#include <thread> +#include <barrier> + +std::shared_ptr<int> s = std::make_shared<int>(42); +std::weak_ptr<int> q = s; +std::atomic<std::weak_ptr<int>> p = q; + +std::barrier bar(2); + +void signaler() +{ + std::this_thread::sleep_for(std::chrono::seconds(1)); + p.store(std::shared_ptr<int>(s, nullptr)); + p.notify_one(); + bar.arrive_and_wait(); +} + +int main(int, char**) +{ + std::thread thr(signaler); + p.wait(q); + bar.arrive_and_wait(); + thr.join(); +} -- 2.50.1