[libcxx-commits] [libcxx] 7083c06 - [libc++] Remove _LIBCPP_COMPRESSED_PAIR/TRIPLE from shared_ptr (#200401)
via libcxx-commits
libcxx-commits at lists.llvm.org
Thu Jun 25 02:45:59 PDT 2026
Author: Nikolas Klauser
Date: 2026-06-25T11:45:55+02:00
New Revision: 7083c06372905081929ea43abea4751ef372761a
URL: https://github.com/llvm/llvm-project/commit/7083c06372905081929ea43abea4751ef372761a
DIFF: https://github.com/llvm/llvm-project/commit/7083c06372905081929ea43abea4751ef372761a.diff
LOG: [libc++] Remove _LIBCPP_COMPRESSED_PAIR/TRIPLE from shared_ptr (#200401)
The `shared_ptr` control blocks don't need to be ABI stable as long as
we rename them. This can be thought of as adding a new control block,
which we've done before, and simply never generating the old one again.
This is done via an ABI tag on the control blocks, allowing us to change
their contents in whichever way we want without needing to consider
their ABI. In this case specifically, `_LIBCPP_COMPRESSED_{PAIR,TRIPLE}`
is removed, simplifying the code.
Added:
libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/pointer_destruction.pass.cpp
Modified:
libcxx/docs/ABIGuarantees.rst
libcxx/include/__configuration/attributes.h
libcxx/include/__memory/compressed_pair.h
libcxx/include/__memory/shared_ptr.h
libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/allocate_shared.lwg2070.pass.cpp
Removed:
libcxx/test/libcxx/utilities/memory/util.smartptr/util.smartptr.shared/libcxx.control_block_layout.pass.cpp
################################################################################
diff --git a/libcxx/docs/ABIGuarantees.rst b/libcxx/docs/ABIGuarantees.rst
index 5d3d9891dc664..c566118542210 100644
--- a/libcxx/docs/ABIGuarantees.rst
+++ b/libcxx/docs/ABIGuarantees.rst
@@ -67,7 +67,7 @@ removes these workarounds for platforms that don't care about ABI compatibility.
``_LIBCPP_ABI_NO_COMPRESSED_PAIR_PADDING``
------------------------------------------
-This removes artificial padding from ``_LIBCPP_COMPRESSED_PAIR`` and ``_LIBCPP_COMPRESSED_TRIPLE``.
+This removes artificial padding from ``_LIBCPP_COMPRESSED_PAIR``.
These macros are used inside the associative and unordered containers, ``deque``, ``forward_list``, ``future``,
``list``, ``basic_string``, ``function``, ``shared_ptr``, ``unique_ptr``, and ``vector`` to stay ABI compatible with the
diff --git a/libcxx/include/__configuration/attributes.h b/libcxx/include/__configuration/attributes.h
index cc828466482fd..c543ba9cb9e25 100644
--- a/libcxx/include/__configuration/attributes.h
+++ b/libcxx/include/__configuration/attributes.h
@@ -215,6 +215,15 @@
#endif
#define _LIBCPP_HIDE_FROM_ABI_VIRTUAL _LIBCPP_HIDDEN _LIBCPP_EXCLUDE_FROM_EXPLICIT_INSTANTIATION
+// This macro makes sure that private classes which aren't necessarily part of the ABI change their name every release.
+// The intention is _not_ to put it on every private class, but only on classes where type name stability implies ABI
+// stability. This is essentially the case for classes that are only accessed through the vtable after instantiation,
+// for example the control blocks of `shared_ptr`. These classes can change in ABI breaking ways as long as their name
+// (and hence their vtable symbol) changes too. Note that the vtable and RTTI are not marked with
+// [[gnu::visibility("hidden")]], since the vtable and RTTI should still be deduplicated across dylibs with the same
+// instatiations.
+#define _LIBCPP_HIDE_STRUCT_FROM_ABI [[__gnu__::__abi_tag__(_LIBCPP_TOSTRING(_LIBCPP_ODR_SIGNATURE))]]
+
// Optional attributes
// -------------------
diff --git a/libcxx/include/__memory/compressed_pair.h b/libcxx/include/__memory/compressed_pair.h
index f1f1c920453cf..bb745620b9f85 100644
--- a/libcxx/include/__memory/compressed_pair.h
+++ b/libcxx/include/__memory/compressed_pair.h
@@ -94,16 +94,6 @@ class __compressed_pair_padding<_ToPad, true> {};
_LIBCPP_NO_UNIQUE_ADDRESS ::std::__compressed_pair_padding<T1> _LIBCPP_CONCAT3(__padding1_, __LINE__, _); \
_LIBCPP_NO_UNIQUE_ADDRESS T2 Initializer2; \
_LIBCPP_NO_UNIQUE_ADDRESS ::std::__compressed_pair_padding<T2> _LIBCPP_CONCAT3(__padding2_, __LINE__, _)
-
-# define _LIBCPP_COMPRESSED_TRIPLE(T1, Initializer1, T2, Initializer2, T3, Initializer3) \
- _LIBCPP_NO_UNIQUE_ADDRESS \
- __attribute__((__aligned__(::std::__compressed_pair_alignment<T2>), \
- __aligned__(::std::__compressed_pair_alignment<T3>))) T1 Initializer1; \
- _LIBCPP_NO_UNIQUE_ADDRESS ::std::__compressed_pair_padding<T1> _LIBCPP_CONCAT3(__padding1_, __LINE__, _); \
- _LIBCPP_NO_UNIQUE_ADDRESS T2 Initializer2; \
- _LIBCPP_NO_UNIQUE_ADDRESS ::std::__compressed_pair_padding<T2> _LIBCPP_CONCAT3(__padding2_, __LINE__, _); \
- _LIBCPP_NO_UNIQUE_ADDRESS T3 Initializer3; \
- _LIBCPP_NO_UNIQUE_ADDRESS ::std::__compressed_pair_padding<T3> _LIBCPP_CONCAT3(__padding3_, __LINE__, _)
# else
# define _LIBCPP_COMPRESSED_PAIR(T1, Initializer1, T2, Initializer2) \
struct { \
@@ -112,16 +102,6 @@ class __compressed_pair_padding<_ToPad, true> {};
_LIBCPP_NO_UNIQUE_ADDRESS T2 Initializer2; \
_LIBCPP_NO_UNIQUE_ADDRESS ::std::__compressed_pair_padding<T2> _LIBCPP_CONCAT3(__padding2_, __LINE__, _); \
}
-
-# define _LIBCPP_COMPRESSED_TRIPLE(T1, Initializer1, T2, Initializer2, T3, Initializer3) \
- struct { \
- _LIBCPP_NO_UNIQUE_ADDRESS T1 Initializer1; \
- _LIBCPP_NO_UNIQUE_ADDRESS ::std::__compressed_pair_padding<T1> _LIBCPP_CONCAT3(__padding1_, __LINE__, _); \
- _LIBCPP_NO_UNIQUE_ADDRESS T2 Initializer2; \
- _LIBCPP_NO_UNIQUE_ADDRESS ::std::__compressed_pair_padding<T2> _LIBCPP_CONCAT3(__padding2_, __LINE__, _); \
- _LIBCPP_NO_UNIQUE_ADDRESS T3 Initializer3; \
- _LIBCPP_NO_UNIQUE_ADDRESS ::std::__compressed_pair_padding<T3> _LIBCPP_CONCAT3(__padding3_, __LINE__, _); \
- }
# endif
#else
@@ -130,11 +110,6 @@ class __compressed_pair_padding<_ToPad, true> {};
# define _LIBCPP_COMPRESSED_PAIR(T1, Name1, T2, Name2) \
_LIBCPP_NO_UNIQUE_ADDRESS T1 Name1; \
_LIBCPP_NO_UNIQUE_ADDRESS T2 Name2
-
-# define _LIBCPP_COMPRESSED_TRIPLE(T1, Name1, T2, Name2, T3, Name3) \
- _LIBCPP_NO_UNIQUE_ADDRESS T1 Name1; \
- _LIBCPP_NO_UNIQUE_ADDRESS T2 Name2; \
- _LIBCPP_NO_UNIQUE_ADDRESS T3 Name3
#endif // _LIBCPP_ABI_NO_COMPRESSED_PAIR_PADDING
_LIBCPP_END_NAMESPACE_STD
diff --git a/libcxx/include/__memory/shared_ptr.h b/libcxx/include/__memory/shared_ptr.h
index 0081752f392db..8d12e395bb86c 100644
--- a/libcxx/include/__memory/shared_ptr.h
+++ b/libcxx/include/__memory/shared_ptr.h
@@ -28,7 +28,6 @@
#include <__memory/allocator_destructor.h>
#include <__memory/allocator_traits.h>
#include <__memory/auto_ptr.h>
-#include <__memory/compressed_pair.h>
#include <__memory/construct_at.h>
#include <__memory/destroy.h>
#include <__memory/pointer_traits.h>
@@ -38,9 +37,7 @@
#include <__type_traits/add_reference.h>
#include <__type_traits/conditional.h>
#include <__type_traits/conjunction.h>
-#include <__type_traits/disjunction.h>
#include <__type_traits/enable_if.h>
-#include <__type_traits/integral_constant.h>
#include <__type_traits/is_array.h>
#include <__type_traits/is_constructible.h>
#include <__type_traits/is_convertible.h>
@@ -48,9 +45,9 @@
#include <__type_traits/is_reference.h>
#include <__type_traits/is_same.h>
#include <__type_traits/nat.h>
-#include <__type_traits/negation.h>
#include <__type_traits/remove_cv.h>
#include <__type_traits/remove_extent.h>
+#include <__type_traits/remove_pointer.h>
#include <__type_traits/remove_reference.h>
#include <__type_traits/void_t.h>
#include <__utility/declval.h>
@@ -92,12 +89,16 @@ template <class _Tp>
class weak_ptr;
template <class _Tp, class _Dp, class _Alloc>
-class __shared_ptr_pointer : public __shared_weak_count {
- _LIBCPP_COMPRESSED_TRIPLE(_Tp, __ptr_, _Dp, __deleter_, _Alloc, __alloc_);
+class _LIBCPP_HIDE_STRUCT_FROM_ABI __shared_ptr_pointer final : public __shared_weak_count {
+ using __alloc_t = __allocator_traits_rebind_t<_Alloc, __shared_ptr_pointer>;
+
+ _LIBCPP_NO_UNIQUE_ADDRESS __alloc_t __alloc_;
+ _LIBCPP_NO_UNIQUE_ADDRESS _Dp __deleter_;
+ _LIBCPP_NO_UNIQUE_ADDRESS _Tp __ptr_;
public:
_LIBCPP_HIDE_FROM_ABI __shared_ptr_pointer(_Tp __p, _Dp __d, _Alloc __a)
- : __ptr_(__p), __deleter_(std::move(__d)), __alloc_(std::move(__a)) {}
+ : __alloc_(std::move(__a)), __deleter_(std::move(__d)), __ptr_(__p) {}
#if _LIBCPP_HAS_RTTI
_LIBCPP_HIDE_FROM_ABI_VIRTUAL const void* __get_deleter(const type_info& __t) const _NOEXCEPT override {
@@ -108,17 +109,14 @@ class __shared_ptr_pointer : public __shared_weak_count {
private:
_LIBCPP_HIDE_FROM_ABI_VIRTUAL void __on_zero_shared() _NOEXCEPT override {
__deleter_(__ptr_);
+ __ptr_.~_Tp();
__deleter_.~_Dp();
}
_LIBCPP_HIDE_FROM_ABI_VIRTUAL void __on_zero_shared_weak() _NOEXCEPT override {
- typedef typename __allocator_traits_rebind<_Alloc, __shared_ptr_pointer>::type _Al;
- typedef allocator_traits<_Al> _ATraits;
- typedef pointer_traits<typename _ATraits::pointer> _PTraits;
-
- _Al __a(__alloc_);
- __alloc_.~_Alloc();
- __a.deallocate(_PTraits::pointer_to(*this), 1);
+ __alloc_t __a(__alloc_);
+ __alloc_.~__alloc_t();
+ __a.deallocate(pointer_traits<typename allocator_traits<__alloc_t>::pointer>::pointer_to(*this), 1);
}
};
@@ -128,79 +126,53 @@ class __shared_ptr_pointer : public __shared_weak_count {
struct __for_overwrite_tag {};
template <class _Tp, class _Alloc>
-struct __shared_ptr_emplace : __shared_weak_count {
- using __value_type _LIBCPP_NODEBUG = __remove_cv_t<_Tp>;
+struct _LIBCPP_HIDE_STRUCT_FROM_ABI __shared_ptr_emplace : __shared_weak_count {
+ using __alloc_t _LIBCPP_NODEBUG = __allocator_traits_rebind_t<_Alloc, __shared_ptr_emplace>;
+ using __alloc_traits _LIBCPP_NODEBUG = allocator_traits<__alloc_t>;
+ using __value_type _LIBCPP_NODEBUG = __remove_cv_t<_Tp>;
- template <class... _Args,
- class _Allocator = _Alloc,
- __enable_if_t<is_same<typename _Allocator::value_type, __for_overwrite_tag>::value, int> = 0>
- _LIBCPP_HIDE_FROM_ABI explicit __shared_ptr_emplace(_Alloc __a, _Args&&...) : __storage_(std::move(__a)) {
- static_assert(
- sizeof...(_Args) == 0, "No argument should be provided to the control block when using _for_overwrite");
- ::new (static_cast<void*>(__get_elem())) __value_type;
+ template <class... _Args>
+ _LIBCPP_HIDE_FROM_ABI explicit __shared_ptr_emplace(_Alloc __a, _Args&&... __args) : __alloc_(std::move(__a)) {
+ __alloc_traits::construct(__alloc_, __get_elem(), std::forward<_Args>(__args)...);
}
- template <class... _Args,
- class _Allocator = _Alloc,
- __enable_if_t<!is_same<typename _Allocator::value_type, __for_overwrite_tag>::value, int> = 0>
- _LIBCPP_HIDE_FROM_ABI explicit __shared_ptr_emplace(_Alloc __a, _Args&&... __args) : __storage_(std::move(__a)) {
- using _TpAlloc = typename __allocator_traits_rebind<_Alloc, __value_type>::type;
- _TpAlloc __tmp(*__get_alloc());
- allocator_traits<_TpAlloc>::construct(__tmp, __get_elem(), std::forward<_Args>(__args)...);
- }
-
- _LIBCPP_HIDE_FROM_ABI _Alloc* __get_alloc() _NOEXCEPT { return __storage_.__get_alloc(); }
-
- _LIBCPP_HIDE_FROM_ABI __value_type* __get_elem() _NOEXCEPT { return __storage_.__get_elem(); }
+ _LIBCPP_HIDE_FROM_ABI __value_type* __get_elem() _NOEXCEPT { return reinterpret_cast<__value_type*>(__buffer_); }
private:
- template <class _Allocator = _Alloc,
- __enable_if_t<is_same<typename _Allocator::value_type, __for_overwrite_tag>::value, int> = 0>
- _LIBCPP_HIDE_FROM_ABI void __on_zero_shared_impl() _NOEXCEPT {
- __get_elem()->~__value_type();
- }
-
- template <class _Allocator = _Alloc,
- __enable_if_t<!is_same<typename _Allocator::value_type, __for_overwrite_tag>::value, int> = 0>
- _LIBCPP_HIDE_FROM_ABI void __on_zero_shared_impl() _NOEXCEPT {
- using _TpAlloc = typename __allocator_traits_rebind<_Allocator, __remove_cv_t<_Tp> >::type;
- _TpAlloc __tmp(*__get_alloc());
- allocator_traits<_TpAlloc>::destroy(__tmp, __get_elem());
+ _LIBCPP_HIDE_FROM_ABI_VIRTUAL void __on_zero_shared() _NOEXCEPT override {
+ __alloc_traits::destroy(__alloc_, __get_elem());
}
- _LIBCPP_HIDE_FROM_ABI_VIRTUAL void __on_zero_shared() _NOEXCEPT override { __on_zero_shared_impl(); }
-
_LIBCPP_HIDE_FROM_ABI_VIRTUAL void __on_zero_shared_weak() _NOEXCEPT override {
- using _ControlBlockAlloc = typename __allocator_traits_rebind<_Alloc, __shared_ptr_emplace>::type;
- using _ControlBlockPointer = typename allocator_traits<_ControlBlockAlloc>::pointer;
- _ControlBlockAlloc __tmp(*__get_alloc());
- __storage_.~_Storage();
- allocator_traits<_ControlBlockAlloc>::deallocate(__tmp, pointer_traits<_ControlBlockPointer>::pointer_to(*this), 1);
+ __alloc_t __tmp(__alloc_);
+ __alloc_.~__alloc_t();
+ __alloc_traits::deallocate(__tmp, pointer_traits<typename __alloc_traits::pointer>::pointer_to(*this), 1);
}
- // TODO: It should be possible to refactor this to remove `_Storage` entirely.
- // This class implements the control block for non-array shared pointers created
- // through `std::allocate_shared` and `std::make_shared`.
- struct _Storage {
- struct _Data {
- _LIBCPP_COMPRESSED_PAIR(_Alloc, __alloc_, __value_type, __elem_);
- };
+ _LIBCPP_NO_UNIQUE_ADDRESS __alloc_t __alloc_;
+ _ALIGNAS_TYPE(__value_type) char __buffer_[sizeof(__value_type)];
+};
- _ALIGNAS_TYPE(_Data) char __buffer_[sizeof(_Data)];
+template <class _Tp, class _Alloc>
+struct _LIBCPP_HIDE_STRUCT_FROM_ABI __shared_ptr_emplace_for_overwrite : __shared_weak_count {
+ using __alloc_t _LIBCPP_NODEBUG = __allocator_traits_rebind_t<_Alloc, __shared_ptr_emplace_for_overwrite>;
+ using __value_type _LIBCPP_NODEBUG = __remove_cv_t<_Tp>;
- _LIBCPP_HIDE_FROM_ABI explicit _Storage(_Alloc&& __a) { ::new ((void*)__get_alloc()) _Alloc(std::move(__a)); }
- _LIBCPP_HIDE_FROM_ABI ~_Storage() { __get_alloc()->~_Alloc(); }
+ explicit __shared_ptr_emplace_for_overwrite(_Alloc __a) : __alloc_(std::move(__a)) {}
- _LIBCPP_HIDE_FROM_ABI _Alloc* __get_alloc() _NOEXCEPT {
- return std::addressof(reinterpret_cast<_Data*>(__buffer_)->__alloc_);
- }
+ _LIBCPP_HIDE_FROM_ABI_VIRTUAL void __on_zero_shared() _NOEXCEPT override { __value_.~__value_type(); }
+ _LIBCPP_HIDE_FROM_ABI_VIRTUAL void __on_zero_shared_weak() _NOEXCEPT override {
+ __alloc_t __tmp(__alloc_);
+ __alloc_.~__alloc_t();
+ using __alloc_traits = allocator_traits<__alloc_t>;
+ __alloc_traits::deallocate(__tmp, pointer_traits<typename __alloc_traits::pointer>::pointer_to(*this), 1);
+ }
- _LIBCPP_HIDE_FROM_ABI _LIBCPP_NO_CFI __value_type* __get_elem() _NOEXCEPT {
- return std::addressof(reinterpret_cast<_Data*>(__buffer_)->__elem_);
- }
- };
+ __value_type* __get_elem() _NOEXCEPT { return std::addressof(__value_); }
- _Storage __storage_;
+private:
+ _LIBCPP_NO_UNIQUE_ADDRESS __alloc_t __alloc_;
+ _LIBCPP_NO_UNIQUE_ADDRESS __value_type __value_;
};
struct __shared_ptr_dummy_rebind_allocator_type;
@@ -625,7 +597,26 @@ class _LIBCPP_SHARED_PTR_TRIVIAL_ABI shared_ptr {
}
}
- _LIBCPP_HIDE_FROM_ABI void __enable_weak_this(...) _NOEXCEPT {}
+ template <class _Vp>
+ static __type_identity<_Vp> __get_shared_from_this_impl(const enable_shared_from_this<_Vp>*);
+
+ template <class _Vp, class = void>
+ struct __get_shared_from_this {
+ using type = __nat;
+ };
+
+ template <class _Vp>
+ struct __get_shared_from_this<_Vp, __void_t<decltype(__get_shared_from_this_impl(static_cast<_Vp*>(nullptr)))> > {
+ using type = const enable_shared_from_this<typename decltype(__get_shared_from_this_impl(
+ static_cast<_Vp*>(nullptr)))::type>*;
+ };
+
+ template <
+ class _Yp,
+ class _OrigPtr,
+ __enable_if_t<!is_convertible<_OrigPtr, typename __get_shared_from_this<__remove_pointer_t<_Yp> >::type>::value,
+ int> = 0>
+ void __enable_weak_this(_Yp, _OrigPtr) {}
template <class, class _Yp>
struct __shared_ptr_default_delete : default_delete<_Yp> {};
@@ -652,15 +643,21 @@ shared_ptr(unique_ptr<_Tp, _Dp>) -> shared_ptr<_Tp>;
//
// std::allocate_shared and std::make_shared
//
-template <class _Tp, class _Alloc, class... _Args, __enable_if_t<!is_array<_Tp>::value, int> = 0>
-[[__nodiscard__]] _LIBCPP_HIDE_FROM_ABI shared_ptr<_Tp> allocate_shared(const _Alloc& __a, _Args&&... __args) {
- using _ControlBlock = __shared_ptr_emplace<_Tp, _Alloc>;
+
+template <class _ControlBlock, class _Tp, class _Alloc, class... _Args>
+_LIBCPP_HIDE_FROM_ABI shared_ptr<_Tp> __allocate_shared_impl(const _Alloc& __alloc, _Args&&... __args) {
using _ControlBlockAllocator = typename __allocator_traits_rebind<_Alloc, _ControlBlock>::type;
- __allocation_guard<_ControlBlockAllocator> __guard(__a, 1);
- ::new ((void*)std::addressof(*__guard.__get())) _ControlBlock(__a, std::forward<_Args>(__args)...);
+ __allocation_guard<_ControlBlockAllocator> __guard(__alloc, 1);
+ ::new ((void*)std::addressof(*__guard.__get())) _ControlBlock(__alloc, std::forward<_Args>(__args)...);
auto __control_block = __guard.__release_ptr();
return shared_ptr<_Tp>::__create_with_control_block(
- (*__control_block).__get_elem(), std::addressof(*__control_block));
+ __control_block->__get_elem(), std::__to_address(__control_block));
+}
+
+template <class _Tp, class _Alloc, class... _Args, __enable_if_t<!is_array<_Tp>::value, int> = 0>
+[[__nodiscard__]] _LIBCPP_HIDE_FROM_ABI shared_ptr<_Tp> allocate_shared(const _Alloc& __a, _Args&&... __args) {
+ using _ControlBlock = __shared_ptr_emplace<_Tp, __allocator_traits_rebind_t<_Alloc, __remove_cv_t<_Tp> > >;
+ return std::__allocate_shared_impl<_ControlBlock, _Tp>(__a, __args...);
}
template <class _Tp, class... _Args, __enable_if_t<!is_array<_Tp>::value, int> = 0>
@@ -672,9 +669,9 @@ template <class _Tp, class... _Args, __enable_if_t<!is_array<_Tp>::value, int> =
template <class _Tp, class _Alloc, __enable_if_t<!is_array<_Tp>::value, int> = 0>
[[nodiscard]] _LIBCPP_HIDE_FROM_ABI shared_ptr<_Tp> allocate_shared_for_overwrite(const _Alloc& __a) {
- using _ForOverwriteAllocator = __allocator_traits_rebind_t<_Alloc, __for_overwrite_tag>;
- _ForOverwriteAllocator __alloc(__a);
- return std::allocate_shared<_Tp>(__alloc);
+ using _ControlBlock =
+ __shared_ptr_emplace_for_overwrite<_Tp, __allocator_traits_rebind_t<_Alloc, __remove_cv_t<_Tp>>>;
+ return std::__allocate_shared_impl<_ControlBlock, _Tp>(__a);
}
template <class _Tp, __enable_if_t<!is_array<_Tp>::value, int> = 0>
diff --git a/libcxx/test/libcxx/utilities/memory/util.smartptr/util.smartptr.shared/libcxx.control_block_layout.pass.cpp b/libcxx/test/libcxx/utilities/memory/util.smartptr/util.smartptr.shared/libcxx.control_block_layout.pass.cpp
deleted file mode 100644
index 9cb5b2ffbae97..0000000000000
--- a/libcxx/test/libcxx/utilities/memory/util.smartptr/util.smartptr.shared/libcxx.control_block_layout.pass.cpp
+++ /dev/null
@@ -1,232 +0,0 @@
-//===----------------------------------------------------------------------===//
-//
-// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
-// See https://llvm.org/LICENSE.txt for license information.
-// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-//
-//===----------------------------------------------------------------------===//
-
-// UNSUPPORTED: c++03
-// UNSUPPORTED: libcpp-abi-no-compressed-pair-padding
-
-// This test makes sure that the control block implementation used for non-array
-// types in std::make_shared and std::allocate_shared is ABI compatible with the
-// original implementation.
-//
-// This test is relevant because the implementation of that control block is
-//
diff erent starting in C++20, a change that was required to implement P0674.
-
-#include <cassert>
-#include <cstddef>
-#include <memory>
-#include <tuple>
-#include <type_traits>
-#include <utility>
-
-#include <string>
-#include <vector>
-
-#include "test_macros.h"
-
-struct value_init_tag {};
-
-template <class T, int _Idx, bool CanBeEmptyBase = std::is_empty<T>::value && !std::__is_final_v<T>>
-struct compressed_pair_elem {
- explicit compressed_pair_elem(value_init_tag) : value_() {}
-
- template <class U>
- explicit compressed_pair_elem(U&& u) : value_(std::forward<U>(u)) {}
-
- T& get() { return value_; }
-
-private:
- T value_;
-};
-
-template <class T, int _Idx>
-struct compressed_pair_elem<T, _Idx, true> : private T {
- explicit compressed_pair_elem(value_init_tag) : T() {}
-
- template <class U>
- explicit compressed_pair_elem(U&& u) : T(std::forward<U>(u)) {}
-
- T& get() { return *this; }
-};
-
-template <class T1, class T2>
-class compressed_pair : private compressed_pair_elem<T1, 0>, private compressed_pair_elem<T2, 1> {
-public:
- using Base1 = compressed_pair_elem<T1, 0>;
- using Base2 = compressed_pair_elem<T2, 1>;
-
- template <class U1, class U2>
- explicit compressed_pair(U1&& t1, U2&& t2) : Base1(std::forward<U1>(t1)), Base2(std::forward<U2>(t2)) {}
-
- T1& first() { return static_cast<Base1&>(*this).get(); }
- T2& second() { return static_cast<Base2&>(*this).get(); }
-};
-
-// This is the pre-C++20 implementation of the control block used by non-array
-// std::allocate_shared and std::make_shared. We keep it here so that we can
-// make sure our implementation is backwards compatible with it forever.
-//
-// Of course, the class and its methods were renamed, but the size and layout
-// of the class should remain the same as the original implementation.
-template <class T, class Alloc>
-struct OldEmplaceControlBlock : std::__shared_weak_count {
- explicit OldEmplaceControlBlock(Alloc a) : data_(std::move(a), value_init_tag()) {}
- T* get_elem() noexcept { return std::addressof(data_.second()); }
- Alloc* get_alloc() noexcept { return std::addressof(data_.first()); }
-
-private:
- virtual void __on_zero_shared() noexcept {
- // Not implemented
- }
-
- virtual void __on_zero_shared_weak() noexcept {
- // Not implemented
- }
-
- compressed_pair<Alloc, T> data_;
-};
-
-template <class T, template <class> class Alloc>
-void test() {
- using Old = OldEmplaceControlBlock<T, Alloc<T>>;
- using New = std::__shared_ptr_emplace<T, Alloc<T>>;
-
- static_assert(sizeof(New) == sizeof(Old), "");
- static_assert(alignof(New) == alignof(Old), "");
-
- // Also make sure each member is at the same offset
- Alloc<T> a;
- Old old(a);
- New new_(a);
-
- // 1. Check the stored object
- {
- char const* old_elem = reinterpret_cast<char const*>(old.get_elem());
- char const* new_elem = reinterpret_cast<char const*>(new_.__get_elem());
- std::ptr
diff _t old_offset = old_elem - reinterpret_cast<char const*>(&old);
- std::ptr
diff _t new_offset = new_elem - reinterpret_cast<char const*>(&new_);
- assert(new_offset == old_offset && "offset of stored element changed");
- }
-
- // 2. Check the allocator
- {
- char const* old_alloc = reinterpret_cast<char const*>(old.get_alloc());
- char const* new_alloc = reinterpret_cast<char const*>(new_.__get_alloc());
- std::ptr
diff _t old_offset = old_alloc - reinterpret_cast<char const*>(&old);
- std::ptr
diff _t new_offset = new_alloc - reinterpret_cast<char const*>(&new_);
- assert(new_offset == old_offset && "offset of allocator changed");
- }
-
- // Make sure both types have the same triviality (that has ABI impact since
- // it determined how objects are passed). Both should be non-trivial.
- static_assert(std::is_trivially_copyable<New>::value == std::is_trivially_copyable<Old>::value, "");
- static_assert(
- std::is_trivially_default_constructible<New>::value == std::is_trivially_default_constructible<Old>::value, "");
-}
-
-// Object types to store in the control block
-struct TrivialEmptyType {};
-
-struct alignas(32) OveralignedEmptyType {};
-
-struct TrivialNonEmptyType {
- char c[11];
-};
-
-struct FinalEmptyType final {};
-
-struct NonTrivialType {
- char c[22];
- NonTrivialType() : c{'x'} {}
-};
-
-struct VirtualFunctionType {
- virtual ~VirtualFunctionType() {}
-};
-
-// Allocator types
-template <class T>
-struct TrivialEmptyAlloc {
- using value_type = T;
- TrivialEmptyAlloc() = default;
- template <class U>
- TrivialEmptyAlloc(TrivialEmptyAlloc<U>) {}
- T* allocate(std::size_t) { return nullptr; }
- void deallocate(T*, std::size_t) {}
-};
-
-template <class T>
-struct TrivialNonEmptyAlloc {
- char storage[77];
- using value_type = T;
- TrivialNonEmptyAlloc() = default;
- template <class U>
- TrivialNonEmptyAlloc(TrivialNonEmptyAlloc<U>) {}
- T* allocate(std::size_t) { return nullptr; }
- void deallocate(T*, std::size_t) {}
-};
-
-template <class T>
-struct FinalEmptyAlloc final {
- using value_type = T;
- FinalEmptyAlloc() = default;
- template <class U>
- FinalEmptyAlloc(FinalEmptyAlloc<U>) {}
- T* allocate(std::size_t) { return nullptr; }
- void deallocate(T*, std::size_t) {}
-};
-
-template <class T>
-struct NonTrivialAlloc {
- char storage[88];
- using value_type = T;
- NonTrivialAlloc() {}
- template <class U>
- NonTrivialAlloc(NonTrivialAlloc<U>) {}
- T* allocate(std::size_t) { return nullptr; }
- void deallocate(T*, std::size_t) {}
-};
-
-int main(int, char**) {
- test<TrivialEmptyType, TrivialEmptyAlloc>();
- test<TrivialEmptyType, TrivialNonEmptyAlloc>();
- test<TrivialEmptyType, FinalEmptyAlloc>();
- test<TrivialEmptyType, NonTrivialAlloc>();
-
-#if !defined(TEST_HAS_NO_ALIGNED_ALLOCATION)
- test<OveralignedEmptyType, TrivialEmptyAlloc>();
- test<OveralignedEmptyType, TrivialNonEmptyAlloc>();
- test<OveralignedEmptyType, FinalEmptyAlloc>();
- test<OveralignedEmptyType, NonTrivialAlloc>();
-#endif
-
- test<TrivialNonEmptyType, TrivialEmptyAlloc>();
- test<TrivialNonEmptyType, TrivialNonEmptyAlloc>();
- test<TrivialNonEmptyType, FinalEmptyAlloc>();
- test<TrivialNonEmptyType, NonTrivialAlloc>();
-
- test<FinalEmptyType, TrivialEmptyAlloc>();
- // FinalEmptyType combined with TrivialNonEmptyAlloc, FinalEmptyAlloc or NonTrivialAlloc is known to have an ABI break
- // between LLVM 19 and LLVM 20. It's been deemed not severe enough to cause actual breakage.
-
- test<NonTrivialType, TrivialEmptyAlloc>();
- test<NonTrivialType, TrivialNonEmptyAlloc>();
- test<NonTrivialType, FinalEmptyAlloc>();
- test<NonTrivialType, NonTrivialAlloc>();
-
- test<VirtualFunctionType, TrivialEmptyAlloc>();
- test<VirtualFunctionType, TrivialNonEmptyAlloc>();
- test<VirtualFunctionType, FinalEmptyAlloc>();
- test<VirtualFunctionType, NonTrivialAlloc>();
-
- // Test a few real world types just to make sure we didn't mess up badly somehow
- test<std::string, std::allocator>();
- test<int, std::allocator>();
- test<std::vector<int>, std::allocator>();
-
- return 0;
-}
diff --git a/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/pointer_destruction.pass.cpp b/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/pointer_destruction.pass.cpp
new file mode 100644
index 0000000000000..184a008e2472a
--- /dev/null
+++ b/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/pointer_destruction.pass.cpp
@@ -0,0 +1,75 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// <memory>
+//
+// class shared_ptr
+//
+// Ensure that any constructed (fancy) pointer objects are destroyed.
+
+// XFAIL: FROZEN-CXX03-HEADERS-FIXME
+
+#include <cassert>
+#include <memory>
+#include <utility>
+
+#include "test_macros.h"
+
+static size_t pointer_count = 0;
+
+template <class T>
+struct Pointer {
+ T* ptr_ = nullptr;
+
+ Pointer() : ptr_(nullptr) { ++pointer_count; }
+ Pointer(T* ptr) : ptr_(ptr) { ++pointer_count; }
+ Pointer(nullptr_t) : ptr_(nullptr) { ++pointer_count; }
+ Pointer(const Pointer& other) : ptr_(other.ptr_) { ++pointer_count; }
+ Pointer& operator=(const Pointer& other) {
+ ptr_ = other.ptr_;
+ return *this;
+ }
+ ~Pointer() { --pointer_count; }
+
+ Pointer& operator=(nullptr_t) {
+ ptr_ = nullptr;
+ return *this;
+ }
+
+ T& operator*() const { return *ptr_; }
+ T* operator->() const { return ptr_; }
+
+ explicit operator bool() const { return ptr_; }
+
+ operator T*() { return ptr_; }
+
+ friend bool operator==(const Pointer& lhs, const Pointer& rhs) { return lhs.ptr_ == rhs.ptr_; }
+ friend bool operator!=(const Pointer& lhs, const Pointer& rhs) { return !(lhs == rhs); }
+ friend bool operator==(const Pointer& lhs, nullptr_t) { return lhs == Pointer(nullptr); }
+ friend bool operator!=(const Pointer& lhs, nullptr_t) { return !(lhs == nullptr); }
+
+ static Pointer pointer_to(T& ref) { return Pointer(std::addressof(ref)); }
+};
+
+template <class T>
+struct Deleter {
+ using pointer = Pointer<T>;
+
+ void operator()(pointer ptr) const { delete ptr.ptr_; }
+};
+
+int main(int, char**) {
+ {
+ auto ptr = std::unique_ptr<int, Deleter<int> >(new int());
+ auto v = std::shared_ptr<int>(std::move(ptr));
+ LIBCPP_ASSERT(pointer_count != 0);
+ }
+ assert(pointer_count == 0);
+
+ return 0;
+}
diff --git a/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/allocate_shared.lwg2070.pass.cpp b/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/allocate_shared.lwg2070.pass.cpp
index 222a552e58378..9dc4dd0dc7574 100644
--- a/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/allocate_shared.lwg2070.pass.cpp
+++ b/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/allocate_shared.lwg2070.pass.cpp
@@ -51,19 +51,20 @@ struct MyAllocator {
void deallocate(pointer p, std::ptr
diff _t) { return ::operator delete(p); }
- template <typename ...Args>
- void construct(T* p, Args&& ...args) {
+ template <class U, typename... Args>
+ void construct(U* p, Args&&... args) {
construct_called = true;
destroy_called = false;
allocator_id = id;
- ::new (p) T(std::forward<Args>(args)...);
+ ::new (p) U(std::forward<Args>(args)...);
}
- void destroy(T* p) {
+ template <class U>
+ void destroy(U* p) {
construct_called = false;
destroy_called = true;
allocator_id = id;
- p->~T();
+ p->~U();
}
};
@@ -81,7 +82,8 @@ struct Private {
int id;
private:
- friend FactoryAllocator<Private>;
+ template <class>
+ friend struct FactoryAllocator;
Private(int i) : id(i) {}
~Private() {}
};
@@ -98,8 +100,15 @@ struct FactoryAllocator : std::allocator<T> {
typedef FactoryAllocator<T1> other;
};
- void construct(void* p, int id) { ::new (p) Private(id); }
- void destroy(Private* p) { p->~Private(); }
+ template <class U>
+ void construct(U* p, int id) {
+ ::new (p) U(id);
+ }
+
+ template <class U>
+ void destroy(U* p) {
+ p->~U();
+ }
};
std::shared_ptr<Private> Factory::allocate() {
More information about the libcxx-commits
mailing list