mpi/
attribute.rs

1//! Attribute caching on communicators
2
3use std::{any::TypeId, collections::HashMap, ffi::c_void, os::raw::c_int, ptr, sync::RwLock};
4
5use once_cell::sync::Lazy;
6
7use crate::{ffi, traits::AsRaw};
8
9/// Topology traits
10pub mod traits {
11    pub use super::CommAttribute;
12}
13
14pub(crate) static COMM_ATTRS: Lazy<RwLock<HashMap<TypeId, AttributeKey>>> =
15    Lazy::new(|| RwLock::new(HashMap::new()));
16
17/// Attributes are user data that can be owned by communicators and accessed by
18/// users. They are useful when libraries pass communicators to a different
19/// library and get it back in a callback.
20///
21/// # Standard section(s)
22///
23/// 7.7.1
24pub trait CommAttribute
25where
26    Self: 'static + Sized + Clone,
27{
28    /// When a communicator is duplicated, attributes can either be cloned to
29    /// the new communicator or not propagated at all. Implementations of
30    /// CommAttribute can determine this behavior by defining this associated
31    /// constant. The default does not propagated attributes to cloned
32    /// communicators.
33    const CLONE_ON_DUP: bool = false;
34
35    /// Callback invoked by `MPI Comm_free`, `MPI_Comm_disconnect`, and
36    /// `MPI_Comm_delete_attr` to delete an attribute.
37    ///
38    /// User-defined attributes should not need to override this default
39    /// implementation.
40    ///
41    /// # Safety
42    ///
43    /// This default implementation goes with the boxing in
44    /// `AnyCommunicator::set_attr()`.
45    unsafe extern "C" fn comm_delete_attr_fn(
46        _comm: ffi::MPI_Comm,
47        _key: c_int,
48        val: *mut c_void,
49        _extra_state: *mut c_void,
50    ) -> c_int {
51        let _to_drop = Box::from_raw(val as *mut Self);
52        ffi::MPI_SUCCESS as i32
53    }
54
55    /// Callback invoked by `MPI_Comm_dup()`, `MPI_Comm_idup()`, and variants to
56    /// (optionally) clone the attribute to the new communicator. The behavior
57    /// of this function is determined by `Self::CLONE_ON_DUP`, which should be
58    /// sufficient to obtain desired semantics.
59    ///
60    /// User-defined attributes should not need to override this default
61    /// implementation.
62    ///
63    /// # Safety
64    ///
65    /// This default implementation must only be used with boxed attributes, as
66    /// in `AnyCommunicator::set_attr()`.
67    unsafe extern "C" fn comm_copy_attr_fn(
68        _old_comm: ffi::MPI_Comm,
69        _key: c_int,
70        _extra_state: *mut c_void,
71        val_in: *mut c_void,
72        val_out: *mut c_void,
73        flag: *mut c_int,
74    ) -> c_int {
75        if Self::CLONE_ON_DUP {
76            let b_in = Box::from_raw(val_in as *mut Self);
77            let b_out = b_in.clone();
78            *(val_out as *mut *mut Self) = Box::into_raw(b_out);
79            Box::into_raw(b_in); // deconstruct to avoid dropping
80            *flag = 1;
81        } else {
82            *flag = 0;
83        }
84        ffi::MPI_SUCCESS as i32
85    }
86
87    /// Get the attribute key for this attribute. User keys are provisioned by
88    /// `MPI_Comm_create_keyval()`, which we store in the universe and free
89    /// prior to `MPI_Finalize()` when the universe is dropped.
90    ///
91    /// In multi-language projects for which `MPI_Comm_create_keyval()` is
92    /// called from a different language, the attribute can be set and retrieved
93    /// in Rust by overriding this default implementation to return the foreign
94    /// key.
95    fn get_key() -> AttributeKey {
96        let id = TypeId::of::<Self>();
97        {
98            let comm_attrs = COMM_ATTRS.read().expect("COMM_ATTRS RwLock poisoned");
99            if let Some(key) = comm_attrs.get(&id) {
100                return key.clone();
101            }
102        }
103        let mut key: i32 = 0;
104        unsafe {
105            ffi::MPI_Comm_create_keyval(
106                Some(Self::comm_copy_attr_fn),
107                Some(Self::comm_delete_attr_fn),
108                &mut key,
109                ptr::null_mut(),
110            );
111        }
112        let key = AttributeKey(key);
113        let mut comm_attrs = COMM_ATTRS.write().expect("COMM_ATTRS RwLock poisoned");
114        comm_attrs.insert(id, key.clone());
115        key
116    }
117}
118
119/// Attribute keys are used internally to access attributes. They are obtained
120/// with the associated function `CommAttribute::get_key()`.
121///
122/// User keys are created with `MPI_Comm_create_keyval()` and should be freed
123/// with `MPI_Comm_free_keyval()`. They are provisioned in the default
124/// implementation of `CommAttribute::get_key()` and stored persistently in
125/// `COMM_ATTRS`.
126///
127/// System keys are automatically available and are not passed to
128/// `MPI_Comm_free_keyval()`.
129#[allow(missing_copy_implementations)]
130#[derive(Debug, Clone)]
131pub struct AttributeKey(i32);
132
133impl AttributeKey {
134    /// Create a new attribute key. This is mostly unnecessary for users, but
135    /// may be used when implementing `CommAttribute::get_key()` to be used with
136    /// a foreign keyval.
137    ///
138    /// # Safety
139    ///
140    /// The key must be a valid predefined system keyval or obtained by
141    /// `MPI_Comm_create_keyval()`. Strictly speaking, I think this can be safe,
142    /// but an invalid value will cause `MPI_Comm_get_attr()` and
143    /// `MPI_Comm_set_attr()` to fail. Note that they can also be made to fail
144    /// if `MPI_Comm_free_keyval()` is called after creating an `AttributeKey`
145    /// and before it is used. I'm leaving it `unsafe` to be defensive and
146    /// because the caller should think carefully about lifetime of their
147    /// foreign keyvals when interoperating with Rust.
148    pub unsafe fn new_unchecked(k: i32) -> Self {
149        Self(k)
150    }
151}
152
153unsafe impl AsRaw for AttributeKey {
154    type Raw = c_int;
155    fn as_raw(&self) -> Self::Raw {
156        self.0
157    }
158}
159
160/// For obtaining the universe size attribute
161#[repr(C)]
162#[derive(Clone)]
163pub(crate) struct UniverseSize(c_int);
164
165impl CommAttribute for UniverseSize {
166    fn get_key() -> AttributeKey {
167        unsafe { AttributeKey::new_unchecked(ffi::MPI_UNIVERSE_SIZE as i32) }
168    }
169}
170
171impl TryFrom<&UniverseSize> for usize {
172    type Error = std::num::TryFromIntError;
173    fn try_from(s: &UniverseSize) -> Result<Self, Self::Error> {
174        usize::try_from(s.0)
175    }
176}
177
178/// For obtaining the appnum attribute of MPI_COMM_WORLD
179#[repr(C)]
180#[derive(Clone)]
181pub(crate) struct AppNum(c_int);
182
183impl CommAttribute for AppNum {
184    fn get_key() -> AttributeKey {
185        unsafe { AttributeKey::new_unchecked(ffi::MPI_APPNUM as i32) }
186    }
187}
188
189impl From<&AppNum> for isize {
190    fn from(an: &AppNum) -> Self {
191        an.0 as isize
192    }
193}