[libcxx-commits] [libcxx] [libc++][pstl] Implementation of parallel std::copy_if (PR #229385)
Michael G. Kazakov via libcxx-commits
libcxx-commits at lists.llvm.org
Tue Oct 6 08:58:12 PDT 2026
https://github.com/mikekazakov updated https://github.com/llvm/llvm-project/pull/229385
>From 53267331f4c71a15da86fd84d7dc45cd3613e6da Mon Sep 17 00:00:00 2001
From: Michael Kazakov <mike.kazakov at gmail.com>
Date: Tue, 6 Oct 2026 12:34:01 +0100
Subject: [PATCH 1/2] Implementation of copy_if based on __lookback_scan
---
libcxx/include/CMakeLists.txt | 2 +
libcxx/include/__algorithm/pstl.h | 27 +++
libcxx/include/__pstl/backend_fwd.h | 8 +
libcxx/include/__pstl/backends/libdispatch.h | 69 ++++++
libcxx/include/__pstl/backends/std_thread.h | 28 +++
libcxx/include/__pstl/cpu_algos/copy_if.h | 201 ++++++++++++++++++
libcxx/include/__pstl/decoupled_lookback.h | 161 ++++++++++++++
libcxx/include/module.modulemap.in | 3 +
.../alg.copy/pstl.copy_if.pass.cpp | 158 ++++++++++++++
9 files changed, 657 insertions(+)
create mode 100644 libcxx/include/__pstl/cpu_algos/copy_if.h
create mode 100644 libcxx/include/__pstl/decoupled_lookback.h
create mode 100644 libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/pstl.copy_if.pass.cpp
diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt
index 1ee2081561fd97..198de5be233b55 100644
--- a/libcxx/include/CMakeLists.txt
+++ b/libcxx/include/CMakeLists.txt
@@ -697,6 +697,7 @@ set(files
__pstl/backends/serial.h
__pstl/backends/std_thread.h
__pstl/cpu_algos/any_of.h
+ __pstl/cpu_algos/copy_if.h
__pstl/cpu_algos/cpu_traits.h
__pstl/cpu_algos/fill.h
__pstl/cpu_algos/find_end.h
@@ -715,6 +716,7 @@ set(files
__pstl/cpu_algos/transform.h
__pstl/cpu_algos/transform_reduce.h
__pstl/cpu_algos/uninitialized_algorithms.h
+ __pstl/decoupled_lookback.h
__pstl/dispatch.h
__pstl/handle_exception.h
__random/bernoulli_distribution.h
diff --git a/libcxx/include/__algorithm/pstl.h b/libcxx/include/__algorithm/pstl.h
index 492dd3f3c5d5a7..39927e73584295 100644
--- a/libcxx/include/__algorithm/pstl.h
+++ b/libcxx/include/__algorithm/pstl.h
@@ -92,6 +92,33 @@ copy(_ExecutionPolicy&& __policy, _ForwardIterator __first, _ForwardIterator __l
std::forward<_ExecutionPolicy>(__policy), std::move(__first), std::move(__last), std::move(__result));
}
+template <class _ExecutionPolicy,
+ class _ForwardIterator,
+ class _ForwardOutIterator,
+ class _UnaryPred,
+ class _RawPolicy = __remove_cvref_t<_ExecutionPolicy>,
+ enable_if_t<is_execution_policy_v<_RawPolicy>, int> = 0>
+_LIBCPP_HIDE_FROM_ABI _ForwardOutIterator
+copy_if(_ExecutionPolicy&& __policy,
+ _ForwardIterator __first,
+ _ForwardIterator __last,
+ _ForwardOutIterator __result,
+ _UnaryPred __pred) {
+ _LIBCPP_REQUIRE_CPP17_FORWARD_ITERATOR(
+ _ForwardIterator, "copy_if(first, last, result) requires [first, last) to be ForwardIterators");
+ _LIBCPP_REQUIRE_CPP17_FORWARD_ITERATOR(
+ _ForwardOutIterator, "copy_if(first, last, result) requires result to be a ForwardIterator");
+ _LIBCPP_REQUIRE_CPP17_OUTPUT_ITERATOR(
+ _ForwardOutIterator, decltype(*__first), "copy_if(first, last, result) requires result to be an OutputIterator");
+ using _Implementation = __pstl::__dispatch<__pstl::__copy_if, __pstl::__current_configuration, _RawPolicy>;
+ return __pstl::__handle_exception<_Implementation>(
+ std::forward<_ExecutionPolicy>(__policy),
+ std::move(__first),
+ std::move(__last),
+ std::move(__result),
+ std::move(__pred));
+}
+
template <class _ExecutionPolicy,
class _ForwardIterator,
class _ForwardOutIterator,
diff --git a/libcxx/include/__pstl/backend_fwd.h b/libcxx/include/__pstl/backend_fwd.h
index a5b83f977b7098..64963dcd3bf36b 100644
--- a/libcxx/include/__pstl/backend_fwd.h
+++ b/libcxx/include/__pstl/backend_fwd.h
@@ -296,6 +296,14 @@ struct __copy;
// operator()(_Policy&&, _ForwardIterator __first, _ForwardIterator __last,
// _ForwardOutIterator __out_it) const noexcept;
+template <class _Backend, class _ExecutionPolicy>
+struct __copy_if;
+// template <class _Policy, class _ForwardIterator, class _ForwardOutIterator, class _Predicate>
+// optional<_ForwardOutIterator>
+// operator()(_Policy&&, _ForwardIterator __first, _ForwardIterator __last,
+// _ForwardOutIterator __out_it,
+// _Predicate __pred) const noexcept;
+
template <class _Backend, class _ExecutionPolicy>
struct __copy_n;
// template <class _Policy, class _ForwardIterator, class _Size, class _ForwardOutIterator>
diff --git a/libcxx/include/__pstl/backends/libdispatch.h b/libcxx/include/__pstl/backends/libdispatch.h
index 09ea15d411dd1c..8b62e6ffebf075 100644
--- a/libcxx/include/__pstl/backends/libdispatch.h
+++ b/libcxx/include/__pstl/backends/libdispatch.h
@@ -36,6 +36,7 @@
#include <__optional/optional.h>
#include <__pstl/backend_fwd.h>
#include <__pstl/cpu_algos/any_of.h>
+#include <__pstl/cpu_algos/copy_if.h>
#include <__pstl/cpu_algos/cpu_traits.h>
#include <__pstl/cpu_algos/fill.h>
#include <__pstl/cpu_algos/find_end.h>
@@ -54,6 +55,8 @@
#include <__pstl/cpu_algos/transform.h>
#include <__pstl/cpu_algos/transform_reduce.h>
#include <__pstl/cpu_algos/uninitialized_algorithms.h>
+#include <__pstl/decoupled_lookback.h>
+#include <__thread/thread.h>
#include <__utility/empty.h>
#include <__utility/exception_guard.h>
#include <__utility/move.h>
@@ -273,6 +276,68 @@ struct __cpu_traits<__libdispatch_backend_tag> {
__combiner);
}
+ template <class _Value,
+ class _RandomAccessIterator,
+ class _WorkerPrologue,
+ class _ScanHead,
+ class _ScanMiddle,
+ class _ScanTail,
+ class _WorkerEpilogue>
+ _LIBCPP_HIDE_FROM_ABI static optional<__empty> __lookback_scan(
+ _RandomAccessIterator __first,
+ _RandomAccessIterator __last,
+ _WorkerPrologue __worker_prologue,
+ _ScanHead __scan_head,
+ _ScanMiddle __scan_middle,
+ _ScanTail __scan_tail,
+ _WorkerEpilogue __worker_epilogue) {
+ if (__first == __last)
+ return __empty{}; // nothing to do
+
+ // Partition the input and allocate the lookback storage
+ __libdispatch::__chunk_partitions __partitions = __libdispatch::__partition_chunks(__last - __first);
+ __decoupled_lookback<_Value> __lookback{static_cast<size_t>(__partitions.__chunk_count_ - 1)};
+ if (__partitions.__chunk_count_ > 1 && __lookback.__size() == 0) {
+ return nullopt; // failed to allocate the lookback storage
+ }
+
+ size_t __max_workers_count = thread::hardware_concurrency();
+
+ // Run the single-pass scan with decoupled lookback
+ atomic<size_t> __next_chunk{0};
+ __libdispatch::__dispatch_apply(__max_workers_count, [&](size_t /*__worker_id*/) {
+ auto __worker_ctx = __worker_prologue(
+ static_cast<size_t>(std::max(__partitions.__first_chunk_size_, __partitions.__chunk_size_)));
+
+ size_t __chunk;
+ while ((__chunk = __next_chunk.fetch_add(1, std::memory_order_relaxed)) <
+ static_cast<size_t>(__partitions.__chunk_count_)) {
+ auto __this_chunk_size = __chunk == 0 ? __partitions.__first_chunk_size_ : __partitions.__chunk_size_;
+ auto __index = __chunk == 0 ? 0
+ : (__chunk * __partitions.__chunk_size_) +
+ (__partitions.__first_chunk_size_ - __partitions.__chunk_size_);
+ if (__chunk == 0) {
+ __scan_head(__worker_ctx,
+ __first + __index,
+ __first + __index + __this_chunk_size,
+ __partitions.__chunk_count_ > 1 ? &__lookback.__partition(0) : nullptr);
+ } else if (__chunk < __lookback.__size()) {
+ __scan_middle(
+ __worker_ctx, __first + __index, __first + __index + __this_chunk_size, &__lookback.__partition(__chunk));
+ } else {
+ __scan_tail(__worker_ctx,
+ __first + __index,
+ __first + __index + __this_chunk_size,
+ &__lookback.__partition(__chunk - 1) + 1);
+ }
+
+ __worker_epilogue(std::move(__worker_ctx));
+ }
+ });
+
+ return __empty{};
+ }
+
template <class _RandomAccessIterator, class _Comp, class _LeafSort>
_LIBCPP_HIDE_FROM_ABI static optional<__empty>
__stable_sort(_RandomAccessIterator __first, _RandomAccessIterator __last, _Comp __comp, _LeafSort __leaf_sort) {
@@ -369,6 +434,10 @@ struct __cpu_traits<__libdispatch_backend_tag> {
};
// Mandatory implementations of the computational basis
+template <class _ExecutionPolicy>
+struct __copy_if<__libdispatch_backend_tag, _ExecutionPolicy>
+ : __cpu_parallel_copy_if<__libdispatch_backend_tag, _ExecutionPolicy> {};
+
template <class _ExecutionPolicy>
struct __find_end<__libdispatch_backend_tag, _ExecutionPolicy>
: __cpu_parallel_find_end<__libdispatch_backend_tag, _ExecutionPolicy> {};
diff --git a/libcxx/include/__pstl/backends/std_thread.h b/libcxx/include/__pstl/backends/std_thread.h
index e8b7e2fbd9458d..1f79287c5a7409 100644
--- a/libcxx/include/__pstl/backends/std_thread.h
+++ b/libcxx/include/__pstl/backends/std_thread.h
@@ -14,6 +14,7 @@
#include <__optional/optional.h>
#include <__pstl/backend_fwd.h>
#include <__pstl/cpu_algos/any_of.h>
+#include <__pstl/cpu_algos/copy_if.h>
#include <__pstl/cpu_algos/cpu_traits.h>
#include <__pstl/cpu_algos/fill.h>
#include <__pstl/cpu_algos/find_end.h>
@@ -69,6 +70,29 @@ struct __cpu_traits<__std_thread_backend_tag> {
return __reduce(std::move(__first), std::move(__last), std::move(__init));
}
+ template <class _Value,
+ class _RandomAccessIterator,
+ class _WorkerPrologue,
+ class _ScanHead,
+ class _ScanMiddle,
+ class _ScanTail,
+ class _WorkerEpilogue>
+ _LIBCPP_HIDE_FROM_ABI static optional<__empty> __lookback_scan(
+ _RandomAccessIterator __first,
+ _RandomAccessIterator __last,
+ _WorkerPrologue __worker_prologue,
+ _ScanHead __scan_head,
+ _ScanMiddle /*__scan_middle*/,
+ _ScanTail /*__scan_tail*/,
+ _WorkerEpilogue __worker_epilogue) {
+ if (__first == __last)
+ return __empty{}; // nothing to do
+ auto __ctx = __worker_prologue(static_cast<size_t>(__last - __first));
+ __scan_head(__ctx, __first, __last, nullptr);
+ __worker_epilogue(std::move(__ctx));
+ return __empty{};
+ }
+
template <class _RandomAccessIterator, class _Compare, class _LeafSort>
_LIBCPP_HIDE_FROM_ABI static optional<__empty>
__stable_sort(_RandomAccessIterator __first, _RandomAccessIterator __last, _Compare __comp, _LeafSort __leaf_sort) {
@@ -99,6 +123,10 @@ struct __cpu_traits<__std_thread_backend_tag> {
};
// Mandatory implementations of the computational basis
+template <class _ExecutionPolicy>
+struct __copy_if<__std_thread_backend_tag, _ExecutionPolicy>
+ : __cpu_parallel_copy_if<__std_thread_backend_tag, _ExecutionPolicy> {};
+
template <class _ExecutionPolicy>
struct __find_end<__std_thread_backend_tag, _ExecutionPolicy>
: __cpu_parallel_find_end<__std_thread_backend_tag, _ExecutionPolicy> {};
diff --git a/libcxx/include/__pstl/cpu_algos/copy_if.h b/libcxx/include/__pstl/cpu_algos/copy_if.h
new file mode 100644
index 00000000000000..a5fdc4576fca48
--- /dev/null
+++ b/libcxx/include/__pstl/cpu_algos/copy_if.h
@@ -0,0 +1,201 @@
+//===----------------------------------------------------------------------===//
+//
+// 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_COPY_IF_H
+#define _LIBCPP___PSTL_CPU_ALGOS_COPY_IF_H
+
+#include <__algorithm/copy_if.h>
+#include <__algorithm/fill.h>
+#include <__config>
+#include <__functional/operations.h>
+#include <__iterator/iterator_traits.h>
+#include <__memory/unique_ptr.h>
+#include <__new/global_new_delete.h>
+#include <__new/nothrow_t.h>
+#include <__optional/optional.h>
+#include <__pstl/backend_fwd.h>
+#include <__pstl/cpu_algos/cpu_traits.h>
+#include <__pstl/cpu_algos/find_if.h>
+#include <__pstl/decoupled_lookback.h>
+#include <__type_traits/is_execution_policy.h>
+#include <__utility/move.h>
+#include <climits> // for CHAR_BIT
+
+#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 {
+
+struct _LIBCPP_HIDE_FROM_ABI __dynamic_bitset {
+ size_t __size;
+ std::unique_ptr<size_t[]> __bits;
+
+ _LIBCPP_HIDE_FROM_ABI __dynamic_bitset(size_t __max_chunk_size)
+ : __size((__max_chunk_size + CHAR_BIT * sizeof(size_t) - 1) / (CHAR_BIT * sizeof(size_t))),
+ __bits(new (std::nothrow) size_t[__size]) {}
+ _LIBCPP_HIDE_FROM_ABI __dynamic_bitset(__dynamic_bitset&&) noexcept = default;
+ _LIBCPP_HIDE_FROM_ABI __dynamic_bitset& operator=(__dynamic_bitset&&) noexcept = default;
+ _LIBCPP_HIDE_FROM_ABI bool __test(size_t __pos) const {
+ size_t __word_index = __pos / (CHAR_BIT * sizeof(size_t));
+ size_t __bit_index = __pos % (CHAR_BIT * sizeof(size_t));
+ return (__bits[__word_index] & (size_t(1) << __bit_index)) != 0;
+ }
+ _LIBCPP_HIDE_FROM_ABI void __set(size_t __pos) {
+ size_t __word_index = __pos / (CHAR_BIT * sizeof(size_t));
+ size_t __bit_index = __pos % (CHAR_BIT * sizeof(size_t));
+ __bits[__word_index] |= (size_t(1) << __bit_index);
+ }
+ _LIBCPP_HIDE_FROM_ABI void __reset(size_t __pos) {
+ size_t __word_index = __pos / (CHAR_BIT * sizeof(size_t));
+ size_t __bit_index = __pos % (CHAR_BIT * sizeof(size_t));
+ __bits[__word_index] &= ~(size_t(1) << __bit_index);
+ }
+ _LIBCPP_HIDE_FROM_ABI void __reset() {
+ for (size_t i = 0; i < __size; ++i) {
+ __bits[i] = 0;
+ }
+ }
+};
+
+template <class _ForwardIterator, class _Pred>
+_LIBCPP_HIDE_FROM_ABI size_t __count_and_cache_predicate_results(
+ _ForwardIterator __first, _ForwardIterator __last, const _Pred& __pred, __dynamic_bitset& __bitset) {
+ size_t __occupied_count = 0;
+ size_t __local_idx = 0;
+ for (auto __it = __first; __it != __last; ++__it) {
+ if (__pred(*__it)) {
+ __bitset.__set(__local_idx);
+ ++__occupied_count;
+ }
+ ++__local_idx;
+ }
+ return __occupied_count;
+}
+
+template <class _ForwardIterator1, class _ForwardIterator2>
+_LIBCPP_HIDE_FROM_ABI _ForwardIterator2 __copy_if_flag_set(
+ _ForwardIterator1 __first, _ForwardIterator1 __last, _ForwardIterator2 __result, __dynamic_bitset& __bitset) {
+ size_t __local_idx = 0;
+ for (auto __it = __first; __it != __last; ++__it) {
+ if (__bitset.__test(__local_idx)) {
+ *__result = *__it;
+ ++__result;
+ }
+ ++__local_idx;
+ }
+ return __result;
+}
+
+template <class _Backend, class _RawExecutionPolicy>
+struct __cpu_parallel_copy_if {
+ template <class _Policy, class _ForwardIterator1, class _ForwardIterator2, class _Pred>
+ _LIBCPP_HIDE_FROM_ABI optional<_ForwardIterator2>
+ operator()(_Policy&&, _ForwardIterator1 __first, _ForwardIterator1 __last, _ForwardIterator2 __result, _Pred __pred)
+ 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 __prologue = [](size_t __max_chunk_size) { return __dynamic_bitset(__max_chunk_size); };
+ auto __epilogue = [](__dynamic_bitset&&) {};
+
+ _ForwardIterator2 __out_iter = __result;
+
+ // TODO: support the case when the worker context was not allocated
+
+ auto __scan_head =
+ [&](__dynamic_bitset& __worker_ctx,
+ _ForwardIterator1 __chunk_first,
+ _ForwardIterator1 __chunk_last,
+ __decoupled_lookback_partition<size_t>* __optional_lookback_partition) {
+ __worker_ctx.__reset();
+
+ size_t __occupied_count =
+ __count_and_cache_predicate_results(__chunk_first, __chunk_last, __pred, __worker_ctx);
+
+ if (__optional_lookback_partition != nullptr) {
+ __optional_lookback_partition->__construct_inclusive_prefix(__occupied_count);
+ }
+
+ _ForwardIterator2 __chunk_result = __copy_if_flag_set(__chunk_first, __chunk_last, __result, __worker_ctx);
+
+ if (__optional_lookback_partition == nullptr) {
+ __out_iter = __chunk_result; // TODO: explain
+ }
+ };
+
+ auto __scan_middle =
+ [&](__dynamic_bitset& __worker_ctx,
+ _ForwardIterator1 __chunk_first,
+ _ForwardIterator1 __chunk_last,
+ __decoupled_lookback_partition<size_t>* __lookback_partition) {
+ __worker_ctx.__reset();
+
+ size_t __occupied_count =
+ __count_and_cache_predicate_results(__chunk_first, __chunk_last, __pred, __worker_ctx);
+
+ __lookback_partition->__construct_aggregate(__occupied_count);
+ __decoupled_lookback_partition<size_t>* __prev_partition = __lookback_partition - 1;
+ size_t __exclusive_prefix =
+ (__prev_partition->__acquire_available_status() & __decoupled_lookback_status_prefix_available)
+ ? __prev_partition->__inclusive_prefix()
+ : __calculate_inclusive_prefix_of_partition(__prev_partition, plus<>{});
+
+ __lookback_partition->__construct_inclusive_prefix(__exclusive_prefix + __occupied_count);
+
+ __copy_if_flag_set(__chunk_first, __chunk_last, __result + __exclusive_prefix, __worker_ctx);
+ };
+
+ auto __scan_tail =
+ [&](__dynamic_bitset& __worker_ctx,
+ _ForwardIterator1 __chunk_first,
+ _ForwardIterator1 __chunk_last,
+ __decoupled_lookback_partition<size_t>* __nonexistent_lookback_partition) {
+ __worker_ctx.__reset();
+
+ __count_and_cache_predicate_results(__chunk_first, __chunk_last, __pred, __worker_ctx);
+
+ __decoupled_lookback_partition< size_t >* __prev_partition = __nonexistent_lookback_partition - 1;
+
+ size_t __exclusive_prefix =
+ (__prev_partition->__acquire_available_status() & __decoupled_lookback_status_prefix_available)
+ ? __prev_partition->__inclusive_prefix()
+ : __calculate_inclusive_prefix_of_partition(__prev_partition, plus<>{});
+
+ _ForwardIterator2 __chunk_result =
+ __copy_if_flag_set(__chunk_first, __chunk_last, __result + __exclusive_prefix, __worker_ctx);
+
+ __out_iter = __chunk_result;
+ };
+
+ auto __ret = __cpu_traits<_Backend>::template __lookback_scan<size_t>(
+ __first, __last, __prologue, __scan_head, __scan_middle, __scan_tail, __epilogue);
+ if (!__ret)
+ return nullopt;
+
+ return __out_iter;
+ } else {
+ return std::copy_if(std::move(__first), std::move(__last), std::move(__result), std::move(__pred));
+ }
+ }
+};
+
+} // namespace __pstl
+_LIBCPP_END_NAMESPACE_STD
+
+#endif // _LIBCPP_STD_VER >= 17
+
+_LIBCPP_POP_MACROS
+
+#endif // _LIBCPP___PSTL_CPU_ALGOS_COPY_IF_H
diff --git a/libcxx/include/__pstl/decoupled_lookback.h b/libcxx/include/__pstl/decoupled_lookback.h
new file mode 100644
index 00000000000000..5759cba18ec967
--- /dev/null
+++ b/libcxx/include/__pstl/decoupled_lookback.h
@@ -0,0 +1,161 @@
+//===----------------------------------------------------------------------===//
+//
+// 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_DECOUPLED_LOOKBACK_H
+#define _LIBCPP___PSTL_DECOUPLED_LOOKBACK_H
+
+#include <__atomic/atomic.h>
+#include <__atomic/atomic_sync.h>
+#include <__config>
+#include <__memory/construct_at.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 {
+
+// Based on the paper "Single-pass Parallel Prefix Scan with Decoupled Look-back" (Merrill, Garland, 2016).
+
+inline constexpr unsigned char __decoupled_lookback_status_invalid = 0;
+inline constexpr unsigned char __decoupled_lookback_status_aggregate_available = 1 << 0;
+inline constexpr unsigned char __decoupled_lookback_status_prefix_available = 1 << 1;
+
+template <typename _Tp>
+struct _LIBCPP_HIDE_FROM_ABI __decoupled_lookback_partition {
+ __decoupled_lookback_partition() = default;
+ __decoupled_lookback_partition(const __decoupled_lookback_partition&) = delete;
+ __decoupled_lookback_partition& operator=(const __decoupled_lookback_partition&) = delete;
+
+ // Destructor: destroy the aggregate and inclusive prefix if they have been constructed.
+ ~__decoupled_lookback_partition() {
+ unsigned char __flag = __status_flag.load(std::memory_order_relaxed);
+ if (__flag & __decoupled_lookback_status_aggregate_available) {
+ __aggregate().~_Tp();
+ }
+ if (__flag & __decoupled_lookback_status_prefix_available) {
+ __inclusive_prefix().~_Tp();
+ }
+ }
+
+ // Acquire the current status of the partition.
+ // If no value is published is available yet, wait until one becomes available.
+ _LIBCPP_HIDE_FROM_ABI unsigned char __acquire_available_status() {
+ unsigned char __current_status = __status_flag.load(std::memory_order_acquire);
+ if (__current_status != __decoupled_lookback_status_invalid)
+ return __current_status;
+ std::__atomic_wait(__status_flag, __decoupled_lookback_status_invalid, std::memory_order_acquire);
+ return __status_flag.load(std::memory_order_acquire);
+ }
+
+ // Construct the aggregate value of the partition and publish this change.
+ template <class... _Args>
+ _LIBCPP_HIDE_FROM_ABI void __construct_aggregate(_Args&&... __args) {
+ std::__construct_at(&reinterpret_cast<_Tp&>(__aggregate_storage), std::forward<_Args>(__args)...);
+ __status_flag.store(__status_flag.load(std::memory_order_relaxed) | __decoupled_lookback_status_aggregate_available,
+ std::memory_order_release);
+ std::__atomic_notify_all(__status_flag);
+ }
+
+ // Access the aggregate value of the partition.
+ // Precondition: __acquire_available_status() & __decoupled_lookback_status_aggregate_available
+ _LIBCPP_HIDE_FROM_ABI const _Tp& __aggregate() const { return reinterpret_cast<const _Tp&>(__aggregate_storage); }
+
+ // Construct the inclusive prefix of the partition and publish this change.
+ template <class... _Args>
+ _LIBCPP_HIDE_FROM_ABI void __construct_inclusive_prefix(_Args&&... __args) {
+ std::__construct_at(&reinterpret_cast<_Tp&>(__inclusive_prefix_storage), std::forward<_Args>(__args)...);
+ __status_flag.store(__status_flag.load(std::memory_order_relaxed) | __decoupled_lookback_status_prefix_available,
+ std::memory_order_release);
+ std::__atomic_notify_all(__status_flag);
+ }
+
+ // Access the inclusive prefix of the partition.
+ // Precondition: __acquire_available_status() & __decoupled_lookback_status_prefix_available
+ _LIBCPP_HIDE_FROM_ABI const _Tp& __inclusive_prefix() const {
+ return reinterpret_cast<const _Tp&>(__inclusive_prefix_storage);
+ }
+
+private:
+ // Atomic/waitable flag indicating the status of the partition.
+ std::atomic<unsigned char> __status_flag{__decoupled_lookback_status_invalid};
+
+ // Storage for the aggregate reduced value of the partition.
+ alignas(_Tp) unsigned char __aggregate_storage[sizeof(_Tp)];
+
+ // Storage for the inclusive prefix of the partition.
+ alignas(_Tp) unsigned char __inclusive_prefix_storage[sizeof(_Tp)];
+};
+
+// Calculate an inclusive prefix of a given partition that currently has only an aggregate.
+// Performs reductions of aggregate values until it arrives at a partition with an available prefix.
+// Precondition: __partition->__acquire_available_status() == __decoupled_lookback_status_aggregate_available.
+template <typename _Tp, class _BinaryOperation>
+_LIBCPP_HIDE_FROM_ABI _Tp
+__calculate_inclusive_prefix_of_partition(__decoupled_lookback_partition<_Tp>* __partition, _BinaryOperation __reduce) {
+ // Start at the given partition
+ _Tp __prefix = __partition->__aggregate();
+ while (true) {
+ // Move to the previous partition and check its status.
+ --__partition;
+ unsigned char __status = __partition->__acquire_available_status();
+ if (__status & __decoupled_lookback_status_prefix_available) {
+ // Found a partition with an available prefix - can perform a final reduction and terminate.
+ __prefix = __reduce(__partition->__inclusive_prefix(), std::move(__prefix));
+ break;
+ } else /* if(__status & __decoupled_lookback_status_aggregate_available) */ {
+ // Reduce with another aggregate and continue moving left.
+ __prefix = __reduce(__partition->__aggregate(), std::move(__prefix));
+ }
+ }
+ return __prefix;
+}
+
+template <typename _Tp>
+class _LIBCPP_HIDE_FROM_ABI __decoupled_lookback {
+public:
+ // Allocates the memory for the lookback partitions.
+ // Does not throw, instead sets the size to 0 if allocation fails.
+ _LIBCPP_HIDE_FROM_ABI explicit __decoupled_lookback(size_t __size) {
+ __partitions_ = __size > 0 ? new (std::nothrow) __decoupled_lookback_partition<_Tp>[__size] : nullptr;
+ __partitions_count_ = __partitions_ ? __size : 0;
+ }
+
+ _LIBCPP_HIDE_FROM_ABI ~__decoupled_lookback() { delete[] __partitions_; }
+
+ __decoupled_lookback(const __decoupled_lookback&) = delete;
+
+ __decoupled_lookback& operator=(const __decoupled_lookback&) = delete;
+
+ _LIBCPP_HIDE_FROM_ABI size_t __size() const { return __partitions_count_; }
+
+ _LIBCPP_HIDE_FROM_ABI __decoupled_lookback_partition<_Tp>* __partitions() const { return __partitions_; }
+
+ _LIBCPP_HIDE_FROM_ABI __decoupled_lookback_partition<_Tp>& __partition(size_t __index) const {
+ return __partitions_[__index];
+ }
+
+private:
+ __decoupled_lookback_partition<_Tp>* __partitions_;
+ size_t __partitions_count_;
+};
+
+} // namespace __pstl
+_LIBCPP_END_NAMESPACE_STD
+
+#endif // _LIBCPP_STD_VER >= 17
+
+_LIBCPP_POP_MACROS
+
+#endif // _LIBCPP___PSTL_DECOUPLED_LOOKBACK_H
diff --git a/libcxx/include/module.modulemap.in b/libcxx/include/module.modulemap.in
index 28ae9ee8de46a3..5bbc6f4718a9ea 100644
--- a/libcxx/include/module.modulemap.in
+++ b/libcxx/include/module.modulemap.in
@@ -2426,6 +2426,9 @@ module std {
module any_of {
header "__pstl/cpu_algos/any_of.h"
}
+ module copy_if {
+ header "__pstl/cpu_algos/copy_if.h"
+ }
module cpu_traits {
header "__pstl/cpu_algos/cpu_traits.h"
}
diff --git a/libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/pstl.copy_if.pass.cpp b/libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/pstl.copy_if.pass.cpp
new file mode 100644
index 00000000000000..607569089d589e
--- /dev/null
+++ b/libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/pstl.copy_if.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
+
+// template <class ExecutionPolicy,
+// class ForwardIterator1,
+// class ForwardIterator2,
+// class UnaryPredicate>
+// ForwardIterator2 copy_if(ExecutionPolicy&& exec,
+// ForwardIterator1 first,
+// ForwardIterator1 last,
+// ForwardIterator2 result,
+// UnaryPredicate pred);
+
+#include <algorithm>
+#include <cassert>
+#include <functional>
+#include <iterator>
+#include <limits>
+#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(copy_if);
+
+static_assert(sfinae_test_copy_if<int, int*, int*, int*, bool (*)(int)>);
+static_assert(!sfinae_test_copy_if<std::execution::parallel_policy, int*, int*, int*, bool (*)(int)>);
+
+template <class Iter1, class Iter2>
+struct TestInt {
+ template <class ExecutionPolicy>
+ void operator()(ExecutionPolicy&& policy) {
+ auto always_true = [](int) { return true; };
+ auto always_false = [](int) { return false; };
+ { // Check the return types
+ int in[] = {0};
+ int out[] = {-1};
+ auto res =
+ std::copy_if(policy, Iter1(std::begin(in)), Iter1(std::begin(in)), Iter2(std::begin(out)), always_true);
+ static_assert(std::is_same_v<decltype(res), Iter2>);
+ }
+ { // Empty range
+ int in[] = {0};
+ int out[] = {-1};
+ auto res =
+ std::copy_if(policy, Iter1(std::begin(in)), Iter1(std::begin(in)), Iter2(std::begin(out)), always_true);
+ assert(res == Iter2(std::begin(out)));
+ }
+ { // Single, true
+ int in[] = {0};
+ int out[] = {-1};
+ auto res = std::copy_if(policy, Iter1(std::begin(in)), Iter1(std::end(in)), Iter2(std::begin(out)), always_true);
+ assert(res == Iter2(std::begin(out) + 1));
+ assert(out[0] == 0);
+ }
+ { // Single, false
+ int in[] = {0};
+ int out[] = {-1};
+ auto res = std::copy_if(policy, Iter1(std::begin(in)), Iter1(std::end(in)), Iter2(std::begin(out)), always_false);
+ assert(res == Iter2(std::begin(out)));
+ assert(out[0] == -1);
+ }
+ {
+ // Two, true, false
+ int in[] = {0, 1};
+ int out[] = {-1, -1};
+ auto res = std::copy_if(policy, Iter1(std::begin(in)), Iter1(std::end(in)), Iter2(std::begin(out)), [](int x) {
+ return x == 0;
+ });
+ assert(res == Iter2(std::begin(out) + 1));
+ assert(out[0] == 0);
+ assert(out[1] == -1);
+ }
+ {
+ // Two, false, true
+ int in[] = {0, 1};
+ int out[] = {-1, -1};
+ auto res = std::copy_if(policy, Iter1(std::begin(in)), Iter1(std::end(in)), Iter2(std::begin(out)), [](int x) {
+ return x != 0;
+ });
+ assert(res == Iter2(std::begin(out) + 1));
+ assert(out[0] == 1);
+ assert(out[1] == -1);
+ }
+ { // iotaed input sampled at different lengths, pred: always true
+ int in[1073];
+ int out[1073];
+ std::iota(std::begin(in), std::end(in), 0);
+ runway_sample(std::size(in) + 1, [&](size_t n) {
+ std::fill(std::begin(out), std::end(out), -1);
+ auto res =
+ std::copy_if(policy, Iter1(std::begin(in)), Iter1(std::begin(in) + n), Iter2(std::begin(out)), always_true);
+ assert(res == Iter2(std::begin(out) + n));
+ for (size_t i = 0; i < std::size(out); ++i) {
+ assert(out[i] == (i < n ? in[i] : -1));
+ }
+ });
+ }
+ { // iotaed input sampled at different lengths, pred: always false
+ int in[1073];
+ int out[1073];
+ std::iota(std::begin(in), std::end(in), 0);
+ runway_sample(std::size(in) + 1, [&](size_t n) {
+ std::fill(std::begin(out), std::end(out), -1);
+ auto res = std::copy_if(
+ policy, Iter1(std::begin(in)), Iter1(std::begin(in) + n), Iter2(std::begin(out)), always_false);
+ assert(res == Iter2(std::begin(out)));
+ for (size_t i = 0; i < std::size(out); ++i) {
+ assert(out[i] == -1);
+ }
+ });
+ }
+ { // iotaed input sampled at different lengths, pred: x % 3 == 0
+ int in[1073];
+ int out[1073];
+ std::iota(std::begin(in), std::end(in), 0);
+ runway_sample(std::size(in) + 1, [&](size_t n) {
+ std::fill(std::begin(out), std::end(out), -1);
+ auto res =
+ std::copy_if(policy, Iter1(std::begin(in)), Iter1(std::begin(in) + n), Iter2(std::begin(out)), [](int x) {
+ return x % 3 == 0;
+ });
+ assert(res == Iter2(std::begin(out) + (n + 2) / 3));
+ auto out_count = std::distance(Iter2(std::begin(out)), res);
+ for (size_t i = 0; i < std::size(out); ++i) {
+ if (i < static_cast<size_t>(out_count)) {
+ assert(out[i] % 3 == 0);
+ assert(out[i] == (i > 0 ? out[i - 1] + 3 : 0));
+ } else {
+ assert(out[i] == -1);
+ }
+ }
+ });
+ }
+ }
+};
+
+int main(int, char**) {
+ types::for_each(types::forward_iterator_list<const 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>{});
+ }});
+ return 0;
+}
>From c6b78525c9e6437738ca82b12862261969eea28b Mon Sep 17 00:00:00 2001
From: Michael Kazakov <mike.kazakov at gmail.com>
Date: Tue, 6 Oct 2026 16:57:51 +0100
Subject: [PATCH 2/2] Require C++20 for now
---
libcxx/include/__pstl/backends/libdispatch.h | 7 ++++
libcxx/include/__pstl/cpu_algos/copy_if.h | 39 ++++++++++---------
libcxx/include/__pstl/decoupled_lookback.h | 39 ++++++++++---------
.../alg.copy/pstl.copy_if.pass.cpp | 3 +-
4 files changed, 49 insertions(+), 39 deletions(-)
diff --git a/libcxx/include/__pstl/backends/libdispatch.h b/libcxx/include/__pstl/backends/libdispatch.h
index 8b62e6ffebf075..46ce57013f2971 100644
--- a/libcxx/include/__pstl/backends/libdispatch.h
+++ b/libcxx/include/__pstl/backends/libdispatch.h
@@ -276,6 +276,8 @@ struct __cpu_traits<__libdispatch_backend_tag> {
__combiner);
}
+# if _LIBCPP_STD_VER >= 20 // TODO: remove once https://github.com/llvm/llvm-project/pull/224356 is merged
+
template <class _Value,
class _RandomAccessIterator,
class _WorkerPrologue,
@@ -338,6 +340,8 @@ struct __cpu_traits<__libdispatch_backend_tag> {
return __empty{};
}
+# endif // _LIBCPP_STD_VER >= 20
+
template <class _RandomAccessIterator, class _Comp, class _LeafSort>
_LIBCPP_HIDE_FROM_ABI static optional<__empty>
__stable_sort(_RandomAccessIterator __first, _RandomAccessIterator __last, _Comp __comp, _LeafSort __leaf_sort) {
@@ -434,9 +438,12 @@ struct __cpu_traits<__libdispatch_backend_tag> {
};
// Mandatory implementations of the computational basis
+
+# if _LIBCPP_STD_VER >= 20 // TODO: remove once https://github.com/llvm/llvm-project/pull/224356 is merged
template <class _ExecutionPolicy>
struct __copy_if<__libdispatch_backend_tag, _ExecutionPolicy>
: __cpu_parallel_copy_if<__libdispatch_backend_tag, _ExecutionPolicy> {};
+# endif // _LIBCPP_STD_VER >= 20
template <class _ExecutionPolicy>
struct __find_end<__libdispatch_backend_tag, _ExecutionPolicy>
diff --git a/libcxx/include/__pstl/cpu_algos/copy_if.h b/libcxx/include/__pstl/cpu_algos/copy_if.h
index a5fdc4576fca48..1ce4b7534e24c2 100644
--- a/libcxx/include/__pstl/cpu_algos/copy_if.h
+++ b/libcxx/include/__pstl/cpu_algos/copy_if.h
@@ -33,38 +33,38 @@
_LIBCPP_PUSH_MACROS
#include <__undef_macros>
-#if _LIBCPP_STD_VER >= 17
+#if _LIBCPP_STD_VER >= 20 // TODO: should be 17 once https://github.com/llvm/llvm-project/pull/224356 is merged
_LIBCPP_BEGIN_NAMESPACE_STD
namespace __pstl {
struct _LIBCPP_HIDE_FROM_ABI __dynamic_bitset {
- size_t __size;
- std::unique_ptr<size_t[]> __bits;
+ size_t __size_;
+ std::unique_ptr<size_t[]> __bits_;
_LIBCPP_HIDE_FROM_ABI __dynamic_bitset(size_t __max_chunk_size)
- : __size((__max_chunk_size + CHAR_BIT * sizeof(size_t) - 1) / (CHAR_BIT * sizeof(size_t))),
- __bits(new (std::nothrow) size_t[__size]) {}
+ : __size_((__max_chunk_size + CHAR_BIT * sizeof(size_t) - 1) / (CHAR_BIT * sizeof(size_t))),
+ __bits_(new (std::nothrow) size_t[__size_]) {}
_LIBCPP_HIDE_FROM_ABI __dynamic_bitset(__dynamic_bitset&&) noexcept = default;
_LIBCPP_HIDE_FROM_ABI __dynamic_bitset& operator=(__dynamic_bitset&&) noexcept = default;
_LIBCPP_HIDE_FROM_ABI bool __test(size_t __pos) const {
size_t __word_index = __pos / (CHAR_BIT * sizeof(size_t));
size_t __bit_index = __pos % (CHAR_BIT * sizeof(size_t));
- return (__bits[__word_index] & (size_t(1) << __bit_index)) != 0;
+ return (__bits_[__word_index] & (size_t(1) << __bit_index)) != 0;
}
_LIBCPP_HIDE_FROM_ABI void __set(size_t __pos) {
size_t __word_index = __pos / (CHAR_BIT * sizeof(size_t));
size_t __bit_index = __pos % (CHAR_BIT * sizeof(size_t));
- __bits[__word_index] |= (size_t(1) << __bit_index);
+ __bits_[__word_index] |= (size_t(1) << __bit_index);
}
_LIBCPP_HIDE_FROM_ABI void __reset(size_t __pos) {
size_t __word_index = __pos / (CHAR_BIT * sizeof(size_t));
size_t __bit_index = __pos % (CHAR_BIT * sizeof(size_t));
- __bits[__word_index] &= ~(size_t(1) << __bit_index);
+ __bits_[__word_index] &= ~(size_t(1) << __bit_index);
}
_LIBCPP_HIDE_FROM_ABI void __reset() {
- for (size_t i = 0; i < __size; ++i) {
- __bits[i] = 0;
+ for (size_t i = 0; i < __size_; ++i) {
+ __bits_[i] = 0;
}
}
};
@@ -122,13 +122,14 @@ struct __cpu_parallel_copy_if {
__worker_ctx.__reset();
size_t __occupied_count =
- __count_and_cache_predicate_results(__chunk_first, __chunk_last, __pred, __worker_ctx);
+ __pstl::__count_and_cache_predicate_results(__chunk_first, __chunk_last, __pred, __worker_ctx);
if (__optional_lookback_partition != nullptr) {
__optional_lookback_partition->__construct_inclusive_prefix(__occupied_count);
}
- _ForwardIterator2 __chunk_result = __copy_if_flag_set(__chunk_first, __chunk_last, __result, __worker_ctx);
+ _ForwardIterator2 __chunk_result =
+ __pstl::__copy_if_flag_set(__chunk_first, __chunk_last, __result, __worker_ctx);
if (__optional_lookback_partition == nullptr) {
__out_iter = __chunk_result; // TODO: explain
@@ -143,18 +144,18 @@ struct __cpu_parallel_copy_if {
__worker_ctx.__reset();
size_t __occupied_count =
- __count_and_cache_predicate_results(__chunk_first, __chunk_last, __pred, __worker_ctx);
+ __pstl::__count_and_cache_predicate_results(__chunk_first, __chunk_last, __pred, __worker_ctx);
__lookback_partition->__construct_aggregate(__occupied_count);
__decoupled_lookback_partition<size_t>* __prev_partition = __lookback_partition - 1;
size_t __exclusive_prefix =
(__prev_partition->__acquire_available_status() & __decoupled_lookback_status_prefix_available)
? __prev_partition->__inclusive_prefix()
- : __calculate_inclusive_prefix_of_partition(__prev_partition, plus<>{});
+ : __pstl::__calculate_inclusive_prefix_of_partition(__prev_partition, plus<>{});
__lookback_partition->__construct_inclusive_prefix(__exclusive_prefix + __occupied_count);
- __copy_if_flag_set(__chunk_first, __chunk_last, __result + __exclusive_prefix, __worker_ctx);
+ __pstl::__copy_if_flag_set(__chunk_first, __chunk_last, __result + __exclusive_prefix, __worker_ctx);
};
auto __scan_tail =
@@ -164,17 +165,17 @@ struct __cpu_parallel_copy_if {
__decoupled_lookback_partition<size_t>* __nonexistent_lookback_partition) {
__worker_ctx.__reset();
- __count_and_cache_predicate_results(__chunk_first, __chunk_last, __pred, __worker_ctx);
+ __pstl::__count_and_cache_predicate_results(__chunk_first, __chunk_last, __pred, __worker_ctx);
__decoupled_lookback_partition< size_t >* __prev_partition = __nonexistent_lookback_partition - 1;
size_t __exclusive_prefix =
(__prev_partition->__acquire_available_status() & __decoupled_lookback_status_prefix_available)
? __prev_partition->__inclusive_prefix()
- : __calculate_inclusive_prefix_of_partition(__prev_partition, plus<>{});
+ : __pstl::__calculate_inclusive_prefix_of_partition(__prev_partition, plus<>{});
_ForwardIterator2 __chunk_result =
- __copy_if_flag_set(__chunk_first, __chunk_last, __result + __exclusive_prefix, __worker_ctx);
+ __pstl::__copy_if_flag_set(__chunk_first, __chunk_last, __result + __exclusive_prefix, __worker_ctx);
__out_iter = __chunk_result;
};
@@ -194,7 +195,7 @@ struct __cpu_parallel_copy_if {
} // namespace __pstl
_LIBCPP_END_NAMESPACE_STD
-#endif // _LIBCPP_STD_VER >= 17
+#endif // _LIBCPP_STD_VER >= 20
_LIBCPP_POP_MACROS
diff --git a/libcxx/include/__pstl/decoupled_lookback.h b/libcxx/include/__pstl/decoupled_lookback.h
index 5759cba18ec967..a25c73b3cfca7d 100644
--- a/libcxx/include/__pstl/decoupled_lookback.h
+++ b/libcxx/include/__pstl/decoupled_lookback.h
@@ -21,7 +21,7 @@
_LIBCPP_PUSH_MACROS
#include <__undef_macros>
-#if _LIBCPP_STD_VER >= 17
+#if _LIBCPP_STD_VER >= 20 // TODO: should be 17 once https://github.com/llvm/llvm-project/pull/224356 is merged
_LIBCPP_BEGIN_NAMESPACE_STD
namespace __pstl {
@@ -40,7 +40,7 @@ struct _LIBCPP_HIDE_FROM_ABI __decoupled_lookback_partition {
// Destructor: destroy the aggregate and inclusive prefix if they have been constructed.
~__decoupled_lookback_partition() {
- unsigned char __flag = __status_flag.load(std::memory_order_relaxed);
+ unsigned char __flag = __status_flag_.load(std::memory_order_relaxed);
if (__flag & __decoupled_lookback_status_aggregate_available) {
__aggregate().~_Tp();
}
@@ -52,50 +52,51 @@ struct _LIBCPP_HIDE_FROM_ABI __decoupled_lookback_partition {
// Acquire the current status of the partition.
// If no value is published is available yet, wait until one becomes available.
_LIBCPP_HIDE_FROM_ABI unsigned char __acquire_available_status() {
- unsigned char __current_status = __status_flag.load(std::memory_order_acquire);
+ unsigned char __current_status = __status_flag_.load(std::memory_order_acquire);
if (__current_status != __decoupled_lookback_status_invalid)
return __current_status;
- std::__atomic_wait(__status_flag, __decoupled_lookback_status_invalid, std::memory_order_acquire);
- return __status_flag.load(std::memory_order_acquire);
+ std::__atomic_wait(__status_flag_, __decoupled_lookback_status_invalid, std::memory_order_acquire);
+ return __status_flag_.load(std::memory_order_acquire);
}
// Construct the aggregate value of the partition and publish this change.
template <class... _Args>
_LIBCPP_HIDE_FROM_ABI void __construct_aggregate(_Args&&... __args) {
- std::__construct_at(&reinterpret_cast<_Tp&>(__aggregate_storage), std::forward<_Args>(__args)...);
- __status_flag.store(__status_flag.load(std::memory_order_relaxed) | __decoupled_lookback_status_aggregate_available,
- std::memory_order_release);
- std::__atomic_notify_all(__status_flag);
+ std::__construct_at(&reinterpret_cast<_Tp&>(__aggregate_storage_), std::forward<_Args>(__args)...);
+ __status_flag_.store(
+ __status_flag_.load(std::memory_order_relaxed) | __decoupled_lookback_status_aggregate_available,
+ std::memory_order_release);
+ std::__atomic_notify_all(__status_flag_);
}
// Access the aggregate value of the partition.
// Precondition: __acquire_available_status() & __decoupled_lookback_status_aggregate_available
- _LIBCPP_HIDE_FROM_ABI const _Tp& __aggregate() const { return reinterpret_cast<const _Tp&>(__aggregate_storage); }
+ _LIBCPP_HIDE_FROM_ABI const _Tp& __aggregate() const { return reinterpret_cast<const _Tp&>(__aggregate_storage_); }
// Construct the inclusive prefix of the partition and publish this change.
template <class... _Args>
_LIBCPP_HIDE_FROM_ABI void __construct_inclusive_prefix(_Args&&... __args) {
- std::__construct_at(&reinterpret_cast<_Tp&>(__inclusive_prefix_storage), std::forward<_Args>(__args)...);
- __status_flag.store(__status_flag.load(std::memory_order_relaxed) | __decoupled_lookback_status_prefix_available,
- std::memory_order_release);
- std::__atomic_notify_all(__status_flag);
+ std::__construct_at(&reinterpret_cast<_Tp&>(__inclusive_prefix_storage_), std::forward<_Args>(__args)...);
+ __status_flag_.store(__status_flag_.load(std::memory_order_relaxed) | __decoupled_lookback_status_prefix_available,
+ std::memory_order_release);
+ std::__atomic_notify_all(__status_flag_);
}
// Access the inclusive prefix of the partition.
// Precondition: __acquire_available_status() & __decoupled_lookback_status_prefix_available
_LIBCPP_HIDE_FROM_ABI const _Tp& __inclusive_prefix() const {
- return reinterpret_cast<const _Tp&>(__inclusive_prefix_storage);
+ return reinterpret_cast<const _Tp&>(__inclusive_prefix_storage_);
}
private:
// Atomic/waitable flag indicating the status of the partition.
- std::atomic<unsigned char> __status_flag{__decoupled_lookback_status_invalid};
+ std::atomic<unsigned char> __status_flag_{__decoupled_lookback_status_invalid};
// Storage for the aggregate reduced value of the partition.
- alignas(_Tp) unsigned char __aggregate_storage[sizeof(_Tp)];
+ alignas(_Tp) unsigned char __aggregate_storage_[sizeof(_Tp)];
// Storage for the inclusive prefix of the partition.
- alignas(_Tp) unsigned char __inclusive_prefix_storage[sizeof(_Tp)];
+ alignas(_Tp) unsigned char __inclusive_prefix_storage_[sizeof(_Tp)];
};
// Calculate an inclusive prefix of a given partition that currently has only an aggregate.
@@ -154,7 +155,7 @@ class _LIBCPP_HIDE_FROM_ABI __decoupled_lookback {
} // namespace __pstl
_LIBCPP_END_NAMESPACE_STD
-#endif // _LIBCPP_STD_VER >= 17
+#endif // _LIBCPP_STD_VER >= 20
_LIBCPP_POP_MACROS
diff --git a/libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/pstl.copy_if.pass.cpp b/libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/pstl.copy_if.pass.cpp
index 607569089d589e..1d4f83b63cbd6f 100644
--- a/libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/pstl.copy_if.pass.cpp
+++ b/libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/pstl.copy_if.pass.cpp
@@ -6,7 +6,8 @@
//
//===----------------------------------------------------------------------===//
-// REQUIRES: std-at-least-c++17
+// TODO: should be 17 one https://github.com/llvm/llvm-project/pull/224356 is merged
+// REQUIRES: std-at-least-c++20
// UNSUPPORTED: libcpp-has-no-incomplete-pstl
More information about the libcxx-commits
mailing list