39#include <boost/mpi/collectives/broadcast.hpp>
40#include <boost/mpi/communicator.hpp>
41#include <boost/mpi/environment.hpp>
42#include <boost/mpi/packed_iarchive.hpp>
62using is_allowed_argument =
63 std::integral_constant<
bool,
64 not(std::is_pointer_v<T> ||
65 (!std::is_const_v<std::remove_reference_t<T>> &&
66 std::is_lvalue_reference_v<T>))>;
79template <
class F,
class...
Args>
80auto invoke(
F f, boost::mpi::packed_iarchive &
ia) {
81 static_assert(std::conjunction_v<is_allowed_argument<Args>...>,
82 "Pointers and non-const references are not allowed as "
83 "arguments for callbacks.");
87 std::tuple<std::remove_const_t<std::remove_reference_t<Args>>...> params;
88 std::apply([&
ia](
auto &&...
e) { ((
ia >>
e), ...); }, params);
94 return std::apply(f, std::as_const(params));
104struct callback_concept_t {
110 virtual void operator()(boost::mpi::communicator
const &,
111 boost::mpi::packed_iarchive &)
const = 0;
112 virtual ~callback_concept_t() =
default;
121template <
class F,
class...
Args>
122struct callback_void_t
final :
public callback_concept_t {
125 callback_void_t(callback_void_t
const &) =
delete;
126 callback_void_t(callback_void_t &&) =
delete;
128 template <
class FRef>
129 explicit callback_void_t(
FRef &&f) : m_f(
std::forward<
FRef>(f)) {}
130 void operator()(boost::mpi::communicator
const &,
131 boost::mpi::packed_iarchive &
ia)
const override {
132 detail::invoke<
F,
Args...>(m_f,
ia);
137template <
class T>
struct FunctorTypes;
140template <
class Class,
class Ret,
class...
Args>
141struct FunctorTypes<
Ret (Class::*)(Args...)
const> {
142 using functor_type = Class;
143 using return_type =
Ret;
144 using argument_types = std::tuple<
Args...>;
147template <
class Class,
class Ret,
class...
Args>
148using functor_types_from_args = FunctorTypes<
Ret (Class::*)(
Args...)
const>;
151using functor_types_from_lambda =
152 FunctorTypes<
decltype(&std::remove_reference_t<F>::operator())>;
154template <
class F,
class C,
class R,
class...
Args>
156 return std::make_unique<callback_void_t<C,
Args...>>(std::forward<F>(f));
165template <
typename F>
auto make_model(
F &&f) {
172template <
class...
Args>
auto make_model(
void (*
f_ptr)(
Args...)) {
195 template <
typename F>
196 requires std::is_same_v<
typename detail::functor_types_from_lambda<
209 std::shared_ptr<MpiCallbacks> m_cb;
219 template <
class...
ArgRef>
226 m_cb->call(m_id, std::forward<ArgRef>(
args)...);
234 int id()
const {
return m_id; }
242 static auto &static_callbacks() {
244 std::pair<
void (*)(), std::unique_ptr<detail::callback_concept_t>>>
252 std::shared_ptr<boost::mpi::environment>
mpi_env)
255 m_callback_map.add(
nullptr);
257 for (
auto &[
fp,
handle] : static_callbacks()) {
258 m_func_ptr_to_id[
fp] = m_callback_map.add(
handle.get());
264 if (m_comm.rank() == 0) {
287 template <
typename F>
auto add(
F &&f) {
288 m_callbacks.emplace_back(detail::make_model(std::forward<F>(f)));
289 return m_callback_map.add(m_callbacks.back().get());
302 m_callbacks.emplace_back(detail::make_model(
fp));
303 const int id = m_callback_map.add(m_callbacks.back().get());
304 m_func_ptr_to_id[
reinterpret_cast<void (*)()
>(
fp)] =
id;
316 static_callbacks().emplace_back(
reinterpret_cast<void (*)()
>(
fp),
317 detail::make_model(
fp));
329 void remove(
int id) {
330 std::erase_if(m_callbacks, [ptr = m_callback_map[
id]](
auto const &
e) {
331 return e.get() == ptr;
333 m_callback_map.remove(
id);
348 template <
class...
Args>
void call(
int id,
Args &&...
args)
const {
349 if (m_comm.rank() != 0) {
350 throw std::logic_error(
"Callbacks can only be invoked on rank 0.");
353 assert(m_callback_map.find(
id) != m_callback_map.end() &&
354 "m_callback_map and m_func_ptr_to_id disagree");
357 boost::mpi::packed_oarchive
oa(m_comm);
361 std::apply([&
oa](
auto &&...
e) { ((
oa <<
e), ...); },
362 std::forward_as_tuple(std::forward<Args>(
args)...));
364 boost::mpi::broadcast(m_comm,
oa, 0);
383 const int id = m_func_ptr_to_id.at(
reinterpret_cast<void (*)()
>(
fp));
385 call(
id, std::forward<ArgRef>(
args)...);
419 boost::mpi::packed_iarchive
ia(m_comm);
420 boost::mpi::broadcast(m_comm,
ia, 0);
429 m_callback_map[
request]->operator()(m_comm,
ia);
443 boost::mpi::communicator
const &
comm()
const {
return m_comm; }
449 static constexpr int LOOP_ABORT = 0;
454 boost::mpi::communicator m_comm;
459 std::shared_ptr<boost::mpi::environment> m_mpi_env;
464 std::vector<std::unique_ptr<detail::callback_concept_t>> m_callbacks;
475 std::unordered_map<
void (*)(),
int> m_func_ptr_to_id;
478template <
class...
Args>
505#define REGISTER_CALLBACK(cb) \
506 namespace Communication { \
507 static ::Communication::RegisterCallback register_##cb(&(cb)); \
Keep an enumerated list of T objects, managed by the class.
RAII handle for a callback.
CallbackHandle(std::shared_ptr< MpiCallbacks > cb, F &&f)
CallbackHandle(CallbackHandle &&rhs) noexcept=default
auto operator()(ArgRef &&...args) const
Call the callback managed by this handle.
CallbackHandle(CallbackHandle const &)=delete
CallbackHandle & operator=(CallbackHandle &&rhs) noexcept=default
CallbackHandle & operator=(CallbackHandle const &)=delete
The interface of the MPI callback mechanism.
auto call_all(void(*fp)(Args...), ArgRef &&...args) const
Call a callback on all nodes.
MpiCallbacks(boost::mpi::communicator comm, std::shared_ptr< boost::mpi::environment > mpi_env)
void add(void(*fp)(Args...))
Add a new callback.
boost::mpi::communicator const & comm() const
The boost mpi communicator used by this instance.
void abort_loop()
Abort the MPI loop.
static void add_static(void(*fp)(Args...))
Add a new callback.
MpiCallbacks & operator=(MpiCallbacks const &)=delete
auto call(void(*fp)(Args...), ArgRef &&...args) const
Call a callback on worker nodes.
MpiCallbacks(MpiCallbacks const &)=delete
void loop() const
Start the MPI loop.
Helper class to add callbacks before main.
RegisterCallback()=delete
RegisterCallback(void(*cb)(Args...))
Container for objects that are identified by a numeric id.
cudaStream_t stream[1]
CUDA streams for parallel computing on CPU and GPU.