diff --git a/CMakePresets.json b/CMakePresets.json index a55e18625a0..1772e875466 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -274,19 +274,22 @@ }, { "name": "godbolt-minimal", - "displayName": "Godbolt / Sandboxed Environment", - "description": "Minimal single-node static build optimized for browser-based compilers (Compiler Explorer / Godbolt). Uses system malloc, disables networking, tests, examples, and documentation to minimize binary size and build time.", + "displayName": "Compiler Explorer / Godbolt (Minimal)", + "description": "Minimal single-node static build for Compiler Explorer (godbolt.org) and similar sandboxed environments. Statically links all HPX libraries, disables the distributed runtime and networking, fetches Asio via FetchContent, and uses the system allocator. Skips tests, examples, documentation, and tools to minimize build time. Produces libhpx_wrap.a, libhpx_init.a, libhpx.a, and libhpx_core.a suitable for use with -Wl,-wrap=main on Linux.", "generator": "Ninja", "binaryDir": "${sourceDir}/build/godbolt-minimal", "cacheVariables": { "CMAKE_BUILD_TYPE": "Release", - "HPX_WITH_MALLOC": "system", + "HPX_WITH_CXX_STANDARD": "20", + "HPX_WITH_STATIC_LINKING": "ON", + "HPX_WITH_DISTRIBUTED_RUNTIME": "OFF", "HPX_WITH_NETWORKING": "OFF", + "HPX_WITH_FETCH_ASIO": "ON", + "HPX_WITH_MALLOC": "system", "HPX_WITH_TESTS": "OFF", "HPX_WITH_EXAMPLES": "OFF", "HPX_WITH_DOCUMENTATION": "OFF", - "HPX_WITH_STATIC_LINKING": "ON", - "HPX_WITH_FETCH_ASIO": "ON" + "HPX_WITH_TOOLS": "OFF" } } ], diff --git a/libs/CMakeLists.txt b/libs/CMakeLists.txt index 90a2689f11d..cc782205964 100644 --- a/libs/CMakeLists.txt +++ b/libs/CMakeLists.txt @@ -433,10 +433,10 @@ foreach(lib ${HPX_LIBS}) ) # Clang emits DWARF-5 records while compiling the TracyClient library - if(HPX_TRACY_WITH_TRACY + if(HPX_WITH_TRACY AND (NOT MSVC) - AND (CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_CXX_COMPILER_ID - MATCHES "AppleClang") + AND (CMAKE_CXX_COMPILER_ID MATCHES "Clang") + AND (NOT (CMAKE_CXX_COMPILER_ID MATCHES "AppleClang")) ) target_link_options(hpx_${lib} PRIVATE "-fuse-ld=lld") endif() diff --git a/libs/core/algorithms/include/hpx/parallel/algorithms/unique.hpp b/libs/core/algorithms/include/hpx/parallel/algorithms/unique.hpp index 7077a419775..62f43d47039 100644 --- a/libs/core/algorithms/include/hpx/parallel/algorithms/unique.hpp +++ b/libs/core/algorithms/include/hpx/parallel/algorithms/unique.hpp @@ -748,7 +748,7 @@ namespace hpx { hpx::traits::is_iterator_v && hpx::is_invocable_v, - hpx::traits::iter_value_t + hpx::traits::iter_value_t > ) // clang-format on @@ -775,7 +775,7 @@ namespace hpx { hpx::traits::is_iterator_v && hpx::is_invocable_v, - hpx::traits::iter_value_t + hpx::traits::iter_value_t > ) // clang-format on diff --git a/libs/core/algorithms/tests/unit/algorithms/unique_copy.cpp b/libs/core/algorithms/tests/unit/algorithms/unique_copy.cpp index 86ac4b13ab2..9a6f0da97bb 100644 --- a/libs/core/algorithms/tests/unit/algorithms/unique_copy.cpp +++ b/libs/core/algorithms/tests/unit/algorithms/unique_copy.cpp @@ -40,6 +40,17 @@ void unique_copy_bad_alloc_test() test_unique_copy_bad_alloc(); } +void unique_copy_constraint_test() +{ + std::cout << "--- unique_copy_constraint_test ---" << std::endl; + // Exercises the corrected predicate constraint in hpx::unique_copy: + // the requires clause must check iter_value_t for both arguments, + // not iter_value_t for the second argument. + test_unique_copy_constraint(); + test_unique_copy_constraint(); + test_unique_copy_constraint(); +} + /////////////////////////////////////////////////////////////////////////////// int hpx_main(hpx::program_options::variables_map& vm) { @@ -53,6 +64,7 @@ int hpx_main(hpx::program_options::variables_map& vm) unique_copy_test(); unique_copy_exception_test(); unique_copy_bad_alloc_test(); + unique_copy_constraint_test(); std::cout << "Test Finish!" << std::endl; diff --git a/libs/core/algorithms/tests/unit/algorithms/unique_copy_tests.hpp b/libs/core/algorithms/tests/unit/algorithms/unique_copy_tests.hpp index c013dcb00c5..e1b242df1cb 100644 --- a/libs/core/algorithms/tests/unit/algorithms/unique_copy_tests.hpp +++ b/libs/core/algorithms/tests/unit/algorithms/unique_copy_tests.hpp @@ -518,3 +518,172 @@ void test_unique_copy_bad_alloc() test_unique_copy_bad_alloc_async(seq(task), IteratorTag()); test_unique_copy_bad_alloc_async(par(task), IteratorTag()); } + +/////////////////////////////////////////////////////////////////////////////// +// Verify that the predicate constraint in hpx::unique_copy is checked against +// the INPUT iterator's value type (iter_value_t), not the output +// iterator's value type. This directly exercises the corrected requires clause. +// +// All calls use a predicate typed explicitly on int (the input element type), +// confirming the CPO correctly dispatches with a strongly-typed binary predicate +// across all execution policies. +template +void test_unique_copy_constraint() +{ + using namespace hpx::execution; + using base_iterator = std::vector::iterator; + using iterator = test::test_iterator; + + // Strongly-typed binary predicate on the input element type (int). + // Before the fix, the parallel overload's requires clause checked + // is_invocable_v, iter_value_t>, + // which could silently mis-constrain when InIter and OutIter differ. + auto typed_pred = [](int const a, int const b) -> bool { return a == b; }; + + std::size_t const size = 10007; + std::vector c(size), dest_res(size), dest_sol(size); + std::generate(std::begin(c), std::end(c), random_fill(0, 6)); + + // --- seq policy --- + { + auto result = hpx::unique_copy(seq, iterator(std::begin(c)), + iterator(std::end(c)), iterator(std::begin(dest_res)), typed_pred); + auto solution = std::unique_copy(std::begin(c), std::end(c), + std::begin(dest_sol), [](int a, int b) { return a == b; }); + HPX_TEST(test::equal(std::begin(dest_res), result.base(), + std::begin(dest_sol), solution)); + } + + // --- par policy --- + { + auto result = hpx::unique_copy(par, iterator(std::begin(c)), + iterator(std::end(c)), iterator(std::begin(dest_res)), typed_pred); + auto solution = std::unique_copy(std::begin(c), std::end(c), + std::begin(dest_sol), [](int a, int b) { return a == b; }); + HPX_TEST(test::equal(std::begin(dest_res), result.base(), + std::begin(dest_sol), solution)); + } + + // --- par_unseq policy --- + { + auto result = hpx::unique_copy(par_unseq, iterator(std::begin(c)), + iterator(std::end(c)), iterator(std::begin(dest_res)), typed_pred); + auto solution = std::unique_copy(std::begin(c), std::end(c), + std::begin(dest_sol), [](int a, int b) { return a == b; }); + HPX_TEST(test::equal(std::begin(dest_res), result.base(), + std::begin(dest_sol), solution)); + } + + // --- seq(task) async --- + { + auto f = hpx::unique_copy(seq(task), iterator(std::begin(c)), + iterator(std::end(c)), iterator(std::begin(dest_res)), typed_pred); + auto result = f.get(); + auto solution = std::unique_copy(std::begin(c), std::end(c), + std::begin(dest_sol), [](int a, int b) { return a == b; }); + HPX_TEST(test::equal(std::begin(dest_res), result.base(), + std::begin(dest_sol), solution)); + } + + // --- par(task) async --- + { + auto f = hpx::unique_copy(par(task), iterator(std::begin(c)), + iterator(std::end(c)), iterator(std::begin(dest_res)), typed_pred); + auto result = f.get(); + auto solution = std::unique_copy(std::begin(c), std::end(c), + std::begin(dest_sol), [](int a, int b) { return a == b; }); + HPX_TEST(test::equal(std::begin(dest_res), result.base(), + std::begin(dest_sol), solution)); + } + + // --- Edge case: empty range --- + // Ensures the constraint does not misfire on zero-length inputs. + { + std::vector empty_src; + std::vector empty_dst; + auto result = hpx::unique_copy(par, empty_src.begin(), empty_src.end(), + empty_dst.begin(), typed_pred); + HPX_TEST(result == empty_dst.begin()); + + result = hpx::unique_copy(seq, empty_src.begin(), empty_src.end(), + empty_dst.begin(), typed_pred); + HPX_TEST(result == empty_dst.begin()); + } + + // --- Edge case: single element --- + // Must copy the one element verbatim; no predicate calls occur. + { + std::vector single_src = {42}; + std::vector single_dst(1, 0); + + auto result = hpx::unique_copy(seq, single_src.begin(), + single_src.end(), single_dst.begin(), typed_pred); + HPX_TEST(result == single_dst.begin() + 1); + HPX_TEST(single_dst[0] == 42); + + result = hpx::unique_copy(par, single_src.begin(), single_src.end(), + single_dst.begin(), typed_pred); + HPX_TEST(result == single_dst.begin() + 1); + HPX_TEST(single_dst[0] == 42); + } + + // --- Edge case: all elements identical -> only one element in output --- + { + std::vector all_same(100, 7); + std::vector dst_res(100, 0), dst_sol(100, 0); + + auto result = hpx::unique_copy( + par, all_same.begin(), all_same.end(), dst_res.begin(), typed_pred); + auto solution = std::unique_copy(all_same.begin(), all_same.end(), + dst_sol.begin(), [](int a, int b) { return a == b; }); + + HPX_TEST(result == dst_res.begin() + 1); + HPX_TEST( + test::equal(dst_res.begin(), result, dst_sol.begin(), solution)); + } + + // --- Edge case: no consecutive duplicates -> full copy --- + { + std::vector no_dup = {1, 2, 3, 4, 5}; + std::vector dst_res(5, 0), dst_sol(5, 0); + + auto result = hpx::unique_copy( + par, no_dup.begin(), no_dup.end(), dst_res.begin(), typed_pred); + auto solution = std::unique_copy(no_dup.begin(), no_dup.end(), + dst_sol.begin(), [](int a, int b) { return a == b; }); + + HPX_TEST(result == dst_res.begin() + 5); + HPX_TEST( + test::equal(dst_res.begin(), result, dst_sol.begin(), solution)); + } + + // --- Edge case: two elements, identical --- + { + std::vector two_same = {9, 9}; + std::vector dst_res(2, 0), dst_sol(2, 0); + + auto result = hpx::unique_copy( + par, two_same.begin(), two_same.end(), dst_res.begin(), typed_pred); + auto solution = std::unique_copy(two_same.begin(), two_same.end(), + dst_sol.begin(), [](int a, int b) { return a == b; }); + + HPX_TEST(result == dst_res.begin() + 1); + HPX_TEST( + test::equal(dst_res.begin(), result, dst_sol.begin(), solution)); + } + + // --- Edge case: two elements, distinct --- + { + std::vector two_diff = {3, 5}; + std::vector dst_res(2, 0), dst_sol(2, 0); + + auto result = hpx::unique_copy( + par, two_diff.begin(), two_diff.end(), dst_res.begin(), typed_pred); + auto solution = std::unique_copy(two_diff.begin(), two_diff.end(), + dst_sol.begin(), [](int a, int b) { return a == b; }); + + HPX_TEST(result == dst_res.begin() + 2); + HPX_TEST( + test::equal(dst_res.begin(), result, dst_sol.begin(), solution)); + } +} diff --git a/libs/core/executors/CMakeLists.txt b/libs/core/executors/CMakeLists.txt index 8deb1494338..c6cbf1324a5 100644 --- a/libs/core/executors/CMakeLists.txt +++ b/libs/core/executors/CMakeLists.txt @@ -26,6 +26,9 @@ set(executors_headers hpx/executors/execution_policy_parameters.hpp hpx/executors/execution_policy_scheduling_property.hpp hpx/executors/execution_policy.hpp + hpx/executors/executor_scheduler.hpp + hpx/executors/executor_scheduler_bulk.hpp + hpx/executors/executor_scheduler_fwd.hpp hpx/executors/explicit_scheduler_executor.hpp hpx/executors/fork_join_executor.hpp hpx/executors/limiting_executor.hpp diff --git a/libs/core/executors/include/hpx/executors/executor_scheduler.hpp b/libs/core/executors/include/hpx/executors/executor_scheduler.hpp new file mode 100644 index 00000000000..9838d2f1182 --- /dev/null +++ b/libs/core/executors/include/hpx/executors/executor_scheduler.hpp @@ -0,0 +1,150 @@ +// Copyright (c) 2026 The STE||AR-Group +// +// SPDX-License-Identifier: BSL-1.0 +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +/// \file parallel/executors/executor_scheduler.hpp + +#pragma once + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace hpx::execution::experimental { + /////////////////////////////////////////////////////////////////////////// + HPX_CXX_CORE_EXPORT template + struct executor_operation_state + { + HPX_NO_UNIQUE_ADDRESS std::decay_t exec_; + HPX_NO_UNIQUE_ADDRESS std::decay_t receiver_; + + template + executor_operation_state(Exec&& exec, Recv&& recv) + : exec_(HPX_FORWARD(Exec, exec)) + , receiver_(HPX_FORWARD(Recv, recv)) + { + } + + executor_operation_state(executor_operation_state&&) = delete; + executor_operation_state(executor_operation_state const&) = delete; + executor_operation_state& operator=( + executor_operation_state&&) = delete; + executor_operation_state& operator=( + executor_operation_state const&) = delete; + + ~executor_operation_state() = default; + + void start() & noexcept + { + hpx::detail::try_catch_exception_ptr( + [this]() { + // Capture receiver_ by move into the task lambda so + // that the operation state can safely be destroyed + // before set_value() completes (post may move *this). + hpx::parallel::execution::post( + exec_, [r = HPX_MOVE(receiver_)]() mutable { + hpx::execution::experimental::set_value( + HPX_MOVE(r)); + }); + }, + [this](std::exception_ptr ep) { + hpx::execution::experimental::set_error( + HPX_MOVE(receiver_), HPX_MOVE(ep)); + }); + } + }; + + /////////////////////////////////////////////////////////////////////////// + HPX_CXX_CORE_EXPORT template + struct executor_sender + { + using sender_concept = hpx::execution::experimental::sender_t; + + HPX_NO_UNIQUE_ADDRESS std::decay_t exec_; + + using completion_signatures = + hpx::execution::experimental::completion_signatures< + hpx::execution::experimental::set_value_t(), + hpx::execution::experimental::set_error_t(std::exception_ptr)>; + + template + static consteval auto get_completion_signatures() noexcept + -> completion_signatures + { + return {}; + } + + constexpr auto get_env() const noexcept + { + struct env + { + std::decay_t const& exec_; + + constexpr auto + query(hpx::execution::experimental::get_completion_scheduler_t< + hpx::execution::experimental::set_value_t>) const noexcept + { + return executor_scheduler{exec_}; + } + }; + return env{exec_}; + } + + template + auto connect(Receiver&& receiver) && + { + return executor_operation_state>{ + HPX_MOVE(exec_), HPX_FORWARD(Receiver, receiver)}; + } + + template + auto connect(Receiver&& receiver) const& + { + return executor_operation_state>{ + exec_, HPX_FORWARD(Receiver, receiver)}; + } + }; + + /////////////////////////////////////////////////////////////////////////// + HPX_CXX_CORE_EXPORT template + struct executor_scheduler + { + using executor_type = std::decay_t; + + HPX_NO_UNIQUE_ADDRESS executor_type exec_; + + constexpr executor_scheduler() = default; + + template + requires(!std::same_as, executor_scheduler>) + constexpr explicit executor_scheduler(Exec&& exec) + : exec_(HPX_FORWARD(Exec, exec)) + { + } + + constexpr bool operator==(executor_scheduler const& rhs) const noexcept + { + return exec_ == rhs.exec_; + } + + constexpr bool operator!=(executor_scheduler const& rhs) const noexcept + { + return !(*this == rhs); + } + + constexpr executor_sender schedule() const noexcept + { + return {exec_}; + } + }; +} // namespace hpx::execution::experimental diff --git a/libs/core/executors/include/hpx/executors/executor_scheduler_bulk.hpp b/libs/core/executors/include/hpx/executors/executor_scheduler_bulk.hpp new file mode 100644 index 00000000000..c2fca3e1704 --- /dev/null +++ b/libs/core/executors/include/hpx/executors/executor_scheduler_bulk.hpp @@ -0,0 +1,173 @@ +// Copyright (c) 2026 The STE||AR-Group +// +// SPDX-License-Identifier: BSL-1.0 +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#pragma once + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace hpx::execution::experimental { + + namespace detail { + template + struct executor_bulk_receiver + { + using receiver_concept = hpx::execution::experimental::receiver_t; + + HPX_NO_UNIQUE_ADDRESS std::decay_t exec_; + HPX_NO_UNIQUE_ADDRESS std::decay_t receiver_; + HPX_NO_UNIQUE_ADDRESS std::decay_t shape_; + HPX_NO_UNIQUE_ADDRESS std::decay_t f_; + + template + void set_error(Error&& error) noexcept + { + hpx::execution::experimental::set_error( + HPX_MOVE(receiver_), HPX_FORWARD(Error, error)); + } + + void set_stopped() noexcept + { + hpx::execution::experimental::set_stopped(HPX_MOVE(receiver_)); + } + + template + void set_value(Ts&&... ts) noexcept + { + hpx::detail::try_catch_exception_ptr( + [&]() { + hpx::parallel::execution::bulk_sync_execute( + exec_, f_, shape_, ts...); + + hpx::execution::experimental::set_value( + HPX_MOVE(receiver_), HPX_FORWARD(Ts, ts)...); + }, + [&](std::exception_ptr ep) { + hpx::execution::experimental::set_error( + HPX_MOVE(receiver_), HPX_MOVE(ep)); + }); + } + }; + + template + struct executor_bulk_sender + { + HPX_NO_UNIQUE_ADDRESS std::decay_t exec_; + HPX_NO_UNIQUE_ADDRESS std::decay_t sender_; + HPX_NO_UNIQUE_ADDRESS std::decay_t shape_; + HPX_NO_UNIQUE_ADDRESS std::decay_t f_; + + using sender_concept = hpx::execution::experimental::sender_t; + + template + static consteval auto get_completion_signatures() noexcept + -> decltype(hpx::execution::experimental:: + transform_completion_signatures( + hpx::execution::experimental:: + completion_signatures_of_t{}, + hpx::execution::experimental::keep_completion< + hpx::execution::experimental::set_value_t>{}, + hpx::execution::experimental::keep_completion< + hpx::execution::experimental::set_error_t>{}, + hpx::execution::experimental::keep_completion< + hpx::execution::experimental::set_stopped_t>{}, + hpx::execution::experimental::completion_signatures< + hpx::execution::experimental::set_error_t( + std::exception_ptr)>{})) + { + return {}; + } + + struct env + { + std::decay_t const& pred_snd; + std::decay_t const& exec; + + template + requires( + meta::value> && + hpx::execution::experimental::detail:: + has_completion_scheduler_v>) + constexpr auto query( + hpx::execution::experimental::get_completion_scheduler_t< + CPO> + tag) const noexcept + { + return tag(hpx::execution::experimental::get_env(pred_snd)); + } + + constexpr auto + query(hpx::execution::experimental::get_completion_scheduler_t< + hpx::execution::experimental::set_value_t>) const noexcept + { + return hpx::execution::experimental::executor_scheduler< + Executor>{exec}; + } + }; + + constexpr auto get_env() const noexcept + { + return env{sender_, exec_}; + } + + template + auto connect(Receiver&& receiver) && + { + return hpx::execution::experimental::connect(HPX_MOVE(sender_), + executor_bulk_receiver, + Shape, F>{HPX_MOVE(exec_), + HPX_FORWARD(Receiver, receiver), HPX_MOVE(shape_), + HPX_MOVE(f_)}); + } + + template + auto connect(Receiver&& receiver) & + { + return hpx::execution::experimental::connect(sender_, + executor_bulk_receiver, + Shape, F>{ + exec_, HPX_FORWARD(Receiver, receiver), shape_, f_}); + } + + template + auto connect(Receiver&& receiver) const& + { + return hpx::execution::experimental::connect(sender_, + executor_bulk_receiver, + Shape, F>{ + exec_, HPX_FORWARD(Receiver, receiver), shape_, f_}); + } + }; + } // namespace detail + + template + auto tag_invoke(bulk_t, executor_scheduler const& sched, + Sender&& sender, Shape const& shape, F&& f) + { + return detail::executor_bulk_sender, + Shape, std::decay_t>{ + sched.exec_, HPX_FORWARD(Sender, sender), shape, HPX_FORWARD(F, f)}; + } +} // namespace hpx::execution::experimental diff --git a/libs/core/executors/include/hpx/executors/executor_scheduler_fwd.hpp b/libs/core/executors/include/hpx/executors/executor_scheduler_fwd.hpp new file mode 100644 index 00000000000..a81c3f850e8 --- /dev/null +++ b/libs/core/executors/include/hpx/executors/executor_scheduler_fwd.hpp @@ -0,0 +1,25 @@ +// Copyright (c) 2007-2026 Hartmut Kaiser +// +// SPDX-License-Identifier: BSL-1.0 +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +/// \file parallel/executors/fwd/executor_scheduler_fwd.hpp + +#pragma once + +#include + +namespace hpx::execution::experimental { + + // Forward declarations, see executor_scheduler.hpp + HPX_CXX_CORE_EXPORT template + struct executor_scheduler; + + HPX_CXX_CORE_EXPORT template + struct executor_sender; + + HPX_CXX_CORE_EXPORT template + struct executor_operation_state; + +} // namespace hpx::execution::experimental diff --git a/libs/core/executors/include/hpx/executors/parallel_executor.hpp b/libs/core/executors/include/hpx/executors/parallel_executor.hpp index 2c8820ba093..84ae4efd79d 100644 --- a/libs/core/executors/include/hpx/executors/parallel_executor.hpp +++ b/libs/core/executors/include/hpx/executors/parallel_executor.hpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -156,7 +157,8 @@ namespace hpx::execution { } public: - parallel_policy_executor_base(parallel_policy_executor_base const& rhs) + parallel_policy_executor_base( + parallel_policy_executor_base const& rhs) noexcept : pool_(rhs.pool_) , policy_(rhs.policy_) , first_core_(rhs.first_core_) @@ -169,7 +171,7 @@ namespace hpx::execution { // NOLINTEND(bugprone-crtp-constructor-accessibility) parallel_policy_executor_base& operator=( - parallel_policy_executor_base const& rhs) + parallel_policy_executor_base const& rhs) noexcept { if (this != &rhs) { @@ -458,6 +460,8 @@ namespace hpx::execution { #if defined(__NVCC__) || defined(__CUDACC__) constexpr ~parallel_policy_executor() {} +#else + ~parallel_policy_executor() = default; #endif private: @@ -669,15 +673,22 @@ namespace hpx::execution { #endif } } - /// \endcond public: /// \cond NOINTERNAL + constexpr hpx::execution::experimental::executor_scheduler< + parallel_policy_executor> + query(hpx::execution::experimental::get_scheduler_t) const noexcept + { + return hpx::execution::experimental::executor_scheduler< + parallel_policy_executor>(*this); + } + constexpr bool operator==( parallel_policy_executor const& rhs) const noexcept { return base_type::policy_ == rhs.policy_ && - base_type::pool_ == rhs.pool; + base_type::pool_ == rhs.pool_; } constexpr bool operator!=( @@ -997,6 +1008,7 @@ namespace hpx::execution { #endif } } + /// \endcond public: diff --git a/libs/core/executors/include/hpx/executors/sequenced_executor.hpp b/libs/core/executors/include/hpx/executors/sequenced_executor.hpp index 6d61e286ba3..2c46a50ca4d 100644 --- a/libs/core/executors/include/hpx/executors/sequenced_executor.hpp +++ b/libs/core/executors/include/hpx/executors/sequenced_executor.hpp @@ -10,6 +10,7 @@ #include #include +#include #include #include #include @@ -236,6 +237,16 @@ namespace hpx::execution { #endif } + public: + /// \cond NOINTERNAL + constexpr hpx::execution::experimental::executor_scheduler< + sequenced_executor> + query(hpx::execution::experimental::get_scheduler_t) const noexcept + { + return hpx::execution::experimental::executor_scheduler< + sequenced_executor>(*this); + } + private: friend class hpx::serialization::access; diff --git a/libs/core/executors/tests/unit/CMakeLists.txt b/libs/core/executors/tests/unit/CMakeLists.txt index cfc469a0704..9ed5d1014c7 100644 --- a/libs/core/executors/tests/unit/CMakeLists.txt +++ b/libs/core/executors/tests/unit/CMakeLists.txt @@ -9,6 +9,7 @@ set(tests annotation_property created_executor execution_policy_mappings + executor_algorithm_bulk explicit_scheduler_executor fork_join_executor fork_join_executor_from diff --git a/libs/core/executors/tests/unit/executor_algorithm_bulk.cpp b/libs/core/executors/tests/unit/executor_algorithm_bulk.cpp new file mode 100644 index 00000000000..7d64bdc36b3 --- /dev/null +++ b/libs/core/executors/tests/unit/executor_algorithm_bulk.cpp @@ -0,0 +1,92 @@ +// Copyright (c) 2026 The STE||AR-Group +// +// SPDX-License-Identifier: BSL-1.0 +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace ex = hpx::execution::experimental; + +void test_sequential_bulk() +{ + hpx::execution::sequenced_executor exec; + std::atomic call_count{0}; + + // Obtain a P2300 scheduler from the executor via query(get_scheduler_t{}) + auto sched = exec.query(ex::get_scheduler_t{}); + + auto snd = ex::schedule(sched) | ex::bulk(10, [&](int i) { + (void) i; + ++call_count; + // Should run sequentially, so it's safe + }); + + hpx::this_thread::experimental::sync_wait(snd); + + HPX_TEST_EQ(call_count.load(), 10); +} + +void test_parallel_bulk() +{ + hpx::execution::parallel_executor exec; + std::atomic call_count{0}; + + auto sched = exec.query(ex::get_scheduler_t{}); + + auto snd = ex::schedule(sched) | ex::bulk(1000, [&](int i) { + (void) i; + ++call_count; + }); + + hpx::this_thread::experimental::sync_wait(snd); + + HPX_TEST_EQ(call_count.load(), 1000); +} + +void test_parallel_bulk_with_value() +{ + hpx::execution::parallel_executor exec; + std::atomic call_count{0}; + + auto sched = exec.query(ex::get_scheduler_t{}); + + auto snd = ex::schedule(sched) | ex::then([]() { return 42; }) | + ex::bulk(100, [&](int i, int val) { + (void) i; + HPX_TEST_EQ(val, 42); + ++call_count; + }); + + hpx::this_thread::experimental::sync_wait(snd); + + HPX_TEST_EQ(call_count.load(), 100); +} + +int hpx_main() +{ + test_sequential_bulk(); + test_parallel_bulk(); + test_parallel_bulk_with_value(); + return hpx::local::finalize(); +} + +int main(int argc, char* argv[]) +{ + std::vector const cfg = {"hpx.os_threads=all"}; + hpx::local::init_params init_args; + init_args.cfg = cfg; + + HPX_TEST_EQ_MSG(hpx::local::init(hpx_main, argc, argv, init_args), 0, + "HPX main exited with non-zero status"); + + return hpx::util::report_errors(); +}