[libcxx-commits] [libcxx] [libc++][pstl] Implementation of parallel std::reverse() based on parallel for_each (PR #213487)
Michael G. Kazakov via libcxx-commits
libcxx-commits at lists.llvm.org
Sat Aug 1 13:25:39 PDT 2026
https://github.com/mikekazakov created https://github.com/llvm/llvm-project/pull/213487
Part of #99938.
>From 24201ea1cc305bfb42ce1fea8d329bb6af264250 Mon Sep 17 00:00:00 2001
From: Michael Kazakov <mike.kazakov at gmail.com>
Date: Sat, 1 Aug 2026 21:17:19 +0100
Subject: [PATCH] Implementation of a parallel reverse() on top of a parallel
for_each()
---
libcxx/include/CMakeLists.txt | 1 +
libcxx/include/__algorithm/pstl.h | 12 +++
libcxx/include/__pstl/backend_fwd.h | 6 ++
libcxx/include/__pstl/backends/libdispatch.h | 5 +
libcxx/include/__pstl/backends/serial.h | 11 +++
libcxx/include/__pstl/backends/std_thread.h | 5 +
libcxx/include/__pstl/cpu_algos/reverse.h | 68 ++++++++++++++
.../pstl.iterator-requirements.verify.cpp | 4 +
.../alg.reverse/pstl.reverse.pass.cpp | 92 +++++++++++++++++++
.../pstl.exception_handling.pass.cpp | 5 +
10 files changed, 209 insertions(+)
create mode 100644 libcxx/include/__pstl/cpu_algos/reverse.h
create mode 100644 libcxx/test/std/algorithms/alg.modifying.operations/alg.reverse/pstl.reverse.pass.cpp
diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt
index 5034d5f6fe95b..dc1d8bb26bac4 100644
--- a/libcxx/include/CMakeLists.txt
+++ b/libcxx/include/CMakeLists.txt
@@ -691,6 +691,7 @@ set(files
__pstl/cpu_algos/for_each.h
__pstl/cpu_algos/merge.h
__pstl/cpu_algos/mismatch.h
+ __pstl/cpu_algos/reverse.h
__pstl/cpu_algos/stable_sort.h
__pstl/cpu_algos/transform.h
__pstl/cpu_algos/transform_reduce.h
diff --git a/libcxx/include/__algorithm/pstl.h b/libcxx/include/__algorithm/pstl.h
index bf6c2a030d348..2c628c4c8698f 100644
--- a/libcxx/include/__algorithm/pstl.h
+++ b/libcxx/include/__algorithm/pstl.h
@@ -560,6 +560,18 @@ generate_n(_ExecutionPolicy&& __policy, _ForwardIterator __first, _Size __n, _Ge
std::forward<_ExecutionPolicy>(__policy), std::move(__first), std::move(__n), std::move(__gen));
}
+template <class _ExecutionPolicy,
+ class _BidirectionalIterator,
+ class _RawPolicy = __remove_cvref_t<_ExecutionPolicy>,
+ enable_if_t<is_execution_policy_v<_RawPolicy>, int> = 0>
+_LIBCPP_HIDE_FROM_ABI void
+reverse(_ExecutionPolicy&& __policy, _BidirectionalIterator __first, _BidirectionalIterator __last) {
+ _LIBCPP_REQUIRE_CPP17_BIDIRECTIONAL_ITERATOR(_BidirectionalIterator, "reverse requires a BidirectionalIterator");
+ using _Implementation = __pstl::__dispatch<__pstl::__reverse, __pstl::__current_configuration, _RawPolicy>;
+ __pstl::__handle_exception<_Implementation>(
+ std::forward<_ExecutionPolicy>(__policy), std::move(__first), std::move(__last));
+}
+
template <class _ExecutionPolicy,
class _BidirectionalIterator,
class _ForwardIterator,
diff --git a/libcxx/include/__pstl/backend_fwd.h b/libcxx/include/__pstl/backend_fwd.h
index eaa987a2a3c94..248c90ba16596 100644
--- a/libcxx/include/__pstl/backend_fwd.h
+++ b/libcxx/include/__pstl/backend_fwd.h
@@ -166,6 +166,12 @@ struct __generate_n;
// optional<__empty>
// operator()(_Policy&&, _ForwardIterator __first, _Size __n, _Generator __gen) const noexcept;
+template <class _Backend, class _ExecutionPolicy>
+struct __reverse;
+// template <class _Policy, class _BidirectionalIterator>
+// optional<__empty>
+// operator()(_Policy&&, _BidirectionalIterator __first, _BidirectionalIterator __last) const noexcept;
+
template <class _Backend, class _ExecutionPolicy>
struct __reverse_copy;
// template <class _Policy, class _BidirectionalIterator, class _ForwardIterator>
diff --git a/libcxx/include/__pstl/backends/libdispatch.h b/libcxx/include/__pstl/backends/libdispatch.h
index 5e89c6fe31cd8..361c86937534c 100644
--- a/libcxx/include/__pstl/backends/libdispatch.h
+++ b/libcxx/include/__pstl/backends/libdispatch.h
@@ -38,6 +38,7 @@
#include <__pstl/cpu_algos/for_each.h>
#include <__pstl/cpu_algos/merge.h>
#include <__pstl/cpu_algos/mismatch.h>
+#include <__pstl/cpu_algos/reverse.h>
#include <__pstl/cpu_algos/stable_sort.h>
#include <__pstl/cpu_algos/transform.h>
#include <__pstl/cpu_algos/transform_reduce.h>
@@ -373,6 +374,10 @@ template <class _ExecutionPolicy>
struct __mismatch<__libdispatch_backend_tag, _ExecutionPolicy>
: __cpu_parallel_mismatch<__libdispatch_backend_tag, _ExecutionPolicy> {};
+template <class _ExecutionPolicy>
+struct __reverse<__libdispatch_backend_tag, _ExecutionPolicy>
+ : __cpu_parallel_reverse<__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 d90022ee37145..6dc062057f64c 100644
--- a/libcxx/include/__pstl/backends/serial.h
+++ b/libcxx/include/__pstl/backends/serial.h
@@ -14,6 +14,7 @@
#include <__algorithm/for_each.h>
#include <__algorithm/merge.h>
#include <__algorithm/mismatch.h>
+#include <__algorithm/reverse.h>
#include <__algorithm/stable_sort.h>
#include <__algorithm/transform.h>
#include <__config>
@@ -106,6 +107,16 @@ struct __merge<__serial_backend_tag, _ExecutionPolicy> {
}
};
+template <class _ExecutionPolicy>
+struct __reverse<__serial_backend_tag, _ExecutionPolicy> {
+ template <class _Policy, class _BidirectionalIterator>
+ _LIBCPP_HIDE_FROM_ABI optional<__empty>
+ operator()(_Policy&&, _BidirectionalIterator __first, _BidirectionalIterator __last) const noexcept {
+ std::reverse(std::move(__first), std::move(__last));
+ return __empty{};
+ }
+};
+
template <class _ExecutionPolicy>
struct __stable_sort<__serial_backend_tag, _ExecutionPolicy> {
template <class _Policy, class _RandomAccessIterator, class _Comp>
diff --git a/libcxx/include/__pstl/backends/std_thread.h b/libcxx/include/__pstl/backends/std_thread.h
index 93935d22b9442..5fdf6f8aeab90 100644
--- a/libcxx/include/__pstl/backends/std_thread.h
+++ b/libcxx/include/__pstl/backends/std_thread.h
@@ -18,6 +18,7 @@
#include <__pstl/cpu_algos/for_each.h>
#include <__pstl/cpu_algos/merge.h>
#include <__pstl/cpu_algos/mismatch.h>
+#include <__pstl/cpu_algos/reverse.h>
#include <__pstl/cpu_algos/stable_sort.h>
#include <__pstl/cpu_algos/transform.h>
#include <__pstl/cpu_algos/transform_reduce.h>
@@ -105,6 +106,10 @@ template <class _ExecutionPolicy>
struct __mismatch<__std_thread_backend_tag, _ExecutionPolicy>
: __cpu_parallel_mismatch<__std_thread_backend_tag, _ExecutionPolicy> {};
+template <class _ExecutionPolicy>
+struct __reverse<__std_thread_backend_tag, _ExecutionPolicy>
+ : __cpu_parallel_reverse<__std_thread_backend_tag, _ExecutionPolicy> {};
+
template <class _ExecutionPolicy>
struct __stable_sort<__std_thread_backend_tag, _ExecutionPolicy>
: __cpu_parallel_stable_sort<__std_thread_backend_tag, _ExecutionPolicy> {};
diff --git a/libcxx/include/__pstl/cpu_algos/reverse.h b/libcxx/include/__pstl/cpu_algos/reverse.h
new file mode 100644
index 0000000000000..5e5a1a0a4529e
--- /dev/null
+++ b/libcxx/include/__pstl/cpu_algos/reverse.h
@@ -0,0 +1,68 @@
+//===----------------------------------------------------------------------===//
+//
+// 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_REVERSE_H
+#define _LIBCPP___PSTL_CPU_ALGOS_REVERSE_H
+
+#include <__algorithm/reverse.h>
+#include <__algorithm/swap_ranges.h>
+#include <__config>
+#include <__iterator/concepts.h>
+#include <__iterator/iterator_traits.h>
+#include <__iterator/reverse_iterator.h>
+#include <__optional/optional.h>
+#include <__pstl/backend_fwd.h>
+#include <__pstl/cpu_algos/cpu_traits.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_reverse {
+ template <class _Policy, class _ForwardIterator>
+ _LIBCPP_HIDE_FROM_ABI optional<__empty>
+ operator()(_Policy&&, _ForwardIterator __first, _ForwardIterator __last) const noexcept {
+ if constexpr (__is_parallel_execution_policy_v<_RawExecutionPolicy> &&
+ __has_random_access_iterator_category_or_concept<_ForwardIterator>::value) {
+ // Perform a chunked for_each on the first half of the range.
+ auto __n = (__last - __first) / 2;
+ return __cpu_traits<_Backend>::__for_each(
+ __first, __first + __n, [__first, __last](_ForwardIterator __i, _ForwardIterator __j) {
+ // Derive the last position of the mirrored range.
+ _ForwardIterator __mirror_last = __last - (__i - __first);
+ // Swap the elements in the range of the first half with their mirrored counterparts in the second half.
+ std::swap_ranges(
+ std::move(__i), std::move(__j), std::reverse_iterator<_ForwardIterator>(std::move(__mirror_last)));
+ });
+ } else {
+ // Non-random access iterators currently cannot be processed in parallel, use the sequential implementation.
+ std::reverse(std::move(__first), std::move(__last));
+ return __empty{};
+ }
+ }
+};
+
+} // namespace __pstl
+_LIBCPP_END_NAMESPACE_STD
+
+#endif // _LIBCPP_STD_VER >= 17
+
+_LIBCPP_POP_MACROS
+
+#endif // _LIBCPP___PSTL_CPU_ALGOS_REVERSE_H
diff --git a/libcxx/test/libcxx/algorithms/pstl.iterator-requirements.verify.cpp b/libcxx/test/libcxx/algorithms/pstl.iterator-requirements.verify.cpp
index 636c4cf84ab2d..a8ab2501517e0 100644
--- a/libcxx/test/libcxx/algorithms/pstl.iterator-requirements.verify.cpp
+++ b/libcxx/test/libcxx/algorithms/pstl.iterator-requirements.verify.cpp
@@ -125,6 +125,10 @@ void f(non_forward_iterator non_fwd,
pol, non_fwd, non_fwd, it, it, pred); // expected-error@*:* {{static assertion failed: lexicographical_compare}}
}
+ {
+ std::reverse(pol, non_bidir, non_bidir); // expected-error@*:* {{static assertion failed: reverse}}
+ }
+
{
(void)std::reverse_copy(
pol, non_bidir, non_bidir, it); // expected-error@*:* {{static assertion failed: reverse_copy}}
diff --git a/libcxx/test/std/algorithms/alg.modifying.operations/alg.reverse/pstl.reverse.pass.cpp b/libcxx/test/std/algorithms/alg.modifying.operations/alg.reverse/pstl.reverse.pass.cpp
new file mode 100644
index 0000000000000..11a64a026bb7a
--- /dev/null
+++ b/libcxx/test/std/algorithms/alg.modifying.operations/alg.reverse/pstl.reverse.pass.cpp
@@ -0,0 +1,92 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+
+// template <class ExecutionPolicy,
+// class BidirectionalIterator>
+// void reverse(ExecutionPolicy&& exec,
+// BidirectionalIterator first,
+// BidirectionalIterator last);
+
+#include <algorithm>
+#include <cassert>
+#include <functional>
+#include <iterator>
+#include <limits>
+#include <numeric>
+#include <type_traits>
+
+#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(reverse);
+
+static_assert(sfinae_test_reverse<int, int*, int*>);
+static_assert(!sfinae_test_reverse<std::execution::parallel_policy, int*, int*>);
+
+template <class Iter>
+struct Test {
+ template <class ExecutionPolicy>
+ void operator()(ExecutionPolicy&& policy) {
+ { // Check the return type
+ static_assert(std::is_same_v<
+ void,
+ decltype(std::reverse(
+ std::declval<std::execution::parallel_policy>(), std::declval<Iter>(), std::declval<Iter>()))>);
+ }
+ { // Empty range
+ int a[] = {0};
+ std::reverse(policy, Iter(std::begin(a)), Iter(std::begin(a)));
+ }
+ { // Single element
+ int a[] = {0};
+ std::reverse(policy, Iter(std::begin(a)), Iter(std::end(a)));
+ assert(a[0] == 0);
+ }
+ { // Two elements
+ int a[] = {0, 1};
+ std::reverse(policy, Iter(std::begin(a)), Iter(std::end(a)));
+ assert(a[0] == 1);
+ assert(a[1] == 0);
+ }
+ { // Three elements
+ int a[] = {0, 1, 2};
+ std::reverse(policy, Iter(std::begin(a)), Iter(std::end(a)));
+ assert(a[0] == 2);
+ assert(a[1] == 1);
+ assert(a[2] == 0);
+ }
+ { // Four elements
+ int a[] = {0, 1, 2, 3};
+ std::reverse(policy, Iter(std::begin(a)), Iter(std::end(a)));
+ assert(a[0] == 3);
+ assert(a[1] == 2);
+ assert(a[2] == 1);
+ assert(a[3] == 0);
+ }
+ { // Many iotaed elements, reverse up to a sampled position, check and reverse back
+ int a[1073];
+ std::iota(std::begin(a), std::end(a), 1);
+ runway_sample(std::size(a) + 1, [&](size_t i) {
+ std::reverse(policy, Iter(std::begin(a)), Iter(std::begin(a) + i));
+ for (size_t j = 0; j < i; ++j) {
+ assert(a[j] == static_cast<int>(i - j));
+ }
+ std::reverse(policy, Iter(std::begin(a)), Iter(std::begin(a) + i));
+ });
+ }
+ }
+};
+
+int main(int, char**) { types::for_each(types::bidirectional_iterator_list<int*>{}, TestIteratorWithPolicies<Test>{}); }
diff --git a/libcxx/test/std/algorithms/pstl.exception_handling.pass.cpp b/libcxx/test/std/algorithms/pstl.exception_handling.pass.cpp
index a505e1441d2a9..66cd7ff632ef6 100644
--- a/libcxx/test/std/algorithms/pstl.exception_handling.pass.cpp
+++ b/libcxx/test/std/algorithms/pstl.exception_handling.pass.cpp
@@ -226,6 +226,11 @@ int main(int, char**) {
});
}
+ {
+ // reverse(first, last)
+ assert_non_throwing([=, &policy] { std::reverse(policy, std::move(first1), std::move(last1)); });
+ }
+
{
// reverse_copy(first, last, dest)
assert_non_throwing([=, &policy] {
More information about the libcxx-commits
mailing list