From 4cdddbbe0be7213d6fff940ec9a414582548aa10 Mon Sep 17 00:00:00 2001 From: Marshall Clow Date: Fri, 27 Sep 2024 13:02:27 -0700 Subject: [PATCH] Remove 'unintialized_relocate', add 'relocate' --- libcxx/include/__memory/relocate.h | 67 +++++- .../__memory/uninitialized_algorithms.h | 199 ------------------ 2 files changed, 65 insertions(+), 201 deletions(-) diff --git a/libcxx/include/__memory/relocate.h b/libcxx/include/__memory/relocate.h index aa04003c162acc..f98c1acb3a0fda 100644 --- a/libcxx/include/__memory/relocate.h +++ b/libcxx/include/__memory/relocate.h @@ -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 @@ -15,9 +16,71 @@ _LIBCPP_BEGIN_NAMESPACE_STD #if _LIBCPP_STD_VER >= 20 template - 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); +} + +template +constexpr +T* relocate(T* __begin, T* __end, T* __new_location) +{ + static_assert(is_trivially_relocatable_v || is_nothrow_move_constructible_v); +// 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) + return std::trivially_relocate(__begin, __end, __new_location); + } + + if constexpr (is_move_constructible_v) { + // 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)) { + T* 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. + T* next = __begin; + T* dest = __new_location; + while (next != __end) { + ::new(dest) T(std::move(*next)); + next->~T(); + ++next; ++dest; + } + } + else { + // When moving to a higher address that overlaps, we must go backward through + // the range. + T* next = __end; + T* dest = __new_location + (__end-__begin); + while (next != __begin) { + --next; --dest; + ::new(dest) T(std::move(*next)); + next->~T(); + } + } + 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 diff --git a/libcxx/include/__memory/uninitialized_algorithms.h b/libcxx/include/__memory/uninitialized_algorithms.h index 64294cec9a7afa..bdf28d54570da6 100644 --- a/libcxx/include/__memory/uninitialized_algorithms.h +++ b/libcxx/include/__memory/uninitialized_algorithms.h @@ -657,205 +657,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 -concept nothrow_input_iterator = input_iterator<_Iter>; - -template -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 -concept relocatable_iterators - = std::same_as, std::iter_value_t<_OutIter>> - and std::is_lvalue_reference_v> - and std::is_lvalue_reference_v>; - - -template -concept no_fail_relocatable_iterators - = relocatable_iterators<_InIter, _OutIter> - and ( std::is_nothrow_move_constructible_v> - or std::is_trivially_relocatable_v> - ); -} // unnamed namespace - -// Forward looking for asserting preconditions --- tune feature macro as needed -#ifndef __cpp_contracts -# define contract_assert(...) assert(__VA_ARGS__) -#endif - - -template - 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>) { - (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 - 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>) { - (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 - requires relocatable_iterators<_InIter, _OutIter> - and (!no_fail_relocatable_iterators<_InIter, _OutIter>) - and std::is_move_constructible_v> -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>) - __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