[libcxx-commits] [libcxx] [libc++][pstl] Implementation of parallel std::swap_ranges() based on parallel __for_each (PR #224967)
Michael G. Kazakov via libcxx-commits
libcxx-commits at lists.llvm.org
Sun Sep 20 14:27:00 PDT 2026
https://github.com/mikekazakov created https://github.com/llvm/llvm-project/pull/224967
None
>From dd8957256e07cb569bbdf8200cf0bdbab0b643f4 Mon Sep 17 00:00:00 2001
From: Michael Kazakov <mike.kazakov at gmail.com>
Date: Sun, 20 Sep 2026 22:24:04 +0100
Subject: [PATCH] Added an implementation of parallel std::swap_ranges()
---
libcxx/include/CMakeLists.txt | 1 +
libcxx/include/__algorithm/pstl.h | 14 ++
libcxx/include/__pstl/backend_fwd.h | 7 +
libcxx/include/__pstl/backends/libdispatch.h | 5 +
libcxx/include/__pstl/backends/serial.h | 10 ++
libcxx/include/__pstl/backends/std_thread.h | 5 +
libcxx/include/__pstl/cpu_algos/swap_ranges.h | 71 ++++++++
libcxx/include/module.modulemap.in | 3 +
.../pstl.iterator-requirements.verify.cpp | 5 +
.../alg.swap/pstl.swap_ranges.pass.cpp | 158 ++++++++++++++++++
.../pstl.exception_handling.pass.cpp | 7 +
11 files changed, 286 insertions(+)
create mode 100644 libcxx/include/__pstl/cpu_algos/swap_ranges.h
create mode 100644 libcxx/test/std/algorithms/alg.modifying.operations/alg.swap/pstl.swap_ranges.pass.cpp
diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt
index 252126c0ceb702..1ee2081561fd97 100644
--- a/libcxx/include/CMakeLists.txt
+++ b/libcxx/include/CMakeLists.txt
@@ -711,6 +711,7 @@ set(files
__pstl/cpu_algos/search.h
__pstl/cpu_algos/search_n.h
__pstl/cpu_algos/stable_sort.h
+ __pstl/cpu_algos/swap_ranges.h
__pstl/cpu_algos/transform.h
__pstl/cpu_algos/transform_reduce.h
__pstl/cpu_algos/uninitialized_algorithms.h
diff --git a/libcxx/include/__algorithm/pstl.h b/libcxx/include/__algorithm/pstl.h
index eefc90da274093..492dd3f3c5d5a7 100644
--- a/libcxx/include/__algorithm/pstl.h
+++ b/libcxx/include/__algorithm/pstl.h
@@ -1057,6 +1057,20 @@ stable_sort(_ExecutionPolicy&& __policy, _RandomAccessIterator __first, _RandomA
std::forward<_ExecutionPolicy>(__policy), std::move(__first), std::move(__last), less{});
}
+template <class _ExecutionPolicy,
+ class _ForwardIterator1,
+ class _ForwardIterator2,
+ class _RawPolicy = __remove_cvref_t<_ExecutionPolicy>,
+ enable_if_t<is_execution_policy_v<_RawPolicy>, int> = 0>
+_LIBCPP_HIDE_FROM_ABI _ForwardIterator2 swap_ranges(
+ _ExecutionPolicy&& __policy, _ForwardIterator1 __first1, _ForwardIterator1 __last1, _ForwardIterator2 __first2) {
+ _LIBCPP_REQUIRE_CPP17_FORWARD_ITERATOR(_ForwardIterator1, "swap_ranges requires ForwardIterators");
+ _LIBCPP_REQUIRE_CPP17_FORWARD_ITERATOR(_ForwardIterator2, "swap_ranges requires ForwardIterators");
+ using _Implementation = __pstl::__dispatch<__pstl::__swap_ranges, __pstl::__current_configuration, _RawPolicy>;
+ return __pstl::__handle_exception<_Implementation>(
+ std::forward<_ExecutionPolicy>(__policy), std::move(__first1), std::move(__last1), std::move(__first2));
+}
+
template <class _ExecutionPolicy,
class _ForwardIterator,
class _ForwardOutIterator,
diff --git a/libcxx/include/__pstl/backend_fwd.h b/libcxx/include/__pstl/backend_fwd.h
index 570afef8a8a346..a5b83f977b7098 100644
--- a/libcxx/include/__pstl/backend_fwd.h
+++ b/libcxx/include/__pstl/backend_fwd.h
@@ -154,6 +154,13 @@ struct __search;
// operator()(_Policy&&, _ForwardIterator1 __first1, _ForwardIterator1 __last1,
// _ForwardIterator2 __first2, _ForwardIterator2 __last2, _BinaryPredicate __pred) const noexcept;
+template <class _Backend, class _ExecutionPolicy>
+struct __swap_ranges;
+// template <class _Policy, class _ForwardIterator1, class _ForwardIterator2>
+// optional<_ForwardIterator2>
+// operator()(_Policy&&, _ForwardIterator1 __first1, _ForwardIterator1 __last1,
+// _ForwardIterator2 __first2) const noexcept;
+
template <class _Backend, class _ExecutionPolicy>
struct __replace;
// template <class _Policy, class _ForwardIterator, class _Tp>
diff --git a/libcxx/include/__pstl/backends/libdispatch.h b/libcxx/include/__pstl/backends/libdispatch.h
index cab5577bbc545d..09ea15d411dd1c 100644
--- a/libcxx/include/__pstl/backends/libdispatch.h
+++ b/libcxx/include/__pstl/backends/libdispatch.h
@@ -50,6 +50,7 @@
#include <__pstl/cpu_algos/search.h>
#include <__pstl/cpu_algos/search_n.h>
#include <__pstl/cpu_algos/stable_sort.h>
+#include <__pstl/cpu_algos/swap_ranges.h>
#include <__pstl/cpu_algos/transform.h>
#include <__pstl/cpu_algos/transform_reduce.h>
#include <__pstl/cpu_algos/uninitialized_algorithms.h>
@@ -412,6 +413,10 @@ template <class _ExecutionPolicy>
struct __search_n<__libdispatch_backend_tag, _ExecutionPolicy>
: __cpu_parallel_search_n<__libdispatch_backend_tag, _ExecutionPolicy> {};
+template <class _ExecutionPolicy>
+struct __swap_ranges<__libdispatch_backend_tag, _ExecutionPolicy>
+ : __cpu_parallel_swap_ranges<__libdispatch_backend_tag, _ExecutionPolicy> {};
+
template <class _ExecutionPolicy>
struct __stable_sort<__libdispatch_backend_tag, _ExecutionPolicy>
: __cpu_parallel_stable_sort<__libdispatch_backend_tag, _ExecutionPolicy> {};
diff --git a/libcxx/include/__pstl/backends/serial.h b/libcxx/include/__pstl/backends/serial.h
index c779d583089d2c..c06e4d31ff3cba 100644
--- a/libcxx/include/__pstl/backends/serial.h
+++ b/libcxx/include/__pstl/backends/serial.h
@@ -22,6 +22,7 @@
#include <__algorithm/search.h>
#include <__algorithm/search_n.h>
#include <__algorithm/stable_sort.h>
+#include <__algorithm/swap_ranges.h>
#include <__algorithm/transform.h>
#include <__config>
#include <__memory/uninitialized_algorithms.h>
@@ -206,6 +207,15 @@ struct __stable_sort<__serial_backend_tag, _ExecutionPolicy> {
}
};
+template <class _ExecutionPolicy>
+struct __swap_ranges<__serial_backend_tag, _ExecutionPolicy> {
+ template <class _Policy, class _ForwardIterator1, class _ForwardIterator2>
+ _LIBCPP_HIDE_FROM_ABI optional<_ForwardIterator2> operator()(
+ _Policy&&, _ForwardIterator1 __first1, _ForwardIterator1 __last1, _ForwardIterator2 __first2) const noexcept {
+ return std::swap_ranges(std::move(__first1), std::move(__last1), std::move(__first2));
+ }
+};
+
template <class _ExecutionPolicy>
struct __transform<__serial_backend_tag, _ExecutionPolicy> {
template <class _Policy, class _ForwardIterator, class _ForwardOutIterator, class _UnaryOperation>
diff --git a/libcxx/include/__pstl/backends/std_thread.h b/libcxx/include/__pstl/backends/std_thread.h
index bdbddb9d36300e..e8b7e2fbd9458d 100644
--- a/libcxx/include/__pstl/backends/std_thread.h
+++ b/libcxx/include/__pstl/backends/std_thread.h
@@ -28,6 +28,7 @@
#include <__pstl/cpu_algos/search.h>
#include <__pstl/cpu_algos/search_n.h>
#include <__pstl/cpu_algos/stable_sort.h>
+#include <__pstl/cpu_algos/swap_ranges.h>
#include <__pstl/cpu_algos/transform.h>
#include <__pstl/cpu_algos/transform_reduce.h>
#include <__pstl/cpu_algos/uninitialized_algorithms.h>
@@ -146,6 +147,10 @@ template <class _ExecutionPolicy>
struct __stable_sort<__std_thread_backend_tag, _ExecutionPolicy>
: __cpu_parallel_stable_sort<__std_thread_backend_tag, _ExecutionPolicy> {};
+template <class _ExecutionPolicy>
+struct __swap_ranges<__std_thread_backend_tag, _ExecutionPolicy>
+ : __cpu_parallel_swap_ranges<__std_thread_backend_tag, _ExecutionPolicy> {};
+
template <class _ExecutionPolicy>
struct __transform<__std_thread_backend_tag, _ExecutionPolicy>
: __cpu_parallel_transform<__std_thread_backend_tag, _ExecutionPolicy> {};
diff --git a/libcxx/include/__pstl/cpu_algos/swap_ranges.h b/libcxx/include/__pstl/cpu_algos/swap_ranges.h
new file mode 100644
index 00000000000000..8e771fc02348d4
--- /dev/null
+++ b/libcxx/include/__pstl/cpu_algos/swap_ranges.h
@@ -0,0 +1,71 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 _LIBCPP___PSTL_CPU_ALGOS_SWAP_RANGES_H
+#define _LIBCPP___PSTL_CPU_ALGOS_SWAP_RANGES_H
+
+#include <__algorithm/swap_ranges.h>
+#include <__config>
+#include <__iterator/concepts.h>
+#include <__iterator/iterator_traits.h>
+#include <__optional/nullopt_t.h>
+#include <__optional/optional.h>
+#include <__pstl/backend_fwd.h>
+#include <__pstl/cpu_algos/cpu_traits.h>
+#include <__pstl/cpu_algos/for_each.h>
+#include <__type_traits/is_execution_policy.h>
+#include <__utility/move.h>
+
+#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
+# pragma GCC system_header
+#endif
+
+_LIBCPP_PUSH_MACROS
+#include <__undef_macros>
+
+#if _LIBCPP_STD_VER >= 17
+
+_LIBCPP_BEGIN_NAMESPACE_STD
+
+namespace __pstl {
+
+template <class _Backend, class _RawExecutionPolicy>
+struct __cpu_parallel_swap_ranges {
+ template <class _Policy, class _ForwardIterator1, class _ForwardIterator2>
+ _LIBCPP_HIDE_FROM_ABI optional<_ForwardIterator2> operator()(
+ _Policy&&, _ForwardIterator1 __first1, _ForwardIterator1 __last1, _ForwardIterator2 __first2) const noexcept {
+ if constexpr (__is_parallel_execution_policy_v<_RawExecutionPolicy> &&
+ __has_random_access_iterator_category_or_concept<_ForwardIterator1>::value &&
+ __has_random_access_iterator_category_or_concept<_ForwardIterator2>::value) {
+ auto __res = __pstl::__parallel_for_each_iter_pair<_Backend>(
+ __first1,
+ __last1,
+ __first2,
+ [](_ForwardIterator1 __brick_first1, _ForwardIterator1 __brick_last1, _ForwardIterator2 __brick_first2) {
+ std::swap_ranges(std::move(__brick_first1), std::move(__brick_last1), std::move(__brick_first2));
+ });
+ if (!__res) {
+ return std::nullopt; // Failed to run the algorithm, propagate the error.
+ }
+ return __first2 + (__last1 - __first1);
+ } else {
+ // Non-random access iterators cannot be processed in parallel, fall back to the sequential implementation.
+ return std::swap_ranges(std::move(__first1), std::move(__last1), std::move(__first2));
+ }
+ }
+};
+
+} // namespace __pstl
+
+_LIBCPP_END_NAMESPACE_STD
+
+#endif // _LIBCPP_STD_VER >= 17
+
+_LIBCPP_POP_MACROS
+
+#endif // _LIBCPP___PSTL_CPU_ALGOS_SWAP_RANGES_H
diff --git a/libcxx/include/module.modulemap.in b/libcxx/include/module.modulemap.in
index 072280038aa586..28ae9ee8de46a3 100644
--- a/libcxx/include/module.modulemap.in
+++ b/libcxx/include/module.modulemap.in
@@ -2471,6 +2471,9 @@ module std {
header "__pstl/cpu_algos/stable_sort.h"
export std_core.utility_core.empty
}
+ module swap_ranges {
+ header "__pstl/cpu_algos/swap_ranges.h"
+ }
module transform {
header "__pstl/cpu_algos/transform.h"
}
diff --git a/libcxx/test/libcxx/algorithms/pstl.iterator-requirements.verify.cpp b/libcxx/test/libcxx/algorithms/pstl.iterator-requirements.verify.cpp
index bbc669bfdc5548..a5ce8b80c381e5 100644
--- a/libcxx/test/libcxx/algorithms/pstl.iterator-requirements.verify.cpp
+++ b/libcxx/test/libcxx/algorithms/pstl.iterator-requirements.verify.cpp
@@ -281,6 +281,11 @@ void f(non_input_iterator non_input,
(void)std::stable_sort(pol, non_fwd, non_fwd, pred); // expected-error@*:* {{static assertion failed: stable_sort}}
}
+ {
+ (void)std::swap_ranges(pol, non_fwd, non_fwd, it); // expected-error@*:* {{static assertion failed: swap_ranges}}
+ (void)std::swap_ranges(pol, it, it, non_fwd); // expected-error@*:* {{static assertion failed: swap_ranges}}
+ }
+
{
(void)std::transform(pol, non_fwd, non_fwd, out, func); // expected-error@*:* {{static assertion failed: transform}}
(void)std::transform(pol, it, it, non_fwd, func); // expected-error@*:* {{static assertion failed: transform}}
diff --git a/libcxx/test/std/algorithms/alg.modifying.operations/alg.swap/pstl.swap_ranges.pass.cpp b/libcxx/test/std/algorithms/alg.modifying.operations/alg.swap/pstl.swap_ranges.pass.cpp
new file mode 100644
index 00000000000000..e5a552b9c5dd6a
--- /dev/null
+++ b/libcxx/test/std/algorithms/alg.modifying.operations/alg.swap/pstl.swap_ranges.pass.cpp
@@ -0,0 +1,158 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++17
+
+// UNSUPPORTED: libcpp-has-no-incomplete-pstl
+
+// <algorithm>
+
+// template <class ExecutionPolicy,
+// class ForwardIterator1,
+// class ForwardIterator2>
+// ForwardIterator2 swap_ranges(ExecutionPolicy&& exec,
+// ForwardIterator1 first1, ForwardIterator1 last1,
+// ForwardIterator2 last2);
+
+#include <array>
+#include <algorithm>
+#include <cassert>
+#include <cstring>
+#include <functional>
+#include <iterator>
+#include <memory>
+#include <numeric>
+
+#include "test_execution_policies.h"
+#include "test_iterators.h"
+#include "test_macros.h"
+#include "type_algorithms.h"
+#include "runway_sample.h"
+
+EXECUTION_POLICY_SFINAE_TEST(swap_ranges);
+
+static_assert(sfinae_test_swap_ranges<int, int*, int*, int*>);
+static_assert(!sfinae_test_swap_ranges<std::execution::parallel_policy, int*, int*, int*>);
+
+template <class Iter1, class Iter2>
+struct TestInt {
+ template <class ExecutionPolicy>
+ void operator()(ExecutionPolicy&& policy) {
+ { // Check the return type
+ int a[] = {0};
+ int b[] = {0};
+ auto ret = std::swap_ranges(policy, Iter1(std::begin(a)), Iter1(std::end(a)), Iter2(std::begin(b)));
+ ASSERT_SAME_TYPE(decltype(ret), Iter2);
+ assert(ret == Iter2(std::end(b)));
+ }
+ { // Size=1
+ int a[] = {1};
+ int b[] = {2};
+ std::swap_ranges(policy, Iter1(std::begin(a)), Iter1(std::end(a)), Iter2(std::begin(b)));
+ assert(a[0] == 2);
+ assert(b[0] == 1);
+ }
+ { // Size=2
+ int a[] = {1, 2};
+ int b[] = {4, 5};
+ std::swap_ranges(policy, Iter1(std::begin(a)), Iter1(std::end(a)), Iter2(std::begin(b)));
+ assert(a[0] == 4 && a[1] == 5);
+ assert(b[0] == 1 && b[1] == 2);
+ }
+ { // Size=3
+ std::array<int, 3> a = {1, 2, 3}, a0 = a;
+ std::array<int, 3> b = {4, 5, 6}, b0 = b;
+ std::swap_ranges(policy, Iter1(a.data()), Iter1(a.data() + a.size()), Iter2(b.data()));
+ assert(a == b0);
+ assert(b == a0);
+ }
+ { // Size=4
+ std::array<int, 4> a = {1, 2, 3, 4}, a0 = a;
+ std::array<int, 4> b = {5, 6, 7, 8}, b0 = b;
+ std::swap_ranges(policy, Iter1(a.data()), Iter1(a.data() + a.size()), Iter2(b.data()));
+ assert(a == b0);
+ assert(b == a0);
+ }
+ { // Size=5
+ std::array<int, 5> a = {1, 2, 3, 4, 5}, a0 = a;
+ std::array<int, 5> b = {6, 7, 8, 9, 10}, b0 = b;
+ std::swap_ranges(policy, Iter1(a.data()), Iter1(a.data() + a.size()), Iter2(b.data()));
+ assert(a == b0);
+ assert(b == a0);
+ }
+ { // Different sampled sizes
+ constexpr size_t n = 1073;
+ std::array<int, n> a0;
+ std::array<int, n> b0;
+ std::iota(a0.begin(), a0.end(), 0); // a0 is 0, 1, 2, ...
+ std::iota(b0.begin(), b0.end(), 10000); // b0 is 10000, 10001, 10002, ...
+ runway_sample(n + 1, [&](size_t size) {
+ std::array<int, n> a;
+ std::array<int, n> b;
+ // Copy the first 'size' elements and fill the rest with sentinel values
+ std::copy_n(a0.begin(), size, a.begin());
+ std::fill(a.begin() + size, a.end(), -1);
+ std::copy_n(b0.begin(), size, b.begin());
+ std::fill(b.begin() + size, b.end(), -2);
+ auto ret = std::swap_ranges(policy, Iter1(a.data()), Iter1(a.data() + size), Iter2(b.data()));
+ assert(ret == Iter2(b.data() + size));
+ // Check that the first 'size' elements have been swapped correctly, and the rest remain as sentinel values
+ assert(std::equal(a.begin(), a.begin() + size, b0.begin()));
+ assert(std::all_of(a.begin() + size, a.end(), [](int x) { return x == -1; }));
+ assert(std::equal(b.begin(), b.begin() + size, a0.begin()));
+ assert(std::all_of(b.begin() + size, b.end(), [](int x) { return x == -2; }));
+ });
+ }
+ }
+};
+
+template <class Iter1, class Iter2>
+struct TestArrayInt3 {
+ template <class ExecutionPolicy>
+ void operator()(ExecutionPolicy&& policy) {
+ std::array<std::array<int, 3>, 3> a = {{{0, 1, 2}, {3, 4, 5}, {6, 7, 8}}}, a0 = a;
+ std::array<std::array<int, 3>, 3> b = {{{9, 8, 7}, {6, 5, 4}, {3, 2, 1}}}, b0 = b;
+ std::swap_ranges(policy, Iter1(a.data()), Iter1(a.data() + a.size()), Iter2(b.data()));
+ assert(a == b0);
+ assert(b == a0);
+ }
+};
+
+template <class Iter1, class Iter2>
+struct TestUniquePtr {
+ template <class ExecutionPolicy>
+ void operator()(ExecutionPolicy&& policy) {
+ std::unique_ptr<int> a[] = {std::make_unique<int>(1), std::make_unique<int>(2), std::make_unique<int>(3)};
+ std::unique_ptr<int> b[] = {std::make_unique<int>(4), std::make_unique<int>(5), std::make_unique<int>(6)};
+ std::swap_ranges(policy, Iter1(std::begin(a)), Iter1(std::end(a)), Iter2(std::begin(b)));
+ assert(*a[0] == 4 && *a[1] == 5 && *a[2] == 6);
+ assert(*b[0] == 1 && *b[1] == 2 && *b[2] == 3);
+ }
+};
+
+int main(int, char**) {
+ types::for_each(types::forward_iterator_list<int*>{}, types::apply_type_identity{[](auto v) {
+ using Iter = typename decltype(v)::type;
+ types::for_each(
+ types::forward_iterator_list<int*>{},
+ TestIteratorWithPolicies<types::partial_instantiation<TestInt, Iter>::template apply>{});
+ }});
+ types::for_each(types::forward_iterator_list<std::array<int, 3>*>{}, types::apply_type_identity{[](auto v) {
+ using Iter = typename decltype(v)::type;
+ types::for_each(
+ types::forward_iterator_list<std::array<int, 3>*>{},
+ TestIteratorWithPolicies<types::partial_instantiation<TestArrayInt3, Iter>::template apply>{});
+ }});
+ types::for_each(types::forward_iterator_list<std::unique_ptr<int>*>{}, types::apply_type_identity{[](auto v) {
+ using Iter = typename decltype(v)::type;
+ types::for_each(
+ types::forward_iterator_list<std::unique_ptr<int>*>{},
+ TestIteratorWithPolicies<types::partial_instantiation<TestUniquePtr, Iter>::template apply>{});
+ }});
+ return 0;
+}
diff --git a/libcxx/test/std/algorithms/pstl.exception_handling.pass.cpp b/libcxx/test/std/algorithms/pstl.exception_handling.pass.cpp
index f6aeaf68eb96ba..64f7b396813055 100644
--- a/libcxx/test/std/algorithms/pstl.exception_handling.pass.cpp
+++ b/libcxx/test/std/algorithms/pstl.exception_handling.pass.cpp
@@ -588,6 +588,13 @@ int main(int, char**) {
(void)std::uninitialized_move_n(policy, std::move(first1), n, std::move(dest));
});
}
+
+ {
+ // swap_ranges(first1, last1, first2)
+ assert_non_throwing([=, &policy] {
+ (void)std::swap_ranges(policy, std::move(first1), std::move(last1), std::move(first2));
+ });
+ }
}
});
More information about the libcxx-commits
mailing list