Hello,

The attached patch changes std::weak_ptr "converting move constructor/assignment" -- that is, from a rvalue weak_ptr<Derived> to a weak_ptr<Base>.

In the general case, such conversion requires lock()ing the weak_ptr because the pointed-to object might have already been destroyed, and a Derived->Base pointer conversion requires dereferencing the pointer iff Base is a virtual base class of Derived. Therefore the correct thing to do in the general case is to lock() the weak_ptr, call get() on the resulting shared_ptr, and do the pointer conversion while this shared_ptr is alive.

However, the newly added __is_virtual_base_of builtin allows us to micro-optimize this pointer upcast, and avoid calling lock() (and paying for the associated atomic operations) in case Base is *not* a virtual base class of Derived. In this case we can "just" perform the upcast; as far as I know, no ABI requires dereferencing the pointer in the non-virtual inheritance case.

Thanks,
--
Giuseppe D'Angelo
From c67349ec3ac53573a5a36edeedaea9e8f5e41c97 Mon Sep 17 00:00:00 2001
From: Giuseppe D'Angelo <giuseppe.dang...@kdab.com>
Date: Wed, 2 Oct 2024 18:14:34 +0200
Subject: [PATCH] libstdc++: use is_virtual_base_of in std::weak_ptr operations

Converting a weak_ptr<Derived> to a weak_ptr<Base> requires calling
lock() on the source object in the general case.

Although the source weak_ptr<Derived> does contain a raw pointer to
Derived, we can't just get it and (up)cast it to Base, as that will
dereference the pointer in case Base is a virtual base class of Derived.

We don't know if the managed object is still alive, and therefore if
this operation is safe to do; we therefore temporarily lock() the source
weak_ptr, do the cast using the resulting shared_ptr, and then discard
this shared_ptr. Simply checking the strong counter isn't sufficient,
because if multiple threads are involved then we'd have a race / TOCTOU
problem; the object may get destroyed after we check the strong counter
and before we cast the pointer.

However lock() is not necessary if we know that Base is *not* a virtual
base class of Derived; in this case we can avoid the relatively
expensive call to lock() and just cast the pointer. This commit uses
the newly added builtin to detect this case and optimize std::weak_ptr's
converting constructors and assignment operations.

libstdc++-v3/ChangeLog:

	* include/bits/shared_ptr_base.h (__weak_ptr): Avoid calling
        lock() when converting or assigning a weak_ptr<Derived> to
        a weak_ptr<Base> in case Base is not a virtual base of Derived.
	* testsuite/20_util/weak_ptr/cons/virtual_bases.cc: New test.

Signed-off-by: Giuseppe D'Angelo <giuseppe.dang...@kdab.com>
---
 libstdc++-v3/include/bits/shared_ptr_base.h   | 19 +++--
 .../20_util/weak_ptr/cons/virtual_bases.cc    | 80 +++++++++++++++++++
 2 files changed, 94 insertions(+), 5 deletions(-)
 create mode 100644 libstdc++-v3/testsuite/20_util/weak_ptr/cons/virtual_bases.cc

diff --git a/libstdc++-v3/include/bits/shared_ptr_base.h b/libstdc++-v3/include/bits/shared_ptr_base.h
index ee01594ce0c..a4b52ae4565 100644
--- a/libstdc++-v3/include/bits/shared_ptr_base.h
+++ b/libstdc++-v3/include/bits/shared_ptr_base.h
@@ -1992,6 +1992,15 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       template<typename _Yp>
 	using _Assignable = _Compatible<_Yp, __weak_ptr&>;
 
+      // Helper for construction/assignment:
+      template<typename _Yp, typename = _Compatible<_Yp>>
+	static _Tp* __safe_upcast(const __weak_ptr<_Yp, _Lp> &__r)
+	{
+	  if _GLIBCXX17_CONSTEXPR (__builtin_is_virtual_base_of(_Tp, _Yp))
+	    return __r.lock().get();
+	  return __r._M_ptr;
+	}
+
     public:
       using element_type = typename remove_extent<_Tp>::type;
 
@@ -2019,8 +2028,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       // in multithreaded programs __r._M_ptr may be invalidated at any point.
       template<typename _Yp, typename = _Compatible<_Yp>>
 	__weak_ptr(const __weak_ptr<_Yp, _Lp>& __r) noexcept
-	: _M_refcount(__r._M_refcount)
-        { _M_ptr = __r.lock().get(); }
+	: _M_ptr(__safe_upcast(__r)), _M_refcount(__r._M_refcount)
+        { }
 
       template<typename _Yp, typename = _Compatible<_Yp>>
 	__weak_ptr(const __shared_ptr<_Yp, _Lp>& __r) noexcept
@@ -2033,7 +2042,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 
       template<typename _Yp, typename = _Compatible<_Yp>>
 	__weak_ptr(__weak_ptr<_Yp, _Lp>&& __r) noexcept
-	: _M_ptr(__r.lock().get()), _M_refcount(std::move(__r._M_refcount))
+	: _M_ptr(__safe_upcast(__r)), _M_refcount(std::move(__r._M_refcount))
         { __r._M_ptr = nullptr; }
 
       __weak_ptr&
@@ -2043,7 +2052,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	_Assignable<_Yp>
 	operator=(const __weak_ptr<_Yp, _Lp>& __r) noexcept
 	{
-	  _M_ptr = __r.lock().get();
+	  _M_ptr = __safe_upcast(__r);
 	  _M_refcount = __r._M_refcount;
 	  return *this;
 	}
@@ -2068,7 +2077,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	_Assignable<_Yp>
 	operator=(__weak_ptr<_Yp, _Lp>&& __r) noexcept
 	{
-	  _M_ptr = __r.lock().get();
+	  _M_ptr = __safe_upcast(__r);
 	  _M_refcount = std::move(__r._M_refcount);
 	  __r._M_ptr = nullptr;
 	  return *this;
diff --git a/libstdc++-v3/testsuite/20_util/weak_ptr/cons/virtual_bases.cc b/libstdc++-v3/testsuite/20_util/weak_ptr/cons/virtual_bases.cc
new file mode 100644
index 00000000000..ac3e4bce9da
--- /dev/null
+++ b/libstdc++-v3/testsuite/20_util/weak_ptr/cons/virtual_bases.cc
@@ -0,0 +1,80 @@
+// { dg-do run { target c++11 } }
+
+#include <memory>
+#include <testsuite_hooks.h>
+
+struct BaseBase { virtual ~BaseBase() = default; };
+struct Base : BaseBase { virtual ~Base() = default; };
+struct Derived1 : Base { virtual ~Derived1() = default; };
+struct Derived2 : virtual Base { virtual ~Derived2() = default; };
+struct Derived3 : virtual Base { virtual ~Derived3() = default; };
+struct Derived4 : Derived2, Derived3 { virtual ~Derived4() = default; };
+struct Derived5 : Derived4 { virtual ~Derived5() = default; };
+
+template<typename T>
+void test01()
+{
+  std::shared_ptr<T> ptr(new T);
+  VERIFY(ptr);
+
+  std::weak_ptr<T> wptr1 = ptr;
+  VERIFY(wptr1.lock());
+
+  std::weak_ptr<Base> wptr2 = ptr;
+  VERIFY(wptr2.lock());
+
+  std::weak_ptr<Base> wptr3 = wptr1;
+  VERIFY(wptr3.lock());
+
+  std::weak_ptr<BaseBase> wptr4 = ptr;
+  VERIFY(wptr4.lock());
+
+  std::weak_ptr<BaseBase> wptr5 = std::move(wptr1);
+  VERIFY(wptr5.lock());
+
+  ptr.reset();
+
+  VERIFY(!wptr1.lock());
+  VERIFY(!wptr2.lock());
+  VERIFY(!wptr3.lock());
+  VERIFY(!wptr4.lock());
+  VERIFY(!wptr5.lock());
+}
+
+template<typename T>
+void test02()
+{
+  std::shared_ptr<T> ptr(new T);
+  VERIFY(ptr);
+
+  std::weak_ptr<T> wptr1 = ptr;
+  VERIFY(wptr1.lock());
+
+  std::weak_ptr<Base> wptr2 = ptr;
+  VERIFY(wptr2.lock());
+
+  ptr.reset();
+
+  std::weak_ptr<Base> wptr3 = wptr1;
+  std::weak_ptr<BaseBase> wptr4 = wptr1;
+  std::weak_ptr<BaseBase> wptr5 = std::move(wptr1);
+
+  VERIFY(!wptr1.lock());
+  VERIFY(!wptr2.lock());
+  VERIFY(!wptr3.lock());
+  VERIFY(!wptr4.lock());
+  VERIFY(!wptr5.lock());
+}
+
+int main()
+{
+  test01<Derived1>();
+  test01<Derived2>();
+  test01<Derived4>();
+  test01<Derived5>();
+
+  test02<Derived1>();
+  test02<Derived2>();
+  test02<Derived4>();
+  test02<Derived5>();
+}
-- 
2.34.1

Attachment: smime.p7s
Description: S/MIME Cryptographic Signature

Reply via email to