Skip to content

Commit

Permalink
Merge pull request #35 from mclow/relocate-1
Browse files Browse the repository at this point in the history
Remove 'unintialized_relocate', add 'relocate'
  • Loading branch information
cor3ntin authored Oct 5, 2024
2 parents 7859fce + 33f4253 commit 4c4b7b0
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 213 deletions.
6 changes: 4 additions & 2 deletions libcxx/include/__memory/relocate.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#define _LIBCPP___MEMORY_RELOCATE_H

#include <__config>
#include <__functional/operations.h>
#include <__type_traits/is_const.h>
#include <__type_traits/is_trivially_relocatable.h>
#include <cstddef>
Expand All @@ -15,9 +16,10 @@ _LIBCPP_BEGIN_NAMESPACE_STD
#if _LIBCPP_STD_VER >= 20

template <class _Tp>
requires(is_trivially_relocatable_v<_Tp> && !is_const_v<_Tp>)
_LIBCPP_EXPORTED_FROM_ABI _Tp* trivially_relocate(_Tp* __begin, _Tp* __end, _Tp* __new_location) noexcept {
return __builtin_trivially_relocate(__new_location, __begin, sizeof(_Tp) * (__end - __begin));
static_assert(is_trivially_relocatable_v<_Tp> && !is_const_v<_Tp>);
(void) __builtin_trivially_relocate(__new_location, __begin, sizeof(_Tp) * (__end - __begin));
return __new_location + (__end - __begin);
}

#endif
Expand Down
268 changes: 67 additions & 201 deletions libcxx/include/__memory/uninitialized_algorithms.h
Original file line number Diff line number Diff line change
Expand Up @@ -539,6 +539,71 @@ class _AllocatorDestroyRangeReverse {
_Iter& __last_;
};

#if _LIBCPP_STD_VER >= 20

template <class _Tp>
constexpr
_Tp* relocate(_Tp* __begin, _Tp* __end, _Tp* __new_location)
{
static_assert(is_trivially_relocatable_v<_Tp> || is_nothrow_move_constructible_v<_Tp>);
// When relocating to the same location, do nothing.
if (__begin == __new_location || __begin == __end)
return __new_location + (__end - __begin);

// Then, if we are not evaluating at compile time and the type supports
// trivial relocation, delegate to `trivially_relocate`.
if ! consteval {
if constexpr (is_trivially_relocatable_v<_Tp>)
return std::trivially_relocate(__begin, __end, __new_location);
}

if constexpr (is_move_constructible_v<_Tp>) {
// For nontrivial relocatable types or any time during constant
// evaluation, we must detect overlapping ranges and act accordingly,
// which can be done only if the type is movable.
if ! consteval {
// At run time, when there is no overlap, we can, using other Standard
// Library algorithms, do all moves at once followed by all destructions.
if (less{}(__end,__new_location) || less{}(__new_location + (__end-__begin), __begin)) {
_Tp* __result = uninitialized_move(__begin, __end, __new_location);
std::destroy(__begin,__end);
return __result;
}
}

if (less{}(__new_location,__begin) || less{}(__end,__new_location)) {
// Any move to a lower address in memory or any nonoverlapping move can be
// done by iterating forward through the range.
_Tp* __next = __begin;
_Tp* __dest = __new_location;
while (__next != __end) {
::new(__dest) _Tp(std::move(*__next));
__next->~_Tp();
++__next; ++__dest;
}
}
else {
// When moving to a higher address that overlaps, we must go backward through
// the range.
_Tp* __next = __end;
_Tp* __dest = __new_location + (__end - __begin);
while (__next != __begin) {
--__next; --__dest;
::new(__dest) _Tp(std::move(*__next));
__next->~_Tp();
}
}
return __new_location + (__end - __begin);
}

// The only way to reach this point is during constant evaluation where type `T`
// is trivially relocatable but not move constructible. Such cases are not supported
// so we mark this branch as unreachable.
unreachable();
}

#endif

// Copy-construct [__first1, __last1) in [__first2, __first2 + N), where N is distance(__first1, __last1).
//
// The caller has to ensure that __first2 can hold at least N uninitialized elements. If an exception is thrown the
Expand Down Expand Up @@ -627,8 +692,8 @@ __uninitialized_allocator_relocate(_Alloc& __alloc, _Tp* __first, _Tp* __last, _

#if _LIBCPP_STD_VER >= 20
if (!__libcpp_is_constant_evaluated()) {
if constexpr (is_trivially_relocatable_v<_Tp>) {
(void) trivially_relocate(__first, __last, const_cast<__remove_const_t<_Tp>*>(__result));
if constexpr (is_trivially_relocatable_v<_Tp> || is_nothrow_move_constructible_v<_Tp>) {
(void) relocate(__first, __last, const_cast<__remove_const_t<_Tp>*>(__result));
return ;
}
}
Expand Down Expand Up @@ -657,205 +722,6 @@ __uninitialized_allocator_relocate(_Alloc& __alloc, _Tp* __first, _Tp* __last, _
}
}

// Trivial relocation stuff
#if _LIBCPP_STD_VER >= 20

namespace {
// Implementation details --- using an anonymous namespace within this single TU prototype
template <class _Iter>
concept nothrow_input_iterator = input_iterator<_Iter>;

template <class _Iter>
concept nothrow_forward_iterator = forward_iterator<_Iter>;

// We need a concept to distinguish the "not" case at the end --- subsumption
// does not kick in, so could maybe be just a constexpr boolean variable
// template.

template <class _InIter, class _OutIter>
concept relocatable_iterators
= std::same_as<std::iter_value_t<_InIter>, std::iter_value_t<_OutIter>>
and std::is_lvalue_reference_v<std::iter_reference_t<_InIter>>
and std::is_lvalue_reference_v<std::iter_reference_t<_OutIter>>;


template <class _InIter, class _OutIter>
concept no_fail_relocatable_iterators
= relocatable_iterators<_InIter, _OutIter>
and ( std::is_nothrow_move_constructible_v<std::iter_value_t<_InIter>>
or std::is_trivially_relocatable_v<std::iter_value_t<_InIter>>
);
} // unnamed namespace

// Forward looking for asserting preconditions --- tune feature macro as needed
#ifndef __cpp_contracts
# define contract_assert(...) assert(__VA_ARGS__)
#endif


template <contiguous_iterator _InIter, contiguous_iterator _OutIter>
requires no_fail_relocatable_iterators<_InIter, _OutIter>
auto unintialized_relocate(_InIter __first, _InIter __last, _OutIter __result) noexcept
-> _OutIter {
// Note that by design, none of the operations in this implementation are
// potentially-throwing --- hence there are no concerns about exception
// safety

// One implementation bug deferred from proof of concept is that we assume
// that the object pointer type can be implicitly converted to the
// contiguous output iterator when creating the return value. I have yet __result
// confirm that this is mandated by the relevant concepts
// Using pointers to relocate whole range at once, and remove
// no-throw-iterator concerns from support for contiguous iterators. There
// remains corner-case concerns to clean up for throwing `operator*` and
// dereferencing past-the-end iterators for empty ranges.
auto * __begin = std::addressof(*__first);
auto * __end = __begin + (__last - __first);
auto * __new_location = std::addressof(*__result);

// This check is redundant if we follow all the branches below, but makes
// the design intent clear.
if (__begin == __new_location)
return __result;

// For trivially relocatable type, just delegate to the core language primitive
if constexpr (is_trivially_relocatable_v<std::iter_value_t<_InIter>>) {
(void) trivially_relocate(__begin, __end, __new_location);
return __result + (__last - __first);
}

// For non-trivial relocation, we must detect and support overlapping ranges
// ourselves. At this point we no longer need to worry about trivial
// relocatable types but are not move-constructible.

if (__less<>{}(__end, __new_location) || __less<>{}(__new_location + (__end - __begin), __begin)) {
// No overlap
_OutIter __result0 = uninitialized_move(__first, __last, __result);
destroy(__begin, __end);
return __result0;
}
else if (__less<>{}(__begin, __new_location)) {
// move-and-destroy each member, back to front

// Implementation note: we could just modify `__new_location` rather than
// create the local `dest`, but want to clearly show the algorithm
// without micro-optimizing.
auto * __dest = __new_location + (__end - __begin);
while (__dest != __new_location) {
__dest = std::__construct_at(--__dest, std::move(*--__end));
std::__destroy_at(__end);
}
return __result + (__last - __first);

}
else if (__begin == __new_location) {
// Note that this is the same check redundantly hoisted out of the
// if/elif/else flow, but is NOT redundant here
return __result;
}
else {
// move-and-destroy each member, front to back
while (__first != __last) {
(void) __construct_at(std::addressof(*__result), std::move(*__first));
std::__destroy_at(std::addressof(*__first));
++__result;
++__first;
}
return __result;
}

__libcpp_unreachable();
}

template <nothrow_input_iterator _InIter, nothrow_input_iterator _OutIter>
requires no_fail_relocatable_iterators<_InIter, _OutIter>
auto unintialized_relocate(_InIter __first, _InIter __last, _OutIter __result) noexcept
-> _OutIter {
// There is no support for overlapping ranges unless both iterator types are
// contiguous iterators, which would be subsumed by the contiguous itetator
// overload.

// Note that there are trivially relocatable types that are not no-throw
// move constructible and vice-versa, so we need to handle both cases. In
// doing so, we should pick a preferred implementation for types that are
// both trivially relocatable and no-throw move constructible.
if (__first == __last)
return __result;

// Think carefully about iterator invalidation when destroying elements.
// The post-increment on `__first` seems important, in case element
// destruction invalidates an iterator, which would be the case for a
// pointer.
while (__first != __last) {
auto * __begin = std::addressof(*__first++);
auto * __new_location = std::addressof(*__result++);

if constexpr(is_trivially_relocatable_v<std::iter_value_t<_InIter>>) {
(void) trivially_relocate(__begin, __begin + 1, __new_location);
}
else {
(void) std::__construct_at(__new_location, std::move(*__begin));
std::__destroy_at(__begin);
}
}

return __result;
}

template <std::forward_iterator _InIter, nothrow_input_iterator _OutIter>
requires relocatable_iterators<_InIter, _OutIter>
and (!no_fail_relocatable_iterators<_InIter, _OutIter>)
and std::is_move_constructible_v<std::iter_value_t<_InIter>>
auto unintialized_relocate(_InIter __first, _InIter __last, _OutIter __result) // NOT declared noexcept
-> _OutIter {
// The move-constructor can throw, so relocation is not guaranteed to
// succeed. The opt for the vector-like strong exception safety guarantee
// for this operation: if move can throw, but the type is
// copy-constructible, then *copy* the range, so that we can safely unwind
// if an exception is thrown without leaving the initially relocated
// elements in an unknown state. Otherwise, we will *move* all of the
// elements, and leave those elements in an unspecified valid state. Only
// if the copy/move operation succeeds are the original elements destroyed.
if (__first == __last)
return __result;

if constexpr(std::contiguous_iterator<_InIter> and std::contiguous_iterator<_OutIter>) {
// Note that once we complete overload resolution, all viable iterators
// referring to trivially relocatable types should have directed to an
// overload above. We might still have contiguous iterators to
// move-constructible types that may throw, and are not trivially
// relocatable, which means we may be able to assert preconditions that
// overlapping ranges are not supported.
// check for overlapping range with a contract_assert
#if defined(__clang__)
contract_assert(!__is_pointer_in_range(std::addressof(*__first), std::addressof(*__last),
std::addressof(*__result)));
#else
const size_t __count = __last - __first;
auto * __begin = std::addressof(*__first);
auto * __end = __begin + __count; // `__last` is often not a derefencerable iterator
auto * __new_location = std::addressof(*__result);
auto __less_ = std::less<>{};
// Note that __end == __new_location is fine
contract_assert(__less_(__end, __new_location) || __less_(__new_location + __count, __begin));
#endif
}

// Remember original `__result` may be invalidated by this operation, so we need
// to capture the return value.
if constexpr(is_copy_constructible_v<std::iter_value_t<_InIter>>)
__result = std::uninitialized_copy(__first, __last, __result); // cleans up new range if any copy throws
else
__result = std::uninitialized_move(__first, __last, __result); // cleans up new range if any move throws

std::__destroy(__first, __last); // "Library UB" if destructor throws
return __result;
}

// Need to add the ranges stuff here

#endif

_LIBCPP_END_NAMESPACE_STD

_LIBCPP_POP_MACROS
Expand Down
20 changes: 10 additions & 10 deletions libcxx/include/vector
Original file line number Diff line number Diff line change
Expand Up @@ -1577,12 +1577,12 @@ vector<_Tp, _Allocator>::erase(const_iterator __position) {
difference_type __ps = __position - cbegin();
pointer __p = this->__begin_ + __ps;
#if _LIBCPP_STD_VER >= 20
if constexpr (is_trivially_relocatable_v<_Tp>) {
if constexpr (is_trivially_relocatable_v<_Tp> || is_nothrow_move_constructible_v<_Tp>) {
size_type __old_size = size();
// destroy the element at __p
__alloc_traits::destroy(__alloc(), std::__to_address(__p));
// move the rest down
(void) trivially_relocate(__p + 1, this->__end_, __p);
(void) relocate(__p + 1, this->__end_, __p);
// update the end
this->__end_--;
__annotate_shrink(__old_size);
Expand All @@ -1600,15 +1600,15 @@ vector<_Tp, _Allocator>::erase(const_iterator __first, const_iterator __last) {
pointer __p = this->__begin_ + (__first - begin());
if (__first != __last) {
#if _LIBCPP_STD_VER >= 20
if constexpr (is_trivially_relocatable_v<_Tp>) {
if constexpr (is_trivially_relocatable_v<_Tp> || is_nothrow_move_constructible_v<_Tp>) {
size_type __old_size = size();
size_type __count = __last - __first;
pointer __p0 = __p + __count;
// destroy the elements at [__p, __p0)
for (pointer __to_destroy = __p; __to_destroy < __p0; ++__to_destroy)
__alloc_traits::destroy(__alloc(), std::__to_address(__to_destroy));
// move the rest down
(void) trivially_relocate(std::__to_address(__p0), std::__to_address(this->__end_), std::__to_address(__p));
(void) relocate(std::__to_address(__p0), std::__to_address(this->__end_), std::__to_address(__p));
// update the end
this->__end_ -= __count;
__annotate_shrink(__old_size);
Expand Down Expand Up @@ -1644,10 +1644,10 @@ vector<_Tp, _Allocator>::insert(const_iterator __position, const_reference __x)
__construct_one_at_end(__x);
} else {
#if _LIBCPP_STD_VER >= 20
if constexpr (is_trivially_relocatable_v<_Tp>) {
if constexpr (is_trivially_relocatable_v<_Tp> || is_nothrow_move_constructible_v<_Tp>) {
// Make space by trivially relocating everything
_ConstructTransaction __tx(*this, 1);
(void) trivially_relocate(std::__to_address(__p), std::__to_address(this->__end_), __p + 1);
(void) relocate(std::__to_address(__p), std::__to_address(this->__end_), __p + 1);
// construct the new element (not assign!)
const_pointer __xr = pointer_traits<const_pointer>::pointer_to(__x);
if (std::__is_pointer_in_range(std::__to_address(__p), std::__to_address(__end_), std::addressof(__x)))
Expand Down Expand Up @@ -1684,10 +1684,10 @@ vector<_Tp, _Allocator>::insert(const_iterator __position, value_type&& __x) {
__construct_one_at_end(std::move(__x));
} else {
#if _LIBCPP_STD_VER >= 20
if constexpr (is_trivially_relocatable_v<_Tp>) {
if constexpr (is_trivially_relocatable_v<_Tp> || is_nothrow_move_constructible_v<_Tp>) {
// Make space by trivially relocating everything
_ConstructTransaction __tx(*this, 1);
(void) trivially_relocate(std::__to_address(__p), std::__to_address(this->__end_), __p + 1);
(void) relocate(std::__to_address(__p), std::__to_address(this->__end_), __p + 1);
// construct the new element (not assign!)
__alloc_traits::construct(this->__alloc(), std::__to_address(__p), std::forward<value_type>(__x));
++__tx.__pos_;
Expand Down Expand Up @@ -1719,10 +1719,10 @@ vector<_Tp, _Allocator>::emplace(const_iterator __position, _Args&&... __args) {
__construct_one_at_end(std::forward<_Args>(__args)...);
} else {
#if _LIBCPP_STD_VER >= 20
if constexpr (is_trivially_relocatable_v<_Tp>) {
if constexpr (is_trivially_relocatable_v<_Tp> || is_nothrow_move_constructible_v<_Tp>) {
// Make space by trivially relocating everything
_ConstructTransaction __tx(*this, 1);
(void) trivially_relocate(std::__to_address(__p), std::__to_address(this->__end_), __p + 1);
(void) relocate(std::__to_address(__p), std::__to_address(this->__end_), __p + 1);
// construct the new element
__alloc_traits::construct(this->__alloc(), std::__to_address(__p), std::forward<_Args>(__args)...);
++__tx.__pos_;
Expand Down

0 comments on commit 4c4b7b0

Please sign in to comment.