#![warn(missing_docs)]
#![warn(clippy::all)]
use std::ffi::c_char;
mod comm;
mod datatype;
mod error;
mod ffi;
mod info;
mod persistent;
mod request;
#[cfg(feature = "numa")]
pub mod slurm;
mod status;
mod topology;
#[cfg(feature = "rma")]
mod window;
pub use comm::{Communicator, SplitType};
pub use datatype::{
BytePermutable, DatatypeTag, DoubleInt, FloatInt, Int2, LongDoubleInt, LongInt, MpiDatatype,
MpiIndexedDatatype, ShortInt,
};
pub use error::{Error, MpiErrorClass, Result};
pub use info::Info;
pub use persistent::PersistentRequest;
pub use request::Request;
pub use status::Status;
#[cfg(feature = "numa")]
pub use topology::SlurmInfo;
pub use topology::{HostEntry, TopologyInfo};
#[cfg(feature = "rma")]
pub use window::{LockAllGuard, LockGuard, LockType, SharedWindow};
use std::marker::PhantomData;
use std::sync::atomic::{AtomicBool, Ordering};
static MPI_INITIALIZED: AtomicBool = AtomicBool::new(false);
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[repr(i32)]
pub enum ThreadLevel {
Single = 0,
Funneled = 1,
Serialized = 2,
Multiple = 3,
}
#[cfg_attr(not(feature = "rma"), doc = "```compile_fail")]
#[cfg_attr(
not(feature = "rma"),
doc = "// This must not compile without --features rma."
)]
#[cfg_attr(not(feature = "rma"), doc = "let _ = ferrompi::ReduceOp::Replace;")]
#[cfg_attr(not(feature = "rma"), doc = "```")]
#[cfg_attr(feature = "rma", doc = "```no_run")]
#[cfg_attr(
feature = "rma",
doc = "// With --features rma, ReduceOp::Replace is available."
)]
#[cfg_attr(feature = "rma", doc = "let _ = ferrompi::ReduceOp::Replace;")]
#[cfg_attr(feature = "rma", doc = "```")]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(i32)]
pub enum ReduceOp {
Sum = 0,
Max = 1,
Min = 2,
Prod = 3,
BitwiseOr = 4,
BitwiseAnd = 5,
BitwiseXor = 6,
LogicalOr = 7,
LogicalAnd = 8,
LogicalXor = 9,
MaxLoc = 10,
MinLoc = 11,
#[cfg(feature = "rma")]
Replace = 12,
#[cfg(feature = "rma")]
NoOp = 13,
}
pub struct Mpi {
thread_level: ThreadLevel,
_marker: PhantomData<*const ()>,
}
impl Mpi {
pub fn init() -> Result<Self> {
Self::init_thread(ThreadLevel::Single)
}
pub fn init_thread(required: ThreadLevel) -> Result<Self> {
if MPI_INITIALIZED.swap(true, Ordering::SeqCst) {
return Err(Error::AlreadyInitialized);
}
let mut provided: i32 = 0;
let ret = unsafe { ffi::ferrompi_init_thread(required as i32, &mut provided) };
if ret != 0 {
MPI_INITIALIZED.store(false, Ordering::SeqCst);
return Err(Error::Mpi {
class: MpiErrorClass::Raw(ret),
code: ret,
message: format!("MPI_Init_thread failed with code {ret}"),
operation: Some("init_thread"),
});
}
let thread_level = match provided {
0 => ThreadLevel::Single,
1 => ThreadLevel::Funneled,
2 => ThreadLevel::Serialized,
_ => ThreadLevel::Multiple,
};
Ok(Mpi {
thread_level,
_marker: PhantomData,
})
}
pub fn thread_level(&self) -> ThreadLevel {
self.thread_level
}
pub fn world(&self) -> Communicator {
Communicator::world()
}
pub fn wtime() -> f64 {
unsafe { ffi::ferrompi_wtime() }
}
pub fn library_version() -> Result<String> {
let mut buf = [0u8; 8192];
let mut len: i32 = 0;
let ret = unsafe {
ffi::ferrompi_get_library_version(buf.as_mut_ptr().cast::<c_char>(), &mut len)
};
Error::check_with_op(ret, "get_library_version")?;
let len = (len.max(0) as usize).min(buf.len());
let s = std::str::from_utf8(&buf[..len])
.map_err(|_| Error::Internal("Invalid UTF-8 in library version string".into()))?;
Ok(s.trim_end().to_string())
}
pub fn version() -> Result<String> {
let mut buf = [0u8; 256];
let mut len: i32 = 0;
let ret = unsafe { ffi::ferrompi_get_version(buf.as_mut_ptr().cast::<c_char>(), &mut len) };
if ret != 0 {
return Err(Error::from_code_with_op(ret, "get_version"));
}
let len = (len.max(0) as usize).min(buf.len());
let s = std::str::from_utf8(&buf[..len])
.map_err(|_| Error::Internal("Invalid UTF-8 in version string".into()))?;
Ok(s.to_string())
}
pub fn is_initialized() -> bool {
let mut flag: i32 = 0;
unsafe { ffi::ferrompi_initialized(&mut flag) };
flag != 0
}
pub fn is_finalized() -> bool {
let mut flag: i32 = 0;
unsafe { ffi::ferrompi_finalized(&mut flag) };
flag != 0
}
}
impl Drop for Mpi {
fn drop(&mut self) {
if MPI_INITIALIZED.load(Ordering::SeqCst) {
unsafe {
ffi::ferrompi_finalize();
}
MPI_INITIALIZED.store(false, Ordering::SeqCst);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn thread_level_ordering() {
assert!(ThreadLevel::Single < ThreadLevel::Funneled);
assert!(ThreadLevel::Funneled < ThreadLevel::Serialized);
assert!(ThreadLevel::Serialized < ThreadLevel::Multiple);
}
#[test]
fn thread_level_equality() {
assert_eq!(ThreadLevel::Single, ThreadLevel::Single);
assert_eq!(ThreadLevel::Funneled, ThreadLevel::Funneled);
assert_eq!(ThreadLevel::Serialized, ThreadLevel::Serialized);
assert_eq!(ThreadLevel::Multiple, ThreadLevel::Multiple);
assert_ne!(ThreadLevel::Single, ThreadLevel::Multiple);
assert_ne!(ThreadLevel::Funneled, ThreadLevel::Serialized);
}
#[test]
fn thread_level_repr_values() {
assert_eq!(ThreadLevel::Single as i32, 0);
assert_eq!(ThreadLevel::Funneled as i32, 1);
assert_eq!(ThreadLevel::Serialized as i32, 2);
assert_eq!(ThreadLevel::Multiple as i32, 3);
}
#[test]
fn thread_level_debug_clone() {
let level = ThreadLevel::Funneled;
let cloned = level;
assert_eq!(format!("{cloned:?}"), "Funneled");
assert_eq!(format!("{:?}", ThreadLevel::Single), "Single");
assert_eq!(format!("{:?}", ThreadLevel::Serialized), "Serialized");
assert_eq!(format!("{:?}", ThreadLevel::Multiple), "Multiple");
}
#[test]
fn reduce_op_repr_values() {
let ops = [
(ReduceOp::Sum, 0),
(ReduceOp::Max, 1),
(ReduceOp::Min, 2),
(ReduceOp::Prod, 3),
(ReduceOp::BitwiseOr, 4),
(ReduceOp::BitwiseAnd, 5),
(ReduceOp::BitwiseXor, 6),
(ReduceOp::LogicalOr, 7),
(ReduceOp::LogicalAnd, 8),
(ReduceOp::LogicalXor, 9),
(ReduceOp::MaxLoc, 10),
(ReduceOp::MinLoc, 11),
];
for (op, expected) in ops {
assert_eq!(op as i32, expected);
}
#[cfg(feature = "rma")]
{
assert_eq!(ReduceOp::Replace as i32, 12);
assert_eq!(ReduceOp::NoOp as i32, 13);
}
}
#[test]
fn reduce_op_equality() {
assert_eq!(ReduceOp::Sum, ReduceOp::Sum);
assert_eq!(ReduceOp::Max, ReduceOp::Max);
assert_eq!(ReduceOp::Min, ReduceOp::Min);
assert_eq!(ReduceOp::Prod, ReduceOp::Prod);
assert_eq!(ReduceOp::BitwiseOr, ReduceOp::BitwiseOr);
assert_eq!(ReduceOp::BitwiseAnd, ReduceOp::BitwiseAnd);
assert_eq!(ReduceOp::BitwiseXor, ReduceOp::BitwiseXor);
assert_eq!(ReduceOp::LogicalOr, ReduceOp::LogicalOr);
assert_eq!(ReduceOp::LogicalAnd, ReduceOp::LogicalAnd);
assert_eq!(ReduceOp::LogicalXor, ReduceOp::LogicalXor);
assert_eq!(ReduceOp::MaxLoc, ReduceOp::MaxLoc);
assert_eq!(ReduceOp::MinLoc, ReduceOp::MinLoc);
assert_ne!(ReduceOp::Sum, ReduceOp::Max);
assert_ne!(ReduceOp::Min, ReduceOp::Prod);
assert_ne!(ReduceOp::Sum, ReduceOp::Prod);
assert_ne!(ReduceOp::BitwiseOr, ReduceOp::BitwiseAnd);
assert_ne!(ReduceOp::LogicalOr, ReduceOp::LogicalAnd);
assert_ne!(ReduceOp::Sum, ReduceOp::BitwiseOr);
assert_ne!(ReduceOp::MaxLoc, ReduceOp::MinLoc);
assert_ne!(ReduceOp::MaxLoc, ReduceOp::Max);
}
#[test]
fn reduce_op_debug_clone() {
let op = ReduceOp::Sum;
let cloned = op;
assert_eq!(format!("{cloned:?}"), "Sum");
assert_eq!(format!("{:?}", ReduceOp::Max), "Max");
assert_eq!(format!("{:?}", ReduceOp::Min), "Min");
assert_eq!(format!("{:?}", ReduceOp::Prod), "Prod");
assert_eq!(format!("{:?}", ReduceOp::BitwiseOr), "BitwiseOr");
assert_eq!(format!("{:?}", ReduceOp::BitwiseAnd), "BitwiseAnd");
assert_eq!(format!("{:?}", ReduceOp::BitwiseXor), "BitwiseXor");
assert_eq!(format!("{:?}", ReduceOp::LogicalOr), "LogicalOr");
assert_eq!(format!("{:?}", ReduceOp::LogicalAnd), "LogicalAnd");
assert_eq!(format!("{:?}", ReduceOp::LogicalXor), "LogicalXor");
assert_eq!(format!("{:?}", ReduceOp::MaxLoc), "MaxLoc");
assert_eq!(format!("{:?}", ReduceOp::MinLoc), "MinLoc");
}
#[test]
fn reduce_op_all_variants_match_c_switch() {
let variants = [
(ReduceOp::Sum, 0i32),
(ReduceOp::Max, 1),
(ReduceOp::Min, 2),
(ReduceOp::Prod, 3),
(ReduceOp::BitwiseOr, 4),
(ReduceOp::BitwiseAnd, 5),
(ReduceOp::BitwiseXor, 6),
(ReduceOp::LogicalOr, 7),
(ReduceOp::LogicalAnd, 8),
(ReduceOp::LogicalXor, 9),
(ReduceOp::MaxLoc, 10),
(ReduceOp::MinLoc, 11),
];
for (op, expected) in variants {
assert_eq!(op as i32, expected);
}
#[cfg(feature = "rma")]
{
assert_eq!(ReduceOp::Replace as i32, 12);
assert_eq!(ReduceOp::NoOp as i32, 13);
}
}
#[cfg(feature = "rma")]
#[test]
fn replace_noop_discriminants() {
assert_eq!(ReduceOp::Replace as i32, 12);
assert_eq!(ReduceOp::NoOp as i32, 13);
}
}