Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions libs/core/async_mpi/include/hpx/async_mpi/transform_mpi.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,38 @@ namespace hpx::mpi::experimental {
}
}

// Register a completion callback for an MPI request whose backing
// function returns void. The receiver `r` and the upstream
// arguments `ts...` are captured into a lambda whose lifetime is
// owned by the polling driver until `MPI_Test*` reports completion.
//
// Contract:
// * `Ts...` must be passed by the caller in the same shape they
// were forwarded into the user-supplied MPI function. The
// lambda's pack-capture moves them once into `keep_alive`; if
// the caller has already moved them, we'd capture moved-from
// state. Callers in this file (`transform_mpi_receiver`) pass
// them unforwarded into `f` and then forward into this helper
// to satisfy the single-move invariant.
// * The lambda fires on the polling thread (inside `poll()` in
// `mpi_future.cpp`), not on the receiver's preferred completion
// scheduler. Receivers must be safe to invoke on that thread.
//
// The `static_assert` below makes the receiver-shape requirement
// explicit at the helper boundary so misuse fails here with a clear
// message rather than deep inside the captured lambda's body.
template <typename R, typename... Ts>
void set_value_request_callback_void(
MPI_Request request, R&& r, Ts&&... ts)
{
static_assert(
std::is_invocable_v<hpx::execution::experimental::set_value_t,
std::decay_t<R>&&>,
"set_value_request_callback_void: the receiver R must be "
"invocable as `set_value(receiver)` (no value args) for the "
"void MPI-return path. Did you mean to use the "
"_non_void variant?");

detail::add_request_callback(
[r = HPX_FORWARD(R, r), ... keep_alive = HPX_FORWARD(Ts, ts)](
int status) mutable {
Expand All @@ -56,10 +84,26 @@ namespace hpx::mpi::experimental {
request);
}

// Register a completion callback for an MPI request whose backing
// function returns a value `res` to forward to the receiver.
// Same contract as the void overload; additionally, `res` is
// captured separately so it can be forwarded to the receiver's
// `set_value` on completion.
template <typename R, typename InvokeResult, typename... Ts>
void set_value_request_callback_non_void(
MPI_Request request, R&& r, InvokeResult&& res, Ts&&... ts)
{
static_assert(!std::is_void_v<std::decay_t<InvokeResult>>,
"set_value_request_callback_non_void: InvokeResult must be "
"non-void; for void-returning MPI functions use the _void "
"variant.");
static_assert(
std::is_invocable_v<hpx::execution::experimental::set_value_t,
std::decay_t<R>&&, std::decay_t<InvokeResult>&&>,
"set_value_request_callback_non_void: the receiver R must "
"be invocable as `set_value(receiver, result)` for the "
"non-void MPI-return path.");

detail::add_request_callback(
[r = HPX_FORWARD(R, r), res = HPX_FORWARD(InvokeResult, res),
... keep_alive = HPX_FORWARD(Ts, ts)](int status) mutable {
Expand Down
34 changes: 34 additions & 0 deletions libs/core/async_mpi/tests/unit/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,37 @@ foreach(test ${tests})
add_hpx_unit_test("modules.async_mpi" ${test} ${${test}_PARAMETERS})

endforeach()

if(HPX_WITH_COMPILE_ONLY_TESTS)
# Negative compile-only tests that exercise the static_asserts at the top of
# detail::set_value_request_callback_{void,non_void}. Each test passes a
# deliberately-wrong receiver shape and is expected to fail compilation with
# the assert's diagnostic message.
set(compile_tests)

if(HPX_WITH_FAIL_COMPILE_TESTS)
set(fail_compile_tests fail_compile_set_value_request_callback_void
fail_compile_set_value_request_callback_non_void
)
foreach(fail_compile_test ${fail_compile_tests})
set(${fail_compile_test}_FLAGS FAILURE_EXPECTED)
endforeach()

set(compile_tests ${compile_tests} ${fail_compile_tests})
endif()

foreach(compile_test ${compile_tests})
set(sources ${compile_test}.cpp)

source_group("Source Files" FILES ${sources})

add_hpx_unit_compile_test(
"modules.async_mpi" ${compile_test}
SOURCES ${sources} ${${compile_test}_FLAGS}
DEPENDENCIES Mpi::mpi
FOLDER "Tests/Unit/Modules/Core/AsyncMPI/CompileOnly"
)

endforeach()

endif()
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// 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)

// This must fail compiling.
//
// Exercises the receiver-shape static_assert at the top of
// `detail::set_value_request_callback_non_void` which requires that the
// receiver be invocable as `set_value(receiver, result)` -- this is the
// non-void MPI-return path. Below we deliberately pass a receiver that
// takes no value argument, so the receiver-shape check should fire at
// compile time with the assert's diagnostic message.

#include <hpx/async_mpi/transform_mpi.hpp>
#include <hpx/modules/execution_base.hpp>

#include <exception>
#include <utility>

namespace ex = hpx::execution::experimental;

// A receiver shape that takes no value -- incompatible with the
// non-void-return callback path which needs set_value(rcv, res).
struct receiver_taking_no_value
{
using receiver_concept = ex::receiver_t;
void set_value() && noexcept {}
void set_error(std::exception_ptr) && noexcept {}
void set_stopped() && noexcept {}
};

int main()
{
MPI_Request req{};
receiver_taking_no_value r;
int res = 42;
// Instantiating this template should fire the receiver-shape
// static_assert in set_value_request_callback_non_void.
hpx::mpi::experimental::detail::set_value_request_callback_non_void(
req, std::move(r), std::move(res));
return 0;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// 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)

// This must fail compiling.
//
// Exercises the static_assert at the top of
// `detail::set_value_request_callback_void` which requires that the
// receiver be invocable as `set_value(receiver)` (no value args) -- this
// is the void MPI-return path. Below we deliberately pass a receiver that
// requires an int value argument, so the receiver-shape check should
// fire at compile time with the assert's diagnostic message.

#include <hpx/async_mpi/transform_mpi.hpp>
#include <hpx/modules/execution_base.hpp>

#include <exception>
#include <utility>

namespace ex = hpx::execution::experimental;

// A receiver shape that requires an int value -- incompatible with the
// void-return callback path.
struct receiver_requiring_int_value
{
using receiver_concept = ex::receiver_t;
void set_value(int) && noexcept {}
void set_error(std::exception_ptr) && noexcept {}
void set_stopped() && noexcept {}
};

int main()
{
MPI_Request req{};
receiver_requiring_int_value r;
// Instantiating this template should fire the static_assert in
// set_value_request_callback_void.
hpx::mpi::experimental::detail::set_value_request_callback_void(
req, std::move(r));
return 0;
}
Loading