[libcxx-commits] [libcxx] e27a122 - [libc++] Support arrays in make_shared and allocate_shared (P0674R1)

Louis Dionne via libcxx-commits libcxx-commits at lists.llvm.org
Wed Apr 6 05:43:04 PDT 2022


Author: Louis Dionne
Date: 2022-04-06T08:42:55-04:00
New Revision: e27a122b3abc5000e99239f3df6e6d4e32b7dc81

URL: https://github.com/llvm/llvm-project/commit/e27a122b3abc5000e99239f3df6e6d4e32b7dc81
DIFF: https://github.com/llvm/llvm-project/commit/e27a122b3abc5000e99239f3df6e6d4e32b7dc81.diff

LOG: [libc++] Support arrays in make_shared and allocate_shared (P0674R1)

This patch implements P0674R1, i.e. support for arrays in std::make_shared
and std::allocate_shared.

Co-authored-by: Zoe Carver <z.zoelec2 at gmail.com>

Differential Revision: https://reviews.llvm.org/D62641

Added: 
    libcxx/test/libcxx/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/allocate_shared.array.zero_size.compile.fail.cpp
    libcxx/test/libcxx/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/make_shared.array.zero_size.compile.fail.cpp
    libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/allocate_shared.array.bounded.pass.cpp
    libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/allocate_shared.array.unbounded.pass.cpp
    libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/make_shared.array.bounded.pass.cpp
    libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/make_shared.array.unbounded.pass.cpp
    libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/types.h

Modified: 
    libcxx/docs/FeatureTestMacroTable.rst
    libcxx/docs/ReleaseNotes.rst
    libcxx/docs/Status/Cxx20Papers.csv
    libcxx/include/__memory/shared_ptr.h
    libcxx/include/__memory/uninitialized_algorithms.h
    libcxx/include/memory
    libcxx/include/version
    libcxx/test/std/language.support/support.limits/support.limits.general/memory.version.pass.cpp
    libcxx/test/std/language.support/support.limits/support.limits.general/version.version.pass.cpp
    libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/allocate_shared.pass.cpp
    libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/allocate_shared_construct.pass.cpp
    libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/make_shared.pass.cpp
    libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/make_shared.private.compile.fail.cpp
    libcxx/utils/generate_feature_test_macro_components.py

Removed: 
    


################################################################################
diff  --git a/libcxx/docs/FeatureTestMacroTable.rst b/libcxx/docs/FeatureTestMacroTable.rst
index 8877ec0a48bc6..e305a78e0c456 100644
--- a/libcxx/docs/FeatureTestMacroTable.rst
+++ b/libcxx/docs/FeatureTestMacroTable.rst
@@ -268,6 +268,8 @@ Status
     ------------------------------------------------- -----------------
     ``__cpp_lib_semaphore``                           ``201907L``
     ------------------------------------------------- -----------------
+    ``__cpp_lib_shared_ptr_arrays``                   ``201707L``
+    ------------------------------------------------- -----------------
     ``__cpp_lib_shift``                               ``201806L``
     ------------------------------------------------- -----------------
     ``__cpp_lib_smart_ptr_for_overwrite``             *unimplemented*

diff  --git a/libcxx/docs/ReleaseNotes.rst b/libcxx/docs/ReleaseNotes.rst
index ea42b56f29d1a..dc86c58844a41 100644
--- a/libcxx/docs/ReleaseNotes.rst
+++ b/libcxx/docs/ReleaseNotes.rst
@@ -42,6 +42,8 @@ New Features
 
 - Implemented P1165R1 (Make stateful allocator propagation more consistent for ``operator+(basic_string)``)
 
+- Implemented P0674R1 (Support arrays in make_shared and allocate_shared)
+
 - `pop_heap` now uses an algorithm known as "bottom-up heapsort" or
   "heapsort with bounce" to reduce the number of comparisons, and rearranges
   elements using move-assignment instead of `swap`.

diff  --git a/libcxx/docs/Status/Cxx20Papers.csv b/libcxx/docs/Status/Cxx20Papers.csv
index 438e1f07b896c..de07254a48425 100644
--- a/libcxx/docs/Status/Cxx20Papers.csv
+++ b/libcxx/docs/Status/Cxx20Papers.csv
@@ -1,6 +1,6 @@
 "Paper #","Group","Paper Name","Meeting","Status","First released version"
 "`P0463R1 <https://wg21.link/P0463R1>`__","LWG","Endian just Endian","Toronto","|Complete|","7.0"
-"`P0674R1 <https://wg21.link/P0674R1>`__","LWG","Extending make_shared to Support Arrays","Toronto","",""
+"`P0674R1 <https://wg21.link/P0674R1>`__","LWG","Extending make_shared to Support Arrays","Toronto","|Complete|","15.0"
 "","","","","",""
 "`P0020R6 <https://wg21.link/P0020R6>`__","LWG","Floating Point Atomic","Albuquerque","",""
 "`P0053R7 <https://wg21.link/P0053R7>`__","LWG","C++ Synchronized Buffered Ostream","Albuquerque","",""

diff  --git a/libcxx/include/__memory/shared_ptr.h b/libcxx/include/__memory/shared_ptr.h
index 8560d3079758a..397da8f8fa1cf 100644
--- a/libcxx/include/__memory/shared_ptr.h
+++ b/libcxx/include/__memory/shared_ptr.h
@@ -15,12 +15,15 @@
 #include <__functional/binary_function.h>
 #include <__functional/operations.h>
 #include <__functional/reference_wrapper.h>
+#include <__iterator/access.h>
 #include <__memory/addressof.h>
 #include <__memory/allocation_guard.h>
 #include <__memory/allocator.h>
 #include <__memory/allocator_traits.h>
 #include <__memory/compressed_pair.h>
+#include <__memory/construct_at.h>
 #include <__memory/pointer_traits.h>
+#include <__memory/uninitialized_algorithms.h>
 #include <__memory/unique_ptr.h>
 #include <__utility/forward.h>
 #include <__utility/move.h>
@@ -963,6 +966,216 @@ shared_ptr<_Tp> make_shared(_Args&& ...__args)
     return _VSTD::allocate_shared<_Tp>(allocator<_Tp>(), _VSTD::forward<_Args>(__args)...);
 }
 
+#if _LIBCPP_STD_VER > 17
+
+template <size_t _Alignment>
+struct __sp_aligned_storage {
+    alignas(_Alignment) char __storage[_Alignment];
+};
+
+template <class _Tp, class _Alloc>
+struct __unbounded_array_control_block;
+
+template <class _Tp, class _Alloc>
+struct __unbounded_array_control_block<_Tp[], _Alloc> : __shared_weak_count
+{
+    _LIBCPP_HIDE_FROM_ABI constexpr
+    _Tp* __get_data() noexcept { return __data_; }
+
+    _LIBCPP_HIDE_FROM_ABI
+    explicit __unbounded_array_control_block(_Alloc const& __alloc, size_t __count, _Tp const& __arg)
+        : __alloc_(__alloc), __count_(__count)
+    {
+        std::__uninitialized_allocator_fill_n(__alloc_, std::begin(__data_), __count_, __arg);
+    }
+
+    _LIBCPP_HIDE_FROM_ABI
+    explicit __unbounded_array_control_block(_Alloc const& __alloc, size_t __count)
+        : __alloc_(__alloc), __count_(__count)
+    {
+        std::__uninitialized_allocator_value_construct_n(__alloc_, std::begin(__data_), __count_);
+    }
+
+    // Returns the number of bytes required to store a control block followed by the given number
+    // of elements of _Tp, with the whole storage being aligned to a multiple of _Tp's alignment.
+    _LIBCPP_HIDE_FROM_ABI
+    static constexpr size_t __bytes_for(size_t __elements) {
+        // When there's 0 elements, the control block alone is enough since it holds one element.
+        // Otherwise, we allocate one fewer element than requested because the control block already
+        // holds one. Also, we use the bitwise formula below to ensure that we allocate enough bytes
+        // for the whole allocation to be a multiple of _Tp's alignment. That formula is taken from [1].
+        //
+        // [1]: https://en.wikipedia.org/wiki/Data_structure_alignment#Computing_padding
+        size_t __bytes = __elements == 0 ? sizeof(__unbounded_array_control_block)
+                                         : (__elements - 1) * sizeof(_Tp) + sizeof(__unbounded_array_control_block);
+        constexpr size_t __align = alignof(_Tp);
+        return (__bytes + __align - 1) & ~(__align - 1);
+    }
+
+    _LIBCPP_HIDE_FROM_ABI
+    ~__unbounded_array_control_block() override { } // can't be `= default` because of the sometimes-non-trivial union member __data_
+
+private:
+    void __on_zero_shared() _NOEXCEPT override {
+        __allocator_traits_rebind_t<_Alloc, _Tp> __value_alloc(__alloc_);
+        std::__allocator_destroy_multidimensional(__value_alloc, __data_, __data_ + __count_);
+    }
+
+    void __on_zero_shared_weak() _NOEXCEPT override {
+        using _AlignedStorage = __sp_aligned_storage<alignof(__unbounded_array_control_block)>;
+        using _StorageAlloc = __allocator_traits_rebind_t<_Alloc, _AlignedStorage>;
+        using _PointerTraits = pointer_traits<typename allocator_traits<_StorageAlloc>::pointer>;
+
+        _StorageAlloc __tmp(__alloc_);
+        __alloc_.~_Alloc();
+        size_t __size = __unbounded_array_control_block::__bytes_for(__count_);
+        _AlignedStorage* __storage = reinterpret_cast<_AlignedStorage*>(this);
+        allocator_traits<_StorageAlloc>::deallocate(__tmp, _PointerTraits::pointer_to(*__storage), __size);
+    }
+
+    _LIBCPP_NO_UNIQUE_ADDRESS _Alloc __alloc_;
+    size_t __count_;
+    union {
+        _Tp __data_[1];
+    };
+};
+
+template<class _Array, class _Alloc, class... _Arg>
+_LIBCPP_HIDE_FROM_ABI
+shared_ptr<_Array> __allocate_shared_unbounded_array(const _Alloc& __a, size_t __n, _Arg&& ...__arg)
+{
+    static_assert(is_unbounded_array_v<_Array>);
+    // We compute the number of bytes necessary to hold the control block and the
+    // array elements. Then, we allocate an array of properly-aligned dummy structs
+    // large enough to hold the control block and array. This allows shifting the
+    // burden of aligning memory properly from us to the allocator.
+    using _ControlBlock = __unbounded_array_control_block<_Array, _Alloc>;
+    using _AlignedStorage = __sp_aligned_storage<alignof(_ControlBlock)>;
+    using _StorageAlloc = __allocator_traits_rebind_t<_Alloc, _AlignedStorage>;
+    __allocation_guard<_StorageAlloc> __guard(__a, _ControlBlock::__bytes_for(__n) / sizeof(_AlignedStorage));
+    _ControlBlock* __control_block = reinterpret_cast<_ControlBlock*>(std::addressof(*__guard.__get()));
+    std::construct_at(__control_block, __a, __n, std::forward<_Arg>(__arg)...);
+    __guard.__release_ptr();
+    return shared_ptr<_Array>::__create_with_control_block(__control_block->__get_data(), __control_block);
+}
+
+template <class _Tp, class _Alloc>
+struct __bounded_array_control_block;
+
+template <class _Tp, size_t _Count, class _Alloc>
+struct __bounded_array_control_block<_Tp[_Count], _Alloc>
+    : __shared_weak_count
+{
+    _LIBCPP_HIDE_FROM_ABI constexpr
+    _Tp* __get_data() noexcept { return __data_; }
+
+    _LIBCPP_HIDE_FROM_ABI
+    explicit __bounded_array_control_block(_Alloc const& __alloc, _Tp const& __arg) : __alloc_(__alloc) {
+        std::__uninitialized_allocator_fill_n(__alloc_, std::addressof(__data_[0]), _Count, __arg);
+    }
+
+    _LIBCPP_HIDE_FROM_ABI
+    explicit __bounded_array_control_block(_Alloc const& __alloc) : __alloc_(__alloc) {
+        std::__uninitialized_allocator_value_construct_n(__alloc_, std::addressof(__data_[0]), _Count);
+    }
+
+    _LIBCPP_HIDE_FROM_ABI
+    ~__bounded_array_control_block() override { } // can't be `= default` because of the sometimes-non-trivial union member __data_
+
+private:
+    void __on_zero_shared() _NOEXCEPT override {
+        __allocator_traits_rebind_t<_Alloc, _Tp> __value_alloc(__alloc_);
+        std::__allocator_destroy_multidimensional(__value_alloc, __data_, __data_ + _Count);
+    }
+
+    void __on_zero_shared_weak() _NOEXCEPT override {
+        using _ControlBlockAlloc = __allocator_traits_rebind_t<_Alloc, __bounded_array_control_block>;
+        using _PointerTraits = pointer_traits<typename allocator_traits<_ControlBlockAlloc>::pointer>;
+
+        _ControlBlockAlloc __tmp(__alloc_);
+        __alloc_.~_Alloc();
+        allocator_traits<_ControlBlockAlloc>::deallocate(__tmp, _PointerTraits::pointer_to(*this), sizeof(*this));
+    }
+
+    _LIBCPP_NO_UNIQUE_ADDRESS _Alloc __alloc_;
+    union {
+        _Tp __data_[_Count];
+    };
+};
+
+template<class _Array, class _Alloc, class... _Arg>
+_LIBCPP_HIDE_FROM_ABI
+shared_ptr<_Array> __allocate_shared_bounded_array(const _Alloc& __a, _Arg&& ...__arg)
+{
+    static_assert(is_bounded_array_v<_Array>);
+    using _ControlBlock = __bounded_array_control_block<_Array, _Alloc>;
+    using _ControlBlockAlloc = __allocator_traits_rebind_t<_Alloc, _ControlBlock>;
+
+    __allocation_guard<_ControlBlockAlloc> __guard(__a, 1);
+    _ControlBlock* __control_block = reinterpret_cast<_ControlBlock*>(std::addressof(*__guard.__get()));
+    std::construct_at(__control_block, __a, std::forward<_Arg>(__arg)...);
+    __guard.__release_ptr();
+    return shared_ptr<_Array>::__create_with_control_block(__control_block->__get_data(), __control_block);
+}
+
+template<class _Tp, class _Alloc, class = __enable_if_t<is_bounded_array<_Tp>::value>>
+_LIBCPP_HIDE_FROM_ABI
+shared_ptr<_Tp> allocate_shared(const _Alloc& __a)
+{
+    return std::__allocate_shared_bounded_array<_Tp>(__a);
+}
+
+template<class _Tp, class _Alloc, class = __enable_if_t<is_bounded_array<_Tp>::value>>
+_LIBCPP_HIDE_FROM_ABI
+shared_ptr<_Tp> allocate_shared(const _Alloc& __a, const remove_extent_t<_Tp>& __u)
+{
+    return std::__allocate_shared_bounded_array<_Tp>(__a, __u);
+}
+
+template<class _Tp, class _Alloc, class = __enable_if_t<is_unbounded_array<_Tp>::value>>
+_LIBCPP_HIDE_FROM_ABI
+shared_ptr<_Tp> allocate_shared(const _Alloc& __a, size_t __n)
+{
+    return std::__allocate_shared_unbounded_array<_Tp>(__a, __n);
+}
+
+template<class _Tp, class _Alloc, class = __enable_if_t<is_unbounded_array<_Tp>::value>>
+_LIBCPP_HIDE_FROM_ABI
+shared_ptr<_Tp> allocate_shared(const _Alloc& __a, size_t __n, const remove_extent_t<_Tp>& __u)
+{
+    return std::__allocate_shared_unbounded_array<_Tp>(__a, __n, __u);
+}
+
+template<class _Tp, class = __enable_if_t<is_bounded_array<_Tp>::value>>
+_LIBCPP_HIDE_FROM_ABI
+shared_ptr<_Tp> make_shared()
+{
+    return std::__allocate_shared_bounded_array<_Tp>(allocator<_Tp>());
+}
+
+template<class _Tp, class = __enable_if_t<is_bounded_array<_Tp>::value>>
+_LIBCPP_HIDE_FROM_ABI
+shared_ptr<_Tp> make_shared(const remove_extent_t<_Tp>& __u)
+{
+    return std::__allocate_shared_bounded_array<_Tp>(allocator<_Tp>(), __u);
+}
+
+template<class _Tp, class = __enable_if_t<is_unbounded_array<_Tp>::value>>
+_LIBCPP_HIDE_FROM_ABI
+shared_ptr<_Tp> make_shared(size_t __n)
+{
+    return std::__allocate_shared_unbounded_array<_Tp>(allocator<_Tp>(), __n);
+}
+
+template<class _Tp, class = __enable_if_t<is_unbounded_array<_Tp>::value>>
+_LIBCPP_HIDE_FROM_ABI
+shared_ptr<_Tp> make_shared(size_t __n, const remove_extent_t<_Tp>& __u)
+{
+    return std::__allocate_shared_unbounded_array<_Tp>(allocator<_Tp>(), __n, __u);
+}
+
+#endif // _LIBCPP_STD_VER > 17
+
 template<class _Tp, class _Up>
 inline _LIBCPP_INLINE_VISIBILITY
 bool

diff  --git a/libcxx/include/__memory/uninitialized_algorithms.h b/libcxx/include/__memory/uninitialized_algorithms.h
index fc55b49aef2eb..dfdfdf4eddf1d 100644
--- a/libcxx/include/__memory/uninitialized_algorithms.h
+++ b/libcxx/include/__memory/uninitialized_algorithms.h
@@ -13,10 +13,13 @@
 #include <__config>
 #include <__iterator/iterator_traits.h>
 #include <__memory/addressof.h>
+#include <__memory/allocator_traits.h>
 #include <__memory/construct_at.h>
 #include <__memory/voidify.h>
 #include <__utility/move.h>
 #include <__utility/pair.h>
+#include <__utility/transaction.h>
+#include <type_traits>
 
 #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
 #  pragma GCC system_header
@@ -346,6 +349,153 @@ uninitialized_move_n(_InputIterator __ifirst, _Size __n, _ForwardIterator __ofir
 
 #endif // _LIBCPP_STD_VER > 14
 
+#if _LIBCPP_STD_VER > 17
+
+// Destroys every element in the range [first, last) FROM RIGHT TO LEFT using allocator
+// destruction. If elements are themselves C-style arrays, they are recursively destroyed
+// in the same manner.
+//
+// This function assumes that destructors do not throw, and that the allocator is bound to
+// the correct type.
+template<class _Alloc, class _BidirIter, class = __enable_if_t<
+    __is_cpp17_bidirectional_iterator<_BidirIter>::value
+>>
+_LIBCPP_HIDE_FROM_ABI
+constexpr void __allocator_destroy_multidimensional(_Alloc& __alloc, _BidirIter __first, _BidirIter __last) noexcept {
+    using _ValueType = typename iterator_traits<_BidirIter>::value_type;
+    static_assert(is_same_v<typename allocator_traits<_Alloc>::value_type, _ValueType>,
+        "The allocator should already be rebound to the correct type");
+
+    if (__first == __last)
+        return;
+
+    if constexpr (is_array_v<_ValueType>) {
+        static_assert(!is_unbounded_array_v<_ValueType>,
+            "arrays of unbounded arrays don't exist, but if they did we would mess up here");
+
+        using _Element = remove_extent_t<_ValueType>;
+        __allocator_traits_rebind_t<_Alloc, _Element> __elem_alloc(__alloc);
+        do {
+            --__last;
+            decltype(auto) __array = *__last;
+            std::__allocator_destroy_multidimensional(__elem_alloc, __array, __array + extent_v<_ValueType>);
+        } while (__last != __first);
+    } else {
+        do {
+            --__last;
+            allocator_traits<_Alloc>::destroy(__alloc, std::addressof(*__last));
+        } while (__last != __first);
+    }
+}
+
+// Constructs the object at the given location using the allocator's construct method.
+//
+// If the object being constructed is an array, each element of the array is allocator-constructed,
+// recursively. If an exception is thrown during the construction of an array, the initialized
+// elements are destroyed in reverse order of initialization using allocator destruction.
+//
+// This function assumes that the allocator is bound to the correct type.
+template<class _Alloc, class _Tp>
+_LIBCPP_HIDE_FROM_ABI
+constexpr void __allocator_construct_at(_Alloc& __alloc, _Tp* __loc) {
+    static_assert(is_same_v<typename allocator_traits<_Alloc>::value_type, _Tp>,
+        "The allocator should already be rebound to the correct type");
+
+    if constexpr (is_array_v<_Tp>) {
+        using _Element = remove_extent_t<_Tp>;
+        __allocator_traits_rebind_t<_Alloc, _Element> __elem_alloc(__alloc);
+        size_t __i = 0;
+        _Tp& __array = *__loc;
+
+        // If an exception is thrown, destroy what we have constructed so far in reverse order.
+        __transaction __guard([&]() { std::__allocator_destroy_multidimensional(__elem_alloc, __array, __array + __i); });
+        for (; __i != extent_v<_Tp>; ++__i) {
+            std::__allocator_construct_at(__elem_alloc, std::addressof(__array[__i]));
+        }
+        __guard.__complete();
+    } else {
+        allocator_traits<_Alloc>::construct(__alloc, __loc);
+    }
+}
+
+// Constructs the object at the given location using the allocator's construct method, passing along
+// the provided argument.
+//
+// If the object being constructed is an array, the argument is also assumed to be an array. Each
+// each element of the array being constructed is allocator-constructed from the corresponding
+// element of the argument array. If an exception is thrown during the construction of an array,
+// the initialized elements are destroyed in reverse order of initialization using allocator
+// destruction.
+//
+// This function assumes that the allocator is bound to the correct type.
+template<class _Alloc, class _Tp, class _Arg>
+_LIBCPP_HIDE_FROM_ABI
+constexpr void __allocator_construct_at(_Alloc& __alloc, _Tp* __loc, _Arg const& __arg) {
+    static_assert(is_same_v<typename allocator_traits<_Alloc>::value_type, _Tp>,
+        "The allocator should already be rebound to the correct type");
+
+    if constexpr (is_array_v<_Tp>) {
+        static_assert(is_array_v<_Arg>,
+            "Provided non-array initialization argument to __allocator_construct_at when "
+            "trying to construct an array.");
+
+        using _Element = remove_extent_t<_Tp>;
+        __allocator_traits_rebind_t<_Alloc, _Element> __elem_alloc(__alloc);
+        size_t __i = 0;
+        _Tp& __array = *__loc;
+
+        // If an exception is thrown, destroy what we have constructed so far in reverse order.
+        __transaction __guard([&]() { std::__allocator_destroy_multidimensional(__elem_alloc, __array, __array + __i); });
+        for (; __i != extent_v<_Tp>; ++__i) {
+            std::__allocator_construct_at(__elem_alloc, std::addressof(__array[__i]), __arg[__i]);
+        }
+        __guard.__complete();
+    } else {
+        allocator_traits<_Alloc>::construct(__alloc, __loc, __arg);
+    }
+}
+
+// Given a range starting at it and containing n elements, initializes each element in the
+// range from left to right using the construct method of the allocator (rebound to the
+// correct type).
+//
+// If an exception is thrown, the initialized elements are destroyed in reverse order of
+// initialization using allocator_traits destruction. If the elements in the range are C-style
+// arrays, they are initialized element-wise using allocator construction, and recursively so.
+template<class _Alloc, class _BidirIter, class _Tp, class _Size = typename iterator_traits<_BidirIter>::
diff erence_type>
+_LIBCPP_HIDE_FROM_ABI
+constexpr void __uninitialized_allocator_fill_n(_Alloc& __alloc, _BidirIter __it, _Size __n, _Tp const& __value) {
+    using _ValueType = typename iterator_traits<_BidirIter>::value_type;
+    __allocator_traits_rebind_t<_Alloc, _ValueType> __value_alloc(__alloc);
+    _BidirIter __begin = __it;
+
+    // If an exception is thrown, destroy what we have constructed so far in reverse order.
+    __transaction __guard([&]() { std::__allocator_destroy_multidimensional(__value_alloc, __begin, __it); });
+    for (; __n != 0; --__n, ++__it) {
+        std::__allocator_construct_at(__value_alloc, std::addressof(*__it), __value);
+    }
+    __guard.__complete();
+}
+
+// Same as __uninitialized_allocator_fill_n, but doesn't pass any initialization argument
+// to the allocator's construct method, which results in value initialization.
+template<class _Alloc, class _BidirIter, class _Size = typename iterator_traits<_BidirIter>::
diff erence_type>
+_LIBCPP_HIDE_FROM_ABI
+constexpr void __uninitialized_allocator_value_construct_n(_Alloc& __alloc, _BidirIter __it, _Size __n) {
+    using _ValueType = typename iterator_traits<_BidirIter>::value_type;
+    __allocator_traits_rebind_t<_Alloc, _ValueType> __value_alloc(__alloc);
+    _BidirIter __begin = __it;
+
+    // If an exception is thrown, destroy what we have constructed so far in reverse order.
+    __transaction __guard([&]() { std::__allocator_destroy_multidimensional(__value_alloc, __begin, __it); });
+    for (; __n != 0; --__n, ++__it) {
+        std::__allocator_construct_at(__value_alloc, std::addressof(*__it));
+    }
+    __guard.__complete();
+}
+
+#endif // _LIBCPP_STD_VER > 17
+
 _LIBCPP_END_NAMESPACE_STD
 
 #endif // _LIBCPP___MEMORY_UNINITIALIZED_ALGORITHMS_H

diff  --git a/libcxx/include/memory b/libcxx/include/memory
index 53172c58c3b4a..8d577245999d4 100644
--- a/libcxx/include/memory
+++ b/libcxx/include/memory
@@ -661,9 +661,29 @@ template<class E, class T, class Y>
 template<class D, class T> D* get_deleter(shared_ptr<T> const& p) noexcept;
 
 template<class T, class... Args>
-    shared_ptr<T> make_shared(Args&&... args);
+    shared_ptr<T> make_shared(Args&&... args); // T is not an array
 template<class T, class A, class... Args>
-    shared_ptr<T> allocate_shared(const A& a, Args&&... args);
+    shared_ptr<T> allocate_shared(const A& a, Args&&... args); // T is not an array
+
+template<class T>
+    shared_ptr<T> make_shared(size_t N); // T is U[] (since C++20)
+template<class T, class A>
+    shared_ptr<T> allocate_shared(const A& a, size_t N); // T is U[] (since C++20)
+
+template<class T>
+    shared_ptr<T> make_shared(); // T is U[N] (since C++20)
+template<class T, class A>
+    shared_ptr<T> allocate_shared(const A& a); // T is U[N] (since C++20)
+
+template<class T>
+    shared_ptr<T> make_shared(size_t N, const remove_extent_t<T>& u); // T is U[] (since C++20)
+template<class T, class A>
+    shared_ptr<T> allocate_shared(const A& a, size_t N, const remove_extent_t<T>& u); // T is U[] (since C++20)
+
+template<class T> shared_ptr<T>
+    make_shared(const remove_extent_t<T>& u); // T is U[N] (since C++20)
+template<class T, class A>
+    shared_ptr<T> allocate_shared(const A& a, const remove_extent_t<T>& u); // T is U[N] (since C++20)
 
 template<class T>
 class weak_ptr

diff  --git a/libcxx/include/version b/libcxx/include/version
index 003a42cf59e6a..258adedffffa4 100644
--- a/libcxx/include/version
+++ b/libcxx/include/version
@@ -154,7 +154,8 @@ __cpp_lib_sample                                        201603L <algorithm>
 __cpp_lib_scoped_lock                                   201703L <mutex>
 __cpp_lib_semaphore                                     201907L <semaphore>
 __cpp_lib_shared_mutex                                  201505L <shared_mutex>
-__cpp_lib_shared_ptr_arrays                             201611L <memory>
+__cpp_lib_shared_ptr_arrays                             201707L <memory>
+                                                        201611L // C++17
 __cpp_lib_shared_ptr_weak_type                          201606L <memory>
 __cpp_lib_shared_timed_mutex                            201402L <shared_mutex>
 __cpp_lib_shift                                         201806L <algorithm>
@@ -353,6 +354,8 @@ __cpp_lib_void_t                                        201411L <type_traits>
 # if !defined(_LIBCPP_HAS_NO_THREADS) && !defined(_LIBCPP_AVAILABILITY_DISABLE_FTM___cpp_lib_semaphore)
 #   define __cpp_lib_semaphore                          201907L
 # endif
+# undef  __cpp_lib_shared_ptr_arrays
+# define __cpp_lib_shared_ptr_arrays                    201707L
 # define __cpp_lib_shift                                201806L
 // # define __cpp_lib_smart_ptr_for_overwrite              202002L
 // # define __cpp_lib_source_location                      201907L

diff  --git a/libcxx/test/libcxx/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/allocate_shared.array.zero_size.compile.fail.cpp b/libcxx/test/libcxx/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/allocate_shared.array.zero_size.compile.fail.cpp
new file mode 100644
index 0000000000000..3c678d0e4d67d
--- /dev/null
+++ b/libcxx/test/libcxx/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/allocate_shared.array.zero_size.compile.fail.cpp
@@ -0,0 +1,22 @@
+//===----------------------------------------------------------------------===//
+//
+// 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, c++11, c++14, c++17
+
+// Make sure that std::allocate_shared<T[0]>(...) fails at compile-time.
+// While Clang and GCC appear to support T[0] as a language extension, that support is
+// unreliable (for example T[0] doesn't match a T[N] partial specialization on Clang as
+// of writing this). So instead, we make sure that this doesn't work at all with our
+// implementation.
+
+#include <memory>
+
+void f() {
+  auto p = std::allocate_shared<int[0]>(std::allocator<int[0]>());
+  (void)p;
+}

diff  --git a/libcxx/test/libcxx/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/make_shared.array.zero_size.compile.fail.cpp b/libcxx/test/libcxx/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/make_shared.array.zero_size.compile.fail.cpp
new file mode 100644
index 0000000000000..35964a52f6de2
--- /dev/null
+++ b/libcxx/test/libcxx/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/make_shared.array.zero_size.compile.fail.cpp
@@ -0,0 +1,22 @@
+//===----------------------------------------------------------------------===//
+//
+// 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, c++11, c++14, c++17
+
+// Make sure that std::make_shared<T[0]>(...) fails at compile-time.
+// While Clang and GCC appear to support T[0] as a language extension, that support is
+// unreliable (for example T[0] doesn't match a T[N] partial specialization on Clang as
+// of writing this). So instead, we make sure that this doesn't work at all with our
+// implementation.
+
+#include <memory>
+
+void f() {
+  auto p = std::make_shared<int[0]>();
+  (void)p;
+}

diff  --git a/libcxx/test/std/language.support/support.limits/support.limits.general/memory.version.pass.cpp b/libcxx/test/std/language.support/support.limits/support.limits.general/memory.version.pass.cpp
index 20fe2514b4eff..cd9d4fa7a09d2 100644
--- a/libcxx/test/std/language.support/support.limits/support.limits.general/memory.version.pass.cpp
+++ b/libcxx/test/std/language.support/support.limits/support.limits.general/memory.version.pass.cpp
@@ -29,6 +29,7 @@
     __cpp_lib_ranges                              201811L [C++20]
     __cpp_lib_raw_memory_algorithms               201606L [C++17]
     __cpp_lib_shared_ptr_arrays                   201611L [C++17]
+                                                  201707L [C++20]
     __cpp_lib_shared_ptr_weak_type                201606L [C++17]
     __cpp_lib_smart_ptr_for_overwrite             202002L [C++20]
     __cpp_lib_to_address                          201711L [C++20]
@@ -374,8 +375,8 @@
 # ifndef __cpp_lib_shared_ptr_arrays
 #   error "__cpp_lib_shared_ptr_arrays should be defined in c++20"
 # endif
-# if __cpp_lib_shared_ptr_arrays != 201611L
-#   error "__cpp_lib_shared_ptr_arrays should have the value 201611L in c++20"
+# if __cpp_lib_shared_ptr_arrays != 201707L
+#   error "__cpp_lib_shared_ptr_arrays should have the value 201707L in c++20"
 # endif
 
 # ifndef __cpp_lib_shared_ptr_weak_type
@@ -525,8 +526,8 @@
 # ifndef __cpp_lib_shared_ptr_arrays
 #   error "__cpp_lib_shared_ptr_arrays should be defined in c++2b"
 # endif
-# if __cpp_lib_shared_ptr_arrays != 201611L
-#   error "__cpp_lib_shared_ptr_arrays should have the value 201611L in c++2b"
+# if __cpp_lib_shared_ptr_arrays != 201707L
+#   error "__cpp_lib_shared_ptr_arrays should have the value 201707L in c++2b"
 # endif
 
 # ifndef __cpp_lib_shared_ptr_weak_type

diff  --git a/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.pass.cpp b/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.pass.cpp
index a46e4ea11434a..8a01b07713add 100644
--- a/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.pass.cpp
+++ b/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.pass.cpp
@@ -141,6 +141,7 @@
     __cpp_lib_semaphore                            201907L [C++20]
     __cpp_lib_shared_mutex                         201505L [C++17]
     __cpp_lib_shared_ptr_arrays                    201611L [C++17]
+                                                   201707L [C++20]
     __cpp_lib_shared_ptr_weak_type                 201606L [C++17]
     __cpp_lib_shared_timed_mutex                   201402L [C++14]
     __cpp_lib_shift                                201806L [C++20]
@@ -3347,8 +3348,8 @@
 # ifndef __cpp_lib_shared_ptr_arrays
 #   error "__cpp_lib_shared_ptr_arrays should be defined in c++20"
 # endif
-# if __cpp_lib_shared_ptr_arrays != 201611L
-#   error "__cpp_lib_shared_ptr_arrays should have the value 201611L in c++20"
+# if __cpp_lib_shared_ptr_arrays != 201707L
+#   error "__cpp_lib_shared_ptr_arrays should have the value 201707L in c++20"
 # endif
 
 # ifndef __cpp_lib_shared_ptr_weak_type
@@ -4720,8 +4721,8 @@
 # ifndef __cpp_lib_shared_ptr_arrays
 #   error "__cpp_lib_shared_ptr_arrays should be defined in c++2b"
 # endif
-# if __cpp_lib_shared_ptr_arrays != 201611L
-#   error "__cpp_lib_shared_ptr_arrays should have the value 201611L in c++2b"
+# if __cpp_lib_shared_ptr_arrays != 201707L
+#   error "__cpp_lib_shared_ptr_arrays should have the value 201707L in c++2b"
 # endif
 
 # ifndef __cpp_lib_shared_ptr_weak_type

diff  --git a/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/allocate_shared.array.bounded.pass.cpp b/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/allocate_shared.array.bounded.pass.cpp
new file mode 100644
index 0000000000000..06e767fd17831
--- /dev/null
+++ b/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/allocate_shared.array.bounded.pass.cpp
@@ -0,0 +1,418 @@
+//===----------------------------------------------------------------------===//
+//
+// 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, c++11, c++14, c++17
+
+// Aligned deallocation isn't provided before macOS 10.14, and some tests for overaligned types
+// below require that feature.
+// XFAIL: use_system_cxx_lib && target={{.+}}-apple-macosx{{10.9|10.10|10.11|10.12|10.13}}
+
+// <memory>
+
+// shared_ptr
+
+// template<class T, class A>
+// shared_ptr<T> allocate_shared(const A& a); // T is U[N]
+//
+// template<class T, class A>
+// shared_ptr<T> allocate_shared(const A& a, const remove_extent_t<T>& u); // T is U[N]
+
+#include <cassert>
+#include <concepts>
+#include <cstdint> // std::uintptr_t
+#include <memory>
+#include <utility>
+
+#include "min_allocator.h"
+#include "operator_hijacker.h"
+#include "types.h"
+
+template <class T, class ...Args>
+concept CanAllocateShared = requires(Args&& ...args) {
+  { std::allocate_shared<T>(std::forward<Args>(args)...) } -> std::same_as<std::shared_ptr<T>>;
+};
+
+int main(int, char**) {
+  // Make sure we initialize elements correctly
+  {
+    // Without passing an initial value
+    {
+      using Array = int[8];
+      std::shared_ptr<Array> ptr = std::allocate_shared<Array>(std::allocator<Array>());
+      for (unsigned i = 0; i < 8; ++i) {
+        assert(ptr[i] == 0);
+      }
+    }
+    {
+      using Array = int[8][3];
+      std::shared_ptr<Array> ptr = std::allocate_shared<Array>(std::allocator<Array>());
+      for (unsigned i = 0; i < 8; ++i) {
+        assert(ptr[i][0] == 0);
+        assert(ptr[i][1] == 0);
+        assert(ptr[i][2] == 0);
+      }
+    }
+    {
+      using Array = int[8][3][2];
+      std::shared_ptr<Array> ptr = std::allocate_shared<Array>(std::allocator<Array>());
+      for (unsigned i = 0; i < 8; ++i) {
+        assert(ptr[i][0][0] == 0);
+        assert(ptr[i][0][1] == 0);
+        assert(ptr[i][1][0] == 0);
+        assert(ptr[i][1][1] == 0);
+        assert(ptr[i][2][0] == 0);
+        assert(ptr[i][2][1] == 0);
+      }
+    }
+
+    // Passing an initial value
+    {
+      using Array = int[8];
+      int init = 42;
+      std::shared_ptr<Array> ptr = std::allocate_shared<Array>(std::allocator<Array>(), init);
+      for (unsigned i = 0; i < 8; ++i) {
+        assert(ptr[i] == init);
+      }
+    }
+    {
+      using Array = int[8][3];
+      int init[3] = {42, 43, 44};
+      std::shared_ptr<Array> ptr = std::allocate_shared<Array>(std::allocator<Array>(), init);
+      for (unsigned i = 0; i < 8; ++i) {
+        assert(ptr[i][0] == 42);
+        assert(ptr[i][1] == 43);
+        assert(ptr[i][2] == 44);
+      }
+    }
+    {
+      using Array = int[8][3][2];
+      int init[3][2] = {{31, 32}, {41, 42}, {51, 52}};
+      std::shared_ptr<Array> ptr = std::allocate_shared<Array>(std::allocator<Array>(), init);
+      for (unsigned i = 0; i < 8; ++i) {
+        assert(ptr[i][0][0] == 31);
+        assert(ptr[i][0][1] == 32);
+        assert(ptr[i][1][0] == 41);
+        assert(ptr[i][1][1] == 42);
+        assert(ptr[i][2][0] == 51);
+        assert(ptr[i][2][1] == 52);
+      }
+    }
+  }
+
+  // Make sure array elements are destroyed in reverse order
+  {
+    // Without passing an initial value
+    {
+      using Array = DestroyInReverseOrder[8];
+      DestroyInReverseOrder::reset();
+      {
+        std::shared_ptr<Array> ptr = std::allocate_shared<Array>(std::allocator<Array>());
+        assert(DestroyInReverseOrder::alive() == 8);
+      }
+      assert(DestroyInReverseOrder::alive() == 0);
+    }
+    {
+      using Array = DestroyInReverseOrder[8][3];
+      DestroyInReverseOrder::reset();
+      {
+        std::shared_ptr<Array> ptr = std::allocate_shared<Array>(std::allocator<Array>());
+        assert(DestroyInReverseOrder::alive() == 8 * 3);
+      }
+      assert(DestroyInReverseOrder::alive() == 0);
+    }
+    {
+      using Array = DestroyInReverseOrder[8][3][2];
+      DestroyInReverseOrder::reset();
+      {
+        std::shared_ptr<Array> ptr = std::allocate_shared<Array>(std::allocator<Array>());
+        assert(DestroyInReverseOrder::alive() == 8 * 3 * 2);
+      }
+      assert(DestroyInReverseOrder::alive() == 0);
+    }
+
+    // Passing an initial value
+    {
+      using Array = DestroyInReverseOrder[8];
+      int count = 0;
+      DestroyInReverseOrder init(&count);
+      int init_count = 1;
+      {
+        std::shared_ptr<Array> ptr = std::allocate_shared<Array>(std::allocator<Array>(), init);
+        assert(count == 8 + init_count);
+      }
+      assert(count == init_count);
+    }
+    {
+      using Array = DestroyInReverseOrder[8][3];
+      int count = 0;
+      DestroyInReverseOrder init[3] = {&count, &count, &count};
+      int init_count = 3;
+      {
+        std::shared_ptr<Array> ptr = std::allocate_shared<Array>(std::allocator<Array>(), init);
+        assert(count == 8 * 3 + init_count);
+      }
+      assert(count == init_count);
+    }
+    {
+      using Array = DestroyInReverseOrder[8][3][2];
+      int count = 0;
+      DestroyInReverseOrder init[3][2] = {{&count, &count}, {&count, &count}, {&count, &count}};
+      int init_count = 3 * 2;
+      {
+        std::shared_ptr<Array> ptr = std::allocate_shared<Array>(std::allocator<Array>(), init);
+        assert(count == 8 * 3 * 2 + init_count);
+      }
+      assert(count == init_count);
+    }
+  }
+
+  // Count the number of copies being made
+  {
+    // Without passing an initial value
+    {
+      using Array = CountCopies[8];
+      CountCopies::reset();
+      std::shared_ptr<Array> ptr = std::allocate_shared<Array>(std::allocator<Array>());
+      assert(CountCopies::copies() == 0);
+    }
+    {
+      using Array = CountCopies[8][3];
+      CountCopies::reset();
+      std::shared_ptr<Array> ptr = std::allocate_shared<Array>(std::allocator<Array>());;
+      assert(CountCopies::copies() == 0);
+    }
+    {
+      using Array = CountCopies[8][3][2];
+      CountCopies::reset();
+      std::shared_ptr<Array> ptr = std::allocate_shared<Array>(std::allocator<Array>());;
+      assert(CountCopies::copies() == 0);
+    }
+
+    // Passing an initial value
+    {
+      using Array = CountCopies[8];
+      int copies = 0;
+      CountCopies init(&copies);
+      std::shared_ptr<Array> ptr = std::allocate_shared<Array>(std::allocator<Array>(), init);
+      assert(copies == 8);
+    }
+    {
+      using Array = CountCopies[8][3];
+      int copies = 0;
+      CountCopies init[3] = {&copies, &copies, &copies};
+      std::shared_ptr<Array> ptr = std::allocate_shared<Array>(std::allocator<Array>(), init);
+      assert(copies == 8 * 3);
+    }
+    {
+      using Array = CountCopies[8][3][2];
+      int copies = 0;
+      CountCopies init[3][2] = {{&copies, &copies}, {&copies, &copies}, {&copies, &copies}};
+      std::shared_ptr<Array> ptr = std::allocate_shared<Array>(std::allocator<Array>(), init);
+      assert(copies == 8 * 3 * 2);
+    }
+  }
+
+  // Make sure array elements are aligned properly when the array contains an overaligned type.
+  //
+  // Here, we don't need to test both the with-initial-value and without-initial-value code paths,
+  // since we're just checking the alignment and both are going to use the same code path unless
+  // the implementation is completely crazy.
+  {
+    auto check_alignment = []<class T> {
+      {
+        using Array = T[8];
+        std::shared_ptr ptr = std::allocate_shared<Array>(std::allocator<Array>());
+        for (int i = 0; i < 8; ++i) {
+          T* p = std::addressof(ptr[i]);
+          assert(reinterpret_cast<std::uintptr_t>(p) % alignof(T) == 0);
+        }
+      }
+      {
+        using Array = T[8][3];
+        std::shared_ptr ptr = std::allocate_shared<Array>(std::allocator<Array>());
+        for (int i = 0; i < 8; ++i) {
+          for (int j = 0; j < 3; ++j) {
+            T* p = std::addressof(ptr[i][j]);
+            assert(reinterpret_cast<std::uintptr_t>(p) % alignof(T) == 0);
+          }
+        }
+      }
+      {
+        using Array = T[8][3][2];
+        std::shared_ptr ptr = std::allocate_shared<Array>(std::allocator<Array>());
+        for (int i = 0; i < 8; ++i) {
+          for (int j = 0; j < 3; ++j) {
+            for (int k = 0; k < 2; ++k) {
+              T* p = std::addressof(ptr[i][j][k]);
+              assert(reinterpret_cast<std::uintptr_t>(p) % alignof(T) == 0);
+            }
+          }
+        }
+      }
+    };
+
+    struct Empty { };
+    check_alignment.operator()<Empty>();
+    check_alignment.operator()<OverAligned>();
+    check_alignment.operator()<MaxAligned>();
+
+    // test non corner cases as well while we're at it
+    struct Foo { int i; char c; };
+    check_alignment.operator()<int>();
+    check_alignment.operator()<Foo>();
+  }
+
+  // Make sure that we destroy all the elements constructed so far when an exception
+  // is thrown. Also make sure that we do it in reverse order of construction.
+#ifndef TEST_HAS_NO_EXCEPTIONS
+  {
+    struct Sentinel : ThrowOnConstruction, DestroyInReverseOrder { };
+
+    // Without passing an initial value
+    {
+      using Array = Sentinel[8];
+      for (int i = 0; i < 8; ++i) {
+        ThrowOnConstruction::throw_after(i);
+        DestroyInReverseOrder::reset();
+        try {
+          std::shared_ptr<Array> ptr = std::allocate_shared<Array>(std::allocator<Array>());
+          assert(false);
+        } catch (ThrowOnConstruction::exception const&) {
+          assert(DestroyInReverseOrder::alive() == 0);
+        }
+      }
+    }
+    {
+      using Array = Sentinel[8][3];
+      for (int i = 0; i < 8 * 3; ++i) {
+        ThrowOnConstruction::throw_after(i);
+        DestroyInReverseOrder::reset();
+        try {
+          std::shared_ptr<Array> ptr = std::allocate_shared<Array>(std::allocator<Array>());
+          assert(false);
+        } catch (ThrowOnConstruction::exception const&) {
+          assert(DestroyInReverseOrder::alive() == 0);
+        }
+      }
+    }
+    {
+      using Array = Sentinel[8][3][2];
+      for (int i = 0; i < 8 * 3 * 2; ++i) {
+        ThrowOnConstruction::throw_after(i);
+        DestroyInReverseOrder::reset();
+        try {
+          std::shared_ptr<Array> ptr = std::allocate_shared<Array>(std::allocator<Array>());
+          assert(false);
+        } catch (ThrowOnConstruction::exception const&) {
+          assert(DestroyInReverseOrder::alive() == 0);
+        }
+      }
+    }
+
+    // Passing an initial value
+    {
+      using Array = Sentinel[8];
+      for (int i = 0; i < 8; ++i) {
+        DestroyInReverseOrder::reset();
+        ThrowOnConstruction::reset();
+        Sentinel init;
+        ThrowOnConstruction::throw_after(i);
+        try {
+          std::shared_ptr<Array> ptr = std::allocate_shared<Array>(std::allocator<Array>(), init);
+          assert(false);
+        } catch (ThrowOnConstruction::exception const&) {
+          assert(DestroyInReverseOrder::alive() == 1);
+        }
+      }
+    }
+    {
+      using Array = Sentinel[8][3];
+      for (int i = 0; i < 8 * 3; ++i) {
+        DestroyInReverseOrder::reset();
+        ThrowOnConstruction::reset();
+        Sentinel init[3] = {};
+        ThrowOnConstruction::throw_after(i);
+        try {
+          std::shared_ptr<Array> ptr = std::allocate_shared<Array>(std::allocator<Array>(), init);
+          assert(false);
+        } catch (ThrowOnConstruction::exception const&) {
+          assert(DestroyInReverseOrder::alive() == 3);
+        }
+      }
+    }
+    {
+      using Array = Sentinel[8][3][2];
+      for (int i = 0; i < 8 * 3 * 2; ++i) {
+        DestroyInReverseOrder::reset();
+        ThrowOnConstruction::reset();
+        Sentinel init[3][2] = {};
+        ThrowOnConstruction::throw_after(i);
+        try {
+          std::shared_ptr<Array> ptr = std::allocate_shared<Array>(std::allocator<Array>(), init);
+          assert(false);
+        } catch (ThrowOnConstruction::exception const&) {
+          assert(DestroyInReverseOrder::alive() == 3 * 2);
+        }
+      }
+    }
+  }
+#endif // TEST_HAS_NO_EXCEPTIONS
+
+  // Test with another allocator that's not std::allocator
+  {
+    // Without passing an initial value
+    {
+      using Array = int[8][3];
+      std::shared_ptr<Array> ptr = std::allocate_shared<Array>(min_allocator<Array>());
+      for (unsigned i = 0; i < 8; ++i) {
+        assert(ptr[i][0] == 0);
+        assert(ptr[i][1] == 0);
+        assert(ptr[i][2] == 0);
+      }
+    }
+
+    // Passing an initial value
+    {
+      using Array = int[8][3];
+      int init[3] = {42, 43, 44};
+      std::shared_ptr<Array> ptr = std::allocate_shared<Array>(min_allocator<Array>(), init);
+      for (unsigned i = 0; i < 8; ++i) {
+        assert(ptr[i][0] == 42);
+        assert(ptr[i][1] == 43);
+        assert(ptr[i][2] == 44);
+      }
+    }
+  }
+
+  // Make sure the version without an initialization argument works even for non-movable types
+  {
+    using Array = NonMovable[8][3];
+    std::shared_ptr<Array> ptr = std::allocate_shared<Array>(std::allocator<Array>());
+    (void)ptr;
+  }
+
+  // Make sure std::allocate_shared handles badly-behaved types properly
+  {
+    using Array = operator_hijacker[3];
+    std::shared_ptr<Array> p1 = std::allocate_shared<Array>(std::allocator<Array>());
+    std::shared_ptr<Array> p2 = std::allocate_shared<Array>(std::allocator<Array>(), operator_hijacker());
+    assert(p1 != nullptr);
+    assert(p2 != nullptr);
+  }
+
+  // Check that we SFINAE-away for invalid arguments
+  {
+    struct T { };
+    static_assert( CanAllocateShared<T[8], std::allocator<T[8]>>);
+    static_assert( CanAllocateShared<T[8], std::allocator<T[8]>, T>);
+    static_assert(!CanAllocateShared<T[8], std::allocator<T[8]>, T, int>); // too many arguments
+    static_assert(!CanAllocateShared<T[8], std::allocator<T[8]>, int>); // T is not constructible from int
+  }
+
+  return 0;
+}

diff  --git a/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/allocate_shared.array.unbounded.pass.cpp b/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/allocate_shared.array.unbounded.pass.cpp
new file mode 100644
index 0000000000000..5842c478361b0
--- /dev/null
+++ b/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/allocate_shared.array.unbounded.pass.cpp
@@ -0,0 +1,460 @@
+//===----------------------------------------------------------------------===//
+//
+// 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, c++11, c++14, c++17
+
+// Aligned deallocation isn't provided before macOS 10.14, and some tests for overaligned types
+// below require that feature.
+// XFAIL: use_system_cxx_lib && target={{.+}}-apple-macosx{{10.9|10.10|10.11|10.12|10.13}}
+
+// <memory>
+
+// shared_ptr
+
+// template<class T, class A>
+// shared_ptr<T> allocate_shared(const A& a, size_t N); // T is U[]
+//
+// template<class T, class A>
+// shared_ptr<T> allocate_shared(const A& a, size_t N, const remove_extent_t<T>& u); // T is U[]
+
+// Ignore error about requesting a large alignment not being ABI compatible with older AIX systems.
+#ifdef _AIX
+# pragma clang diagnostic ignored "-Waix-compat"
+#endif
+
+#include <cassert>
+#include <concepts>
+#include <cstdint> // std::uintptr_t
+#include <memory>
+#include <utility>
+
+#include "min_allocator.h"
+#include "operator_hijacker.h"
+#include "test_macros.h"
+#include "types.h"
+
+template <class T, class ...Args>
+concept CanAllocateShared = requires(Args&& ...args) {
+  { std::allocate_shared<T>(std::forward<Args>(args)...) } -> std::same_as<std::shared_ptr<T>>;
+};
+
+int main(int, char**) {
+  // Check behavior for a zero-sized array
+  {
+    // Without passing an initial value
+    {
+      using Array = int[];
+      std::shared_ptr<Array> ptr = std::allocate_shared<Array>(std::allocator<Array>(), 0);
+      assert(ptr != nullptr);
+    }
+
+    // Passing an initial value
+    {
+      using Array = int[];
+      std::shared_ptr<Array> ptr = std::allocate_shared<Array>(std::allocator<Array>(), 0, 42);
+      assert(ptr != nullptr);
+    }
+  }
+
+  // Check behavior for a 1-sized array
+  {
+    // Without passing an initial value
+    {
+      using Array = int[];
+      std::shared_ptr<Array> ptr = std::allocate_shared<Array>(std::allocator<Array>(), 1);
+      assert(ptr != nullptr);
+      assert(ptr[0] == 0);
+    }
+
+    // Passing an initial value
+    {
+      using Array = int[];
+      std::shared_ptr<Array> ptr = std::allocate_shared<Array>(std::allocator<Array>(), 1, 42);
+      assert(ptr != nullptr);
+      assert(ptr[0] == 42);
+    }
+  }
+
+  // Make sure we initialize elements correctly
+  {
+    // Without passing an initial value
+    {
+      using Array = int[];
+      std::shared_ptr<Array> ptr = std::allocate_shared<Array>(std::allocator<Array>(), 8);
+      for (unsigned i = 0; i < 8; ++i) {
+        assert(ptr[i] == 0);
+      }
+    }
+    {
+      using Array = int[][3];
+      std::shared_ptr<Array> ptr = std::allocate_shared<Array>(std::allocator<Array>(), 8);
+      for (unsigned i = 0; i < 8; ++i) {
+        assert(ptr[i][0] == 0);
+        assert(ptr[i][1] == 0);
+        assert(ptr[i][2] == 0);
+      }
+    }
+    {
+      using Array = int[][3][2];
+      std::shared_ptr<Array> ptr = std::allocate_shared<Array>(std::allocator<Array>(), 8);
+      for (unsigned i = 0; i < 8; ++i) {
+        assert(ptr[i][0][0] == 0);
+        assert(ptr[i][0][1] == 0);
+        assert(ptr[i][1][0] == 0);
+        assert(ptr[i][1][1] == 0);
+        assert(ptr[i][2][0] == 0);
+        assert(ptr[i][2][1] == 0);
+      }
+    }
+
+    // Passing an initial value
+    {
+      using Array = int[];
+      int init = 42;
+      std::shared_ptr<Array> ptr = std::allocate_shared<Array>(std::allocator<Array>(), 8, init);
+      for (unsigned i = 0; i < 8; ++i) {
+        assert(ptr[i] == init);
+      }
+    }
+    {
+      using Array = int[][3];
+      int init[3] = {42, 43, 44};
+      std::shared_ptr<Array> ptr = std::allocate_shared<Array>(std::allocator<Array>(), 8, init);
+      for (unsigned i = 0; i < 8; ++i) {
+        assert(ptr[i][0] == 42);
+        assert(ptr[i][1] == 43);
+        assert(ptr[i][2] == 44);
+      }
+    }
+    {
+      using Array = int[][3][2];
+      int init[3][2] = {{31, 32}, {41, 42}, {51, 52}};
+      std::shared_ptr<Array> ptr = std::allocate_shared<Array>(std::allocator<Array>(), 8, init);
+      for (unsigned i = 0; i < 8; ++i) {
+        assert(ptr[i][0][0] == 31);
+        assert(ptr[i][0][1] == 32);
+        assert(ptr[i][1][0] == 41);
+        assert(ptr[i][1][1] == 42);
+        assert(ptr[i][2][0] == 51);
+        assert(ptr[i][2][1] == 52);
+      }
+    }
+  }
+
+  // Make sure array elements are destroyed in reverse order
+  {
+    // Without passing an initial value
+    {
+      using Array = DestroyInReverseOrder[];
+      DestroyInReverseOrder::reset();
+      {
+        std::shared_ptr<Array> ptr = std::allocate_shared<Array>(std::allocator<Array>(), 8);
+        assert(DestroyInReverseOrder::alive() == 8);
+      }
+      assert(DestroyInReverseOrder::alive() == 0);
+    }
+    {
+      using Array = DestroyInReverseOrder[][3];
+      DestroyInReverseOrder::reset();
+      {
+        std::shared_ptr<Array> ptr = std::allocate_shared<Array>(std::allocator<Array>(), 8);
+        assert(DestroyInReverseOrder::alive() == 8 * 3);
+      }
+      assert(DestroyInReverseOrder::alive() == 0);
+    }
+    {
+      using Array = DestroyInReverseOrder[][3][2];
+      DestroyInReverseOrder::reset();
+      {
+        std::shared_ptr<Array> ptr = std::allocate_shared<Array>(std::allocator<Array>(), 8);
+        assert(DestroyInReverseOrder::alive() == 8 * 3 * 2);
+      }
+      assert(DestroyInReverseOrder::alive() == 0);
+    }
+
+    // Passing an initial value
+    {
+      using Array = DestroyInReverseOrder[];
+      int count = 0;
+      DestroyInReverseOrder init(&count);
+      int init_count = 1;
+      {
+        std::shared_ptr<Array> ptr = std::allocate_shared<Array>(std::allocator<Array>(), 8, init);
+        assert(count == 8 + init_count);
+      }
+      assert(count == init_count);
+    }
+    {
+      using Array = DestroyInReverseOrder[][3];
+      int count = 0;
+      DestroyInReverseOrder init[3] = {&count, &count, &count};
+      int init_count = 3;
+      {
+        std::shared_ptr<Array> ptr = std::allocate_shared<Array>(std::allocator<Array>(), 8, init);
+        assert(count == 8 * 3 + init_count);
+      }
+      assert(count == init_count);
+    }
+    {
+      using Array = DestroyInReverseOrder[][3][2];
+      int count = 0;
+      DestroyInReverseOrder init[3][2] = {{&count, &count}, {&count, &count}, {&count, &count}};
+      int init_count = 3 * 2;
+      {
+        std::shared_ptr<Array> ptr = std::allocate_shared<Array>(std::allocator<Array>(), 8, init);
+        assert(count == 8 * 3 * 2 + init_count);
+      }
+      assert(count == init_count);
+    }
+  }
+
+  // Count the number of copies being made
+  {
+    // Without passing an initial value
+    {
+      using Array = CountCopies[];
+      CountCopies::reset();
+      std::shared_ptr<Array> ptr = std::allocate_shared<Array>(std::allocator<Array>(), 8);
+      assert(CountCopies::copies() == 0);
+    }
+    {
+      using Array = CountCopies[][3];
+      CountCopies::reset();
+      std::shared_ptr<Array> ptr = std::allocate_shared<Array>(std::allocator<Array>(), 8);
+      assert(CountCopies::copies() == 0);
+    }
+    {
+      using Array = CountCopies[][3][2];
+      CountCopies::reset();
+      std::shared_ptr<Array> ptr = std::allocate_shared<Array>(std::allocator<Array>(), 8);
+      assert(CountCopies::copies() == 0);
+    }
+
+    // Passing an initial value
+    {
+      using Array = CountCopies[];
+      int copies = 0;
+      CountCopies init(&copies);
+      std::shared_ptr<Array> ptr = std::allocate_shared<Array>(std::allocator<Array>(), 8, init);
+      assert(copies == 8);
+    }
+    {
+      using Array = CountCopies[][3];
+      int copies = 0;
+      CountCopies init[3] = {&copies, &copies, &copies};
+      std::shared_ptr<Array> ptr = std::allocate_shared<Array>(std::allocator<Array>(), 8, init);
+      assert(copies == 8 * 3);
+    }
+    {
+      using Array = CountCopies[][3][2];
+      int copies = 0;
+      CountCopies init[3][2] = {{&copies, &copies}, {&copies, &copies}, {&copies, &copies}};
+      std::shared_ptr<Array> ptr = std::allocate_shared<Array>(std::allocator<Array>(), 8, init);
+      assert(copies == 8 * 3 * 2);
+    }
+  }
+
+  // Make sure array elements are aligned properly when the array contains an overaligned type.
+  //
+  // Here, we don't need to test both the with-initial-value and without-initial-value code paths,
+  // since we're just checking the alignment and both are going to use the same code path unless
+  // the implementation is completely crazy.
+  {
+    auto check_alignment = []<class T> {
+      {
+        using Array = T[];
+        std::shared_ptr ptr = std::allocate_shared<Array>(std::allocator<Array>(), 8);
+        for (int i = 0; i < 8; ++i) {
+          T* p = std::addressof(ptr[i]);
+          assert(reinterpret_cast<std::uintptr_t>(p) % alignof(T) == 0);
+        }
+      }
+      {
+        using Array = T[][3];
+        std::shared_ptr ptr = std::allocate_shared<Array>(std::allocator<Array>(), 8);
+        for (int i = 0; i < 8; ++i) {
+          for (int j = 0; j < 3; ++j) {
+            T* p = std::addressof(ptr[i][j]);
+            assert(reinterpret_cast<std::uintptr_t>(p) % alignof(T) == 0);
+          }
+        }
+      }
+      {
+        using Array = T[][3][2];
+        std::shared_ptr ptr = std::allocate_shared<Array>(std::allocator<Array>(), 8);
+        for (int i = 0; i < 8; ++i) {
+          for (int j = 0; j < 3; ++j) {
+            for (int k = 0; k < 2; ++k) {
+              T* p = std::addressof(ptr[i][j][k]);
+              assert(reinterpret_cast<std::uintptr_t>(p) % alignof(T) == 0);
+            }
+          }
+        }
+      }
+    };
+
+    struct Empty { };
+    check_alignment.operator()<Empty>();
+    check_alignment.operator()<OverAligned>();
+    check_alignment.operator()<MaxAligned>();
+
+    // test non corner cases as well while we're at it
+    struct Foo { int i; char c; };
+    check_alignment.operator()<int>();
+    check_alignment.operator()<Foo>();
+  }
+
+  // Make sure that we destroy all the elements constructed so far when an exception
+  // is thrown. Also make sure that we do it in reverse order of construction.
+#ifndef TEST_HAS_NO_EXCEPTIONS
+  {
+    struct Sentinel : ThrowOnConstruction, DestroyInReverseOrder { };
+
+    // Without passing an initial value
+    {
+      using Array = Sentinel[];
+      for (int i = 0; i < 8; ++i) {
+        ThrowOnConstruction::throw_after(i);
+        DestroyInReverseOrder::reset();
+        try {
+          std::shared_ptr<Array> ptr = std::allocate_shared<Array>(std::allocator<Array>(), 8);
+          assert(false);
+        } catch (ThrowOnConstruction::exception const&) {
+          assert(DestroyInReverseOrder::alive() == 0);
+        }
+      }
+    }
+    {
+      using Array = Sentinel[][3];
+      for (int i = 0; i < 8 * 3; ++i) {
+        ThrowOnConstruction::throw_after(i);
+        DestroyInReverseOrder::reset();
+        try {
+          std::shared_ptr<Array> ptr = std::allocate_shared<Array>(std::allocator<Array>(), 8);
+          assert(false);
+        } catch (ThrowOnConstruction::exception const&) {
+          assert(DestroyInReverseOrder::alive() == 0);
+        }
+      }
+    }
+    {
+      using Array = Sentinel[][3][2];
+      for (int i = 0; i < 8 * 3 * 2; ++i) {
+        ThrowOnConstruction::throw_after(i);
+        DestroyInReverseOrder::reset();
+        try {
+          std::shared_ptr<Array> ptr = std::allocate_shared<Array>(std::allocator<Array>(), 8);
+          assert(false);
+        } catch (ThrowOnConstruction::exception const&) {
+          assert(DestroyInReverseOrder::alive() == 0);
+        }
+      }
+    }
+
+    // Passing an initial value
+    {
+      using Array = Sentinel[];
+      for (int i = 0; i < 8; ++i) {
+        DestroyInReverseOrder::reset();
+        ThrowOnConstruction::reset();
+        Sentinel init;
+        ThrowOnConstruction::throw_after(i);
+        try {
+          std::shared_ptr<Array> ptr = std::allocate_shared<Array>(std::allocator<Array>(), 8, init);
+          assert(false);
+        } catch (ThrowOnConstruction::exception const&) {
+          assert(DestroyInReverseOrder::alive() == 1);
+        }
+      }
+    }
+    {
+      using Array = Sentinel[][3];
+      for (int i = 0; i < 8 * 3; ++i) {
+        DestroyInReverseOrder::reset();
+        ThrowOnConstruction::reset();
+        Sentinel init[3] = {};
+        ThrowOnConstruction::throw_after(i);
+        try {
+          std::shared_ptr<Array> ptr = std::allocate_shared<Array>(std::allocator<Array>(), 8, init);
+          assert(false);
+        } catch (ThrowOnConstruction::exception const&) {
+          assert(DestroyInReverseOrder::alive() == 3);
+        }
+      }
+    }
+    {
+      using Array = Sentinel[][3][2];
+      for (int i = 0; i < 8 * 3 * 2; ++i) {
+        DestroyInReverseOrder::reset();
+        ThrowOnConstruction::reset();
+        Sentinel init[3][2] = {};
+        ThrowOnConstruction::throw_after(i);
+        try {
+          std::shared_ptr<Array> ptr = std::allocate_shared<Array>(std::allocator<Array>(), 8, init);
+          assert(false);
+        } catch (ThrowOnConstruction::exception const&) {
+          assert(DestroyInReverseOrder::alive() == 3 * 2);
+        }
+      }
+    }
+  }
+#endif // TEST_HAS_NO_EXCEPTIONS
+
+  // Test with another allocator that's not std::allocator
+  {
+    // Without passing an initial value
+    {
+      using Array = int[][3];
+      std::shared_ptr<Array> ptr = std::allocate_shared<Array>(min_allocator<Array>(), 8);
+      for (unsigned i = 0; i < 8; ++i) {
+        assert(ptr[i][0] == 0);
+        assert(ptr[i][1] == 0);
+        assert(ptr[i][2] == 0);
+      }
+    }
+
+    // Passing an initial value
+    {
+      using Array = int[][3];
+      int init[3] = {42, 43, 44};
+      std::shared_ptr<Array> ptr = std::allocate_shared<Array>(min_allocator<Array>(), 8, init);
+      for (unsigned i = 0; i < 8; ++i) {
+        assert(ptr[i][0] == 42);
+        assert(ptr[i][1] == 43);
+        assert(ptr[i][2] == 44);
+      }
+    }
+  }
+
+  // Make sure the version without an initialization argument works even for non-movable types
+  {
+    using Array = NonMovable[][3];
+    std::shared_ptr<Array> ptr = std::allocate_shared<Array>(std::allocator<Array>(), 8);
+    (void)ptr;
+  }
+
+  // Make sure std::allocate_shared handles badly-behaved types properly
+  {
+    using Array = operator_hijacker[];
+    std::shared_ptr<Array> p1 = std::allocate_shared<Array>(std::allocator<Array>(), 3);
+    std::shared_ptr<Array> p2 = std::allocate_shared<Array>(std::allocator<Array>(), 3, operator_hijacker());
+    assert(p1 != nullptr);
+    assert(p2 != nullptr);
+  }
+
+  // Check that we SFINAE-away for invalid arguments
+  {
+    struct T { };
+    static_assert( CanAllocateShared<T[], std::allocator<T[]>, std::size_t>);
+    static_assert( CanAllocateShared<T[], std::allocator<T[]>, std::size_t, T>);
+    static_assert(!CanAllocateShared<T[], std::allocator<T[]>, std::size_t, T, int>); // too many arguments
+    static_assert(!CanAllocateShared<T[], std::allocator<T[]>, std::size_t, int>); // T not constructible from int
+  }
+
+  return 0;
+}

diff  --git a/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/allocate_shared.pass.cpp b/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/allocate_shared.pass.cpp
index 472cb3d0a7d7c..ac7a9913b0e91 100644
--- a/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/allocate_shared.pass.cpp
+++ b/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/allocate_shared.pass.cpp
@@ -11,15 +11,17 @@
 // shared_ptr
 
 // template<class T, class A, class... Args>
-//    shared_ptr<T> allocate_shared(const A& a, Args&&... args);
+// shared_ptr<T> allocate_shared(const A& a, Args&&... args); // T is not an array
 
 #include <memory>
 #include <new>
 #include <cstdlib>
 #include <cassert>
-#include "test_macros.h"
-#include "test_allocator.h"
+
 #include "min_allocator.h"
+#include "operator_hijacker.h"
+#include "test_allocator.h"
+#include "test_macros.h"
 
 int new_count = 0;
 
@@ -172,6 +174,14 @@ int main(int, char**)
     }
     assert(A::count == 0);
 
+    // Make sure std::allocate_shared handles badly-behaved types properly
+    {
+        std::shared_ptr<operator_hijacker> p1 = std::allocate_shared<operator_hijacker>(min_allocator<operator_hijacker>());
+        std::shared_ptr<operator_hijacker> p2 = std::allocate_shared<operator_hijacker>(min_allocator<operator_hijacker>(), operator_hijacker());
+        assert(p1 != nullptr);
+        assert(p2 != nullptr);
+    }
+
     // Test that we don't call construct before C++20.
 #if TEST_STD_VER < 20
     {

diff  --git a/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/allocate_shared_construct.pass.cpp b/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/allocate_shared_construct.pass.cpp
index d45d6e124b871..908aa7f100cd5 100644
--- a/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/allocate_shared_construct.pass.cpp
+++ b/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/allocate_shared_construct.pass.cpp
@@ -13,7 +13,7 @@
 // shared_ptr
 
 // template<class T, class A, class... Args>
-//    shared_ptr<T> allocate_shared(const A& a, Args&&... args);
+// shared_ptr<T> allocate_shared(const A& a, Args&&... args);
 
 // This test checks that allocator_traits::construct is used in allocate_shared
 // as requested in C++20 (via P0674R1).

diff  --git a/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/make_shared.array.bounded.pass.cpp b/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/make_shared.array.bounded.pass.cpp
new file mode 100644
index 0000000000000..20fa508fa77af
--- /dev/null
+++ b/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/make_shared.array.bounded.pass.cpp
@@ -0,0 +1,390 @@
+//===----------------------------------------------------------------------===//
+//
+// 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, c++11, c++14, c++17
+
+// Aligned deallocation isn't provided before macOS 10.14, and some tests for overaligned types
+// below require that feature.
+// XFAIL: use_system_cxx_lib && target={{.+}}-apple-macosx{{10.9|10.10|10.11|10.12|10.13}}
+
+// <memory>
+
+// shared_ptr
+
+// template<class T>
+// shared_ptr<T> make_shared(); // T is U[N]
+//
+// template<class T>
+// shared_ptr<T> make_shared(const remove_extent_t<T>& u); // T is U[N]
+
+#include <cassert>
+#include <concepts>
+#include <cstdint> // std::uintptr_t
+#include <memory>
+#include <utility>
+
+#include "operator_hijacker.h"
+#include "types.h"
+
+template <class T, class ...Args>
+concept CanMakeShared = requires(Args&& ...args) {
+  { std::make_shared<T>(std::forward<Args>(args)...) } -> std::same_as<std::shared_ptr<T>>;
+};
+
+int main(int, char**) {
+  // Make sure we initialize elements correctly
+  {
+    // Without passing an initial value
+    {
+      using Array = int[8];
+      std::shared_ptr<Array> ptr = std::make_shared<Array>();
+      for (unsigned i = 0; i < 8; ++i) {
+        assert(ptr[i] == 0);
+      }
+    }
+    {
+      using Array = int[8][3];
+      std::shared_ptr<Array> ptr = std::make_shared<Array>();
+      for (unsigned i = 0; i < 8; ++i) {
+        assert(ptr[i][0] == 0);
+        assert(ptr[i][1] == 0);
+        assert(ptr[i][2] == 0);
+      }
+    }
+    {
+      using Array = int[8][3][2];
+      std::shared_ptr<Array> ptr = std::make_shared<Array>();
+      for (unsigned i = 0; i < 8; ++i) {
+        assert(ptr[i][0][0] == 0);
+        assert(ptr[i][0][1] == 0);
+        assert(ptr[i][1][0] == 0);
+        assert(ptr[i][1][1] == 0);
+        assert(ptr[i][2][0] == 0);
+        assert(ptr[i][2][1] == 0);
+      }
+    }
+
+    // Passing an initial value
+    {
+      using Array = int[8];
+      int init = 42;
+      std::shared_ptr<Array> ptr = std::make_shared<Array>(init);
+      for (unsigned i = 0; i < 8; ++i) {
+        assert(ptr[i] == init);
+      }
+    }
+    {
+      using Array = int[8][3];
+      int init[3] = {42, 43, 44};
+      std::shared_ptr<Array> ptr = std::make_shared<Array>(init);
+      for (unsigned i = 0; i < 8; ++i) {
+        assert(ptr[i][0] == 42);
+        assert(ptr[i][1] == 43);
+        assert(ptr[i][2] == 44);
+      }
+    }
+    {
+      using Array = int[8][3][2];
+      int init[3][2] = {{31, 32}, {41, 42}, {51, 52}};
+      std::shared_ptr<Array> ptr = std::make_shared<Array>(init);
+      for (unsigned i = 0; i < 8; ++i) {
+        assert(ptr[i][0][0] == 31);
+        assert(ptr[i][0][1] == 32);
+        assert(ptr[i][1][0] == 41);
+        assert(ptr[i][1][1] == 42);
+        assert(ptr[i][2][0] == 51);
+        assert(ptr[i][2][1] == 52);
+      }
+    }
+  }
+
+  // Make sure array elements are destroyed in reverse order
+  {
+    // Without passing an initial value
+    {
+      using Array = DestroyInReverseOrder[8];
+      DestroyInReverseOrder::reset();
+      {
+        std::shared_ptr<Array> ptr = std::make_shared<Array>();
+        assert(DestroyInReverseOrder::alive() == 8);
+      }
+      assert(DestroyInReverseOrder::alive() == 0);
+    }
+    {
+      using Array = DestroyInReverseOrder[8][3];
+      DestroyInReverseOrder::reset();
+      {
+        std::shared_ptr<Array> ptr = std::make_shared<Array>();
+        assert(DestroyInReverseOrder::alive() == 8 * 3);
+      }
+      assert(DestroyInReverseOrder::alive() == 0);
+    }
+    {
+      using Array = DestroyInReverseOrder[8][3][2];
+      DestroyInReverseOrder::reset();
+      {
+        std::shared_ptr<Array> ptr = std::make_shared<Array>();
+        assert(DestroyInReverseOrder::alive() == 8 * 3 * 2);
+      }
+      assert(DestroyInReverseOrder::alive() == 0);
+    }
+
+    // Passing an initial value
+    {
+      using Array = DestroyInReverseOrder[8];
+      int count = 0;
+      DestroyInReverseOrder init(&count);
+      int init_count = 1;
+      {
+        std::shared_ptr<Array> ptr = std::make_shared<Array>(init);
+        assert(count == 8 + init_count);
+      }
+      assert(count == init_count);
+    }
+    {
+      using Array = DestroyInReverseOrder[8][3];
+      int count = 0;
+      DestroyInReverseOrder init[3] = {&count, &count, &count};
+      int init_count = 3;
+      {
+        std::shared_ptr<Array> ptr = std::make_shared<Array>(init);
+        assert(count == 8 * 3 + init_count);
+      }
+      assert(count == init_count);
+    }
+    {
+      using Array = DestroyInReverseOrder[8][3][2];
+      int count = 0;
+      DestroyInReverseOrder init[3][2] = {{&count, &count}, {&count, &count}, {&count, &count}};
+      int init_count = 3 * 2;
+      {
+        std::shared_ptr<Array> ptr = std::make_shared<Array>(init);
+        assert(count == 8 * 3 * 2 + init_count);
+      }
+      assert(count == init_count);
+    }
+  }
+
+  // Count the number of copies being made
+  {
+    // Without passing an initial value
+    {
+      using Array = CountCopies[8];
+      CountCopies::reset();
+      std::shared_ptr<Array> ptr = std::make_shared<Array>();
+      assert(CountCopies::copies() == 0);
+    }
+    {
+      using Array = CountCopies[8][3];
+      CountCopies::reset();
+      std::shared_ptr<Array> ptr = std::make_shared<Array>();
+      assert(CountCopies::copies() == 0);
+    }
+    {
+      using Array = CountCopies[8][3][2];
+      CountCopies::reset();
+      std::shared_ptr<Array> ptr = std::make_shared<Array>();
+      assert(CountCopies::copies() == 0);
+    }
+
+    // Passing an initial value
+    {
+      using Array = CountCopies[8];
+      int copies = 0;
+      CountCopies init(&copies);
+      std::shared_ptr<Array> ptr = std::make_shared<Array>(init);
+      assert(copies == 8);
+    }
+    {
+      using Array = CountCopies[8][3];
+      int copies = 0;
+      CountCopies init[3] = {&copies, &copies, &copies};
+      std::shared_ptr<Array> ptr = std::make_shared<Array>(init);
+      assert(copies == 8 * 3);
+    }
+    {
+      using Array = CountCopies[8][3][2];
+      int copies = 0;
+      CountCopies init[3][2] = {{&copies, &copies}, {&copies, &copies}, {&copies, &copies}};
+      std::shared_ptr<Array> ptr = std::make_shared<Array>(init);
+      assert(copies == 8 * 3 * 2);
+    }
+  }
+
+  // Make sure array elements are aligned properly when the array contains an overaligned type.
+  //
+  // Here, we don't need to test both the with-initial-value and without-initial-value code paths,
+  // since we're just checking the alignment and both are going to use the same code path unless
+  // the implementation is completely crazy.
+  {
+    auto check_alignment = []<class T> {
+      {
+        using Array = T[8];
+        std::shared_ptr ptr = std::make_shared<Array>();
+        for (int i = 0; i < 8; ++i) {
+          T* p = std::addressof(ptr[i]);
+          assert(reinterpret_cast<std::uintptr_t>(p) % alignof(T) == 0);
+        }
+      }
+      {
+        using Array = T[8][3];
+        std::shared_ptr ptr = std::make_shared<Array>();
+        for (int i = 0; i < 8; ++i) {
+          for (int j = 0; j < 3; ++j) {
+            T* p = std::addressof(ptr[i][j]);
+            assert(reinterpret_cast<std::uintptr_t>(p) % alignof(T) == 0);
+          }
+        }
+      }
+      {
+        using Array = T[8][3][2];
+        std::shared_ptr ptr = std::make_shared<Array>();
+        for (int i = 0; i < 8; ++i) {
+          for (int j = 0; j < 3; ++j) {
+            for (int k = 0; k < 2; ++k) {
+              T* p = std::addressof(ptr[i][j][k]);
+              assert(reinterpret_cast<std::uintptr_t>(p) % alignof(T) == 0);
+            }
+          }
+        }
+      }
+    };
+
+    struct Empty { };
+    check_alignment.operator()<Empty>();
+    check_alignment.operator()<OverAligned>();
+    check_alignment.operator()<MaxAligned>();
+
+    // test non corner cases as well while we're at it
+    struct Foo { int i; char c; };
+    check_alignment.operator()<int>();
+    check_alignment.operator()<Foo>();
+  }
+
+  // Make sure that we destroy all the elements constructed so far when an exception
+  // is thrown. Also make sure that we do it in reverse order of construction.
+#ifndef TEST_HAS_NO_EXCEPTIONS
+  {
+    struct Sentinel : ThrowOnConstruction, DestroyInReverseOrder { };
+
+    // Without passing an initial value
+    {
+      using Array = Sentinel[8];
+      for (int i = 0; i < 8; ++i) {
+        ThrowOnConstruction::throw_after(i);
+        DestroyInReverseOrder::reset();
+        try {
+          std::shared_ptr<Array> ptr = std::make_shared<Array>();
+          assert(false);
+        } catch (ThrowOnConstruction::exception const&) {
+          assert(DestroyInReverseOrder::alive() == 0);
+        }
+      }
+    }
+    {
+      using Array = Sentinel[8][3];
+      for (int i = 0; i < 8 * 3; ++i) {
+        ThrowOnConstruction::throw_after(i);
+        DestroyInReverseOrder::reset();
+        try {
+          std::shared_ptr<Array> ptr = std::make_shared<Array>();
+          assert(false);
+        } catch (ThrowOnConstruction::exception const&) {
+          assert(DestroyInReverseOrder::alive() == 0);
+        }
+      }
+    }
+    {
+      using Array = Sentinel[8][3][2];
+      for (int i = 0; i < 8 * 3 * 2; ++i) {
+        ThrowOnConstruction::throw_after(i);
+        DestroyInReverseOrder::reset();
+        try {
+          std::shared_ptr<Array> ptr = std::make_shared<Array>();
+          assert(false);
+        } catch (ThrowOnConstruction::exception const&) {
+          assert(DestroyInReverseOrder::alive() == 0);
+        }
+      }
+    }
+
+    // Passing an initial value
+    {
+      using Array = Sentinel[8];
+      for (int i = 0; i < 8; ++i) {
+        DestroyInReverseOrder::reset();
+        ThrowOnConstruction::reset();
+        Sentinel init;
+        ThrowOnConstruction::throw_after(i);
+        try {
+          std::shared_ptr<Array> ptr = std::make_shared<Array>(init);
+          assert(false);
+        } catch (ThrowOnConstruction::exception const&) {
+          assert(DestroyInReverseOrder::alive() == 1);
+        }
+      }
+    }
+    {
+      using Array = Sentinel[8][3];
+      for (int i = 0; i < 8 * 3; ++i) {
+        DestroyInReverseOrder::reset();
+        ThrowOnConstruction::reset();
+        Sentinel init[3] = {};
+        ThrowOnConstruction::throw_after(i);
+        try {
+          std::shared_ptr<Array> ptr = std::make_shared<Array>(init);
+          assert(false);
+        } catch (ThrowOnConstruction::exception const&) {
+          assert(DestroyInReverseOrder::alive() == 3);
+        }
+      }
+    }
+    {
+      using Array = Sentinel[8][3][2];
+      for (int i = 0; i < 8 * 3 * 2; ++i) {
+        DestroyInReverseOrder::reset();
+        ThrowOnConstruction::reset();
+        Sentinel init[3][2] = {};
+        ThrowOnConstruction::throw_after(i);
+        try {
+          std::shared_ptr<Array> ptr = std::make_shared<Array>(init);
+          assert(false);
+        } catch (ThrowOnConstruction::exception const&) {
+          assert(DestroyInReverseOrder::alive() == 3 * 2);
+        }
+      }
+    }
+  }
+#endif // TEST_HAS_NO_EXCEPTIONS
+
+  // Make sure the version without an initialization argument works even for non-movable types
+  {
+    using Array = NonMovable[8][3];
+    std::shared_ptr<Array> ptr = std::make_shared<Array>();
+    (void)ptr;
+  }
+
+  // Make sure std::make_shared handles badly-behaved types properly
+  {
+    using Array = operator_hijacker[3];
+    std::shared_ptr<Array> p1 = std::make_shared<Array>();
+    std::shared_ptr<Array> p2 = std::make_shared<Array>(operator_hijacker());
+    assert(p1 != nullptr);
+    assert(p2 != nullptr);
+  }
+
+  // Check that we SFINAE-away for invalid arguments
+  {
+    struct T { };
+    static_assert( CanMakeShared<T[8]>);
+    static_assert( CanMakeShared<T[8], T>);
+    static_assert(!CanMakeShared<T[8], T, int>); // too many arguments
+  }
+
+  return 0;
+}

diff  --git a/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/make_shared.array.unbounded.pass.cpp b/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/make_shared.array.unbounded.pass.cpp
new file mode 100644
index 0000000000000..44365b0bf4f6e
--- /dev/null
+++ b/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/make_shared.array.unbounded.pass.cpp
@@ -0,0 +1,431 @@
+//===----------------------------------------------------------------------===//
+//
+// 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, c++11, c++14, c++17
+
+// Aligned deallocation isn't provided before macOS 10.14, and some tests for overaligned types
+// below require that feature.
+// XFAIL: use_system_cxx_lib && target={{.+}}-apple-macosx{{10.9|10.10|10.11|10.12|10.13}}
+
+// <memory>
+
+// shared_ptr
+
+// template<class T> shared_ptr<T> make_shared(size_t N); // T is U[]
+//
+// template<class T>
+// shared_ptr<T> make_shared(size_t N, const remove_extent_t<T>& u); // T is U[]
+
+// Ignore error about requesting a large alignment not being ABI compatible with older AIX systems.
+#ifdef _AIX
+# pragma clang diagnostic ignored "-Waix-compat"
+#endif
+
+#include <cassert>
+#include <concepts>
+#include <cstdint> // std::uintptr_t
+#include <memory>
+#include <utility>
+
+#include "operator_hijacker.h"
+#include "test_macros.h"
+#include "types.h"
+
+template <class T, class ...Args>
+concept CanMakeShared = requires(Args&& ...args) {
+  { std::make_shared<T>(std::forward<Args>(args)...) } -> std::same_as<std::shared_ptr<T>>;
+};
+
+int main(int, char**) {
+  // Check behavior for a zero-sized array
+  {
+    // Without passing an initial value
+    {
+      using Array = int[];
+      std::shared_ptr<Array> ptr = std::make_shared<Array>(0);
+      assert(ptr != nullptr);
+    }
+
+    // Passing an initial value
+    {
+      using Array = int[];
+      std::shared_ptr<Array> ptr = std::make_shared<Array>(0, 42);
+      assert(ptr != nullptr);
+    }
+  }
+
+  // Check behavior for a 1-sized array
+  {
+    // Without passing an initial value
+    {
+      using Array = int[];
+      std::shared_ptr<Array> ptr = std::make_shared<Array>(1);
+      assert(ptr != nullptr);
+      assert(ptr[0] == 0);
+    }
+
+    // Passing an initial value
+    {
+      using Array = int[];
+      std::shared_ptr<Array> ptr = std::make_shared<Array>(1, 42);
+      assert(ptr != nullptr);
+      assert(ptr[0] == 42);
+    }
+  }
+
+  // Make sure we initialize elements correctly
+  {
+    // Without passing an initial value
+    {
+      using Array = int[];
+      std::shared_ptr<Array> ptr = std::make_shared<Array>(8);
+      for (unsigned i = 0; i < 8; ++i) {
+        assert(ptr[i] == 0);
+      }
+    }
+    {
+      using Array = int[][3];
+      std::shared_ptr<Array> ptr = std::make_shared<Array>(8);
+      for (unsigned i = 0; i < 8; ++i) {
+        assert(ptr[i][0] == 0);
+        assert(ptr[i][1] == 0);
+        assert(ptr[i][2] == 0);
+      }
+    }
+    {
+      using Array = int[][3][2];
+      std::shared_ptr<Array> ptr = std::make_shared<Array>(8);
+      for (unsigned i = 0; i < 8; ++i) {
+        assert(ptr[i][0][0] == 0);
+        assert(ptr[i][0][1] == 0);
+        assert(ptr[i][1][0] == 0);
+        assert(ptr[i][1][1] == 0);
+        assert(ptr[i][2][0] == 0);
+        assert(ptr[i][2][1] == 0);
+      }
+    }
+
+    // Passing an initial value
+    {
+      using Array = int[];
+      int init = 42;
+      std::shared_ptr<Array> ptr = std::make_shared<Array>(8, init);
+      for (unsigned i = 0; i < 8; ++i) {
+        assert(ptr[i] == init);
+      }
+    }
+    {
+      using Array = int[][3];
+      int init[3] = {42, 43, 44};
+      std::shared_ptr<Array> ptr = std::make_shared<Array>(8, init);
+      for (unsigned i = 0; i < 8; ++i) {
+        assert(ptr[i][0] == 42);
+        assert(ptr[i][1] == 43);
+        assert(ptr[i][2] == 44);
+      }
+    }
+    {
+      using Array = int[][3][2];
+      int init[3][2] = {{31, 32}, {41, 42}, {51, 52}};
+      std::shared_ptr<Array> ptr = std::make_shared<Array>(8, init);
+      for (unsigned i = 0; i < 8; ++i) {
+        assert(ptr[i][0][0] == 31);
+        assert(ptr[i][0][1] == 32);
+        assert(ptr[i][1][0] == 41);
+        assert(ptr[i][1][1] == 42);
+        assert(ptr[i][2][0] == 51);
+        assert(ptr[i][2][1] == 52);
+      }
+    }
+  }
+
+  // Make sure array elements are destroyed in reverse order
+  {
+    // Without passing an initial value
+    {
+      using Array = DestroyInReverseOrder[];
+      DestroyInReverseOrder::reset();
+      {
+        std::shared_ptr<Array> ptr = std::make_shared<Array>(8);
+        assert(DestroyInReverseOrder::alive() == 8);
+      }
+      assert(DestroyInReverseOrder::alive() == 0);
+    }
+    {
+      using Array = DestroyInReverseOrder[][3];
+      DestroyInReverseOrder::reset();
+      {
+        std::shared_ptr<Array> ptr = std::make_shared<Array>(8);
+        assert(DestroyInReverseOrder::alive() == 8 * 3);
+      }
+      assert(DestroyInReverseOrder::alive() == 0);
+    }
+    {
+      using Array = DestroyInReverseOrder[][3][2];
+      DestroyInReverseOrder::reset();
+      {
+        std::shared_ptr<Array> ptr = std::make_shared<Array>(8);
+        assert(DestroyInReverseOrder::alive() == 8 * 3 * 2);
+      }
+      assert(DestroyInReverseOrder::alive() == 0);
+    }
+
+    // Passing an initial value
+    {
+      using Array = DestroyInReverseOrder[];
+      int count = 0;
+      DestroyInReverseOrder init(&count);
+      int init_count = 1;
+      {
+        std::shared_ptr<Array> ptr = std::make_shared<Array>(8, init);
+        assert(count == 8 + init_count);
+      }
+      assert(count == init_count);
+    }
+    {
+      using Array = DestroyInReverseOrder[][3];
+      int count = 0;
+      DestroyInReverseOrder init[3] = {&count, &count, &count};
+      int init_count = 3;
+      {
+        std::shared_ptr<Array> ptr = std::make_shared<Array>(8, init);
+        assert(count == 8 * 3 + init_count);
+      }
+      assert(count == init_count);
+    }
+    {
+      using Array = DestroyInReverseOrder[][3][2];
+      int count = 0;
+      DestroyInReverseOrder init[3][2] = {{&count, &count}, {&count, &count}, {&count, &count}};
+      int init_count = 3 * 2;
+      {
+        std::shared_ptr<Array> ptr = std::make_shared<Array>(8, init);
+        assert(count == 8 * 3 * 2 + init_count);
+      }
+      assert(count == init_count);
+    }
+  }
+
+  // Count the number of copies being made
+  {
+    // Without passing an initial value
+    {
+      using Array = CountCopies[];
+      CountCopies::reset();
+      std::shared_ptr<Array> ptr = std::make_shared<Array>(8);
+      assert(CountCopies::copies() == 0);
+    }
+    {
+      using Array = CountCopies[][3];
+      CountCopies::reset();
+      std::shared_ptr<Array> ptr = std::make_shared<Array>(8);
+      assert(CountCopies::copies() == 0);
+    }
+    {
+      using Array = CountCopies[][3][2];
+      CountCopies::reset();
+      std::shared_ptr<Array> ptr = std::make_shared<Array>(8);
+      assert(CountCopies::copies() == 0);
+    }
+
+    // Passing an initial value
+    {
+      using Array = CountCopies[];
+      int copies = 0;
+      CountCopies init(&copies);
+      std::shared_ptr<Array> ptr = std::make_shared<Array>(8, init);
+      assert(copies == 8);
+    }
+    {
+      using Array = CountCopies[][3];
+      int copies = 0;
+      CountCopies init[3] = {&copies, &copies, &copies};
+      std::shared_ptr<Array> ptr = std::make_shared<Array>(8, init);
+      assert(copies == 8 * 3);
+    }
+    {
+      using Array = CountCopies[][3][2];
+      int copies = 0;
+      CountCopies init[3][2] = {{&copies, &copies}, {&copies, &copies}, {&copies, &copies}};
+      std::shared_ptr<Array> ptr = std::make_shared<Array>(8, init);
+      assert(copies == 8 * 3 * 2);
+    }
+  }
+
+  // Make sure array elements are aligned properly when the array contains an overaligned type.
+  //
+  // Here, we don't need to test both the with-initial-value and without-initial-value code paths,
+  // since we're just checking the alignment and both are going to use the same code path unless
+  // the implementation is completely crazy.
+  {
+    auto check_alignment = []<class T> {
+      {
+        using Array = T[];
+        std::shared_ptr ptr = std::make_shared<Array>(8);
+        for (int i = 0; i < 8; ++i) {
+          T* p = std::addressof(ptr[i]);
+          assert(reinterpret_cast<std::uintptr_t>(p) % alignof(T) == 0);
+        }
+      }
+      {
+        using Array = T[][3];
+        std::shared_ptr ptr = std::make_shared<Array>(8);
+        for (int i = 0; i < 8; ++i) {
+          for (int j = 0; j < 3; ++j) {
+            T* p = std::addressof(ptr[i][j]);
+            assert(reinterpret_cast<std::uintptr_t>(p) % alignof(T) == 0);
+          }
+        }
+      }
+      {
+        using Array = T[][3][2];
+        std::shared_ptr ptr = std::make_shared<Array>(8);
+        for (int i = 0; i < 8; ++i) {
+          for (int j = 0; j < 3; ++j) {
+            for (int k = 0; k < 2; ++k) {
+              T* p = std::addressof(ptr[i][j][k]);
+              assert(reinterpret_cast<std::uintptr_t>(p) % alignof(T) == 0);
+            }
+          }
+        }
+      }
+    };
+
+    struct Empty { };
+    check_alignment.operator()<Empty>();
+    check_alignment.operator()<OverAligned>();
+    check_alignment.operator()<MaxAligned>();
+
+    // test non corner cases as well while we're at it
+    struct Foo { int i; char c; };
+    check_alignment.operator()<int>();
+    check_alignment.operator()<Foo>();
+  }
+
+  // Make sure that we destroy all the elements constructed so far when an exception
+  // is thrown. Also make sure that we do it in reverse order of construction.
+#ifndef TEST_HAS_NO_EXCEPTIONS
+  {
+    struct Sentinel : ThrowOnConstruction, DestroyInReverseOrder { };
+
+    // Without passing an initial value
+    {
+      using Array = Sentinel[];
+      for (int i = 0; i < 8; ++i) {
+        ThrowOnConstruction::throw_after(i);
+        DestroyInReverseOrder::reset();
+        try {
+          std::shared_ptr<Array> ptr = std::make_shared<Array>(8);
+          assert(false);
+        } catch (ThrowOnConstruction::exception const&) {
+          assert(DestroyInReverseOrder::alive() == 0);
+        }
+      }
+    }
+    {
+      using Array = Sentinel[][3];
+      for (int i = 0; i < 8 * 3; ++i) {
+        ThrowOnConstruction::throw_after(i);
+        DestroyInReverseOrder::reset();
+        try {
+          std::shared_ptr<Array> ptr = std::make_shared<Array>(8);
+          assert(false);
+        } catch (ThrowOnConstruction::exception const&) {
+          assert(DestroyInReverseOrder::alive() == 0);
+        }
+      }
+    }
+    {
+      using Array = Sentinel[][3][2];
+      for (int i = 0; i < 8 * 3 * 2; ++i) {
+        ThrowOnConstruction::throw_after(i);
+        DestroyInReverseOrder::reset();
+        try {
+          std::shared_ptr<Array> ptr = std::make_shared<Array>(8);
+          assert(false);
+        } catch (ThrowOnConstruction::exception const&) {
+          assert(DestroyInReverseOrder::alive() == 0);
+        }
+      }
+    }
+
+    // Passing an initial value
+    {
+      using Array = Sentinel[];
+      for (int i = 0; i < 8; ++i) {
+        DestroyInReverseOrder::reset();
+        ThrowOnConstruction::reset();
+        Sentinel init;
+        ThrowOnConstruction::throw_after(i);
+        try {
+          std::shared_ptr<Array> ptr = std::make_shared<Array>(8, init);
+          assert(false);
+        } catch (ThrowOnConstruction::exception const&) {
+          assert(DestroyInReverseOrder::alive() == 1);
+        }
+      }
+    }
+    {
+      using Array = Sentinel[][3];
+      for (int i = 0; i < 8 * 3; ++i) {
+        DestroyInReverseOrder::reset();
+        ThrowOnConstruction::reset();
+        Sentinel init[3] = {};
+        ThrowOnConstruction::throw_after(i);
+        try {
+          std::shared_ptr<Array> ptr = std::make_shared<Array>(8, init);
+          assert(false);
+        } catch (ThrowOnConstruction::exception const&) {
+          assert(DestroyInReverseOrder::alive() == 3);
+        }
+      }
+    }
+    {
+      using Array = Sentinel[][3][2];
+      for (int i = 0; i < 8 * 3 * 2; ++i) {
+        DestroyInReverseOrder::reset();
+        ThrowOnConstruction::reset();
+        Sentinel init[3][2] = {};
+        ThrowOnConstruction::throw_after(i);
+        try {
+          std::shared_ptr<Array> ptr = std::make_shared<Array>(8, init);
+          assert(false);
+        } catch (ThrowOnConstruction::exception const&) {
+          assert(DestroyInReverseOrder::alive() == 3 * 2);
+        }
+      }
+    }
+  }
+#endif // TEST_HAS_NO_EXCEPTIONS
+
+  // Make sure the version without an initialization argument works even for non-movable types
+  {
+    using Array = NonMovable[][3];
+    std::shared_ptr<Array> ptr = std::make_shared<Array>(8);
+    (void)ptr;
+  }
+
+    // Make sure std::make_shared handles badly-behaved types properly
+    {
+      using Array = operator_hijacker[];
+      std::shared_ptr<Array> p1 = std::make_shared<Array>(3);
+      std::shared_ptr<Array> p2 = std::make_shared<Array>(3, operator_hijacker());
+      assert(p1 != nullptr);
+      assert(p2 != nullptr);
+    }
+
+  // Check that we SFINAE-away for invalid arguments
+  {
+    struct T { };
+    static_assert( CanMakeShared<T[], std::size_t>);
+    static_assert( CanMakeShared<T[], std::size_t, T>);
+    static_assert(!CanMakeShared<T[], std::size_t, T, int>); // too many arguments
+  }
+
+  return 0;
+}

diff  --git a/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/make_shared.pass.cpp b/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/make_shared.pass.cpp
index e835b6263efac..23a904f8ae11f 100644
--- a/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/make_shared.pass.cpp
+++ b/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/make_shared.pass.cpp
@@ -10,13 +10,15 @@
 
 // shared_ptr
 
-// template<class T, class... Args> shared_ptr<T> make_shared(Args&&... args);
+// template<class T, class... Args>
+// shared_ptr<T> make_shared(Args&&... args); // T is not an array
 
 #include <memory>
 #include <cassert>
 
-#include "test_macros.h"
 #include "count_new.h"
+#include "operator_hijacker.h"
+#include "test_macros.h"
 
 struct A
 {
@@ -122,6 +124,14 @@ int main(int, char**)
 #endif
     assert(A::count == 0);
 
+    // Make sure std::make_shared handles badly-behaved types properly
+    {
+      std::shared_ptr<operator_hijacker> p1 = std::make_shared<operator_hijacker>();
+      std::shared_ptr<operator_hijacker> p2 = std::make_shared<operator_hijacker>(operator_hijacker());
+      assert(p1 != nullptr);
+      assert(p2 != nullptr);
+    }
+
     test<bool>(true);
     test<int>(3);
     test<double>(5.0);

diff  --git a/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/make_shared.private.compile.fail.cpp b/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/make_shared.private.compile.fail.cpp
index e17d7424be444..fec160a9854db 100644
--- a/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/make_shared.private.compile.fail.cpp
+++ b/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/make_shared.private.compile.fail.cpp
@@ -10,7 +10,8 @@
 
 // shared_ptr
 
-// template<class T, class... Args> shared_ptr<T> make_shared(Args&&... args);
+// template<class T, class... Args>
+// shared_ptr<T> make_shared(Args&&... args);
 
 #include <memory>
 #include <cassert>

diff  --git a/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/types.h b/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/types.h
new file mode 100644
index 0000000000000..7cd65231404fa
--- /dev/null
+++ b/libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/types.h
@@ -0,0 +1,99 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef TEST_STD_UTILITIES_MEMORY_UTIL_SMARTPTR_SHARED_CREATE_TYPES_H
+#define TEST_STD_UTILITIES_MEMORY_UTIL_SMARTPTR_SHARED_CREATE_TYPES_H
+
+#include <cassert>
+#include <cstddef>
+
+#include "test_macros.h"
+
+struct DestroyInReverseOrder {
+  static void reset() { global_count_ = 0; }
+  static int alive() { return global_count_; }
+
+  DestroyInReverseOrder()
+    : DestroyInReverseOrder(&global_count_)
+  { }
+
+  constexpr DestroyInReverseOrder(int* count)
+    : count_(count), value_(*count)
+  { ++*count_; }
+
+  constexpr DestroyInReverseOrder(DestroyInReverseOrder const& other)
+    : count_(other.count_), value_(*other.count_)
+  { ++*count_; }
+
+  constexpr int value() const { return value_; }
+
+  // Ensure that we destroy these objects in the reverse order as they were created.
+  constexpr ~DestroyInReverseOrder() {
+    --*count_;
+    assert(*count_ == value_);
+  }
+
+private:
+  int* count_;
+  int value_;
+  static int global_count_;
+};
+
+int DestroyInReverseOrder::global_count_ = 0;
+
+struct NonMovable {
+  NonMovable() = default;
+  NonMovable(NonMovable&&) = delete;
+};
+
+struct CountCopies {
+  static void reset() { global_count_ = 0; }
+  static int copies() { return global_count_; }
+
+  constexpr CountCopies() : copies_(&global_count_) { }
+  constexpr CountCopies(int* counter) : copies_(counter) { }
+  constexpr CountCopies(CountCopies const& other) : copies_(other.copies_) { ++*copies_; }
+
+private:
+  int* copies_;
+  static int global_count_;
+};
+
+int CountCopies::global_count_ = 0;
+
+struct alignas(alignof(std::max_align_t) * 2) OverAligned { };
+
+struct MaxAligned {
+  std::max_align_t foo;
+};
+
+#ifndef TEST_HAS_NO_EXCEPTIONS
+struct ThrowOnConstruction {
+  struct exception : std::exception { };
+
+  ThrowOnConstruction() { on_construct(); }
+  ThrowOnConstruction(ThrowOnConstruction const&) { on_construct(); }
+
+  static void reset() { throw_after_ = -1; }
+  static void throw_after(int n) { throw_after_ = n; }
+
+private:
+  static int throw_after_;
+  void on_construct() {
+    if (throw_after_ == 0)
+      throw exception{};
+
+    if (throw_after_ != -1)
+      --throw_after_;
+  }
+};
+
+int ThrowOnConstruction::throw_after_ = -1;
+#endif // TEST_HAS_NO_EXCEPTIONS
+
+#endif // TEST_STD_UTILITIES_MEMORY_UTIL_SMARTPTR_SHARED_CREATE_TYPES_H

diff  --git a/libcxx/utils/generate_feature_test_macro_components.py b/libcxx/utils/generate_feature_test_macro_components.py
index 191d0ee01cc9e..61cee0dd2d332 100755
--- a/libcxx/utils/generate_feature_test_macro_components.py
+++ b/libcxx/utils/generate_feature_test_macro_components.py
@@ -608,7 +608,7 @@ def add_version_header(tc):
     "libcxx_guard": "!defined(_LIBCPP_HAS_NO_THREADS) && !defined(_LIBCPP_AVAILABILITY_DISABLE_FTM___cpp_lib_shared_mutex)",
   }, {
     "name": "__cpp_lib_shared_ptr_arrays",
-    "values": { "c++17": 201611 },
+    "values": { "c++17": 201611, "c++20": 201707 },
     "headers": ["memory"],
   }, {
     "name": "__cpp_lib_shared_ptr_weak_type",


        


More information about the libcxx-commits mailing list