hwlocality/object/
depth.rs

1//! Object depth
2//!
3//! An hwloc topology is a tree of [`TopologyObject`]s. Within this tree, almost
4//! every [`ObjectType`] that exists in the topology has its own, designated
5//! depth, with the exception of [Group] objects which may exist at multiple
6//! depths. This mechanism makes it possible to cheaply look up objects of most
7//! types in the topology by just querying the associated depth, at the expense
8//! of allowing some counterintuitive depth jumps from parents of depth N to
9//! children of depth `N + M` with `M != 1` in complex topologies with
10//! heterogeneous CPU cores.
11//!
12//! Like [Group] objects, [Memory], [I/O] and [Misc] objects can appear at
13//! multiple depths of the topology. However, these objects types are handled
14//! differently from [Group] objects. Instead of having a normal depth like all
15//! other objects, they use a "virtual depth" mechanism where all objects of
16//! these types appear to reside at the same virtual depth. This extends the
17//! cheap depth lookup of normal object types to these special object types, at
18//! the expense of making the depth type not totally ordered.
19//!
20//! [Group]: ObjectType::Group
21//! [I/O]: ObjectType::is_io()
22//! [Memory]: ObjectType::is_memory()
23//! [Misc]: ObjectType::Misc
24
25// Main docs: https://hwloc.readthedocs.io/en/stable/group__hwlocality__levels.html
26
27use crate::ffi::{int::PositiveInt, unknown::UnknownVariant};
28#[cfg(doc)]
29use crate::object::{types::ObjectType, TopologyObject};
30#[cfg(feature = "hwloc-2_1_0")]
31use hwlocality_sys::HWLOC_TYPE_DEPTH_MEMCACHE;
32use hwlocality_sys::{
33    hwloc_get_type_depth_e, HWLOC_TYPE_DEPTH_BRIDGE, HWLOC_TYPE_DEPTH_MISC,
34    HWLOC_TYPE_DEPTH_MULTIPLE, HWLOC_TYPE_DEPTH_NUMANODE, HWLOC_TYPE_DEPTH_OS_DEVICE,
35    HWLOC_TYPE_DEPTH_PCI_DEVICE, HWLOC_TYPE_DEPTH_UNKNOWN,
36};
37#[cfg(any(test, feature = "proptest"))]
38use proptest::prelude::*;
39#[allow(unused)]
40#[cfg(test)]
41use similar_asserts::assert_eq;
42use std::{fmt, num::TryFromIntError};
43use thiserror::Error;
44
45/// Depth of a normal object (not Memory, I/O or Misc)
46pub type NormalDepth = PositiveInt;
47
48/// Valid object/type depth values
49///
50/// See the [module-level documentation](self) for context.
51#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
52#[doc(alias = "hwloc_get_type_depth_e")]
53pub enum Depth {
54    /// Depth of a normal object (not Memory, I/O or Misc)
55    Normal(NormalDepth),
56
57    /// Virtual depth for [`ObjectType::NUMANode`]
58    #[doc(alias = "HWLOC_TYPE_DEPTH_NUMANODE")]
59    NUMANode,
60
61    /// Virtual depth for [`ObjectType::Bridge`]
62    #[doc(alias = "HWLOC_TYPE_DEPTH_BRIDGE")]
63    Bridge,
64
65    /// Virtual depth for [`ObjectType::PCIDevice`]
66    #[doc(alias = "HWLOC_TYPE_DEPTH_PCI_DEVICE")]
67    PCIDevice,
68
69    /// Virtual depth for [`ObjectType::OSDevice`]
70    #[doc(alias = "HWLOC_TYPE_DEPTH_OS_DEVICE")]
71    OSDevice,
72
73    /// Virtual depth for [`ObjectType::Misc`]
74    #[doc(alias = "HWLOC_TYPE_DEPTH_MISC")]
75    Misc,
76
77    /// Virtual depth for [`ObjectType::MemCache`]
78    #[cfg(feature = "hwloc-2_1_0")]
79    #[doc(alias = "HWLOC_TYPE_DEPTH_MEMCACHE")]
80    MemCache,
81    // NOTE: Also add new virtual depths to the VIRTUAL_DEPTHS array and its
82    //       type-specific declination below
83    //
84    /// Unknown [`hwloc_get_type_depth_e`] from hwloc
85    Unknown(UnknownVariant<hwloc_get_type_depth_e>),
86}
87//
88impl Depth {
89    /// Assert that this should be a normal object depth, panicking if it turns
90    /// out not to be the case.
91    pub fn expect_normal(self) -> NormalDepth {
92        NormalDepth::try_from(self).expect("Not a normal object depth")
93    }
94
95    /// List of virtual depths
96    pub const VIRTUAL_DEPTHS: &'static [Self] = &[
97        #[cfg(feature = "hwloc-2_1_0")]
98        Self::MemCache,
99        Self::NUMANode,
100        Self::Bridge,
101        Self::PCIDevice,
102        Self::OSDevice,
103        Self::Misc,
104    ];
105
106    /// List of memory object virtual depths
107    pub const MEMORY_DEPTHS: &'static [Self] = &[
108        #[cfg(feature = "hwloc-2_1_0")]
109        Self::MemCache,
110        Self::NUMANode,
111    ];
112
113    /// List of I/O object virtual depths
114    pub const IO_DEPTHS: &'static [Self] = &[Self::Bridge, Self::PCIDevice, Self::OSDevice];
115
116    /// Decode depth results from hwloc
117    ///
118    /// # Safety
119    ///
120    /// This type normally maintains the invariant that it holds a valid hwloc
121    /// input, and safe code relies on this to treat any C representation of
122    /// this enum as valid to send to hwloc. Therefore, you must enforce that
123    /// either of the following is true:
124    ///
125    /// - `value` is a known hwloc depth valueor was emitted by hwloc as output,
126    ///   and therefore is known/suspected to be a safe hwloc input.
127    /// - The output of `from_hwloc` from a `value` that is _not_ a known-good
128    ///   hwloc input is never sent to any hwloc API, either directly or via a
129    ///   safe `hwlocality` method. This possibility is mainly provided for
130    ///   unit testing code and not meant to be used on a larger scale.
131    pub(crate) unsafe fn from_hwloc(
132        value: hwloc_get_type_depth_e,
133    ) -> Result<Self, TypeToDepthError> {
134        match value {
135            normal if normal >= 0 => {
136                let normal = NormalDepth::try_from_c_int(normal)
137                    .expect("NormalDepth should support all positive depths");
138                Ok(normal.into())
139            }
140            HWLOC_TYPE_DEPTH_UNKNOWN => Err(TypeToDepthError::Nonexistent),
141            HWLOC_TYPE_DEPTH_MULTIPLE => Err(TypeToDepthError::Multiple),
142            HWLOC_TYPE_DEPTH_NUMANODE => Ok(Self::NUMANode),
143            HWLOC_TYPE_DEPTH_BRIDGE => Ok(Self::Bridge),
144            HWLOC_TYPE_DEPTH_PCI_DEVICE => Ok(Self::PCIDevice),
145            HWLOC_TYPE_DEPTH_OS_DEVICE => Ok(Self::OSDevice),
146            HWLOC_TYPE_DEPTH_MISC => Ok(Self::Misc),
147            #[cfg(feature = "hwloc-2_1_0")]
148            HWLOC_TYPE_DEPTH_MEMCACHE => Ok(Self::MemCache),
149            other => Ok(Self::Unknown(UnknownVariant(other))),
150        }
151    }
152
153    /// Convert back to the hwloc depth format
154    pub(crate) fn to_hwloc(self) -> hwloc_get_type_depth_e {
155        match self {
156            Self::Normal(value) => value.to_c_int(),
157            Self::NUMANode => HWLOC_TYPE_DEPTH_NUMANODE,
158            Self::Bridge => HWLOC_TYPE_DEPTH_BRIDGE,
159            Self::PCIDevice => HWLOC_TYPE_DEPTH_PCI_DEVICE,
160            Self::OSDevice => HWLOC_TYPE_DEPTH_OS_DEVICE,
161            Self::Misc => HWLOC_TYPE_DEPTH_MISC,
162            #[cfg(feature = "hwloc-2_1_0")]
163            Self::MemCache => HWLOC_TYPE_DEPTH_MEMCACHE,
164            Self::Unknown(unknown) => unknown.get(),
165        }
166    }
167}
168//
169#[cfg(any(test, feature = "proptest"))]
170impl Arbitrary for Depth {
171    type Parameters = <NormalDepth as Arbitrary>::Parameters;
172    type Strategy = prop::strategy::TupleUnion<(
173        prop::strategy::WA<
174            prop::strategy::Map<<NormalDepth as Arbitrary>::Strategy, fn(NormalDepth) -> Self>,
175        >,
176        prop::strategy::WA<prop::sample::Select<Self>>,
177    )>;
178
179    fn arbitrary_with(args: Self::Parameters) -> Self::Strategy {
180        prop_oneof![
181            4 => NormalDepth::arbitrary_with(args).prop_map(Self::Normal),
182            1 => prop::sample::select(Self::VIRTUAL_DEPTHS)
183        ]
184    }
185}
186//
187impl Default for Depth {
188    fn default() -> Self {
189        Self::from(NormalDepth::default())
190    }
191}
192//
193impl fmt::Display for Depth {
194    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
195        #[allow(clippy::wildcard_enum_match_arm)]
196        match self {
197            Self::Normal(d) => <NormalDepth as fmt::Display>::fmt(d, f),
198            abnormal => {
199                let s = format!("<{abnormal:?}>");
200                f.pad(&s)
201            }
202        }
203    }
204}
205//
206impl From<NormalDepth> for Depth {
207    fn from(value: NormalDepth) -> Self {
208        Self::Normal(value)
209    }
210}
211//
212impl PartialEq<NormalDepth> for Depth {
213    fn eq(&self, other: &NormalDepth) -> bool {
214        *self == Self::Normal(*other)
215    }
216}
217//
218impl PartialEq<Depth> for NormalDepth {
219    fn eq(&self, other: &Depth) -> bool {
220        other == self
221    }
222}
223//
224impl PartialEq<usize> for Depth {
225    fn eq(&self, other: &usize) -> bool {
226        Self::try_from(*other) == Ok(*self)
227    }
228}
229//
230impl PartialEq<Depth> for usize {
231    fn eq(&self, other: &Depth) -> bool {
232        other == self
233    }
234}
235//
236impl TryFrom<usize> for Depth {
237    type Error = TryFromIntError;
238
239    fn try_from(value: usize) -> Result<Self, TryFromIntError> {
240        NormalDepth::try_from(value).map(Self::from)
241    }
242}
243//
244impl TryFrom<Depth> for NormalDepth {
245    type Error = Depth;
246
247    fn try_from(value: Depth) -> Result<Self, Depth> {
248        if let Depth::Normal(depth) = value {
249            Ok(depth)
250        } else {
251            Err(value)
252        }
253    }
254}
255//
256impl TryFrom<Depth> for usize {
257    type Error = Depth;
258
259    fn try_from(value: Depth) -> Result<Self, Depth> {
260        NormalDepth::try_from(value).map(Self::from)
261    }
262}
263
264/// Error from an hwloc query looking for the depth of a certain object type
265#[derive(Copy, Clone, Debug, Eq, Error, Hash, PartialEq)]
266pub enum TypeToDepthError {
267    /// No object of the requested type exists in the topology
268    #[doc(alias = "HWLOC_TYPE_DEPTH_UNKNOWN")]
269    #[error("no object of requested type exists in the topology")]
270    Nonexistent,
271
272    /// Objects of the requested type exist at different depths in the topology
273    ///
274    /// At the time of writing, this can only happen with [`ObjectType::Group`].
275    #[doc(alias = "HWLOC_TYPE_DEPTH_MULTIPLE")]
276    #[error("objects of requested type exist at different depths in the topology")]
277    Multiple,
278}
279
280#[cfg(test)]
281mod tests {
282    use crate::tests::assert_panics;
283
284    use super::*;
285    #[allow(unused)]
286    use similar_asserts::assert_eq;
287    use static_assertions::{assert_impl_all, assert_not_impl_any, assert_type_eq_all};
288    use std::{
289        error::Error,
290        fmt::{Binary, Debug, Display, LowerExp, LowerHex, Octal, Pointer, UpperExp, UpperHex},
291        hash::Hash,
292        io::{self, Read},
293        ops::Deref,
294        panic::UnwindSafe,
295    };
296
297    // Check that public types in this module keep implementing all expected
298    // traits, in the interest of detecting future semver-breaking changes
299    assert_impl_all!(Depth:
300        Copy, Debug, Default, Display, From<NormalDepth>, Hash,
301        PartialEq<NormalDepth>, PartialEq<usize>, Sized, Sync, TryFrom<usize>,
302        TryInto<NormalDepth>, TryInto<usize>, Unpin, UnwindSafe
303    );
304    assert_not_impl_any!(Depth:
305        Binary, Deref, Drop, Error, IntoIterator, LowerExp, LowerHex,
306        Octal, PartialOrd, Pointer, Read, UpperExp, UpperHex, fmt::Write,
307        io::Write
308    );
309    assert_type_eq_all!(NormalDepth, PositiveInt);
310    assert_impl_all!(TypeToDepthError:
311        Copy, Error, Hash, Sized, Sync, Unpin, UnwindSafe
312    );
313    assert_not_impl_any!(TypeToDepthError:
314        Binary, Default, Deref, Drop, IntoIterator, LowerExp, LowerHex, Octal,
315        PartialOrd, Pointer, Read, UpperExp, UpperHex, fmt::Write, io::Write
316    );
317
318    #[test]
319    fn special_values() {
320        assert_eq!(Depth::default(), Depth::from(NormalDepth::default()));
321        assert_eq!(
322            // SAFETY: Expected output from hwloc
323            unsafe { Depth::from_hwloc(HWLOC_TYPE_DEPTH_UNKNOWN) },
324            Err(TypeToDepthError::Nonexistent)
325        );
326        assert_eq!(
327            // SAFETY: Expected output from hwloc
328            unsafe { Depth::from_hwloc(HWLOC_TYPE_DEPTH_MULTIPLE) },
329            Err(TypeToDepthError::Multiple)
330        );
331        const RAW_DEPTHS: &[hwloc_get_type_depth_e] = &[
332            #[cfg(feature = "hwloc-2_1_0")]
333            {
334                HWLOC_TYPE_DEPTH_MEMCACHE
335            },
336            HWLOC_TYPE_DEPTH_NUMANODE,
337            HWLOC_TYPE_DEPTH_BRIDGE,
338            HWLOC_TYPE_DEPTH_PCI_DEVICE,
339            HWLOC_TYPE_DEPTH_OS_DEVICE,
340            HWLOC_TYPE_DEPTH_MISC,
341        ];
342        assert_eq!(RAW_DEPTHS.len(), Depth::VIRTUAL_DEPTHS.len());
343        for (&raw, &depth) in RAW_DEPTHS.iter().zip(Depth::VIRTUAL_DEPTHS) {
344            // SAFETY: Expected output from hwloc
345            assert_eq!(unsafe { Depth::from_hwloc(raw) }, Ok(depth))
346        }
347    }
348
349    proptest! {
350        #[test]
351        fn unary(depth: Depth) {
352            // A depth is either normal or virtual
353            prop_assert!(matches!(depth, Depth::Normal(_)) || Depth::VIRTUAL_DEPTHS.contains(&depth));
354            if let Depth::Normal(normal) = depth {
355                prop_assert_eq!(depth.to_string(), normal.to_string());
356                prop_assert_eq!(NormalDepth::try_from(depth), Ok(normal));
357                prop_assert_eq!(usize::try_from(depth).unwrap(), normal);
358                prop_assert_eq!(depth.expect_normal(), normal);
359                prop_assert!(depth.to_hwloc() >= 0);
360            } else {
361                prop_assert_eq!(depth.to_string(), format!("<{depth:?}>"));
362                prop_assert!(NormalDepth::try_from(depth).is_err());
363                prop_assert!(usize::try_from(depth).is_err());
364                assert_panics(|| depth.expect_normal())?;
365                prop_assert!(depth.to_hwloc() <= HWLOC_TYPE_DEPTH_NUMANODE);
366            }
367        }
368
369        #[test]
370        fn from_normal(normal: NormalDepth) {
371            prop_assert_eq!(Depth::from(normal), normal);
372            prop_assert_eq!(normal, Depth::from(normal));
373        }
374    }
375
376    /// Generate [`usize`] values that are mostly in [`NormalDepth`] range to
377    /// make sure the [`from_usize()`] test has good coverage
378    fn mostly_small_usize() -> impl Strategy<Value = usize> {
379        prop_oneof![
380            4 => usize::from(NormalDepth::MIN)..usize::from(NormalDepth::MAX),
381            1 => any::<usize>()
382        ]
383    }
384
385    proptest! {
386        #[test]
387        fn from_usize(value in mostly_small_usize()) {
388            if value < usize::from(NormalDepth::MAX) {
389                prop_assert_eq!(Depth::try_from(value).unwrap(), value);
390                prop_assert_eq!(value, Depth::try_from(value).unwrap());
391            } else {
392                prop_assert!(Depth::try_from(value).is_err());
393            }
394        }
395
396        #[test]
397        fn from_raw(value: hwloc_get_type_depth_e) {
398            // SAFETY: value is not necessarily a valid output from hwloc here,
399            //         but we will not send the resulting invalid depth to any
400            //         hwloc function so we can get away with it.
401            let depth_res = unsafe { Depth::from_hwloc(value) };
402            if value >= 0 {
403                prop_assert!(depth_res.is_ok());
404                prop_assert_eq!(depth_res.unwrap(), usize::try_from(value).unwrap());
405            } else if value == HWLOC_TYPE_DEPTH_UNKNOWN {
406                prop_assert_eq!(depth_res, Err(TypeToDepthError::Nonexistent));
407            } else if value == HWLOC_TYPE_DEPTH_MULTIPLE {
408                prop_assert_eq!(depth_res, Err(TypeToDepthError::Multiple));
409            } else if value
410                > -2 - hwloc_get_type_depth_e::try_from(Depth::VIRTUAL_DEPTHS.len()).unwrap()
411            {
412                prop_assert!(depth_res.is_ok());
413            } else {
414                prop_assert_eq!(depth_res, Ok(Depth::Unknown(UnknownVariant(value))));
415            }
416        }
417
418        #[test]
419        fn eq_int(depth: Depth, normal: NormalDepth) {
420            prop_assert_eq!(depth == normal, depth == Depth::Normal(normal));
421        }
422
423        #[test]
424        fn eq_usize(depth: Depth, value: usize) {
425            prop_assert_eq!(
426                depth == value,
427                PositiveInt::try_from(value).is_ok_and(|value| depth == value)
428            );
429        }
430    }
431}