On 03/12/2024 18:02, Jonathan Wakely wrote:
On Tue, 3 Dec 2024 at 16:56, Jonathan Wakely <jwak...@redhat.com> wrote:On Tue, 3 Dec 2024 at 16:19, Giuseppe D'Angelo <giuseppe.dang...@kdab.com> wrote: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.This is a nice optimization!
Thank you for the review!I think I've incorporated all the changes, new patch is attached. Some other comments...
We would usually use _S_safe_upcast for the name of a static member function, although I think __safe_upcast is OK here.
Well, I've renamed the static helper, while at it.
Do we need the _Compatible constraint on __safe_upcast? It's only called internally from code that has already enforced that constraint, so checking again just slows compilation down.
Sure, it's a tradeoff between re-checking versus having an internal API that might get accidentally misused (if there is no check).
[snip]
We should probably even be using the _GLIBCXX_USE_BUILTIN_TRAIT macro here, so that its use can be disabled.
Out of curiosity, are there some docs regarding what libstdc++ does require out of the compiler? For instance, in this case, I know that both GCC and Clang trunk have the builtin (with the same name), and I wouldn't expect this patch to be cherry-picked into old/stable branches anyhow. Is new libstdc++ + "old" compiler a supported combination? (I guess it may happen on a Linux distro.) But up to which point? Etc.
Thanks, -- Giuseppe D'Angelo
From 196185cb2b1526719b2cc8719d252bf050eea2d9 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. This logic is centralized in _S_safe_upcast, called by the various converting constructors/assignment operators. (_S_safe_upcast): New helper function. * 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 | 25 ++++-- .../20_util/weak_ptr/cons/virtual_bases.cc | 80 +++++++++++++++++++ 2 files changed, 100 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..27e642b4d57 100644 --- a/libstdc++-v3/include/bits/shared_ptr_base.h +++ b/libstdc++-v3/include/bits/shared_ptr_base.h @@ -1992,6 +1992,21 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION template<typename _Yp> using _Assignable = _Compatible<_Yp, __weak_ptr&>; + // Helper for construction/assignment: + template<typename _Yp> + static _Tp* _S_safe_upcast(const __weak_ptr<_Yp, _Lp>& __r) + { +#if _GLIBCXX_USE_BUILTIN_TRAIT(__builtin_is_virtual_base_of) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wc++17-extensions" // if constexpr + if constexpr (!__builtin_is_virtual_base_of(_Tp, _Yp)) + return __r._M_ptr; + else +#pragma GCC diagnostic pop +#endif + return __r.lock().get(); + } + public: using element_type = typename remove_extent<_Tp>::type; @@ -2019,8 +2034,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(_S_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 +2048,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(_S_safe_upcast(__r)), _M_refcount(std::move(__r._M_refcount)) { __r._M_ptr = nullptr; } __weak_ptr& @@ -2043,7 +2058,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION _Assignable<_Yp> operator=(const __weak_ptr<_Yp, _Lp>& __r) noexcept { - _M_ptr = __r.lock().get(); + _M_ptr = _S_safe_upcast(__r); _M_refcount = __r._M_refcount; return *this; } @@ -2068,7 +2083,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION _Assignable<_Yp> operator=(__weak_ptr<_Yp, _Lp>&& __r) noexcept { - _M_ptr = __r.lock().get(); + _M_ptr = _S_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
smime.p7s
Description: S/MIME Cryptographic Signature