From patchwork Fri Jan 14 10:21:01 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jonathan Wakely X-Patchwork-Id: 50018 Return-Path: X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id DB823385C414 for ; Fri, 14 Jan 2022 10:22:31 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org DB823385C414 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gcc.gnu.org; s=default; t=1642155751; bh=YwbmSiwPSR2qDN88mxnfHNgyDijEiGLcK0gjV4lmcYs=; h=To:Subject:Date:List-Id:List-Unsubscribe:List-Archive:List-Post: List-Help:List-Subscribe:From:Reply-To:From; b=MPzweyeZbmIK6/OcPj0pftdUaS/Cs1rP1Q/Yx8maG9H/HCJupqfuRxAZdxFSfM58s wmskSYlr8HPyG/uQlBl28SCMDkNWld2yB1RbnKjiXm3FNf2FzFCsTUvYRylt0wTVqm vr7wY77QogDL8Cf+C5Ov4vEU9v8OOTYJpDLoM8Pc= X-Original-To: gcc-patches@gcc.gnu.org Delivered-To: gcc-patches@gcc.gnu.org Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) by sourceware.org (Postfix) with ESMTPS id 412BD385BF80 for ; Fri, 14 Jan 2022 10:21:09 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.1 sourceware.org 412BD385BF80 Received: from mimecast-mx01.redhat.com (mimecast-mx01.redhat.com [209.132.183.4]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id us-mta-529-UYhOxsQhOSed9z9uQ2L0vA-1; Fri, 14 Jan 2022 05:21:05 -0500 X-MC-Unique: UYhOxsQhOSed9z9uQ2L0vA-1 Received: from smtp.corp.redhat.com (int-mx08.intmail.prod.int.phx2.redhat.com [10.5.11.23]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx01.redhat.com (Postfix) with ESMTPS id D3F1185EE69; Fri, 14 Jan 2022 10:21:03 +0000 (UTC) Received: from localhost (unknown [10.33.36.252]) by smtp.corp.redhat.com (Postfix) with ESMTP id 2FC4916A44; Fri, 14 Jan 2022 10:21:02 +0000 (UTC) To: libstdc++@gcc.gnu.org, gcc-patches@gcc.gnu.org Subject: [committed] libstdc++: Add C++20 std::make_shared enhancements (P0674R1) Date: Fri, 14 Jan 2022 10:21:01 +0000 Message-Id: <20220114102101.2975426-1-jwakely@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.84 on 10.5.11.23 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com X-Spam-Status: No, score=-13.9 required=5.0 tests=BAYES_00, DKIMWL_WL_HIGH, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, RCVD_IN_DNSWL_LOW, RCVD_IN_MSPIKE_H3, RCVD_IN_MSPIKE_WL, SPF_HELO_NONE, SPF_NONE, TXREP autolearn=ham autolearn_force=no version=3.4.4 X-Spam-Checker-Version: SpamAssassin 3.4.4 (2020-01-24) on server2.sourceware.org X-BeenThere: gcc-patches@gcc.gnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Gcc-patches mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-Patchwork-Original-From: Jonathan Wakely via Gcc-patches From: Jonathan Wakely Reply-To: Jonathan Wakely Errors-To: gcc-patches-bounces+patchwork=sourceware.org@gcc.gnu.org Sender: "Gcc-patches" Tested powerpc64-le-linux, pushed to trunk. N.B. we had a patch from JeanHeyd two years ago adding the first part of this (P0674R1), but this is much simpler, works without concepts, and also adds make_shared_for_overwrite. This adds the overloads of std::make_shared and std::allocate_shared for creating arrays, added to C++20 by P0674R1. It also adds std::make_shared_for_overwrite, added to C++20 by P1020R1 (and renamed by P1973R1). The std::make_unique_for_overwite overloads are already supported. The original std::make_shared overload is changed to construct a shared_ptr directly instead of calling std::allocate_shared. This removes a function call at runtime, and avoids having to do overload resolution for std::allocate_shared, now that there are five overloads of it. Allocating a shared array is done by a new __shared_count constructor. An array is allocated with space for additional elements at the end and an instance of new _Sp_counted_array class template is constructed in that unused capacity. The non-array form of std::make_shared_for_overwrite uses the same __shared_count constructor as the original std::make_shared overload, but a new partial specialization of _Sp_counted_ptr_inplace is selected when the allocator's value_type is the new _Sp_overwrite_tag type. That new partial specialization default-initializes its contained object and destroys it with a destructor call rather than using the allocator. Despite being C++20 features, this implementation only uses concepts conditionally, with workarounds when they are not supported. This allows it to work with older non-GCC compilers (Clang 9 and icc 2021). At some point we can simplify the code by removing the workarounds. libstdc++-v3/ChangeLog: * include/bits/shared_ptr.h (__cpp_lib_shared_ptr_weak_type): Correct type of macro value. (shared_ptr): Add additional friend declarations. (make_shared, allocate_shared): Constrain existing overloads and remove static_assert. * include/bits/shared_ptr_base.h (__cpp_lib_smart_ptr_for_overwrite): New macro. (_Sp_counted_ptr_inplace): New partial specialization for use with make_shared_for_overwrite. (__cpp_lib_shared_ptr_arrays): Update value for C++20. (_Sp_counted_array_base): New class template. (_Sp_counted_array): New class template. (__shared_count(_Tp*&, const _Sp_counted_array_base&, _Init)): New constructor for allocating shared arrays. (__shared_ptr(const _Sp_counted_array_base&, _Init)): Likewise. * include/std/version (__cpp_lib_shared_ptr_weak_type): Correct type. (__cpp_lib_shared_ptr_arrays): Update value for C++20. (__cpp_lib_smart_ptr_for_overwrite): New macro. * testsuite/20_util/shared_ptr/creation/99006.cc: Adjust expected errors. * testsuite/20_util/shared_ptr/creation/array.cc: New test. * testsuite/20_util/shared_ptr/creation/overwrite.cc: New test. * testsuite/20_util/shared_ptr/creation/version.cc: New test. * testsuite/20_util/unique_ptr/creation/for_overwrite.cc: Check feature test macro. Test non-trivial default-initialization. --- libstdc++-v3/include/bits/shared_ptr.h | 271 +++++++++++++++- libstdc++-v3/include/bits/shared_ptr_base.h | 299 +++++++++++++++++- libstdc++-v3/include/std/version | 6 +- .../20_util/shared_ptr/creation/99006.cc | 7 +- .../20_util/shared_ptr/creation/array.cc | 224 +++++++++++++ .../20_util/shared_ptr/creation/overwrite.cc | 143 +++++++++ .../20_util/shared_ptr/creation/version.cc | 18 ++ .../unique_ptr/creation/for_overwrite.cc | 25 +- 8 files changed, 975 insertions(+), 18 deletions(-) create mode 100644 libstdc++-v3/testsuite/20_util/shared_ptr/creation/array.cc create mode 100644 libstdc++-v3/testsuite/20_util/shared_ptr/creation/overwrite.cc create mode 100644 libstdc++-v3/testsuite/20_util/shared_ptr/creation/version.cc diff --git a/libstdc++-v3/include/bits/shared_ptr.h b/libstdc++-v3/include/bits/shared_ptr.h index 5fdfcb2c4e1..f44008ed2b1 100644 --- a/libstdc++-v3/include/bits/shared_ptr.h +++ b/libstdc++-v3/include/bits/shared_ptr.h @@ -100,6 +100,57 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION #endif } + /// @cond undocumented + + // Constraint for overloads taking non-array types. +#if __cpp_concepts && __cpp_lib_type_trait_variable_templates + template + requires (!is_array_v<_Tp>) + using _NonArray = _Tp; +#else + template + using _NonArray = __enable_if_t::value, _Tp>; +#endif + +#if __cpp_lib_shared_ptr_arrays >= 201707L + // Constraint for overloads taking array types with unknown bound, U[]. +#if __cpp_concepts + template + requires is_array_v<_Tp> && (extent_v<_Tp> == 0) + using _UnboundedArray = _Tp; +#else + template + using _UnboundedArray + = __enable_if_t<__is_array_unknown_bounds<_Tp>::value, _Tp>; +#endif + + // Constraint for overloads taking array types with known bound, U[N]. +#if __cpp_concepts + template + requires (extent_v<_Tp> != 0) + using _BoundedArray = _Tp; +#else + template + using _BoundedArray + = __enable_if_t<__is_array_known_bounds<_Tp>::value, _Tp>; +#endif + +#if __cpp_lib_smart_ptr_for_overwrite + // Constraint for overloads taking either non-array or bounded array, U[N]. +#if __cpp_concepts + template + requires (!is_array_v<_Tp>) || (extent_v<_Tp> != 0) + using _NotUnboundedArray = _Tp; +#else + template + using _NotUnboundedArray + = __enable_if_t::value, _Tp>; +#endif +#endif // smart_ptr_for_overwrite +#endif // shared_ptr_arrays + + /// @endcond + /** * @brief A smart pointer with reference-counted copy semantics. * @headerfile memory @@ -139,7 +190,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION using element_type = typename __shared_ptr<_Tp>::element_type; #if __cplusplus >= 201703L -# define __cpp_lib_shared_ptr_weak_type 201606 +# define __cpp_lib_shared_ptr_weak_type 201606L /// The corresponding weak_ptr type for this shared_ptr /// @since C++17 using weak_type = weak_ptr<_Tp>; @@ -414,8 +465,71 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION { } template - friend shared_ptr<_Yp> - allocate_shared(const _Alloc& __a, _Args&&... __args); + friend shared_ptr<_NonArray<_Yp>> + allocate_shared(const _Alloc&, _Args&&...); + + template + friend shared_ptr<_NonArray<_Yp>> + make_shared(_Args&&...); + +#if __cpp_lib_shared_ptr_arrays >= 201707L + // This constructor is non-standard, it is used by allocate_shared. + template*> + shared_ptr(const _Sp_counted_array_base<_Alloc>& __a, + _Init __init = nullptr) + : __shared_ptr<_Tp>(__a, __init) + { } + + template + friend shared_ptr<_UnboundedArray<_Yp>> + allocate_shared(const _Alloc&, size_t); + + template + friend shared_ptr<_UnboundedArray<_Yp>> + make_shared(size_t); + + template + friend shared_ptr<_UnboundedArray<_Yp>> + allocate_shared(const _Alloc&, size_t, const remove_extent_t<_Yp>&); + + template + friend shared_ptr<_UnboundedArray<_Yp>> + make_shared(size_t, const remove_extent_t<_Yp>&); + + template + friend shared_ptr<_BoundedArray<_Yp>> + allocate_shared(const _Alloc&); + + template + friend shared_ptr<_BoundedArray<_Yp>> + make_shared(); + + template + friend shared_ptr<_BoundedArray<_Yp>> + allocate_shared(const _Alloc&, const remove_extent_t<_Yp>&); + + template + friend shared_ptr<_BoundedArray<_Yp>> + make_shared(const remove_extent_t<_Yp>&); + +#if __cpp_lib_smart_ptr_for_overwrite + template + friend shared_ptr<_NotUnboundedArray<_Yp>> + allocate_shared_for_overwrite(const _Alloc&); + + template + friend shared_ptr<_NotUnboundedArray<_Yp>> + make_shared_for_overwrite(); + + template + friend shared_ptr<_UnboundedArray<_Yp>> + allocate_shared_for_overwrite(const _Alloc&, size_t); + + template + friend shared_ptr<_UnboundedArray<_Yp>> + make_shared_for_overwrite(size_t); +#endif +#endif // This constructor is non-standard, it is used by weak_ptr::lock(). shared_ptr(const weak_ptr<_Tp>& __r, std::nothrow_t) noexcept @@ -872,11 +986,9 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION * and the new object. */ template - inline shared_ptr<_Tp> + inline shared_ptr<_NonArray<_Tp>> allocate_shared(const _Alloc& __a, _Args&&... __args) { - static_assert(!is_array<_Tp>::value, "make_shared not supported"); - return shared_ptr<_Tp>(_Sp_alloc_shared_tag<_Alloc>{__a}, std::forward<_Args>(__args)...); } @@ -889,14 +1001,153 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION * constructor of @a _Tp. */ template - inline shared_ptr<_Tp> + inline shared_ptr<_NonArray<_Tp>> make_shared(_Args&&... __args) { - typedef typename std::remove_cv<_Tp>::type _Tp_nc; - return std::allocate_shared<_Tp>(std::allocator<_Tp_nc>(), - std::forward<_Args>(__args)...); + using _Alloc = allocator; + _Alloc __a; + return shared_ptr<_Tp>(_Sp_alloc_shared_tag<_Alloc>{__a}, + std::forward<_Args>(__args)...); } +#if __cpp_lib_shared_ptr_arrays >= 201707L + /// @cond undocumented + template> + auto + __make_shared_arr_tag(size_t __n, const _Alloc& __a = _Alloc()) noexcept + { + using _Up = remove_all_extents_t<_Tp>; + using _UpAlloc = __alloc_rebind<_Alloc, _Up>; + size_t __s = sizeof(remove_extent_t<_Tp>) / sizeof(_Up); + if (__builtin_mul_overflow(__s, __n, &__n)) + std::__throw_bad_array_new_length(); + return _Sp_counted_array_base<_UpAlloc>{_UpAlloc(__a), __n}; + } + /// @endcond + + template + inline shared_ptr<_UnboundedArray<_Tp>> + allocate_shared(const _Alloc& __a, size_t __n) + { + return shared_ptr<_Tp>(std::__make_shared_arr_tag<_Tp>(__n, __a)); + } + + template + inline shared_ptr<_UnboundedArray<_Tp>> + make_shared(size_t __n) + { + return shared_ptr<_Tp>(std::__make_shared_arr_tag<_Tp>(__n)); + } + + template + inline shared_ptr<_UnboundedArray<_Tp>> + allocate_shared(const _Alloc& __a, size_t __n, + const remove_extent_t<_Tp>& __u) + { + return shared_ptr<_Tp>(std::__make_shared_arr_tag<_Tp>(__n, __a), + std::__addressof(__u)); + } + + template + inline shared_ptr<_UnboundedArray<_Tp>> + make_shared(size_t __n, const remove_extent_t<_Tp>& __u) + { + return shared_ptr<_Tp>(std::__make_shared_arr_tag<_Tp>(__n), + std::__addressof(__u)); + } + + /// @cond undocumented + template> + auto + __make_shared_arrN_tag(const _Alloc& __a = _Alloc()) noexcept + { + using _Up = remove_all_extents_t<_Tp>; + using _UpAlloc = __alloc_rebind<_Alloc, _Up>; + size_t __n = sizeof(_Tp) / sizeof(_Up); + return _Sp_counted_array_base<_UpAlloc>{_UpAlloc(__a), __n}; + } + /// @endcond + + template + inline shared_ptr<_BoundedArray<_Tp>> + allocate_shared(const _Alloc& __a) + { + return shared_ptr<_Tp>(std::__make_shared_arrN_tag<_Tp>(__a)); + } + + template + inline shared_ptr<_BoundedArray<_Tp>> + make_shared() + { + return shared_ptr<_Tp>(std::__make_shared_arrN_tag<_Tp>()); + } + + template + inline shared_ptr<_BoundedArray<_Tp>> + allocate_shared(const _Alloc& __a, const remove_extent_t<_Tp>& __u) + { + return shared_ptr<_Tp>(std::__make_shared_arrN_tag<_Tp>(__a), + std::__addressof(__u)); + } + + template + inline shared_ptr<_BoundedArray<_Tp>> + make_shared(const remove_extent_t<_Tp>& __u) + { + return shared_ptr<_Tp>(std::__make_shared_arrN_tag<_Tp>(), + std::__addressof(__u)); + } + +#if __cpp_lib_smart_ptr_for_overwrite + template + inline shared_ptr<_NotUnboundedArray<_Tp>> + allocate_shared_for_overwrite(const _Alloc& __a) + { + if constexpr (is_array_v<_Tp>) + return shared_ptr<_Tp>(std::__make_shared_arrN_tag<_Tp>(__a), + _Sp_overwrite_tag{}); + else + { + // Rebind the allocator to _Sp_overwrite_tag, so that the + // relevant _Sp_counted_ptr_inplace specialization is used. + using _Alloc2 = __alloc_rebind<_Alloc, _Sp_overwrite_tag>; + _Alloc2 __a2 = __a; + return shared_ptr<_Tp>(_Sp_alloc_shared_tag<_Alloc2>{__a2}); + } + } + + template + inline shared_ptr<_NotUnboundedArray<_Tp>> + make_shared_for_overwrite() + { + if constexpr (is_array_v<_Tp>) + return shared_ptr<_Tp>(std::__make_shared_arrN_tag<_Tp>(), + _Sp_overwrite_tag{}); + else + { + using _Alloc = allocator<_Sp_overwrite_tag>; + return shared_ptr<_Tp>(_Sp_alloc_shared_tag<_Alloc>{{}}); + } + } + + template + inline shared_ptr<_UnboundedArray<_Tp>> + allocate_shared_for_overwrite(const _Alloc& __a, size_t __n) + { + return shared_ptr<_Tp>(std::__make_shared_arr_tag<_Tp>(__n, __a), + _Sp_overwrite_tag{}); + } + + template + inline shared_ptr<_UnboundedArray<_Tp>> + make_shared_for_overwrite(size_t __n) + { + return shared_ptr<_Tp>(std::__make_shared_arr_tag<_Tp>(__n), + _Sp_overwrite_tag{}); + } +#endif // smart_ptr_for_overwrite +#endif // shared_ptr_arrays + /// std::hash specialization for shared_ptr. template struct hash> diff --git a/libstdc++-v3/include/bits/shared_ptr_base.h b/libstdc++-v3/include/bits/shared_ptr_base.h index e9f4743fedb..e16a9250592 100644 --- a/libstdc++-v3/include/bits/shared_ptr_base.h +++ b/libstdc++-v3/include/bits/shared_ptr_base.h @@ -60,8 +60,11 @@ #include #include #include -#if __cplusplus > 201703L +#if __cplusplus >= 202002L +# include // __bit_floor # include +# include // std::align +# include #endif namespace std _GLIBCXX_VISIBILITY(default) @@ -447,6 +450,11 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION inline void _Sp_counted_ptr::_M_dispose() noexcept { } + // FIXME: once __has_cpp_attribute(__no_unique_address__)) is true for + // all supported compilers we can greatly simplify _Sp_ebo_helper. + // N.B. unconditionally applying the attribute could change layout for + // final types, which currently cannot use EBO so have a unique address. + template struct _Sp_ebo_helper; @@ -640,6 +648,233 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION _Impl _M_impl; }; +#if __cplusplus >= 202002L +# define __cpp_lib_smart_ptr_for_overwrite 202002L + struct _Sp_overwrite_tag { }; + + // Partial specialization used for make_shared_for_overwrite(). + // This partial specialization is used when the allocator's value type + // is the special _Sp_overwrite_tag type. +#if __cpp_concepts + template + requires is_same_v + class _Sp_counted_ptr_inplace<_Tp, _Alloc, _Lp> final +#else + template class _Alloc, _Lock_policy _Lp> + class _Sp_counted_ptr_inplace<_Tp, _Alloc<_Sp_overwrite_tag>, _Lp> final +#endif + : public _Sp_counted_base<_Lp> + { + [[no_unique_address]] _Alloc _M_alloc; + + union { + _Tp _M_obj; + char _M_unused; + }; + + friend class __shared_count<_Lp>; // To be able to call _M_ptr(). + + _Tp* _M_ptr() noexcept { return std::__addressof(_M_obj); } + + public: + using __allocator_type = __alloc_rebind<_Alloc, _Sp_counted_ptr_inplace>; + + _Sp_counted_ptr_inplace(const _Alloc& __a) + : _M_alloc(__a) + { + ::new((void*)_M_ptr()) _Tp; // default-initialized, for overwrite. + } + + ~_Sp_counted_ptr_inplace() noexcept { } + + virtual void + _M_dispose() noexcept + { + _M_obj.~_Tp(); + } + + // Override because the allocator needs to know the dynamic type + virtual void + _M_destroy() noexcept + { + using pointer = typename allocator_traits<__allocator_type>::pointer; + __allocator_type __a(_M_alloc); + auto __p = pointer_traits::pointer_to(*this); + __allocated_ptr<__allocator_type> __guard_ptr{ __a, __p }; + this->~_Sp_counted_ptr_inplace(); + } + + void* + _M_get_deleter(const std::type_info&) noexcept override + { return nullptr; } + }; +#endif // C++20 + +#if __cplusplus <= 201703L +# define __cpp_lib_shared_ptr_arrays 201611L +#else +# define __cpp_lib_shared_ptr_arrays 201707L + + struct _Sp_overwrite_tag; + + // For make_shared, make_shared, allocate_shared etc. + template + struct _Sp_counted_array_base + { + [[no_unique_address]] _Alloc _M_alloc{}; + size_t _M_n = 0; + bool _M_overwrite = false; + + typename allocator_traits<_Alloc>::pointer + _M_alloc_array(size_t __tail) + { + return allocator_traits<_Alloc>::allocate(_M_alloc, _M_n + __tail); + } + + void + _M_dealloc_array(typename allocator_traits<_Alloc>::pointer __p, + size_t __tail) + { + allocator_traits<_Alloc>::deallocate(_M_alloc, __p, _M_n + __tail); + } + + // Init the array elements + template + void + _M_init(typename allocator_traits<_Alloc>::value_type* __p, + _Init __init) + { + using _Tp = remove_pointer_t<_Init>; + using _Up = typename allocator_traits<_Alloc>::value_type; + + if constexpr (is_same_v<_Init, _Sp_overwrite_tag>) + { + std::uninitialized_default_construct_n(__p, _M_n); + _M_overwrite = true; + } + else if (__init == nullptr) + std::__uninitialized_default_n_a(__p, _M_n, _M_alloc); + else if constexpr (!is_array_v<_Tp>) + std::__uninitialized_fill_n_a(__p, _M_n, *__init, _M_alloc); + else + { + struct _Iter + { + using value_type = _Up; + using difference_type = ptrdiff_t; + using pointer = const _Up*; + using reference = const _Up&; + using iterator_category = forward_iterator_tag; + + const _Up* _M_p; + size_t _M_len; + size_t _M_pos; + + _Iter& operator++() { ++_M_pos; return *this; } + _Iter operator++(int) { auto __i(*this); ++_M_pos; return __i; } + + reference operator*() const { return _M_p[_M_pos % _M_len]; } + pointer operator->() const { return _M_p + (_M_pos % _M_len); } + + bool operator==(const _Iter& __i) const + { return _M_pos == __i._M_pos; } + }; + + _Iter __first{_S_first_elem(__init), sizeof(_Tp) / sizeof(_Up)}; + _Iter __last = __first; + __last._M_pos = _M_n; + std::__uninitialized_copy_a(__first, __last, __p, _M_alloc); + } + } + + protected: + // Destroy the array elements + void + _M_dispose_array(typename allocator_traits<_Alloc>::value_type* __p) + { + if (_M_overwrite) + std::destroy_n(__p, _M_n); + else + { + size_t __n = _M_n; + while (__n--) + allocator_traits<_Alloc>::destroy(_M_alloc, __p + __n); + } + } + + private: + template + static _Tp* + _S_first_elem(_Tp* __p) { return __p; } + + template + static auto + _S_first_elem(_Tp (*__p)[_Nm]) { return _S_first_elem(*__p); } + }; + + // Control block for make_shared, make_shared etc. that will be + // placed into unused memory at the end of the array. + template + class _Sp_counted_array final + : public _Sp_counted_base<_Lp>, _Sp_counted_array_base<_Alloc> + { + using pointer = typename allocator_traits<_Alloc>::pointer; + + pointer _M_alloc_ptr; + + auto _M_ptr() const noexcept { return std::to_address(_M_alloc_ptr); } + + friend class __shared_count<_Lp>; // To be able to call _M_ptr(). + + public: + _Sp_counted_array(const _Sp_counted_array_base<_Alloc>& __a, + pointer __p) noexcept + : _Sp_counted_array_base<_Alloc>(__a), _M_alloc_ptr(__p) + { } + + ~_Sp_counted_array() = default; + + virtual void + _M_dispose() noexcept + { + if (this->_M_n) + this->_M_dispose_array(_M_ptr()); + } + + // Override because the allocator needs to know the dynamic type + virtual void + _M_destroy() noexcept + { + _Sp_counted_array_base<_Alloc> __a = *this; + pointer __p = _M_alloc_ptr; + this->~_Sp_counted_array(); + __a._M_dealloc_array(__p, _S_tail()); + } + + // Returns the number of additional array elements that must be + // allocated in order to store a _Sp_counted_array at the end. + static constexpr size_t + _S_tail() + { + // The array elemenent type. + using _Tp = typename allocator_traits<_Alloc>::value_type; + + // The space needed to store a _Sp_counted_array object. + size_t __bytes = sizeof(_Sp_counted_array); + + // Add any padding needed for manual alignment within the buffer. + if constexpr (alignof(_Tp) < alignof(_Sp_counted_array)) + __bytes += alignof(_Sp_counted_array) - alignof(_Tp); + + return (__bytes + sizeof(_Tp) - 1) / sizeof(_Tp); + } + + void* + _M_get_deleter(const std::type_info&) noexcept override + { return nullptr; } + }; +#endif // C++20 + // The default deleter for shared_ptr and shared_ptr. struct __sp_array_delete { @@ -650,12 +885,18 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION template<_Lock_policy _Lp> class __shared_count { + // Prevent _Sp_alloc_shared_tag from matching the shared_ptr(P, D) ctor. template struct __not_alloc_shared_tag { using type = void; }; template struct __not_alloc_shared_tag<_Sp_alloc_shared_tag<_Tp>> { }; +#if __cpp_lib_shared_ptr_arrays >= 201707L + template + struct __not_alloc_shared_tag<_Sp_counted_array_base<_Alloc>> { }; +#endif + public: constexpr __shared_count() noexcept : _M_pi(0) { } @@ -727,6 +968,51 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION __p = __pi->_M_ptr(); } +#if __cpp_lib_shared_ptr_arrays >= 201707L + template + __shared_count(_Tp*& __p, const _Sp_counted_array_base<_Alloc>& __a, + _Init __init) + { + using _Up = remove_all_extents_t<_Tp>; + static_assert(is_same_v<_Up, typename _Alloc::value_type>); + + using _Sp_ca_type = _Sp_counted_array<_Alloc, _Lp>; + const size_t __tail = _Sp_ca_type::_S_tail(); + + struct _Guarded_ptr : _Sp_counted_array_base<_Alloc> + { + typename allocator_traits<_Alloc>::pointer _M_ptr; + + _Guarded_ptr(_Sp_counted_array_base<_Alloc> __a) + : _Sp_counted_array_base<_Alloc>(__a), + _M_ptr(this->_M_alloc_array(_Sp_ca_type::_S_tail())) + { } + + ~_Guarded_ptr() + { + if (_M_ptr) + this->_M_dealloc_array(_M_ptr, _Sp_ca_type::_S_tail()); + } + }; + + _Guarded_ptr __guard{__a}; + _Up* const __raw = std::to_address(__guard._M_ptr); + __guard._M_init(__raw, __init); // might throw + + void* __c = __raw + __a._M_n; + if constexpr (alignof(_Up) < alignof(_Sp_ca_type)) + { + size_t __space = sizeof(_Up) * __tail; + __c = std::align(alignof(_Sp_ca_type), sizeof(_Sp_ca_type), + __c, __space); + } + auto __pi = ::new(__c) _Sp_ca_type(__guard, __guard._M_ptr); + __guard._M_ptr = nullptr; + _M_pi = __pi; + __p = reinterpret_cast<_Tp*>(__raw); + } +#endif + #if _GLIBCXX_USE_DEPRECATED #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" @@ -957,8 +1243,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION _M_pi = nullptr; } -#define __cpp_lib_shared_ptr_arrays 201611L - // Helper traits for shared_ptr of array: // A pointer type Y* is said to be compatible with a pointer type T* when @@ -1420,6 +1704,15 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION friend __shared_ptr<_Tp1, _Lp1> __allocate_shared(const _Alloc& __a, _Args&&... __args); +#if __cpp_lib_shared_ptr_arrays >= 201707L + // This constructor is non-standard, it is used by allocate_shared. + template*> + __shared_ptr(const _Sp_counted_array_base<_Alloc>& __a, + _Init __init = nullptr) + : _M_ptr(), _M_refcount(_M_ptr, __a, __init) + { } +#endif + // This constructor is used by __weak_ptr::lock() and // shared_ptr::shared_ptr(const weak_ptr&, std::nothrow_t). __shared_ptr(const __weak_ptr<_Tp, _Lp>& __r, std::nothrow_t) noexcept diff --git a/libstdc++-v3/include/std/version b/libstdc++-v3/include/std/version index c5d5efb1314..a8b792e9b0f 100644 --- a/libstdc++-v3/include/std/version +++ b/libstdc++-v3/include/std/version @@ -51,8 +51,10 @@ #if _GLIBCXX_HOSTED # define __cpp_lib_allocator_traits_is_always_equal 201411 +#if __cplusplus < 201703L // N.B. updated value in C++20 # define __cpp_lib_shared_ptr_arrays 201611L #endif +#endif #if !defined(__STRICT_ANSI__) // gnu++11 @@ -167,7 +169,7 @@ # define __cpp_lib_scoped_lock 201703 # define __cpp_lib_shared_mutex 201505L #endif -#define __cpp_lib_shared_ptr_weak_type 201606 +#define __cpp_lib_shared_ptr_weak_type 201606L #define __cpp_lib_string_view 201803L #if _GLIBCXX_HAVE_USELOCALE # define __cpp_lib_to_chars 201611L @@ -275,7 +277,9 @@ #if __cpp_lib_atomic_wait || _GLIBCXX_HAVE_POSIX_SEMAPHORE # define __cpp_lib_semaphore 201907L #endif +#define __cpp_lib_shared_ptr_arrays 201707L #define __cpp_lib_shift 201806L +#define __cpp_lib_smart_ptr_for_overwrite 202002L #if __cpp_lib_concepts # define __cpp_lib_span 202002L #endif diff --git a/libstdc++-v3/testsuite/20_util/shared_ptr/creation/99006.cc b/libstdc++-v3/testsuite/20_util/shared_ptr/creation/99006.cc index e070fb9d420..67069a1ec10 100644 --- a/libstdc++-v3/testsuite/20_util/shared_ptr/creation/99006.cc +++ b/libstdc++-v3/testsuite/20_util/shared_ptr/creation/99006.cc @@ -1,9 +1,10 @@ -// FIXME: This should use { target { c++11 && { ! c++20 } } } // { dg-do compile { target { c++11 } } } #include -auto p = std::make_shared(2); // { dg-error "here" } +auto p = std::make_shared(2); // { dg-error "here" "" { target c++17_down } } auto q = std::make_shared(1, 2); // { dg-error "here" } -// { dg-prune-output "static assertion failed" } +// { dg-prune-output "enable_if + +#ifndef __cpp_lib_shared_ptr_arrays +# error "Feature-test macro for make_shared arrays missing in " +#elif __cpp_lib_shared_ptr_arrays < 201707L +# error "Feature-test macro for make_shared arrays has wrong value in " +#endif + +#include + +int counter = 0; + +template +struct Alloc : std::allocator +{ + Alloc() = default; + + template + Alloc(const Alloc&) { } + + T* allocate(std::size_t n) + { + ++counter; + return std::allocator::allocate(n); + } +}; + +void +test01() +{ + Alloc a; + + std::shared_ptr p1 = std::allocate_shared(a, 24); + VERIFY( counter == 1 ); + VERIFY( p1[23] == 0 ); + std::shared_ptr p2 = std::allocate_shared(a); + VERIFY( counter == 2 ); + VERIFY( p2[47] == 0 ); + + std::shared_ptr p3 = std::allocate_shared(a, 3); + VERIFY( counter == 3 ); + VERIFY( p3[2][11] == 0 ); + std::shared_ptr p4 = std::allocate_shared(a); + VERIFY( counter == 4 ); + VERIFY( p4[3][4] == 0 ); +} + +void +test02() +{ + std::shared_ptr p1 = std::make_shared(24); + VERIFY( p1[23] == 0 ); + std::shared_ptr p2 = std::make_shared(); + VERIFY( p2[47] == 0 ); + + std::shared_ptr p3 = std::make_shared(3); + VERIFY( p3[2][11] == 0 ); + std::shared_ptr p4 = std::make_shared(); + VERIFY( p4[3][4] == 0 ); +} + +#include + +std::vector addresses; + +void +test03() +{ + // Verify construction and destruction order + struct Addressed + { + Addressed() { addresses.push_back(me()); } + ~Addressed() { VERIFY( addresses.back() == me() ); addresses.pop_back(); } + + std::uintptr_t me() const { return reinterpret_cast(this); } + }; + + auto check = [](auto shptr) { + std::uintptr_t last = 0; + for (auto a : addresses) + { + VERIFY( a > last ); + last = a; + } + shptr.reset(); + return addresses.empty(); + }; + + VERIFY( check(std::make_shared(3)) ); + VERIFY( check(std::make_shared()) ); +} + +void +test04() +{ + // Verify initial value + auto p1 = std::make_shared(3, 9); + VERIFY( p1[0] == 9 && p1[1] == 9 && p1[2] == 9 ); + + auto p2 = std::make_shared(4); + VERIFY( p2[0] == 4 && p2[1] == 4 ); + + auto p3 = std::make_shared(10, {1,2,3}); + const auto& p3_0 = p3[0]; + VERIFY( p3_0[0] == 1 && p3_0[1] == 2 && p3_0[2] == 3 ); + for (int i = 1; i < 10; ++i) + for (int j = 0; j < 3; ++j) + VERIFY( p3[i][j] == p3_0[j] ); + + auto p4 = std::make_shared({4,5,6}); + const auto& p4_0 = p4[0]; + VERIFY( p4_0[0] == 4 && p4_0[1] == 5 && p4_0[2] == 6 ); + for (int i = 1; i < 10; ++i) + for (int j = 0; j < 3; ++j) + VERIFY( p4[i][j] == p4_0[j] ); + + auto p5 = std::make_shared(10, {{1,2},{3,4},{5,6}}); + const auto& p5_0 = p5[0]; + VERIFY( p5_0[0][0] == 1 && p5_0[0][1] == 2 ); + VERIFY( p5_0[1][0] == 3 && p5_0[1][1] == 4 ); + VERIFY( p5_0[2][0] == 5 && p5_0[2][1] == 6 ); + for (int i = 1; i < 10; ++i) + for (int j = 0; j < 3; ++j) + for (int k = 0; k < 2; ++k) + VERIFY( p5[i][j][k] == p5_0[j][k] ); + + auto p6 = std::make_shared({{7,8},{9,10},{11,12}}); + const auto& p6_0 = p6[0]; + VERIFY( p6_0[0][0] == 7 && p6_0[0][1] == 8 ); + VERIFY( p6_0[1][0] == 9 && p6_0[1][1] == 10 ); + VERIFY( p6_0[2][0] == 11 && p6_0[2][1] == 12 ); + for (int i = 1; i < 4; ++i) + for (int j = 0; j < 3; ++j) + for (int k = 0; k < 2; ++k) + VERIFY( p6[i][j][k] == p6_0[j][k] ); +} + +void +test05() +{ + // Examples from the standard + using namespace std; + + // Example 2 + { + shared_ptr p = make_shared(1024); + // shared_ptr to a value-initialized double[1024] + for (int i = 0; i < 1024; ++i) + VERIFY( p[i] == 0.0 ); + + shared_ptr q = make_shared(6); + // shared_ptr to a value-initialized double[6][2][2] + for (int i = 0; i < 6; ++i) + for (auto& j : q[i]) + for (auto& k : j) + VERIFY( k == 0.0 ); + } + + // Example 3 + { + shared_ptr p = make_shared(); + // shared_ptr to a value-initialized double[1024] + for (int i = 0; i < 1024; ++i) + VERIFY( p[i] == 0.0 ); + + shared_ptr q = make_shared(); + // shared_ptr to a value-initialized double[6][2][2] + for (int i = 0; i < 6; ++i) + for (auto& j : q[i]) + for (auto& k : j) + VERIFY( k == 0.0 ); + } + + // Example 4 + { + shared_ptr p = make_shared(1024, 1.0); + // shared_ptr to a double[1024], where each element is 1.0 + for (int i = 0; i < 1024; ++i) + VERIFY( p[i] == 1.0 ); + + shared_ptr q = make_shared(6, {1.0, 0.0}); + // shared_ptr to a double[6][2], where each double[2] element is {1.0, 0.0} + for (int i = 0; i < 6; ++i) + VERIFY( q[i][0] == 1.0 && q[i][1] == 0.0 ); + + shared_ptr[]> r = make_shared[]>(4, {1, 2}); + // shared_ptr to a vector[4], where each vector has contents {1, 2} + for (int i = 0; i < 4; ++i) + VERIFY( r[i] == vector({1, 2}) ); + } + + // Example 5 + { + shared_ptr p = make_shared(1.0); + // shared_ptr to a double[1024], where each element is 1.0 + for (int i = 0; i < 1024; ++i) + VERIFY( p[i] == 1.0 ); + + shared_ptr q = make_shared({1.0, 0.0}); + // shared_ptr to a double[6][2], where each double[2] element is {1.0, 0.0} + for (int i = 0; i < 6; ++i) + VERIFY( q[i][0] == 1.0 && q[i][1] == 0.0 ); + + shared_ptr[4]> r = make_shared[4]>({1, 2}); + // shared_ptr to a vector[4], where each vector has contents {1, 2} + for (int i = 0; i < 4; ++i) + VERIFY( r[i] == vector({1, 2}) ); + } +} + +int +main() +{ + test01(); + test02(); + test03(); + test04(); + test05(); +} diff --git a/libstdc++-v3/testsuite/20_util/shared_ptr/creation/overwrite.cc b/libstdc++-v3/testsuite/20_util/shared_ptr/creation/overwrite.cc new file mode 100644 index 00000000000..f207fdee097 --- /dev/null +++ b/libstdc++-v3/testsuite/20_util/shared_ptr/creation/overwrite.cc @@ -0,0 +1,143 @@ +// { dg-options "-std=gnu++20 -fno-lifetime-dse -O0" } +// { dg-do run { target c++20 } } + +// C++20 20.11.3.7 shared_ptr Creation [util.smartptr.shared.create] + +#include + +#ifndef __cpp_lib_smart_ptr_for_overwrite +# error "Feature-test macro for make_shared_for_overwrite missing in " +#elif __cpp_lib_smart_ptr_for_overwrite < 202002L +# error "Feature-test macro for make_shared_for_overwrite has wrong value in " +#endif + +#include +#include + +int counter = 0; + +template +struct Alloc : std::allocator +{ + Alloc() = default; + + template + Alloc(const Alloc&) { } + + T* allocate(std::size_t n) + { + ++counter; + void* p = std::allocator::allocate(n); + // need -fno-lifetime-dse to check for these values later. + std::memset(p, 0xff, n * sizeof(T)); + return (T*)p; + } + + void construct(auto*, auto&&...) + { + // The objects must be default-initialized, not using this function. + VERIFY( ! "allocator_traits::construct" ); + } + + void destroy(auto*) + { + // The objects must be destroyed by ~T(), not using this function. + VERIFY( ! "allocator_traits::destroy" ); + } +}; + +void +test01() +{ + Alloc a; + const int expected = 0xffffffff; + + std::shared_ptr p1 = std::allocate_shared_for_overwrite(a); + VERIFY( counter == 1 ); + VERIFY( *p1 == expected ); + std::shared_ptr p2 = std::allocate_shared_for_overwrite(a); + VERIFY( counter == 2 ); + VERIFY( p2[0] == expected ); + p2.reset(); + std::shared_ptr p3 = std::allocate_shared_for_overwrite(a, 88); + VERIFY( counter == 3 ); + VERIFY( p3[0] == expected ); + VERIFY( p3[87] == expected ); + std::shared_ptr p4 = std::allocate_shared_for_overwrite(a); + VERIFY( counter == 4 ); + VERIFY( p4[0][0] == expected ); + VERIFY( p4[2][3] == expected ); + std::shared_ptr p5 = std::allocate_shared_for_overwrite(a, 6); + VERIFY( counter == 5 ); + VERIFY( p5[0][0] == expected ); + VERIFY( p5[5][4] == expected ); + + struct BigBoi { int x[100]; }; + std::shared_ptr p6 = std::allocate_shared_for_overwrite(a); + VERIFY( counter == 6 ); + VERIFY( p6->x[0] == expected ); + std::shared_ptr p7 = std::allocate_shared_for_overwrite(a); + VERIFY( counter == 7 ); + VERIFY( p7[0].x[0] == expected ); + VERIFY( p7[21].x[99] == expected ); + std::shared_ptr p8 = std::allocate_shared_for_overwrite(a, 11); + VERIFY( counter == 8 ); + VERIFY( p8[0].x[0] == expected ); + VERIFY( p8[10].x[10] == expected ); +} + +void +test02() +{ + // These aren't created by the custom allocator, so we can't check that the + // memory was left uninitialized. Just dereference them. + + std::shared_ptr p1 = std::make_shared_for_overwrite(); + (void) *p1; + std::shared_ptr p2 = std::make_shared_for_overwrite(); + (void) p2[0]; + std::shared_ptr p3 = std::make_shared_for_overwrite(88); + (void) p3[0]; + (void) p3[87]; + std::shared_ptr p4 = std::make_shared_for_overwrite(); + (void) p4[0][0]; + (void) p4[2][3]; + std::shared_ptr p5 = std::make_shared_for_overwrite(6); + (void) p5[0][0]; + (void) p5[5][4]; + + struct BigBoi { int x[100]; }; + std::shared_ptr p6 = std::make_shared_for_overwrite(); + (void) p6->x[0]; + std::shared_ptr p7 = std::make_shared_for_overwrite(); + (void) p7[0].x[0]; + (void) p7[21].x[99]; + std::shared_ptr p8 = std::make_shared_for_overwrite(11); + (void) p8[0].x[0]; + (void) p8[10].x[10]; +} + +void +test03() +{ + // Type with non-trivial initialization should still be default-initialized. + struct NonTriv + { + int init = 0xbb; + int uninit; + }; + std::shared_ptr a = std::make_shared_for_overwrite(); + VERIFY( a->init == 0xbb ); + std::shared_ptr b = std::make_shared_for_overwrite(); + VERIFY( b[1].init == 0xbb ); + std::shared_ptr c = std::make_shared_for_overwrite(2); + VERIFY( c[1].init == 0xbb ); +} + +int +main() +{ + test01(); + test02(); + test03(); +} diff --git a/libstdc++-v3/testsuite/20_util/shared_ptr/creation/version.cc b/libstdc++-v3/testsuite/20_util/shared_ptr/creation/version.cc new file mode 100644 index 00000000000..0e47f19061d --- /dev/null +++ b/libstdc++-v3/testsuite/20_util/shared_ptr/creation/version.cc @@ -0,0 +1,18 @@ +// { dg-options "-std=gnu++20" } +// { dg-do preprocess { target c++20 } } + +// C++20 20.11.3.7 shared_ptr Creation [util.smartptr.shared.create] + +#include + +#ifndef __cpp_lib_shared_ptr_arrays +# error "Feature-test macro for make_shared arrays missing in " +#elif __cpp_lib_shared_ptr_arrays < 201707L +# error "Feature-test macro for make_shared arrays has wrong value in " +#endif + +#ifndef __cpp_lib_smart_ptr_for_overwrite +# error "Feature-test macro for make_shared_for_overwrite missing in " +#elif __cpp_lib_smart_ptr_for_overwrite < 202002L +# error "Feature-test macro for make_shared_for_overwrite has wrong value in " +#endif diff --git a/libstdc++-v3/testsuite/20_util/unique_ptr/creation/for_overwrite.cc b/libstdc++-v3/testsuite/20_util/unique_ptr/creation/for_overwrite.cc index c6bcbf08f4d..f736c762a43 100644 --- a/libstdc++-v3/testsuite/20_util/unique_ptr/creation/for_overwrite.cc +++ b/libstdc++-v3/testsuite/20_util/unique_ptr/creation/for_overwrite.cc @@ -1,4 +1,4 @@ -// { dg-options "-std=gnu++20" } +// { dg-options "-std=gnu++20 -fno-lifetime-dse -O0" } // { dg-do run { target c++2a } } // { dg-xfail-run-if "AIX operator new" { powerpc-ibm-aix* } } @@ -22,6 +22,13 @@ // C++20 20.11.1.5 unique_ptr creation [unique.ptr.create] #include + +#ifndef __cpp_lib_smart_ptr_for_overwrite +# error "Feature-test macro for make_unique_for_overwrite missing in " +#elif __cpp_lib_smart_ptr_for_overwrite < 202002L +# error "Feature-test macro for make_unique_for_overwrite has wrong value in " +#endif + #include #include #include @@ -58,9 +65,25 @@ test02() VERIFY( c == 0xaa ); } +void +test03() +{ + // Type with non-trivial initialization should still be default-initialized. + struct NonTriv + { + int init = 0xbb; + int uninit; + }; + std::unique_ptr a = std::make_unique_for_overwrite(); + VERIFY( a->init == 0xbb ); + std::unique_ptr b = std::make_unique_for_overwrite(2); + VERIFY( b[1].init == 0xbb ); +} + int main() { test01(); test02(); + test03(); }