Skip to main content

mpi/
point_to_point.rs

1//! Point-to-point communication. Mirrors `mpi::point_to_point` in rsmpi: the
2//! [`Source`] (receive) and [`Destination`] (send) traits, [`Status`],
3//! [`Message`] (matched probe) and the `send_receive` free functions.
4
5use std::marker::PhantomData;
6
7use crate::datatype::{Buffer, BufferMut, DatatypeRef, Equivalence};
8use crate::request::{Request, Scope};
9use crate::topology::CommData;
10use crate::transport::{self, ANY_SOURCE, ANY_TAG};
11use crate::{Count, Rank, Tag};
12
13/// The default tag used by tagless send/receive calls.
14pub const DEFAULT_TAG: Tag = 0;
15
16// ---- Byte <-> typed helpers (POD reinterpretation for `Equivalence`). ----
17
18pub(crate) fn elem_from_bytes<T: Equivalence>(bytes: &[u8]) -> T {
19    assert!(
20        bytes.len() >= std::mem::size_of::<T>(),
21        "received message smaller than expected scalar"
22    );
23    // SAFETY: `T: Equivalence` is POD; read unaligned since `bytes` may not be
24    // aligned to `T`.
25    unsafe { std::ptr::read_unaligned(bytes.as_ptr() as *const T) }
26}
27
28pub(crate) fn vec_from_bytes<T: Equivalence>(bytes: &[u8]) -> Vec<T> {
29    let size = std::mem::size_of::<T>();
30    let count = bytes.len().checked_div(size).unwrap_or(0);
31    let mut v: Vec<T> = Vec::with_capacity(count);
32    // SAFETY: capacity is `count`, and we copy exactly `count * size` bytes into
33    // properly-aligned storage; `T` is POD so any bit pattern is valid.
34    unsafe {
35        std::ptr::copy_nonoverlapping(bytes.as_ptr(), v.as_mut_ptr() as *mut u8, count * size);
36        v.set_len(count);
37    }
38    v
39}
40
41pub(crate) fn copy_into_buffer<Buf: BufferMut + ?Sized>(buf: &mut Buf, bytes: &[u8]) {
42    // Routes through `scatter_from` so derived-datatype receive buffers place
43    // the bytes according to their type map; the default is a contiguous copy.
44    buf.scatter_from(bytes);
45}
46
47/// The outcome of a receive or probe (`MPI_Status`).
48#[derive(Copy, Clone, Debug, PartialEq, Eq)]
49pub struct Status {
50    source: Rank,
51    tag: Tag,
52    count: Count,
53    byte_len: usize,
54}
55
56impl Status {
57    pub(crate) fn new(source: Rank, tag: Tag, count: Count, byte_len: usize) -> Status {
58        Status {
59            source,
60            tag,
61            count,
62            byte_len,
63        }
64    }
65
66    /// The rank (within the communicator) that sent the message.
67    pub fn source_rank(&self) -> Rank {
68        self.source
69    }
70
71    /// The tag the message was sent with.
72    pub fn tag(&self) -> Tag {
73        self.tag
74    }
75
76    /// Number of elements of datatype `dt` in the message.
77    pub fn count(&self, dt: DatatypeRef) -> Count {
78        self.byte_len.checked_div(dt.size).unwrap_or(0) as Count
79    }
80
81    /// Whether the matching operation was cancelled. Always `false` in this
82    /// implementation (cancellation of already-buffered messages is a no-op).
83    pub fn is_cancelled(&self) -> bool {
84        false
85    }
86
87    /// American-spelling alias of [`Status::is_cancelled`].
88    pub fn is_canceled(&self) -> bool {
89        false
90    }
91}
92
93/// A message that has been matched by a probe but not yet received
94/// (`MPI_Message`). In this implementation the payload has already been pulled
95/// off the wire and is stored inline; receiving it is a local copy.
96pub struct Message {
97    source: Rank,
98    tag: Tag,
99    count: Count,
100    payload: Vec<u8>,
101}
102
103impl Message {
104    /// Receive a previously-probed message into a freshly allocated `Vec`
105    /// (`MPI_Mrecv`).
106    pub fn matched_receive_vec<Msg: Equivalence>(self) -> (Vec<Msg>, Status) {
107        let status = Status::new(self.source, self.tag, self.count, self.payload.len());
108        (vec_from_bytes::<Msg>(&self.payload), status)
109    }
110
111    /// Receive a previously-probed message into an existing buffer.
112    pub fn matched_receive_into<Buf: BufferMut + ?Sized>(self, buf: &mut Buf) -> Status {
113        let status = Status::new(self.source, self.tag, self.count, self.payload.len());
114        copy_into_buffer(buf, &self.payload);
115        status
116    }
117}
118
119/// The receive side of point-to-point communication (`MPI_ANY_SOURCE` or a
120/// specific rank).
121pub trait Source {
122    /// Internal: the communicator this source belongs to.
123    #[doc(hidden)]
124    fn src_comm(&self) -> &CommData;
125
126    /// The rank being received from (or `MPI_ANY_SOURCE`).
127    fn source_rank(&self) -> Rank;
128
129    /// Receive a single value (`MPI_Recv`).
130    fn receive<Msg: Equivalence>(&self) -> (Msg, Status) {
131        self.receive_with_tag(ANY_TAG)
132    }
133
134    /// Receive a single value with a specific tag.
135    fn receive_with_tag<Msg: Equivalence>(&self, tag: Tag) -> (Msg, Status) {
136        let comm = self.src_comm();
137        let (src, t, count, _dt, payload) =
138            transport::runtime().recv(comm.context, self.source_rank(), tag);
139        let val = elem_from_bytes::<Msg>(&payload);
140        (val, Status::new(src, t, count as Count, payload.len()))
141    }
142
143    /// Receive a variable number of values into a new `Vec`.
144    fn receive_vec<Msg: Equivalence>(&self) -> (Vec<Msg>, Status) {
145        self.receive_vec_with_tag(ANY_TAG)
146    }
147
148    /// Receive a variable number of values into a new `Vec`, with a tag.
149    fn receive_vec_with_tag<Msg: Equivalence>(&self, tag: Tag) -> (Vec<Msg>, Status) {
150        let comm = self.src_comm();
151        let (src, t, count, _dt, payload) =
152            transport::runtime().recv(comm.context, self.source_rank(), tag);
153        let v = vec_from_bytes::<Msg>(&payload);
154        (v, Status::new(src, t, count as Count, payload.len()))
155    }
156
157    /// Receive into an existing buffer (`MPI_Recv`).
158    fn receive_into<Buf: BufferMut + ?Sized>(&self, buf: &mut Buf) -> Status {
159        self.receive_into_with_tag(buf, ANY_TAG)
160    }
161
162    /// Receive into an existing buffer, with a tag.
163    fn receive_into_with_tag<Buf: BufferMut + ?Sized>(&self, buf: &mut Buf, tag: Tag) -> Status {
164        let comm = self.src_comm();
165        let (src, t, count, _dt, payload) =
166            transport::runtime().recv(comm.context, self.source_rank(), tag);
167        copy_into_buffer(buf, &payload);
168        Status::new(src, t, count as Count, payload.len())
169    }
170
171    /// Blocking probe (`MPI_Probe`): wait for a matching message, describe it
172    /// without receiving.
173    fn probe(&self) -> Status {
174        self.probe_with_tag(ANY_TAG)
175    }
176
177    /// Blocking probe with a tag.
178    fn probe_with_tag(&self, tag: Tag) -> Status {
179        let comm = self.src_comm();
180        let (src, t, count, _dt, len) =
181            transport::runtime().probe_blocking(comm.context, self.source_rank(), tag);
182        Status::new(src, t, count as Count, len)
183    }
184
185    /// Non-blocking probe (`MPI_Iprobe`).
186    fn immediate_probe(&self) -> Option<Status> {
187        self.immediate_probe_with_tag(ANY_TAG)
188    }
189
190    /// Non-blocking probe with a tag.
191    fn immediate_probe_with_tag(&self, tag: Tag) -> Option<Status> {
192        let comm = self.src_comm();
193        transport::runtime()
194            .probe(comm.context, self.source_rank(), tag)
195            .map(|(src, t, count, _dt, len)| Status::new(src, t, count as Count, len))
196    }
197
198    /// Matched probe (`MPI_Mprobe`): remove a matching message from the queue
199    /// and return a [`Message`] to receive it.
200    fn matched_probe(&self) -> (Message, Status) {
201        self.matched_probe_with_tag(ANY_TAG)
202    }
203
204    /// Matched probe with a tag.
205    fn matched_probe_with_tag(&self, tag: Tag) -> (Message, Status) {
206        let comm = self.src_comm();
207        let (src, t, count, _dt, payload) =
208            transport::runtime().recv(comm.context, self.source_rank(), tag);
209        let status = Status::new(src, t, count as Count, payload.len());
210        (
211            Message {
212                source: src,
213                tag: t,
214                count: count as Count,
215                payload,
216            },
217            status,
218        )
219    }
220
221    /// Non-blocking matched probe (`MPI_Improbe`).
222    fn immediate_matched_probe(&self) -> Option<(Message, Status)> {
223        self.immediate_matched_probe_with_tag(ANY_TAG)
224    }
225
226    /// Non-blocking matched probe with a tag.
227    fn immediate_matched_probe_with_tag(&self, tag: Tag) -> Option<(Message, Status)> {
228        if self.immediate_probe_with_tag(tag).is_some() {
229            Some(self.matched_probe_with_tag(tag))
230        } else {
231            None
232        }
233    }
234
235    /// Non-blocking receive of a single value, returning a [`ReceiveFuture`].
236    fn immediate_receive<Msg: Equivalence>(&self) -> ReceiveFuture<Msg> {
237        self.immediate_receive_with_tag(ANY_TAG)
238    }
239
240    /// Non-blocking receive of a single value with a tag.
241    fn immediate_receive_with_tag<Msg: Equivalence>(&self, tag: Tag) -> ReceiveFuture<Msg> {
242        let comm = self.src_comm();
243        ReceiveFuture {
244            ctx: comm.context,
245            source: self.source_rank(),
246            tag,
247            _p: PhantomData,
248        }
249    }
250
251    /// Non-blocking receive into an existing buffer (`MPI_Irecv`), returning a
252    /// [`Request`] that must be completed via `wait`/`test`.
253    fn immediate_receive_into<'a, Sc, Buf>(
254        &self,
255        scope: Sc,
256        buf: &'a mut Buf,
257    ) -> Request<'a, Buf, Sc>
258    where
259        Buf: 'a + BufferMut + ?Sized,
260        Sc: Scope<'a>,
261    {
262        self.immediate_receive_into_with_tag(scope, buf, ANY_TAG)
263    }
264
265    /// Non-blocking receive into a buffer, with a tag.
266    fn immediate_receive_into_with_tag<'a, Sc, Buf>(
267        &self,
268        scope: Sc,
269        buf: &'a mut Buf,
270        tag: Tag,
271    ) -> Request<'a, Buf, Sc>
272    where
273        Buf: 'a + BufferMut + ?Sized,
274        Sc: Scope<'a>,
275    {
276        let ctx = self.src_comm().context;
277        let source = self.source_rank();
278        let bytes = buf.as_bytes_mut();
279        let ptr = bytes.as_mut_ptr();
280        let len = bytes.len();
281        Request::pending_recv(scope, ptr, len, ctx, source, tag)
282    }
283
284    /// Create a persistent receive request bound to `buf` (`MPI_Recv_init`).
285    /// `start` it (and `wait`) repeatedly to re-post the receive.
286    fn receive_init<'a, Buf: BufferMut + ?Sized>(
287        &self,
288        buf: &'a mut Buf,
289    ) -> crate::request::PersistentRequest<'a> {
290        let ctx = self.src_comm().context;
291        let source = self.source_rank();
292        let bytes = buf.as_bytes_mut();
293        let ptr = bytes.as_mut_ptr();
294        let len = bytes.len();
295        crate::request::PersistentRequest::new_recv(ctx, source, DEFAULT_TAG, ptr, len)
296    }
297}
298
299/// The send side of point-to-point communication.
300pub trait Destination {
301    /// Internal: the communicator this destination belongs to.
302    #[doc(hidden)]
303    fn dst_comm(&self) -> &CommData;
304
305    /// The rank being sent to.
306    fn destination_rank(&self) -> Rank;
307
308    #[doc(hidden)]
309    fn do_try_send<Buf: Buffer + ?Sized>(
310        &self,
311        buf: &Buf,
312        tag: Tag,
313    ) -> Result<(), crate::MpiError> {
314        let comm = self.dst_comm();
315        let dt = buf.as_datatype();
316        transport::runtime()
317            .send(
318                comm.context,
319                comm.rank,
320                comm.world_rank(self.destination_rank()),
321                tag,
322                buf.count() as u64,
323                dt.id,
324                buf.as_bytes(),
325            )
326            .map_err(|e| crate::MpiError::Other(format!("send failed: {e}")))
327    }
328
329    #[doc(hidden)]
330    fn do_send<Buf: Buffer + ?Sized>(&self, buf: &Buf, tag: Tag) {
331        self.do_try_send(buf, tag).expect("MPI send failed");
332    }
333
334    /// Standard-mode blocking send (`MPI_Send`).
335    fn send<Buf: Buffer + ?Sized>(&self, buf: &Buf) {
336        self.do_send(buf, DEFAULT_TAG);
337    }
338
339    /// Standard-mode blocking send with a tag.
340    fn send_with_tag<Buf: Buffer + ?Sized>(&self, buf: &Buf, tag: Tag) {
341        self.do_send(buf, tag);
342    }
343
344    /// Fallible send: returns an error instead of panicking if the transport
345    /// fails (e.g. the destination is unreachable). Honors the spirit of
346    /// [`crate::error::ErrorHandler::Return`] without changing the panic-based
347    /// default API.
348    fn try_send<Buf: Buffer + ?Sized>(&self, buf: &Buf) -> Result<(), crate::MpiError> {
349        self.do_try_send(buf, DEFAULT_TAG)
350    }
351
352    /// Fallible send with a tag.
353    fn try_send_with_tag<Buf: Buffer + ?Sized>(
354        &self,
355        buf: &Buf,
356        tag: Tag,
357    ) -> Result<(), crate::MpiError> {
358        self.do_try_send(buf, tag)
359    }
360
361    /// Buffered-mode send (`MPI_Bsend`). Behaves as a standard send here since
362    /// the transport already buffers on the receiver side.
363    fn buffered_send<Buf: Buffer + ?Sized>(&self, buf: &Buf) {
364        self.do_send(buf, DEFAULT_TAG);
365    }
366
367    /// Buffered-mode send with a tag.
368    fn buffered_send_with_tag<Buf: Buffer + ?Sized>(&self, buf: &Buf, tag: Tag) {
369        self.do_send(buf, tag);
370    }
371
372    /// Synchronous-mode send (`MPI_Ssend`).
373    fn synchronous_send<Buf: Buffer + ?Sized>(&self, buf: &Buf) {
374        self.do_send(buf, DEFAULT_TAG);
375    }
376
377    /// Synchronous-mode send with a tag.
378    fn synchronous_send_with_tag<Buf: Buffer + ?Sized>(&self, buf: &Buf, tag: Tag) {
379        self.do_send(buf, tag);
380    }
381
382    /// Ready-mode send (`MPI_Rsend`).
383    ///
384    /// # Safety
385    ///
386    /// The matching receive must already be posted. This implementation does
387    /// not rely on that, so it is effectively safe, but the `unsafe` marker is
388    /// retained for signature parity.
389    unsafe fn ready_send<Buf: Buffer + ?Sized>(&self, buf: &Buf) {
390        self.do_send(buf, DEFAULT_TAG);
391    }
392
393    /// Ready-mode send with a tag.
394    ///
395    /// # Safety
396    ///
397    /// See [`Destination::ready_send`].
398    unsafe fn ready_send_with_tag<Buf: Buffer + ?Sized>(&self, buf: &Buf, tag: Tag) {
399        self.do_send(buf, tag);
400    }
401
402    /// Non-blocking standard send (`MPI_Isend`), returning a [`Request`].
403    fn immediate_send<'a, Sc, Buf>(&self, scope: Sc, buf: &'a Buf) -> Request<'a, Buf, Sc>
404    where
405        Buf: 'a + Buffer + ?Sized,
406        Sc: Scope<'a>,
407    {
408        self.immediate_send_with_tag(scope, buf, DEFAULT_TAG)
409    }
410
411    /// Non-blocking standard send with a tag.
412    fn immediate_send_with_tag<'a, Sc, Buf>(
413        &self,
414        scope: Sc,
415        buf: &'a Buf,
416        tag: Tag,
417    ) -> Request<'a, Buf, Sc>
418    where
419        Buf: 'a + Buffer + ?Sized,
420        Sc: Scope<'a>,
421    {
422        // Returns promptly: small messages are a single buffered socket write;
423        // large messages use the rendezvous protocol, which only sends a tiny
424        // ready-to-send announcement here and transfers the bulk data later on
425        // a background thread when the receiver is ready. Receives likewise
426        // overlap, since per-connection reader threads keep draining the wire.
427        self.do_send(buf, tag);
428        Request::completed(scope)
429    }
430
431    /// Non-blocking buffered send.
432    fn immediate_buffered_send<'a, Sc, Buf>(&self, scope: Sc, buf: &'a Buf) -> Request<'a, Buf, Sc>
433    where
434        Buf: 'a + Buffer + ?Sized,
435        Sc: Scope<'a>,
436    {
437        self.immediate_send_with_tag(scope, buf, DEFAULT_TAG)
438    }
439
440    /// Non-blocking synchronous send.
441    fn immediate_synchronous_send<'a, Sc, Buf>(
442        &self,
443        scope: Sc,
444        buf: &'a Buf,
445    ) -> Request<'a, Buf, Sc>
446    where
447        Buf: 'a + Buffer + ?Sized,
448        Sc: Scope<'a>,
449    {
450        self.immediate_send_with_tag(scope, buf, DEFAULT_TAG)
451    }
452
453    /// Create a persistent send request bound to `buf` (`MPI_Send_init`).
454    /// `start` it (and `wait`) repeatedly to re-send the current buffer
455    /// contents.
456    fn send_init<'a, Buf: Buffer + ?Sized>(
457        &self,
458        buf: &'a Buf,
459    ) -> crate::request::PersistentRequest<'a> {
460        let comm = self.dst_comm();
461        let bytes = buf.as_bytes();
462        crate::request::PersistentRequest::new_send(
463            comm.context,
464            comm.rank,
465            comm.world_rank(self.destination_rank()),
466            DEFAULT_TAG,
467            buf.as_datatype().id,
468            buf.count() as u64,
469            bytes.as_ptr(),
470            bytes.len(),
471        )
472    }
473}
474
475/// A pending single-value receive (`MPI_Irecv` for a scalar), completed by
476/// [`ReceiveFuture::get`]. Mirrors rsmpi's `ReceiveFuture`.
477pub struct ReceiveFuture<T> {
478    ctx: u32,
479    source: Rank,
480    tag: Tag,
481    _p: PhantomData<T>,
482}
483
484impl<T: Equivalence> ReceiveFuture<T> {
485    /// Block until the message arrives and return it.
486    pub fn get(self) -> (T, Status) {
487        let (src, t, count, _dt, payload) =
488            transport::runtime().recv(self.ctx, self.source, self.tag);
489        (
490            elem_from_bytes::<T>(&payload),
491            Status::new(src, t, count as Count, payload.len()),
492        )
493    }
494}
495
496// ---- send_receive family (MPI_Sendrecv). ----
497
498/// Send `msg` to `destination` and receive a value from `source`
499/// (`MPI_Sendrecv`).
500pub fn send_receive<R, M, D, S>(msg: &M, destination: &D, source: &S) -> (R, Status)
501where
502    M: Equivalence,
503    R: Equivalence,
504    D: Destination,
505    S: Source,
506{
507    destination.send(msg);
508    source.receive::<R>()
509}
510
511/// Send `msg` and receive into `buf`, using explicit tags
512/// (`MPI_Sendrecv`).
513pub fn send_receive_into_with_tags<M, D, B, S>(
514    msg: &M,
515    destination: &D,
516    sendtag: Tag,
517    buf: &mut B,
518    source: &S,
519    receivetag: Tag,
520) -> Status
521where
522    M: Buffer + ?Sized,
523    D: Destination,
524    B: BufferMut + ?Sized,
525    S: Source,
526{
527    destination.send_with_tag(msg, sendtag);
528    source.receive_into_with_tag(buf, receivetag)
529}
530
531/// Send `msg` and receive into `buf` with default tags.
532pub fn send_receive_into<M, D, B, S>(msg: &M, destination: &D, buf: &mut B, source: &S) -> Status
533where
534    M: Buffer + ?Sized,
535    D: Destination,
536    B: BufferMut + ?Sized,
537    S: Source,
538{
539    send_receive_into_with_tags(msg, destination, DEFAULT_TAG, buf, source, ANY_TAG)
540}
541
542/// Send the contents of `buf`, then receive into the same `buf`
543/// (`MPI_Sendrecv_replace`), using explicit tags.
544pub fn send_receive_replace_into_with_tags<B, D, S>(
545    buf: &mut B,
546    destination: &D,
547    sendtag: Tag,
548    source: &S,
549    receivetag: Tag,
550) -> Status
551where
552    B: Buffer + BufferMut + ?Sized,
553    D: Destination,
554    S: Source,
555{
556    let saved = buf.as_bytes().to_vec();
557    destination.send_with_tag(&saved[..], sendtag);
558    source.receive_into_with_tag(buf, receivetag)
559}
560
561/// `MPI_Sendrecv_replace` with default tags.
562pub fn send_receive_replace_into<B, D, S>(buf: &mut B, destination: &D, source: &S) -> Status
563where
564    B: Buffer + BufferMut + ?Sized,
565    D: Destination,
566    S: Source,
567{
568    send_receive_replace_into_with_tags(buf, destination, DEFAULT_TAG, source, ANY_TAG)
569}
570
571// ---- Process / AnyProcess handles. ----
572
573/// A handle to a specific process in a communicator. Implements both
574/// [`Source`] and [`Destination`] (and, via the `collective` module,
575/// [`crate::collective::Root`]).
576#[derive(Copy, Clone)]
577pub struct Process<'a> {
578    pub(crate) comm: &'a CommData,
579    pub(crate) rank: Rank,
580}
581
582impl<'a> Process<'a> {
583    pub(crate) fn new(comm: &'a CommData, rank: Rank) -> Process<'a> {
584        Process { comm, rank }
585    }
586
587    /// The rank of this process within its communicator.
588    pub fn rank(&self) -> Rank {
589        self.rank
590    }
591
592    /// Whether this handle refers to the calling process.
593    pub fn is_self(&self) -> bool {
594        self.rank == self.comm.rank
595    }
596}
597
598impl Source for Process<'_> {
599    fn src_comm(&self) -> &CommData {
600        self.comm
601    }
602    fn source_rank(&self) -> Rank {
603        self.rank
604    }
605}
606
607impl Destination for Process<'_> {
608    fn dst_comm(&self) -> &CommData {
609        self.comm
610    }
611    fn destination_rank(&self) -> Rank {
612        self.rank
613    }
614}
615
616/// A handle matching any source (`MPI_ANY_SOURCE`); implements [`Source`] only.
617#[derive(Copy, Clone)]
618pub struct AnyProcess<'a> {
619    comm: &'a CommData,
620}
621
622impl<'a> AnyProcess<'a> {
623    pub(crate) fn new(comm: &'a CommData) -> AnyProcess<'a> {
624        AnyProcess { comm }
625    }
626}
627
628impl Source for AnyProcess<'_> {
629    fn src_comm(&self) -> &CommData {
630        self.comm
631    }
632    fn source_rank(&self) -> Rank {
633        ANY_SOURCE
634    }
635}