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 uintptr_t. 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::_AtomicRef): Define.
        (_Atomic_count::~_Atomic_count, _Atomic_count::lock)
        (_Atomic_count::unlock, _Atomic_count::_M_swap_unlock)
        (_Atomic_count::notify_one, _Atomic_count::notify_all):
        Use _AtomicRef for atomic operations on _M_val.
        (_Sp_atomic::element_type): Define.
        (_Sp_atomic::_M_val): Change type to uintptr_t, add appropariate
        alignas.
        (_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 <[email protected]>
---
changes in v3:
 * break the lines to fit 80 collumns in commit description
 * fix typos in commit message.
 * make sure that _M_val is sufficiently aligned
   (atomic_ref<uintptr_t>::required_aligment)
 * using __atomic_ref<uintptr_t> for atomic operations on _M_val.
 * remove copyright notice and fix signaller in test.

Testing on x86_64-linux. OK for trunk when test passes?

 libstdc++-v3/include/bits/shared_ptr_atomic.h | 57 ++++++++++++++-----
 libstdc++-v3/python/libstdcxx/v6/printers.py  |  6 +-
 .../20_util/shared_ptr/atomic/pr118757.cc     | 29 ++++++++++
 .../testsuite/20_util/weak_ptr/pr118757.cc    | 30 ++++++++++
 4 files changed, 107 insertions(+), 15 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..cbc4bf621f4 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 = _AtomicRef(_M_val).load(memory_order_relaxed);
          _GLIBCXX_TSAN_MUTEX_DESTROY(&_M_val);
          __glibcxx_assert(!(__val & _S_lock_bit));
          if (auto __pi = reinterpret_cast<pointer>(__val))
@@ -442,18 +443,19 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
        {
          // To acquire the lock we flip the LSB from 0 to 1.
 
-         auto __current = _M_val.load(memory_order_relaxed);
+         _AtomicRef __aref(_M_val);
+         auto __current = __aref.load(memory_order_relaxed);
          while (__current & _S_lock_bit)
            {
 #if __glibcxx_atomic_wait
              __detail::__thread_relax();
 #endif
-             __current = _M_val.load(memory_order_relaxed);
+             __current = __aref.load(memory_order_relaxed);
            }
 
          _GLIBCXX_TSAN_MUTEX_TRY_LOCK(&_M_val);
 
-         while (!_M_val.compare_exchange_strong(__current,
+         while (!__aref.compare_exchange_strong(__current,
                                                 __current | _S_lock_bit,
                                                 __o,
                                                 memory_order_relaxed))
@@ -474,7 +476,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
        unlock(memory_order __o) const noexcept
        {
          _GLIBCXX_TSAN_MUTEX_PRE_UNLOCK(&_M_val);
-         _M_val.fetch_sub(1, __o);
+         _AtomicRef(_M_val).fetch_sub(1, __o);
          _GLIBCXX_TSAN_MUTEX_POST_UNLOCK(&_M_val);
        }
 
@@ -487,7 +489,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 = _AtomicRef(_M_val).exchange(__x, __o);
          _GLIBCXX_TSAN_MUTEX_POST_UNLOCK(&_M_val);
          __c._M_pi = reinterpret_cast<pointer>(__x & ~_S_lock_bit);
        }
@@ -495,19 +497,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);
+         uintptr_t __old_pi 
+           = _AtomicRef(_M_val).fetch_sub(1, memory_order_relaxed) - 1u;
          _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 _AtomicRef(_M_val).load(__o); });
        }
 
        void
        notify_one() noexcept
        {
          _GLIBCXX_TSAN_MUTEX_PRE_SIGNAL(&_M_val);
-         _M_val.notify_one();
+         _AtomicRef(_M_val).notify_one();
          _GLIBCXX_TSAN_MUTEX_POST_SIGNAL(&_M_val);
        }
 
@@ -515,17 +543,18 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
        notify_all() noexcept
        {
          _GLIBCXX_TSAN_MUTEX_PRE_SIGNAL(&_M_val);
-         _M_val.notify_all();
+         _AtomicRef(_M_val).notify_all();
          _GLIBCXX_TSAN_MUTEX_POST_SIGNAL(&_M_val);
        }
 #endif
 
       private:
-       mutable __atomic_base<uintptr_t> _M_val{0};
+       using _AtomicRef = __atomic_ref<uintptr_t>;
+       alignas(_AtomicRef::required_alignment) 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 +637,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 e5336b7fa89..1822d425f88 100644
--- a/libstdc++-v3/python/libstdcxx/v6/printers.py
+++ b/libstdc++-v3/python/libstdcxx/v6/printers.py
@@ -287,7 +287,11 @@ 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']
+            val = self._val['_M_refcount']['_M_val']
+            if val.type.is_scalar: # GCC 16 stores uintptr_t
+                ptr_val = val
+            else: # GCC 12-15 stores std::atomic<uintptr_t>
+                ptr_val = val['_M_i']
             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..d54abd8a039
--- /dev/null
+++ b/libstdc++-v3/testsuite/20_util/shared_ptr/atomic/pr118757.cc
@@ -0,0 +1,29 @@
+// { 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 signaller()
+{
+  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(signaller);
+  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..f048f13aec2
--- /dev/null
+++ b/libstdc++-v3/testsuite/20_util/weak_ptr/pr118757.cc
@@ -0,0 +1,30 @@
+// { 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 signaller()
+{
+  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(signaller);
+  p.wait(q);
+  bar.arrive_and_wait();
+  thr.join();
+}
-- 
2.51.0

Reply via email to