1use crate::map::{truncate_message, MAX_ERROR_MESSAGE_BYTES};
2use crate::mpi_backend::collective::SystemOperation;
3use crate::mpi_backend::traits::*;
4use crate::mpi_check::{is_thread_main, mpi_call};
5use crate::wire::{self, Header, MessageKind, MessageStatus};
6use serde::{de::DeserializeOwned, Serialize};
7use std::fmt::{self, Display, Formatter};
8
9const HEADER_TAG: i32 = 2;
10const PAYLOAD_TAG: i32 = 3;
11const FRAME_ID: u64 = 0;
12const FRAME_ITEMS: u64 = 1;
13
14#[derive(Debug, Copy, Clone, Eq, PartialEq)]
15enum Operation {
16 Broadcast,
17 Scatter,
18 Gather,
19}
20
21impl Operation {
22 const fn code(self) -> i32 {
23 match self {
24 Self::Broadcast => 0,
25 Self::Scatter => 1,
26 Self::Gather => 2,
27 }
28 }
29
30 const fn message_kind(self) -> MessageKind {
31 match self {
32 Self::Broadcast => MessageKind::Broadcast,
33 Self::Scatter => MessageKind::Scatter,
34 Self::Gather => MessageKind::Gather,
35 }
36 }
37}
38
39#[derive(Debug, Copy, Clone, Eq, PartialEq)]
41pub enum PlacementErrorKind {
42 Preflight,
44 Wire,
46 Protocol,
48}
49
50#[derive(Debug, Clone, Eq, PartialEq)]
52pub struct PlacementError {
53 kind: PlacementErrorKind,
54 rank: Option<i32>,
55 message: String,
56}
57
58impl PlacementError {
59 fn new(kind: PlacementErrorKind, rank: Option<i32>, message: impl Display) -> Self {
60 Self {
61 kind,
62 rank,
63 message: truncate_message(message.to_string()),
64 }
65 }
66
67 pub const fn kind(&self) -> PlacementErrorKind {
69 self.kind
70 }
71
72 pub const fn rank(&self) -> Option<i32> {
74 self.rank
75 }
76
77 pub fn message(&self) -> &str {
79 &self.message
80 }
81}
82
83impl Display for PlacementError {
84 fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
85 formatter.write_str(self.message())
86 }
87}
88
89impl std::error::Error for PlacementError {}
90
91struct Failure {
92 kind: PlacementErrorKind,
93 message: String,
94}
95
96impl Failure {
97 fn new(kind: PlacementErrorKind, message: impl Display) -> Self {
98 Self {
99 kind,
100 message: truncate_message(message.to_string()),
101 }
102 }
103}
104
105fn record_failure(slot: &mut Option<Failure>, failure: Failure) {
106 if slot.is_none() {
107 *slot = Some(failure);
108 }
109}
110
111impl PlacementErrorKind {
112 const fn code(self) -> i32 {
113 match self {
114 Self::Preflight => 0,
115 Self::Wire => 1,
116 Self::Protocol => 2,
117 }
118 }
119
120 const fn from_code(code: i32) -> Option<Self> {
121 match code {
122 0 => Some(Self::Preflight),
123 1 => Some(Self::Wire),
124 2 => Some(Self::Protocol),
125 _ => None,
126 }
127 }
128}
129
130fn converge<C: Communicator>(comm: &C, local: Option<Failure>) -> Option<PlacementError> {
131 let rank = mpi_call!(comm.rank());
132 let size = mpi_call!(comm.size());
133 let candidate = if local.is_some() { rank } else { size };
134 let mut winner = candidate;
135 mpi_call!(comm.all_reduce_into(&candidate, &mut winner, SystemOperation::min()));
136 if winner == size {
137 return None;
138 }
139 if winner < 0 || winner >= size {
140 mpi_call!(comm.abort(74));
141 }
142
143 let is_winner = rank == winner;
144 if is_winner && local.is_none() {
145 mpi_call!(comm.abort(74));
146 }
147 let mut kind = local
148 .as_ref()
149 .filter(|_| is_winner)
150 .map_or(PlacementErrorKind::Protocol.code(), |failure| {
151 failure.kind.code()
152 });
153 let mut bytes = local
154 .filter(|_| is_winner)
155 .map_or_else(Vec::new, |failure| failure.message.into_bytes());
156 let mut length = match i32::try_from(bytes.len()) {
157 Ok(length) => length,
158 Err(_) => mpi_call!(comm.abort(74)),
159 };
160
161 mpi_call!(comm.process_at_rank(winner).broadcast_into(&mut kind));
162 mpi_call!(comm.process_at_rank(winner).broadcast_into(&mut length));
163 if length < 0 || length as usize > MAX_ERROR_MESSAGE_BYTES {
164 mpi_call!(comm.abort(74));
165 }
166 if !is_winner {
167 bytes.resize(length as usize, 0);
168 }
169 mpi_call!(comm
170 .process_at_rank(winner)
171 .broadcast_into(bytes.as_mut_slice()));
172
173 let Some(kind) = PlacementErrorKind::from_code(kind) else {
174 mpi_call!(comm.abort(74));
175 };
176 let message = match String::from_utf8(bytes) {
177 Ok(message) => message,
178 Err(_) => mpi_call!(comm.abort(74)),
179 };
180 Some(PlacementError::new(kind, Some(winner), message))
181}
182
183fn preflight<C: Communicator>(
184 comm: &C,
185 operation: Operation,
186 root: i32,
187 local_shape: bool,
188) -> Result<(), PlacementError> {
189 let local_operation = operation.code();
190 let mut min_operation = local_operation;
191 let mut max_operation = local_operation;
192 mpi_call!(comm.all_reduce_into(&local_operation, &mut min_operation, SystemOperation::min()));
193 mpi_call!(comm.all_reduce_into(&local_operation, &mut max_operation, SystemOperation::max()));
194
195 let mut min_root = root;
196 let mut max_root = root;
197 mpi_call!(comm.all_reduce_into(&root, &mut min_root, SystemOperation::min()));
198 mpi_call!(comm.all_reduce_into(&root, &mut max_root, SystemOperation::max()));
199
200 let size = mpi_call!(comm.size());
201 let local = if min_operation != max_operation {
202 Some(Failure::new(
203 PlacementErrorKind::Preflight,
204 "placement operation mismatch",
205 ))
206 } else if min_root != max_root {
207 Some(Failure::new(
208 PlacementErrorKind::Preflight,
209 "placement root mismatch",
210 ))
211 } else if root < 0 || root >= size {
212 Some(Failure::new(
213 PlacementErrorKind::Preflight,
214 "placement root is out of range",
215 ))
216 } else if !local_shape {
217 Some(Failure::new(
218 PlacementErrorKind::Preflight,
219 "placement input shape is invalid",
220 ))
221 } else if !is_thread_main() {
222 Some(Failure::new(
223 PlacementErrorKind::Preflight,
224 "placement helper must run on the MPI main thread",
225 ))
226 } else {
227 None
228 };
229 match converge(comm, local) {
230 Some(error) => Err(error),
231 None => Ok(()),
232 }
233}
234
235fn encode<T: Serialize>(value: &T) -> Result<Vec<u8>, Failure> {
236 wire::encode_payload(value).map_err(|error| Failure::new(PlacementErrorKind::Wire, error))
237}
238
239fn send_frame<C: Communicator>(comm: &C, destination: i32, operation: Operation, payload: &[u8]) {
240 let header = match Header::new(
241 operation.message_kind(),
242 MessageStatus::None,
243 FRAME_ID,
244 FRAME_ITEMS,
245 payload.len() as u64,
246 ) {
247 Ok(header) => header,
248 Err(_) => mpi_call!(comm.abort(74)),
249 };
250 mpi_call!(comm
251 .process_at_rank(destination)
252 .send_with_tag(&header.encode(), HEADER_TAG));
253 mpi_call!(comm
254 .process_at_rank(destination)
255 .send_with_tag(payload, PAYLOAD_TAG));
256}
257
258fn receive_frame<C: Communicator>(comm: &C, source: i32) -> (Vec<u8>, Vec<u8>) {
259 let (header, _) = mpi_call!(comm
261 .process_at_rank(source)
262 .receive_vec_with_tag::<u8>(HEADER_TAG));
263 let (payload, _) = mpi_call!(comm
264 .process_at_rank(source)
265 .receive_vec_with_tag::<u8>(PAYLOAD_TAG));
266 (header, payload)
267}
268
269fn decode_received<T: DeserializeOwned>(
270 header_bytes: &[u8],
271 payload: &[u8],
272 operation: Operation,
273) -> Result<T, Failure> {
274 let header = Header::decode(header_bytes)
275 .map_err(|error| Failure::new(PlacementErrorKind::Wire, error))?;
276 if header.kind() != operation.message_kind()
277 || header.status() != MessageStatus::None
278 || header.batch_id() != FRAME_ID
279 || header.item_count() != FRAME_ITEMS
280 {
281 return Err(Failure::new(
282 PlacementErrorKind::Protocol,
283 "invalid placement frame schema",
284 ));
285 }
286 wire::checked_mpi_count(header.payload_len())
287 .map_err(|error| Failure::new(PlacementErrorKind::Wire, error))?;
288 wire::checked_usize_length(header.payload_len())
289 .map_err(|error| Failure::new(PlacementErrorKind::Wire, error))?;
290 if payload.len() as u64 != header.payload_len() {
291 return Err(Failure::new(
292 PlacementErrorKind::Protocol,
293 "placement payload length does not match its header",
294 ));
295 }
296 wire::checked_mpi_count(payload.len() as u64)
297 .map_err(|error| Failure::new(PlacementErrorKind::Wire, error))?;
298 wire::decode_payload(payload).map_err(|error| Failure::new(PlacementErrorKind::Wire, error))
299}
300
301fn preencode_scatter<T: Serialize>(
302 shards: &mut [Option<T>],
303 root: i32,
304 size: i32,
305) -> (Option<T>, Vec<Option<Vec<u8>>>, Option<Failure>) {
306 let local = shards[root as usize].take();
307 let mut encoded = (0..size).map(|_| None).collect::<Vec<_>>();
308 let mut failure = None;
309 for destination in 0..size {
310 if destination == root {
311 continue;
312 }
313 match shards[destination as usize].as_ref() {
314 Some(value) => match encode(value) {
315 Ok(bytes) => encoded[destination as usize] = Some(bytes),
316 Err(error) => record_failure(&mut failure, error),
317 },
318 None => record_failure(
319 &mut failure,
320 Failure::new(PlacementErrorKind::Protocol, "missing scatter shard"),
321 ),
322 }
323 }
324 (local, encoded, failure)
325}
326
327pub fn broadcast<C, T>(world: &C, root: i32, root_value: Option<T>) -> Result<T, PlacementError>
332where
333 C: Communicator,
334 T: Serialize + DeserializeOwned,
335{
336 let comm = mpi_call!(world.duplicate());
337 let rank = mpi_call!(comm.rank());
338 let size = mpi_call!(comm.size());
339 preflight(
340 &comm,
341 Operation::Broadcast,
342 root,
343 (rank == root) == root_value.is_some(),
344 )?;
345
346 if size == 1 {
347 return Ok(root_value.unwrap_or_else(|| mpi_call!(comm.abort(74))));
348 }
349
350 let (encoded, encode_failure) = if rank == root {
351 match root_value.as_ref() {
352 Some(value) => match encode(value) {
353 Ok(bytes) => (Some(bytes), None),
354 Err(error) => (None, Some(error)),
355 },
356 None => (
357 None,
358 Some(Failure::new(
359 PlacementErrorKind::Protocol,
360 "missing root broadcast value",
361 )),
362 ),
363 }
364 } else {
365 (None, None)
366 };
367 if let Some(error) = converge(&comm, encode_failure) {
368 return Err(error);
369 }
370
371 let mut received = None;
372 let mut local_failure = None;
373 if rank == root {
374 let payload = encoded
375 .as_deref()
376 .unwrap_or_else(|| mpi_call!(comm.abort(74)));
377 for destination in 0..size {
378 if destination != root {
379 send_frame(&comm, destination, Operation::Broadcast, payload);
380 }
381 }
382 } else {
383 let (header, payload) = receive_frame(&comm, root);
384 match decode_received(&header, &payload, Operation::Broadcast) {
385 Ok(value) => received = Some(value),
386 Err(error) => local_failure = Some(error),
387 }
388 }
389 if let Some(error) = converge(&comm, local_failure) {
390 return Err(error);
391 }
392
393 if rank == root {
394 Ok(root_value.unwrap_or_else(|| mpi_call!(comm.abort(74))))
395 } else {
396 Ok(received.unwrap_or_else(|| mpi_call!(comm.abort(74))))
397 }
398}
399
400pub fn scatter<C, T>(world: &C, root: i32, root_shards: Option<Vec<T>>) -> Result<T, PlacementError>
405where
406 C: Communicator,
407 T: Serialize + DeserializeOwned,
408{
409 let comm = mpi_call!(world.duplicate());
410 let rank = mpi_call!(comm.rank());
411 let size = mpi_call!(comm.size());
412 let local_shape = (rank == root) == root_shards.is_some()
413 && (rank != root
414 || root_shards
415 .as_ref()
416 .is_some_and(|shards| shards.len() == size as usize));
417 preflight(&comm, Operation::Scatter, root, local_shape)?;
418
419 let mut shards = root_shards.map(|shards| shards.into_iter().map(Some).collect::<Vec<_>>());
420 if size == 1 {
421 return Ok(shards
422 .as_mut()
423 .and_then(|shards| shards[0].take())
424 .unwrap_or_else(|| mpi_call!(comm.abort(74))));
425 }
426
427 let (local_value, encoded, encode_failure) = if rank == root {
428 preencode_scatter(
429 shards.as_mut().unwrap_or_else(|| mpi_call!(comm.abort(74))),
430 root,
431 size,
432 )
433 } else {
434 (None, Vec::new(), None)
435 };
436 if let Some(error) = converge(&comm, encode_failure) {
437 return Err(error);
438 }
439
440 let mut received = None;
441 let mut local_failure = None;
442 if rank == root {
443 for destination in 0..size {
444 if destination != root {
445 let payload = encoded[destination as usize]
446 .as_deref()
447 .unwrap_or_else(|| mpi_call!(comm.abort(74)));
448 send_frame(&comm, destination, Operation::Scatter, payload);
449 }
450 }
451 } else {
452 let (header, payload) = receive_frame(&comm, root);
453 match decode_received(&header, &payload, Operation::Scatter) {
454 Ok(value) => received = Some(value),
455 Err(error) => local_failure = Some(error),
456 }
457 }
458 if let Some(error) = converge(&comm, local_failure) {
459 return Err(error);
460 }
461
462 if rank == root {
463 Ok(local_value.unwrap_or_else(|| mpi_call!(comm.abort(74))))
464 } else {
465 Ok(received.unwrap_or_else(|| mpi_call!(comm.abort(74))))
466 }
467}
468
469pub fn gather<C, T>(world: &C, root: i32, value: T) -> Result<Option<Vec<T>>, PlacementError>
474where
475 C: Communicator,
476 T: Serialize + DeserializeOwned,
477{
478 let comm = mpi_call!(world.duplicate());
479 let rank = mpi_call!(comm.rank());
480 let size = mpi_call!(comm.size());
481 preflight(&comm, Operation::Gather, root, true)?;
482
483 if size == 1 {
484 return Ok(Some(vec![value]));
485 }
486
487 let (encoded, encode_failure) = if rank == root {
488 (None, None)
489 } else {
490 match encode(&value) {
491 Ok(bytes) => (Some(bytes), None),
492 Err(error) => (None, Some(error)),
493 }
494 };
495 if let Some(error) = converge(&comm, encode_failure) {
496 return Err(error);
497 }
498
499 let mut gathered = if rank == root {
500 let mut values = (0..size).map(|_| None).collect::<Vec<Option<T>>>();
501 values[root as usize] = Some(value);
502 Some(values)
503 } else {
504 None
505 };
506 let mut local_failure = None;
507 if rank == root {
508 for source in 0..size {
509 if source == root {
510 continue;
511 }
512 let (header, payload) = receive_frame(&comm, source);
513 match decode_received(&header, &payload, Operation::Gather) {
514 Ok(value) => {
515 gathered
516 .as_mut()
517 .unwrap_or_else(|| mpi_call!(comm.abort(74)))[source as usize] =
518 Some(value);
519 }
520 Err(error) => record_failure(&mut local_failure, error),
521 }
522 }
523 } else {
524 send_frame(
525 &comm,
526 root,
527 Operation::Gather,
528 encoded
529 .as_deref()
530 .unwrap_or_else(|| mpi_call!(comm.abort(74))),
531 );
532 }
533 if let Some(error) = converge(&comm, local_failure) {
534 return Err(error);
535 }
536
537 if rank == root {
538 let values = gathered.unwrap_or_else(|| mpi_call!(comm.abort(74)));
539 let mut output = Vec::with_capacity(size as usize);
540 for value in values {
541 output.push(value.unwrap_or_else(|| mpi_call!(comm.abort(74))));
542 }
543 Ok(Some(output))
544 } else {
545 Ok(None)
546 }
547}
548
549#[cfg(test)]
550mod tests {
551 use super::*;
552
553 #[test]
554 fn error_accessors_and_bound_are_stable() {
555 let error = PlacementError::new(
556 PlacementErrorKind::Wire,
557 Some(3),
558 "é".repeat(MAX_ERROR_MESSAGE_BYTES),
559 );
560 assert_eq!(error.kind(), PlacementErrorKind::Wire);
561 assert_eq!(error.rank(), Some(3));
562 assert!(error.message().len() <= MAX_ERROR_MESSAGE_BYTES);
563 assert!(error.message().is_char_boundary(error.message().len()));
564 assert_eq!(error.to_string(), error.message());
565 }
566
567 #[test]
568 fn operation_kinds_have_checked_none_status_headers() {
569 for operation in [Operation::Broadcast, Operation::Scatter, Operation::Gather] {
570 let header = Header::new(
571 operation.message_kind(),
572 MessageStatus::None,
573 FRAME_ID,
574 FRAME_ITEMS,
575 0,
576 )
577 .unwrap();
578 assert_eq!(header.kind(), operation.message_kind());
579 assert_eq!(Header::decode(&header.encode()).unwrap(), header);
580 }
581 }
582
583 #[test]
584 fn malformed_announced_length_is_rejected_after_payload_is_available() {
585 let header = Header::new(
586 MessageKind::Broadcast,
587 MessageStatus::None,
588 FRAME_ID,
589 FRAME_ITEMS,
590 8,
591 )
592 .unwrap();
593 let error =
594 decode_received::<i32>(&header.encode(), &[0_u8; 4], Operation::Broadcast).unwrap_err();
595 assert_eq!(error.kind, PlacementErrorKind::Protocol);
596 assert_eq!(
597 error.message,
598 "placement payload length does not match its header"
599 );
600 }
601
602 #[test]
603 fn scatter_preencode_takes_root_shard_without_cloning() {
604 let mut shards = vec![Some(String::from("root")), Some(String::from("remote"))];
605 let (local, encoded, failure) = preencode_scatter(&mut shards, 0, 2);
606 assert_eq!(local.as_deref(), Some("root"));
607 assert!(shards[0].is_none());
608 assert!(shards[1].is_some());
609 assert!(encoded[1].is_some());
610 assert!(failure.is_none());
611 }
612
613 #[test]
614 fn operation_discriminators_are_distinct() {
615 assert_ne!(Operation::Broadcast.code(), Operation::Scatter.code());
616 assert_ne!(Operation::Scatter.code(), Operation::Gather.code());
617 assert_eq!(Operation::Gather.message_kind().code(), 7);
618 }
619}