This is an archive of the discontinued LLVM Phabricator instance.

Support arrays in make_shared and allocate_shared (P0674R1)
ClosedPublic

Authored by ldionne on May 29 2019, 7:25 PM.

Details

Summary

This patch implements P0674R1. It might be a bit rough around the edges, but I wanted to get it out so it can be reviewed and I can make changes to it as I straighten out the last few parts.

The main changes are how __shared_ptr_pointer deallocates itself and (obviously) the added overloads.

Thanks for the help @mclow.lists and @EricWF

Diff Detail

Event Timeline

There are a very large number of changes, so older changes are hidden. Show Older Changes

Thanks a lot for the patch. I've left a lot of comments with suggestions. Feel free to take or leave my suggestions: it's mostly that I had myself started some work on this, so I felt like I should at least put it somewhere instead of just trashing it. Keep what you want and leave the rest.

libcxx/include/memory
516–539
template<class T, class... Args>
    shared_ptr<T> make_shared(Args&&... args); // T is not array
template<class T, class A, class... Args>
    shared_ptr<T> allocate_shared(const A& a, Args&&... args); // T is not 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)
2686

This would need to use Allocator-destruction. In the patch I had started working on, I had this:

// Given an allocator and a range delimited by [first, last), 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.
//
// The allocator does not need to already be bound to the element type of the
// range -- it is rebound appropriately by this function.
template<class _Alloc, class _BidirectionalIterator, class ..._Args>
_LIBCPP_HIDE_FROM_ABI
void __allocator_destroy_multidimensional(_Alloc const& __alloc, _BidirectionalIterator __first, _BidirectionalIterator __last) noexcept {
    using _ValueType = typename iterator_traits<_BidirectionalIterator>::value_type;
    using _ValueAlloc = typename __allocator_traits_rebind<_Alloc, _ValueType>::type;

    if (__first == __last) {
        return;
    }
    _ValueAlloc __value_alloc(__alloc);
    auto __destroy = [&](_BidirectionalIterator __it) {
        if constexpr (is_array_v<_ValueType>) {
            __allocator_destroy_multidimensional(__value_alloc, _VSTD::begin(*__it), _VSTD::end(*__it));
        } else {
            allocator_traits<_ValueAlloc>::destroy(__value_alloc, _VSTD::addressof(*__it));
        }
    };
    for (--__last; __last != __first; --__last) {
        __destroy(__last);
    }
    // handle __first, which is skipped by the loop above
    __destroy(*__first);
}

If you like it, feel free to add it to your patch. I had placed it in __memory/utilities.h since it was meant to be a general purpose helper function. Also, it's not really tested :-)

2703

I would call this something like __shared_ptr_unbounded_array_holder or similar, to make it clear that this handles unbounded arrays only (see my suggestion to have two separate control blocks below).

2706–2708

Why not only have a member of type _Info? Or even better, just have

[[no_unique_address]] _Alloc __alloc_;
size_t __size_;
union { // make sure the default constructor doesn't initialize __data_
  _Tp __data_[1]; // flexible array member trick
};

inline in the control block definition?

2729–2741

Those need to use Allocator construction. I would suggest a more generic algorithm like:

// Given an allocator and a range delimited by [first, first + n), initializes
// the range [first, first + n) from left to right using the allocator's construct.
//
// Each element is initialized using allocator_traits construction, and the given
// arguments are passed to construction. The arguments are not perfect-forwarded
// because that could set us up for double-moves. If the elements of the range
// are themselves C-style arrays, they are recursively constructed in the same
// manner.
//
// If an exception is thrown, the initialized elements are destroyed in reverse
// order of initialization using allocator_traits destruction. This requires that
// the iterator type be a BidirectionalIterator.
//
// The allocator does not need to already be bound to the element type of the
// range -- it is rebound appropriately by this function.
template<class _Alloc, class _BidirectionalIterator, class ..._Args>
_LIBCPP_HIDE_FROM_ABI
void __uninitialized_allocator_construct_multidimensional_n(_Alloc const& __alloc, _BidirectionalIterator __first, size_t __n, _Args&& ...__args) {
    using _ValueType = typename iterator_traits<_BidirectionalIterator>::value_type;
    using _ValueAlloc = typename __allocator_traits_rebind<_Alloc, _ValueType>::type;
    auto __idx = __first;
    _ValueAlloc __value_alloc(__alloc);

#ifndef _LIBCPP_NO_EXCEPTIONS
    try {
#endif
        for (; __n > 0; (void)++__idx, --__n) {
            if constexpr (is_array_v<_ValueType>) {
                size_t __size = _VSTD::distance(_VSTD::begin(*__idx), _VSTD::end(*__idx));
                __uninitialized_allocator_construct_multidimensional_n(__value_alloc, _VSTD::begin(*__idx), __size, __args...);
            } else {
                allocator_traits<_ValueAlloc>::construct(__value_alloc, _VSTD::addressof(*__idx), __args...);
            }
        }
#ifndef _LIBCPP_NO_EXCEPTIONS
    } catch (...) {
        _VSTD::__allocator_destroy_multidimensional(__first, __idx);
        throw;
    }
#endif
}
2747

You need to destroy the elements in reverse order of construction. If you use the algorithm I suggested above, it should suffice to say:

_VSTD:: __allocator_destroy_multidimensional(__get_elem_begin(), __get_elem_end());

or something along those lines.

3558

You seem to be missing make_shared for arrays.

I had started working on a similar patch, and this is what I had. Feel free to cherry-pick stuff from it if it's useful:

#if _LIBCPP_STD_VER > 17
template<class _Tp, class _Alloc, class ..._Args>
_LIBCPP_HIDE_FROM_ABI
shared_ptr<_Tp> __allocate_shared_unbounded_array_impl(const _Alloc& __a, size_t __n, _Args const& ...__args)
{
    ...
}

template<class _Tp, class _Alloc, class ..._Args>
_LIBCPP_HIDE_FROM_ABI
shared_ptr<_Tp> __allocate_shared_bounded_array_impl(const _Alloc& __a, size_t __n, _Args const& ...__args)
{
    ...
}

template<class _Tp, class _Alloc, class = _EnableIf<is_unbounded_array<_Tp>::value> >
_LIBCPP_HIDE_FROM_ABI
shared_ptr<_Tp> allocate_shared(const _Alloc& __a, size_t __n)
{
    return _VSTD::__allocate_shared_unbounded_array_impl<_Tp>(__a, __n);
}

template<class _Tp, class = _EnableIf<is_unbounded_array<_Tp>::value> >
_LIBCPP_HIDE_FROM_ABI
shared_ptr<_Tp> make_shared(size_t __n)
{
    return _VSTD::allocate_shared<_Tp>(allocator<_Tp>(), __n);
}


template<class _Tp, class _Alloc, class = _EnableIf<is_bounded_array<_Tp>::value> >
_LIBCPP_HIDE_FROM_ABI
shared_ptr<_Tp> allocate_shared(const _Alloc& __a)
{
    return _VSTD::__allocate_shared_bounded_array_impl<_Tp>(__a);
}

template<class _Tp, class = _EnableIf<is_bounded_array<_Tp>::value> >
_LIBCPP_HIDE_FROM_ABI
shared_ptr<_Tp> make_shared()
{
    return _VSTD::allocate_shared<_Tp>(allocator<_Tp>());
}


template<class _Tp, class _Alloc, class = _EnableIf<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>& __t)
{
    return _VSTD::__allocate_shared_unbounded_array_impl<_Tp>(__a, __n, __t);
}

template<class _Tp, class = _EnableIf<is_unbounded_array<_Tp>::value> >
_LIBCPP_HIDE_FROM_ABI
shared_ptr<_Tp> make_shared(size_t __n, const remove_extent_t<_Tp>& __t)
{
    return _VSTD::allocate_shared<_Tp>(allocator<_Tp>(), __n, __t);
}


template<class _Tp, class _Alloc, class = _EnableIf<is_bounded_array<_Tp>::value> >
_LIBCPP_HIDE_FROM_ABI
shared_ptr<_Tp> allocate_shared(const _Alloc& __a, const remove_extent_t<_Tp>& __t)
{
    return _VSTD::__allocate_shared_bounded_array_impl<_Tp>(__a, __t);
}

template<class _Tp, class = _EnableIf<is_bounded_array<_Tp>::value> >
_LIBCPP_HIDE_FROM_ABI
shared_ptr<_Tp> make_shared(const remove_extent_t<_Tp>& __t)
{
    return _VSTD::allocate_shared<_Tp>(allocator<_Tp>(), __t);
}
#endif // > C++17
3586

I think it would be useful to have a separate control block for arrays with a known size. That way, we can keep the knowledge of the bounds static.

libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/create_array.pass.cpp
10

I would like to see more tests for:

  1. Multidimensional construction and destruction, and
  2. Making sure that we construct and destroy elements in the correct order (both in normal circumstances but also when an exception is thrown while we're initializing the array).

I know this is complicated to test, but I think it is important. It may make sense to split tests into multiple files to make it easier to understand. Your call.

libcxx/utils/generate_feature_test_macro_components.py
296–297

We need to add "c++20": 201707 and leave the c++17 entry as-is.

ldionne requested changes to this revision.Jan 8 2021, 12:39 PM
This revision now requires changes to proceed.Jan 8 2021, 12:39 PM

Thanks for those suggestions, Louis! Going to add some more tests over the next few days.

libcxx/include/memory
2686

Good point. Added these two functions as members with a few tweaks.

I had placed it in __memory/utilities.h since it was meant to be a general purpose helper function.

I can't think of another place we'd want to use these functions, so I'm going to keep them as more specific member functions. It might make sense to break all the shared_ptr stuff out into its own header at some point, though.

3558

You seem to be missing make_shared for arrays.

Aha! I actually took this from your earlier patch: make_shared can be implemented in terms of allocate_shared, so it can just be one function template with no constraints that calls out to all the various allocate_shared overloads.

I think the way I have this now composes really nicely, so unless you see a reason to use something else, I'm going to keep this.

3586

I considered this, but I don't think it will actually give us any performance benefits, because we have to dynamically allocate it using the provided allocator either way.

libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/create_array.pass.cpp
10

Improved this a little, but I'm going to upload more tests shortly.

Fix based on review.

zoecarver added inline comments.Jan 10 2021, 10:57 PM
libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/create_array.pass.cpp
48

This should help make sure we destroy things in the correct order.

  • Add more multidimensional throwing tests.
ldionne requested changes to this revision.Jan 11 2021, 2:37 PM
ldionne added inline comments.
libcxx/include/memory
2686

Well, one place where we might want to use it is in the statically-sized array version of the block (see below why I think we should have one).

2690

I don't understand the benefit of passing false_type here when we want to default construct, instead of just relying on variadics and always passing down some args... to __construct_elements (and hence to the object's constructors themselves). I think it would be simpler to do that.

Also, I think it makes sense to mark this constructor as explicit, and it needs to be marked with _LIBCPP_HIDE_FROM_ABI.

2710

I see you're using an int here, but what if construction fails at a very large index, the size_t to int narrowing could be problematic. Is there a reason why you're using int?

2753–2758

The way I understand this is that we should actually always call __destroy_elements(__first, __i + 1); to destroy the half-open range [__first, __first + __i + 1) (which includes the last object that was fully constructed, but not the object that was partially constructed). However, when the current element (__first + __i) is an array, it has already been destroyed before we caught the exception, so we can skip it and just destroy [__first, __first + __i).

This implementation makes that intent clearest IMO:

// <explanation>
if constexpr (is_array_v<_ValueType>)
    __destroy_elements(__first, __i);
else
    __destroy_elements(__first, __i + 1);

Do you agree?

2775

We generally use a trailing underscore (like __alloc_) to make it clear it's a member.

3558

Aha! I actually took this from your earlier patch: make_shared can be implemented in terms of allocate_shared, so it can just be one function template with no constraints that calls out to all the various allocate_shared overloads.

I don't think that works, actually. For example, std::make_shared<int[3]>(1, 2, 3) should SFINAE away, but with your implementation that would be a hard error. Can you add a test and see whether that's correct? If so, it means we need to conform to the declarations laid out in the standard (there's often reasons why the standard lists overloads explicitly).

3586

We save on storing the size itself in the control block, and the size is known statically, which could lead to different code being generated (e.g for small arrays). I think it's fairly important to do it, since it isn't very costly for us.

The representation for statically-sized arrays can be:

[[no_unique_address]] _Alloc __alloc_;
union { // make sure the default constructor doesn't initialize __data_
    _Tp __data_[_Np];
};
This revision now requires changes to proceed.Jan 11 2021, 2:37 PM
zoecarver added inline comments.Jan 12 2021, 12:58 PM
libcxx/include/memory
2710

I'm using int because __n will become -1 in the for loop. I could keep it unsigned and just compare __n < "size", but that seems a bit more confusing and would require a second variable. So, I'll make __n a long.

zoecarver updated this revision to Diff 316209.EditedJan 12 2021, 1:06 PM
  • Add static size control block.
  • Fix based on review.
Harbormaster completed remote builds in B84906: Diff 316210.
zoecarver added inline comments.Jan 12 2021, 1:08 PM
libcxx/include/memory
2794

Actually, because we're inheriting this base class, the [[no_unique_address]] isn't needed.

zoecarver added inline comments.Jan 12 2021, 2:08 PM
libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/create_array.pass.cpp
10

Oops, forgot the SFINAE test. I'll add that now.

zoecarver updated this revision to Diff 317693.Jan 19 2021, 2:15 PM
  • Add SFINAE tests.
  • Remove "no_unique_address".
libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/create_array.pass.cpp
10

I know this is complicated to test, but I think it is important. It may make sense to split tests into multiple files to make it easier to understand. Your call.

I personally don't mind the mono-test and it makes things easier to find, IMHO. But I also don't have a strong oppinion at all, would you rather me split this test up?

@ldionne this is ready for re-review when you're ready to re-review it :)

ldionne requested changes to this revision.Feb 9 2021, 8:42 AM

I don't think we gain much from having a single __shared_ptr_array_base common base. Actually, this forces us to have additional virtual methods in both subclasses. Instead, we should just have two different holder classes, one for statically-sized arrays, and one for dynamic arrays.

libcxx/docs/Cxx2aStatusPaperStatus.csv
3

This should be 13.0 now

This revision now requires changes to proceed.Feb 9 2021, 8:42 AM
zoecarver updated this revision to Diff 322476.Feb 9 2021, 12:54 PM
  • Bump LLVM release version.
  • Remove __shared_ptr_array_base.
ldionne requested changes to this revision.Feb 9 2021, 4:09 PM

Thanks a lot for all your work on this patch. This is a tough one to get right. I know I'm requesting a lot, but I think it's important to get it right and we'll be even happier when we finally ship this :-).

libcxx/include/memory
2730

We could also use __first[i] here, I think it's clearer?

2752

I haven't looked for that specifically, but can you confirm that you are testing these two code paths?

2759

Can we name them xx_bounded and xx_unbounded instead? It seems to fit better?

2764–2767

Currently, this function is only used in a single place (which is inside the implementation of this class). Furthermore, we duplicate that logic when we compute the size to allocate down there in make_shared_xx.

One possibility would be to move this to a free function like

template <class T>
size_t __get_shared_ptr_unbounded_block_size(size_t __n) {
    // ...
}

Then we could reuse it from both places. Do you agree that would be an improvement?

2787

Don't we need to run the destructor of __alloc_ here?

2793

Is this legal C++? I thought that was a compiler extension and arrays had to have at least one element?

2803

Since this is only used in a single place in the body of __shared_ptr_array_bound, I think we could just inline the function at its single point of use and remove it. WDYT?

2822

Same comment regarding destruction of __alloc_.

3575

I *think* this can cause us to under allocate, for example if there needs to be padding between __count_ and __data_:

[[no_unique_address]] _Alloc __alloc_;
size_t __count_;
// padding possible here - would that be counted in sizeof(_ControlBlock)?
_DataBlock __data_[0];

This concern is moot if I'm right about _DataBlock __data_[0] being an extension and if we change to _DataBlock __data_[1] instead.

This revision now requires changes to proceed.Feb 9 2021, 4:09 PM
zoecarver marked 6 inline comments as done.Feb 15 2021, 1:41 PM
zoecarver added inline comments.
libcxx/include/memory
2720

Why not pass by value?

2752

Yes, these two code paths are tested. See L324 where we use struct D to throw.

2787

Yep. Added a regression test, too.

2793

Yes, you're right. Fixed. (Supprising that we didn't get a warning, though?)

2803

Agreed. Same with __get_count.

3575

Moot because I fixed __data_[0 -> 1].

However, I thought this was the whole point of using a zero size array at the end of a struct? Otherwise, why not just make an empty struct. So, yes, that padding would be accounted for:

struct UnsizedTail {
  int size;
  alignas(8) char buf[0];
};

// UnsizedTail = {i32, [4 x i8 padding], [0 x i8]
static_assert(sizeof(UnsizedTail) == 8);

That being said... when would there be padding between size_t and an aligned storage member?

zoecarver marked 3 inline comments as done.Feb 15 2021, 1:44 PM
zoecarver added inline comments.
libcxx/include/memory
2720

Whoops. I thought I deleted this comment. This was just a note to myself.

Anyway, I am actually wondering about this. I see _Alloc const& all over the place, but allocators are generally supposed to be small (often empty) types, so why not pass them by value? We're copy constructing the allocator in this function, so it can't be to prevent that.

zoecarver updated this revision to Diff 323831.Feb 15 2021, 1:47 PM
  • Address review comments and add a few more tests.
  • Format test file with clang-format.
ldionne requested changes to this revision.Feb 19 2021, 1:34 PM

This basically LGTM except for the few nitpicks below. We already talked about most of that offline, just adding here so you have a written trace.

libcxx/include/memory
2720

Generally, we take by value (and then move into some internal storage) when we take ownership of an object -- or we add a version that takes that object by rvalue reference. You could do that here and move into __value_alloc if you wanted.

3569

We need to handle the case where __n == 0. Also, we should add a test for it since I'm not seeing anything that says it's not allowed by the API.

libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/create_array.pass.cpp
2

Can we add a test with overaligned types? (like we discussed offline)

10

You can remove c++98 from the Lit features, we don't use it anymore.

48

Can you add a comment mentioning that? It's clever.

This revision now requires changes to proceed.Feb 19 2021, 1:34 PM
zoecarver marked 4 inline comments as done.Feb 21 2021, 12:21 PM
zoecarver added inline comments.
libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/create_array.pass.cpp
48

Done. Thanks :)

zoecarver marked an inline comment as done.
  • Address review comments.
  • Re-generate feature test macros after rebase.
  • Format changes with git-clang-format.
  • Poke buildkit.

OK, that seems to have worked. @ldionne for future reference: the CI was failing to apply this patch because I had two parent revisions and a child revision which were abandoned/already landed. I think the abandoned one was causing problems because it had conflicts. Removing those patches fixed the issue, but it would be nice to automatically skip abandoned parent patches.

  • Fix for -fno-exceptions.
  • Destroy alloc before deallocating this.
  • Cast char to size_t in assert.
zoecarver updated this revision to Diff 327001.Feb 28 2021, 3:13 PM
  • Fix UBSAN and GCC build bots.
zoecarver updated this revision to Diff 327002.Feb 28 2021, 3:14 PM
  • Fix ASAN bots.
  • Format test files.
zoecarver added inline comments.Feb 28 2021, 3:17 PM
libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/create_array.pass.cpp
11

I really don't like disabling this whole test for sanitizer-new-delete. We need this because of the globalMemCounter tests. This is the same thing the unique_ptr tests use.

@ldionne do you know if there's a macro or some other way we can detect if we're running on sanitizer-new-delete? If so, we could just disable the two tests that actually cause this to fail.

ldionne added inline comments.Mar 1 2021, 10:32 AM
libcxx/test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.create/create_array.pass.cpp
11

I don't think there's such a macro. You could split it off into its own test file if you wanted.

zoecarver updated this revision to Diff 327614.Mar 2 2021, 4:04 PM
  • Fix UBSAN bot.
  • Fix macOS bot.
ldionne commandeered this revision.Oct 26 2021, 10:12 AM
ldionne edited reviewers, added: zoecarver; removed: ldionne.

Commandeering to finish this up.

What's the status of this one? Looks stalled, and I understand it's still a blocker for make_{unique,shared}_for_overwrite in D90111

What's the status of this one? Looks stalled, and I understand it's still a blocker for make_{unique,shared}_for_overwrite in D90111

This is rather complicated, but it's basically blocked on D114903.

ldionne updated this revision to Diff 405052.Feb 1 2022, 12:25 PM

Finish this. It turns out this is implementable without the construct_at extension for arrays.

Quuxplusone added inline comments.
libcxx/include/__memory/shared_ptr.h
975–980 ↗(On Diff #405052)

IMO it would be cleaner to eliminate the partial specialization and just write

template <class _Array, class _Alloc>
struct __unbounded_array_control_block : __shared_weak_count
{
    using _Tp = remove_extent_t<_Array>;

Ditto for __bounded_array_control_block below. But admittedly I'd expect this to be marginally more expensive in terms of compile time.

988 ↗(On Diff #405052)

__data_ is an array.

1007–1008 ↗(On Diff #405052)

The condition could be __elements == 0 ? ~~~; the only singularity requiring a special case is at zero. It looks to me as if make_shared<int[]>(0) is intended to be UB: https://eel.is/c++draft/util.smartptr.shared#create-13 — but maybe we want to support it anyway for QoI, or maybe I'm misunderstanding the possibility of returning a pointer to an object of type U[0].

1068 ↗(On Diff #405052)

This isn't ADL-proof — add a regression test!
And then this can just be return __data_; because __data_ is an array, and then this function doesn't need to exist.
Ditto lines 1072, 1077, maybe elsewhere.

1080–1081 ↗(On Diff #405052)

This should be =default...

1084 ↗(On Diff #405052)

...and this should be override (not virtual), IIUC.
(Ditto throughout.)

libcxx/include/__memory/uninitialized_algorithms.h
358–360 ↗(On Diff #405052)

Why does this need to work with non-pointers at all? I'm 99% positive it should take _Tp* instead of an arbitrary _BidirIter. Likewise throughout — there are a lot of these unnecessarily bidirectional-iterator helpers.
Likewise line 363: using _ValueType = _Tp and then there's no more need for _ValueType to exist.

I'm not to familiar with this part of the Standard so didn't do an in-depth review. But I didn't see any oddities.

libcxx/include/__memory/shared_ptr.h
982 ↗(On Diff #405052)

Let's guard against ADL-hijacking. (The required header is already included.)

ldionne updated this revision to Diff 418982.Mar 29 2022, 2:24 PM
ldionne marked 6 inline comments as done.

Address some comments. Updating to poke CI, I'll address remaining comments when I get another minute of free time.

Herald added a project: Restricted Project. · View Herald TranscriptMar 29 2022, 2:24 PM
ldionne added inline comments.Mar 29 2022, 2:24 PM
libcxx/include/__memory/shared_ptr.h
975–980 ↗(On Diff #405052)

I like the fact that we can confirm by inspection that the class is being instantiated on _Tp[] and not _Tp[N]. If I made it the base template, I would probably want to add a static_assert, but then I might as well use the status quo, which is simpler. I'm inclined not to change anything.

988 ↗(On Diff #405052)

I know, but given that we're working with multidimensional arrays, it can become extremely confusing and I think the std::begin adds clarity, so I'm tempted to keep it.

1068 ↗(On Diff #405052)

I don't think this specific one is an ADL issue, because we're taking the address of the array, not the address of the first element (see https://godbolt.org/z/KMjezb6qE). But I'll still simplify this and stop taking the address, which is indeed not necessary. It *is* an issue in places below where we do &__data_[0] and I fixed it there and added tests.

I can't get rid of the method entirely without making __data_ public, which I'd rather not do, so I'll keep it around.

1080–1081 ↗(On Diff #405052)

It turns out this doesn't work because __data_ is sometimes non-trivial, and a non-trivial member in a union results in a the destructor being deleted (which makes sense, because you might need to check which of the non-trivial members of the union needs to be deleted). I added a comment.

ldionne updated this revision to Diff 419151.Mar 30 2022, 8:18 AM
ldionne marked 2 inline comments as done.

Address review comments and fix modular CI. I'll ship this once CI passes.

libcxx/include/__memory/shared_ptr.h
1007–1008 ↗(On Diff #405052)

I actually didn't find anything explicitly prohibiting make_shared<T[]>(0). Furthermore, I had never really thought about it, but based on https://stackoverflow.com/a/1087066/627587 it seems like dynamically-sized arrays of size 0 are definitely a thing and they are supported. Arrays with a static size 0 are not, though.

So yeah, I think make_shared<int[]>(0) & friends definitely need to work, and it must allocate an array with no members. I realized after reading this that I did have tests for this case already. I will add tests to ensure that we reject arrays with a static size of 0.

I reworded the comment and changed the condition to __elements == 0.

libcxx/include/__memory/uninitialized_algorithms.h
358–360 ↗(On Diff #405052)

I don't see the benefit in removing abstraction when the cost of doing it is close to nothing. During development of the algorithm, abstracting to arbitrary iterators was incredibly useful, because at some point I was really confused -- these algorithms handle arrays specially, and arrays decay into pointers. Drawing a clear distinction between the iterator and the value type (which is sometimes an array) was very useful, and in fact there were some subtle bugs in the implementation before I decided to write it this way. So I'm pretty attached to the specific way those are written.

ldionne accepted this revision as: Restricted Project.Mar 30 2022, 8:18 AM
ldionne added inline comments.
libcxx/include/__memory/shared_ptr.h
1007–1008 ↗(On Diff #405052)

Oh yeah, also I did check with other implementations: https://godbolt.org/z/z8nacGhx1

ldionne updated this revision to Diff 419425.Mar 31 2022, 6:25 AM

Investigate CI failure on GCC.

ldionne updated this revision to Diff 419810.Apr 1 2022, 11:08 AM

Fix CI issues.

ldionne updated this revision to Diff 420161.Apr 4 2022, 6:34 AM

Poke CI.

ldionne updated this revision to Diff 420251.Apr 4 2022, 11:15 AM

Try to fix macOS CI.

ldionne updated this revision to Diff 420313.Apr 4 2022, 2:44 PM

Rebase on top of the is_array fix.

ldionne updated this revision to Diff 420496.Apr 5 2022, 7:06 AM

Try to fix CI issues on AIX and GCC.

ldionne updated this revision to Diff 420657.Apr 5 2022, 4:42 PM

Try to fix CI again

Finally! Shipping this now.

This revision was not accepted when it landed; it landed in state Needs Review.Apr 6 2022, 5:43 AM
This revision was automatically updated to reflect the committed changes.