[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