Skip to content

[libc++] memcpy-based codepath for std::swap_ranges #9

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
[libc++] Use __libcpp_memswap in swap_ranges when possible
This follows the same pattern used for `copy`, `copy_backward`, and `move`,
except that `swap_ranges` requires four parameters instead of three.

The new tests mainly verify that the new codepaths are correctly disabled
during constant evaluation. When I wrote them, I was worried that the
trivial codepath might try to form invalid pointer values. But in fact
I believe that the trivial codepath is actually never entered unless
the sentinel is an iterator that unwraps to a pointer type; so we should
be okay in that respect.
  • Loading branch information
Quuxplusone committed Oct 4, 2023
commit 895029b02cb410d13f6b88e725756e77541a1574
64 changes: 64 additions & 0 deletions libcxx/include/__algorithm/copy_move_common.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,13 @@
#include <__type_traits/is_constant_evaluated.h>
#include <__type_traits/is_copy_constructible.h>
#include <__type_traits/is_trivially_assignable.h>
#include <__type_traits/is_trivially_constructible.h>
#include <__type_traits/is_trivially_copyable.h>
#include <__type_traits/is_trivially_destructible.h>
#include <__type_traits/is_volatile.h>
#include <__utility/move.h>
#include <__utility/pair.h>
#include <__utility/swap.h>
#include <cstddef>

#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
Expand Down Expand Up @@ -56,6 +59,20 @@ struct __can_lower_move_assignment_to_memmove {
!is_volatile<_To>::value;
};

template <class _From, class _To>
struct __can_lower_swap_to_memswap {
static const bool value =
__is_always_bitcastable<_From, _To>::value &&
__is_always_bitcastable<_To, _From>::value &&
// These are the operations performed by `From& = std::exchange(To&, From&&)`.
is_trivially_constructible<_To, _To&&>::value &&
is_trivially_assignable<_To&, _From&&>::value &&
is_trivially_assignable<_From&, _To&&>::value &&
is_trivially_destructible<_To>::value &&
!is_volatile<_From>::value &&
!is_volatile<_To>::value;
};

// `memmove` algorithms implementation.

template <class _In, class _Out>
Expand All @@ -79,6 +96,14 @@ __copy_backward_trivial_impl(_In* __first, _In* __last, _Out* __result) {
return std::make_pair(__last, __result);
}

template <class _Tp, class _Up>
_LIBCPP_HIDE_FROM_ABI pair<_Tp*, _Up*>
__swap_ranges_trivial_impl(_Tp* __first1, _Tp* __last1, _Up* __first2) {
const size_t __n = static_cast<size_t>(__last1 - __first1);
std::__libcpp_memswap(__first1, __first2, __n * sizeof(_Tp));
return std::make_pair(__last1, __first2 + __n);
}

// Iterator unwrapping and dispatching to the correct overload.

template <class _F1, class _F2>
Expand Down Expand Up @@ -133,6 +158,45 @@ __dispatch_copy_or_move(_InIter __first, _Sent __last, _OutIter __out_first) {
return std::__unwrap_and_dispatch<_Algorithm>(std::move(__first), std::move(__last), std::move(__out_first));
}

template <class _Algorithm,
class _Iter1,
class _Sent1,
class _Iter2,
class _Sent2,
__enable_if_t<__can_rewrap<_Iter1, _Sent1, _Iter2>::value, int> = 0>
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX17 pair<_Iter1, _Iter2>
__unwrap_and_dispatch(_Iter1 __first1, _Sent1 __last1, _Iter2 __first2, _Sent2 __last2) {
auto __range1 = std::__unwrap_range(__first1, std::move(__last1));
auto __range2 = std::__unwrap_range(__first2, std::move(__last2));
auto __result = _Algorithm()(std::move(__range1.first), std::move(__range1.second), std::move(__range2.first), std::move(__range2.second));
return std::make_pair(std::__rewrap_range<_Sent1>(std::move(__first1), std::move(__result.first)),
std::__rewrap_range<_Sent2>(std::move(__first2), std::move(__result.second)));
}

template <class _Algorithm,
class _Iter1,
class _Sent1,
class _Iter2,
class _Sent2,
__enable_if_t<!__can_rewrap<_Iter1, _Sent1, _Iter2>::value, int> = 0>
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX17 pair<_Iter1, _Iter2>
__unwrap_and_dispatch(_Iter1 __first1, _Sent1 __last1, _Iter2 __first2, _Sent2 __last2) {
return _Algorithm()(std::move(__first1), std::move(__last1), std::move(__first2), std::move(__last2));
}

template <class _AlgPolicy,
class _NaiveAlgorithm,
class _OptimizedAlgorithm,
class _Iter1,
class _Sent1,
class _Iter2,
class _Sent2>
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX17 pair<_Iter1, _Iter2>
__dispatch_copy_or_move(_Iter1 __first1, _Sent1 __last1, _Iter2 __first2, _Sent2 __last2) {
using _Algorithm = __overload<_NaiveAlgorithm, _OptimizedAlgorithm>;
return std::__unwrap_and_dispatch<_Algorithm>(std::move(__first1), std::move(__last1), std::move(__first2), std::move(__last2));
}

_LIBCPP_END_NAMESPACE_STD

#endif // _LIBCPP___ALGORITHM_COPY_MOVE_COMMON_H
86 changes: 68 additions & 18 deletions libcxx/include/__algorithm/swap_ranges.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,43 +9,93 @@
#ifndef _LIBCPP___ALGORITHM_SWAP_RANGES_H
#define _LIBCPP___ALGORITHM_SWAP_RANGES_H

#include <__algorithm/copy_move_common.h>
#include <__algorithm/iterator_operations.h>
#include <__config>
#include <__type_traits/is_constant_evaluated.h>
#include <__utility/move.h>
#include <__utility/pair.h>
#include <__utility/swap.h>

#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
# pragma GCC system_header
#endif

_LIBCPP_BEGIN_NAMESPACE_STD

// 2+2 iterators: the shorter size will be used.
template <class _AlgPolicy, class _ForwardIterator1, class _Sentinel1, class _ForwardIterator2, class _Sentinel2>
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20
pair<_ForwardIterator1, _ForwardIterator2>
__swap_ranges(_ForwardIterator1 __first1, _Sentinel1 __last1, _ForwardIterator2 __first2, _Sentinel2 __last2) {
while (__first1 != __last1 && __first2 != __last2) {
_IterOps<_AlgPolicy>::iter_swap(__first1, __first2);
++__first1;
++__first2;
template <class _AlgPolicy>
struct __swap_ranges_loop {
// 2+2 iterators: the shorter size will be used.
template <class _ForwardIterator1, class _Sentinel1, class _ForwardIterator2, class _Sentinel2>
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20
pair<_ForwardIterator1, _ForwardIterator2>
operator()(_ForwardIterator1 __first1, _Sentinel1 __last1, _ForwardIterator2 __first2, _Sentinel2 __last2) const {
while (__first1 != __last1 && __first2 != __last2) {
_IterOps<_AlgPolicy>::iter_swap(__first1, __first2);
++__first1;
++__first2;
}
return pair<_ForwardIterator1, _ForwardIterator2>(std::move(__first1), std::move(__first2));
}

return pair<_ForwardIterator1, _ForwardIterator2>(std::move(__first1), std::move(__first2));
}
// 2+1 iterators: size2 >= size1.
template <class _ForwardIterator1, class _Sentinel1, class _ForwardIterator2>
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20
pair<_ForwardIterator1, _ForwardIterator2>
operator()(_ForwardIterator1 __first1, _Sentinel1 __last1, _ForwardIterator2 __first2) const {
while (__first1 != __last1) {
_IterOps<_AlgPolicy>::iter_swap(__first1, __first2);
++__first1;
++__first2;
}
return pair<_ForwardIterator1, _ForwardIterator2>(std::move(__first1), std::move(__first2));
}
};

struct __swap_ranges_trivial {
// At this point, the iterators have been unwrapped so any `contiguous_iterator` has been unwrapped to a pointer.
template <class _Tp, class _Up,
__enable_if_t<__can_lower_swap_to_memswap<_Tp, _Up>::value, int> = 0>
_LIBCPP_HIDE_FROM_ABI pair<_Tp*, _Up*>
operator()(_Tp* __first1, _Tp* __last1, _Up* __first2) const {
return std::__swap_ranges_trivial_impl(__first1, __last1, __first2);
}

template <class _Tp, class _Up,
__enable_if_t<__can_lower_swap_to_memswap<_Tp, _Up>::value, int> = 0>
_LIBCPP_HIDE_FROM_ABI pair<_Tp*, _Up*>
operator()(_Tp* __first1, _Tp* __last1, _Up* __first2, _Up* __last2) const {
auto __n1 = __last1 - __first1;
auto __n2 = __last2 - __first2;
auto __n = (__n1 < __n2) ? __n1 : __n2;
return std::__swap_ranges_trivial_impl(__first1, __first1 + __n, __first2);
}
};

// 2+1 iterators: size2 >= size1.
template <class _AlgPolicy, class _ForwardIterator1, class _Sentinel1, class _ForwardIterator2>
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20
inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14
pair<_ForwardIterator1, _ForwardIterator2>
__swap_ranges(_ForwardIterator1 __first1, _Sentinel1 __last1, _ForwardIterator2 __first2) {
while (__first1 != __last1) {
_IterOps<_AlgPolicy>::iter_swap(__first1, __first2);
++__first1;
++__first2;
if (__libcpp_is_constant_evaluated()) {
return __swap_ranges_loop<_AlgPolicy>()(
std::move(__first1), std::move(__last1), std::move(__first2));
} else {
return std::__dispatch_copy_or_move<_AlgPolicy, __swap_ranges_loop<_AlgPolicy>, __swap_ranges_trivial>(
std::move(__first1), std::move(__last1), std::move(__first2));
}
}

return pair<_ForwardIterator1, _ForwardIterator2>(std::move(__first1), std::move(__first2));
template <class _AlgPolicy, class _ForwardIterator1, class _Sentinel1, class _ForwardIterator2, class _Sentinel2>
inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20
pair<_ForwardIterator1, _ForwardIterator2>
__swap_ranges(_ForwardIterator1 __first1, _Sentinel1 __last1, _ForwardIterator2 __first2, _Sentinel2 __last2) {
if (__libcpp_is_constant_evaluated()) {
return __swap_ranges_loop<_AlgPolicy>()(
std::move(__first1), std::move(__last1), std::move(__first2), std::move(__last2));
} else {
return std::__dispatch_copy_or_move<_AlgPolicy, __swap_ranges_loop<_AlgPolicy>, __swap_ranges_trivial>(
std::move(__first1), std::move(__last1), std::move(__first2), std::move(__last2));
}
}

template <class _ForwardIterator1, class _ForwardIterator2>
Expand Down
1 change: 1 addition & 0 deletions libcxx/include/module.modulemap.in
Original file line number Diff line number Diff line change
Expand Up @@ -1085,6 +1085,7 @@ module std_private_algorithm_stable_partition [system
module std_private_algorithm_stable_sort [system] { header "__algorithm/stable_sort.h" }
module std_private_algorithm_swap_ranges [system] {
header "__algorithm/swap_ranges.h"
export std_private_algorithm_copy_move_common
export std_private_algorithm_iterator_operations
}
module std_private_algorithm_three_way_comp_ref_type [system] { header "__algorithm/three_way_comp_ref_type.h" }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//

// <algorithm>

// UNSUPPORTED: c++03, c++11, c++14, c++17

// template<input_iterator I1, sentinel_for<I1> S1, input_iterator I2, sentinel_for<I2> S2>
// requires indirectly_swappable<I1, I2>
// constexpr ranges::swap_ranges_result<I1, I2>
// ranges::swap_ranges(I1 first1, S1 last1, I2 first2, S2 last2);
// template<input_range R1, input_range R2>
// requires indirectly_swappable<iterator_t<R1>, iterator_t<R2>>
// constexpr ranges::swap_ranges_result<borrowed_iterator_t<R1>, borrowed_iterator_t<R2>>
// ranges::swap_ranges(R1&& r1, R2&& r2);

#include <algorithm>
#include <cassert>
#include <ranges>
#include <string>

#include "test_macros.h"

template <class R1, class R2>
constexpr void test_range(const std::string& s1, const std::string& s2, R1&& r1, R2&& r2) {
static_assert(std::ranges::contiguous_range<R1>);
static_assert(std::ranges::contiguous_range<R2>);
assert(std::ranges::distance(r2) < std::ranges::distance(r1));
// We must never form a pointer to r2[r1.size()], not even when the ranges are contiguous,
// because r2 is shorter than r1 and so r2[r1.size()] might be UB.
{
// Longer string first.
auto [it1, it2] = std::ranges::swap_ranges(r1, r2);
assert(it1 == s1.begin() + s2.size());
assert(it2 == s2.end());
assert(s1 == "shorterstringwithnospacesinvolvedtedaftertheendofthe second string");
assert(s2 == "longerstringwiththefirstspaceloca");
}
{
// Shorter string first.
auto [it2, it1] = std::ranges::swap_ranges(r2, r1);
assert(it1 == s1.begin() + s2.size());
assert(it2 == s2.end());
assert(s1 == "longerstringwiththefirstspacelocatedaftertheendofthe second string");
assert(s2 == "shorterstringwithnospacesinvolved");
}
{
// Longer string first, iterator/sentinel version.
auto [it1, it2] = std::ranges::swap_ranges(r1.begin(), r1.end(), r2.begin(), r2.end());
assert(it1 == s1.begin() + s2.size());
assert(it2 == s2.end());
assert(s1 == "shorterstringwithnospacesinvolvedtedaftertheendofthe second string");
assert(s2 == "longerstringwiththefirstspaceloca");
}
{
// Shorter string first, iterator/sentinel version.
auto [it2, it1] = std::ranges::swap_ranges(r2.begin(), r2.end(), r1.begin(), r1.end());
assert(it1 == s1.begin() + s2.size());
assert(it2 == s2.end());
assert(s1 == "longerstringwiththefirstspacelocatedaftertheendofthe second string");
assert(s2 == "shorterstringwithnospacesinvolved");
}
}

constexpr bool test() {
auto isntSpace = [](char ch) { return ch != ' '; };
std::string s1 = "longerstringwiththefirstspacelocatedaftertheendofthe second string";
std::string s2 = "shorterstringwithnospacesinvolved";
{
// The strings themselves.
test_range(s1, s2, s1, s2);
}
{
// Contiguous, non-common ranges.
auto v1 = s1 | std::views::take_while(isntSpace);
auto v2 = s2 | std::views::take_while(isntSpace);
static_assert(std::same_as<decltype(v1), decltype(v2)>);
static_assert(!std::ranges::common_range<decltype(v1)>);
static_assert(!std::ranges::sized_range<decltype(v1)>);
test_range(s1, s2, v1, v2);
}
{
// Contiguous, common ranges.
auto v1 = s1 | std::views::take(52);
auto v2 = s2 | std::views::take(33);
static_assert(std::same_as<decltype(v1), decltype(v2)>);
static_assert(std::ranges::contiguous_range<decltype(v1)>);
static_assert(std::ranges::common_range<decltype(v1)>);
static_assert(std::ranges::sized_range<decltype(v1)>);
test_range(s1, s2, v1, v2);
}
return true;
}

int main(int, char**) {
test();
static_assert(test());

return 0;
}
Loading