use crate::{
ProcessId,
bitmap::{Bitmap, BitmapKind, SpecializedBitmap, SpecializedBitmapRef},
errors::{self, FlagsError, HybridError, RawHwlocError},
ffi::unknown::UnknownVariant,
memory::nodeset::NodeSet,
topology::Topology,
};
#[cfg(doc)]
use crate::{cpu::cpuset::CpuSet, topology::support::MemoryBindingSupport};
use bitflags::bitflags;
use derive_more::Display;
use errno::Errno;
#[cfg(feature = "hwloc-2_11_0")]
use hwlocality_sys::HWLOC_MEMBIND_WEIGHTED_INTERLEAVE;
use hwlocality_sys::{
HWLOC_MEMBIND_BIND, HWLOC_MEMBIND_BYNODESET, HWLOC_MEMBIND_DEFAULT, HWLOC_MEMBIND_FIRSTTOUCH,
HWLOC_MEMBIND_INTERLEAVE, HWLOC_MEMBIND_MIGRATE, HWLOC_MEMBIND_MIXED, HWLOC_MEMBIND_NEXTTOUCH,
HWLOC_MEMBIND_NOCPUBIND, HWLOC_MEMBIND_PROCESS, HWLOC_MEMBIND_STRICT, HWLOC_MEMBIND_THREAD,
hwloc_bitmap_t, hwloc_const_bitmap_t, hwloc_const_topology_t, hwloc_membind_flags_t,
hwloc_membind_policy_t, hwloc_pid_t,
};
use libc::{ENOMEM, ENOSYS, EXDEV};
#[allow(unused)]
#[cfg(test)]
use similar_asserts::assert_eq;
use std::{
borrow::{Borrow, BorrowMut},
ffi::{c_int, c_void},
fmt::{self, Debug, Display},
mem::MaybeUninit,
ops::{Deref, DerefMut},
ptr::NonNull,
};
use strum::{EnumCount, EnumIter, FromRepr};
use thiserror::Error;
impl Topology {
#[doc(alias = "hwloc_alloc")]
pub fn allocate_memory(
&self,
len: usize,
) -> Result<Bytes<'_>, HybridError<MemoryAllocationError<NodeSet>>> {
self.allocate_memory_generic(len)
}
fn allocate_memory_generic<Set: SpecializedBitmap>(
&self,
len: usize,
) -> Result<Bytes<'_>, HybridError<MemoryAllocationError<Set>>> {
unsafe {
self.allocate_memory_impl("hwloc_alloc", &|| None, len, |topology, len| {
hwlocality_sys::hwloc_alloc(topology, len)
})
}
}
#[doc(alias = "hwloc_alloc_membind")]
pub fn allocate_bound_memory<SetRef: SpecializedBitmapRef>(
&self,
len: usize,
set: SetRef,
policy: MemoryBindingPolicy,
original_flags: MemoryBindingFlags,
) -> Result<Bytes<'_>, HybridError<MemoryAllocationError<SetRef::Owned>>> {
let object = MemoryBoundObject::Area;
if !self.is_valid_binding_set(&*set) {
return Err(MemoryBindingError::BadSet(object, set.to_owned()).into());
}
let flags = Self::adjust_flags_for::<SetRef::Owned>(original_flags);
let Some(flags) = flags.validate(object, MemoryBindingOperation::Allocate) else {
return Err(MemoryBindingError::BadFlags(original_flags.into()).into());
};
unsafe {
self.allocate_memory_impl(
"hwloc_alloc_membind",
&|| Some(set.to_owned()),
len,
|topology, len| {
hwlocality_sys::hwloc_alloc_membind(
topology,
len,
set.as_bitmap_ref().as_ptr(),
policy.into(),
flags.bits(),
)
},
)
}
}
#[doc(alias = "hwloc_alloc_membind_policy")]
pub fn binding_allocate_memory<SetRef: SpecializedBitmapRef>(
&self,
len: usize,
set: SetRef,
policy: MemoryBindingPolicy,
flags: MemoryBindingFlags,
) -> Result<Bytes<'_>, HybridError<MemoryAllocationError<SetRef::Owned>>> {
if flags
.validate(MemoryBoundObject::ThisProgram, MemoryBindingOperation::Bind)
.is_none()
{
return Err(MemoryBindingError::BadFlags(flags.into()).into());
}
let set: &SetRef::Owned = &set;
let flags_wo_target = flags.difference(
MemoryBindingFlags::ASSUME_SINGLE_THREAD
| MemoryBindingFlags::PROCESS
| MemoryBindingFlags::THREAD
| MemoryBindingFlags::MIGRATE,
);
if let Ok(bytes) = self.allocate_bound_memory(len, set, policy, flags_wo_target) {
return Ok(bytes);
}
self.bind_memory(set, policy, flags)?;
let mut bytes = self.allocate_memory_generic::<SetRef::Owned>(len)?;
match policy {
MemoryBindingPolicy::FirstTouch | MemoryBindingPolicy::NextTouch => {}
MemoryBindingPolicy::Bind
| MemoryBindingPolicy::Interleave
| MemoryBindingPolicy::Unknown(_) => {
bytes.fill(MaybeUninit::new(0));
}
#[cfg(feature = "hwloc-2_11_0")]
MemoryBindingPolicy::WeightedInterleave => {
bytes.fill(MaybeUninit::new(0));
}
}
Ok(bytes)
}
#[doc(alias = "hwloc_set_membind")]
pub fn bind_memory<SetRef: SpecializedBitmapRef>(
&self,
set: SetRef,
policy: MemoryBindingPolicy,
flags: MemoryBindingFlags,
) -> Result<(), HybridError<MemoryBindingError<SetRef::Owned>>> {
unsafe {
self.bind_memory_impl(
"hwloc_set_membind",
&set,
policy,
flags,
MemoryBoundObject::ThisProgram,
|topology, set, policy, flags| {
hwlocality_sys::hwloc_set_membind(topology, set, policy, flags)
},
)
}
}
#[doc(alias = "HWLOC_MEMBIND_DEFAULT")]
pub fn unbind_memory(
&self,
flags: MemoryBindingFlags,
) -> Result<(), HybridError<MemoryBindingError<NodeSet>>> {
unsafe {
self.unbind_memory_impl(
"hwloc_set_membind",
flags,
MemoryBoundObject::ThisProgram,
|topology, set, policy, flags| {
hwlocality_sys::hwloc_set_membind(topology, set, policy, flags)
},
)
}
}
#[doc(alias = "hwloc_get_membind")]
pub fn memory_binding<Set: SpecializedBitmap>(
&self,
flags: MemoryBindingFlags,
) -> Result<(Set, Option<MemoryBindingPolicy>), HybridError<MemoryBindingError<Set>>> {
unsafe {
self.memory_binding_impl(
"hwloc_get_membind",
flags,
MemoryBoundObject::ThisProgram,
MemoryBindingOperation::GetBinding,
|topology, set, policy, flags| {
hwlocality_sys::hwloc_get_membind(topology, set, policy, flags)
},
)
}
}
#[doc(alias = "hwloc_set_proc_membind")]
pub fn bind_process_memory<SetRef: SpecializedBitmapRef>(
&self,
pid: ProcessId,
set: SetRef,
policy: MemoryBindingPolicy,
flags: MemoryBindingFlags,
) -> Result<(), HybridError<MemoryBindingError<SetRef::Owned>>> {
unsafe {
self.bind_memory_impl(
"hwloc_set_proc_membind",
&set,
policy,
flags,
MemoryBoundObject::Process(pid),
|topology, set, policy, flags| {
hwlocality_sys::hwloc_set_proc_membind(
topology,
hwloc_pid_t::try_from(pid).expect("shouldn't fail for a valid PID"),
set,
policy,
flags,
)
},
)
}
}
pub fn unbind_process_memory(
&self,
pid: ProcessId,
flags: MemoryBindingFlags,
) -> Result<(), HybridError<MemoryBindingError<NodeSet>>> {
unsafe {
self.unbind_memory_impl(
"hwloc_set_proc_membind",
flags,
MemoryBoundObject::Process(pid),
|topology, set, policy, flags| {
hwlocality_sys::hwloc_set_proc_membind(
topology,
hwloc_pid_t::try_from(pid).expect("shouldn't fail for a valid PID"),
set,
policy,
flags,
)
},
)
}
}
#[doc(alias = "hwloc_get_proc_membind")]
pub fn process_memory_binding<Set: SpecializedBitmap>(
&self,
pid: ProcessId,
flags: MemoryBindingFlags,
) -> Result<(Set, Option<MemoryBindingPolicy>), HybridError<MemoryBindingError<Set>>> {
unsafe {
self.memory_binding_impl(
"hwloc_get_proc_membind",
flags,
MemoryBoundObject::Process(pid),
MemoryBindingOperation::GetBinding,
|topology, set, policy, flags| {
hwlocality_sys::hwloc_get_proc_membind(
topology,
hwloc_pid_t::try_from(pid).expect("shouldn't fail for a valid PID"),
set,
policy,
flags,
)
},
)
}
}
#[doc(alias = "hwloc_set_area_membind")]
pub fn bind_memory_area<Target: ?Sized, SetRef: SpecializedBitmapRef>(
&self,
target: &Target,
set: SetRef,
policy: MemoryBindingPolicy,
flags: MemoryBindingFlags,
) -> Result<(), HybridError<MemoryBindingError<SetRef::Owned>>> {
let target_size = std::mem::size_of_val(target);
if target_size == 0 {
return Ok(());
}
let target_ptr: *const Target = target;
unsafe {
self.bind_memory_impl(
"hwloc_set_area_membind",
&set,
policy,
flags,
MemoryBoundObject::Area,
|topology, set, policy, flags| {
hwlocality_sys::hwloc_set_area_membind(
topology,
target_ptr.cast::<c_void>(),
target_size,
set,
policy,
flags,
)
},
)
}
}
pub fn unbind_memory_area<Target: ?Sized>(
&self,
target: &Target,
flags: MemoryBindingFlags,
) -> Result<(), HybridError<MemoryBindingError<NodeSet>>> {
let target_size = std::mem::size_of_val(target);
if target_size == 0 {
return Ok(());
}
let target_ptr: *const Target = target;
unsafe {
self.unbind_memory_impl(
"hwloc_set_area_membind",
flags,
MemoryBoundObject::Area,
|topology, set, policy, flags| {
hwlocality_sys::hwloc_set_area_membind(
topology,
target_ptr.cast::<c_void>(),
target_size,
set,
policy,
flags,
)
},
)
}
}
#[doc(alias = "hwloc_get_area_membind")]
pub fn area_memory_binding<Target: ?Sized, Set: SpecializedBitmap>(
&self,
target: &Target,
flags: MemoryBindingFlags,
) -> Result<(Set, Option<MemoryBindingPolicy>), HybridError<MemoryBindingError<Set>>> {
let target_size = std::mem::size_of_val(target);
if target_size == 0 {
return Err(MemoryBindingError::BadArea.into());
}
let target_ptr: *const Target = target;
unsafe {
self.memory_binding_impl(
"hwloc_get_area_membind",
flags,
MemoryBoundObject::Area,
MemoryBindingOperation::GetBinding,
|topology, set, policy, flags| {
hwlocality_sys::hwloc_get_area_membind(
topology,
target_ptr.cast::<c_void>(),
target_size,
set,
policy,
flags,
)
},
)
}
}
#[doc(alias = "hwloc_get_area_memlocation")]
pub fn area_memory_location<Target: ?Sized, Set: SpecializedBitmap>(
&self,
target: &Target,
flags: MemoryBindingFlags,
) -> Result<Set, HybridError<MemoryBindingError<Set>>> {
let target_size = std::mem::size_of_val(target);
if target_size == 0 {
return Err(MemoryBindingError::BadArea.into());
}
let target_ptr: *const Target = target;
unsafe {
self.memory_binding_impl(
"hwloc_get_area_memlocation",
flags,
MemoryBoundObject::Area,
MemoryBindingOperation::GetLastLocation,
|topology, set, policy, flags| {
*policy = -1;
hwlocality_sys::hwloc_get_area_memlocation(
topology,
target_ptr.cast::<c_void>(),
target_size,
set,
flags,
)
},
)
.map(|(set, _policy)| set)
}
}
pub(crate) fn is_valid_binding_set<Set: SpecializedBitmap>(&self, set: &Set) -> bool {
let set = set.as_bitmap_ref();
if set.is_empty() {
return false;
}
match Set::BITMAP_KIND {
BitmapKind::CpuSet => self.allowed_cpuset().as_bitmap_ref().includes(set),
BitmapKind::NodeSet => self.allowed_nodeset().as_bitmap_ref().includes(set),
}
}
fn adjust_flags_for<Set: SpecializedBitmap>(
mut flags: MemoryBindingFlags,
) -> MemoryBindingFlags {
match Set::BITMAP_KIND {
BitmapKind::CpuSet => flags.remove(MemoryBindingFlags::BY_NODE_SET),
BitmapKind::NodeSet => flags.insert(MemoryBindingFlags::BY_NODE_SET),
}
flags
}
unsafe fn allocate_memory_impl<Set: SpecializedBitmap>(
&self,
api: &'static str,
clone_set: &dyn Fn() -> Option<Set>,
len: usize,
ffi: impl FnOnce(hwloc_const_topology_t, usize) -> *mut c_void,
) -> Result<Bytes<'_>, HybridError<MemoryBindingError<Set>>> {
if len > isize::MAX as usize {
Err(MemoryAllocationError::Unsupported.into())
} else if len > 0 {
errors::call_hwloc_ptr_mut(api, || ffi(self.as_ptr(), len))
.map_err(|raw_err| {
decode_errno(
MemoryBoundObject::Area,
MemoryBindingOperation::Allocate,
clone_set,
raw_err.errno,
)
.map_or_else(|| HybridError::Hwloc(raw_err), HybridError::Rust)
})
.map(|base| unsafe { Bytes::wrap(self, base, len) })
} else {
Ok(unsafe { Bytes::wrap(self, NonNull::dangling(), 0) })
}
}
unsafe fn bind_memory_impl<Set: SpecializedBitmap>(
&self,
api: &'static str,
set: &Set,
policy: MemoryBindingPolicy,
original_flags: MemoryBindingFlags,
target: MemoryBoundObject,
ffi: impl FnOnce(
hwloc_const_topology_t,
hwloc_const_bitmap_t,
hwloc_membind_policy_t,
hwloc_membind_flags_t,
) -> c_int,
) -> Result<(), HybridError<MemoryBindingError<Set>>> {
if !self.is_valid_binding_set(set) {
return Err(MemoryBindingError::BadSet(target, set.to_owned()).into());
}
let operation = MemoryBindingOperation::Bind;
let flags = Self::adjust_flags_for::<Set>(original_flags);
let Some(flags) = flags.validate(target, operation) else {
return Err(MemoryBindingError::BadFlags(original_flags.into()).into());
};
call_hwloc_int(api, target, operation, &|| Some(set.clone()), || {
ffi(
self.as_ptr(),
set.as_bitmap_ref().as_ptr(),
policy.into(),
flags.bits(),
)
})
}
unsafe fn unbind_memory_impl(
&self,
api: &'static str,
flags: MemoryBindingFlags,
target: MemoryBoundObject,
ffi: impl FnOnce(
hwloc_const_topology_t,
hwloc_const_bitmap_t,
hwloc_membind_policy_t,
hwloc_membind_flags_t,
) -> c_int,
) -> Result<(), HybridError<MemoryBindingError<NodeSet>>> {
let operation = MemoryBindingOperation::Unbind;
let Some(flags) = flags.validate(target, operation) else {
return Err(MemoryBindingError::BadFlags(flags.into()).into());
};
call_hwloc_int::<NodeSet>(api, target, operation, &|| None, || {
ffi(
self.as_ptr(),
self.nodeset().as_ptr(),
HWLOC_MEMBIND_DEFAULT,
flags.bits(),
)
})
}
unsafe fn memory_binding_impl<Set: SpecializedBitmap>(
&self,
api: &'static str,
original_flags: MemoryBindingFlags,
target: MemoryBoundObject,
operation: MemoryBindingOperation,
ffi: impl FnOnce(
hwloc_const_topology_t,
hwloc_bitmap_t,
*mut hwloc_membind_policy_t,
hwloc_membind_flags_t,
) -> c_int,
) -> Result<(Set, Option<MemoryBindingPolicy>), HybridError<MemoryBindingError<Set>>> {
let flags = Self::adjust_flags_for::<Set>(original_flags);
let Some(flags) = flags.validate(target, operation) else {
return Err(MemoryBindingError::BadFlags(original_flags.into()).into());
};
let mut set = Bitmap::new();
let mut raw_policy = hwloc_membind_policy_t::MAX;
call_hwloc_int::<Set>(api, target, operation, &|| None, || {
ffi(
self.as_ptr(),
set.as_mut_ptr(),
&raw mut raw_policy,
flags.bits(),
)
})
.map(|()| {
unsafe fn check_policy(
raw_policy: hwloc_membind_policy_t,
) -> Option<MemoryBindingPolicy> {
#[allow(clippy::wildcard_enum_match_arm)]
match unsafe { MemoryBindingPolicy::from_hwloc(raw_policy) } {
MemoryBindingPolicy::Unknown(UnknownVariant(HWLOC_MEMBIND_MIXED)) => None,
#[cfg(not(tarpaulin_include))]
MemoryBindingPolicy::Unknown(UnknownVariant(err)) if err < 0 => {
panic!("Got unexpected negative memory policy #{err}")
}
policy => Some(policy),
}
}
(set.into(), unsafe { check_policy(raw_policy) })
})
}
}
#[cfg(not(tarpaulin_include))]
bitflags! {
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
#[doc(alias = "hwloc_membind_flags_t")]
pub struct MemoryBindingFlags: hwloc_membind_flags_t {
const ASSUME_SINGLE_THREAD = 1 << (hwloc_membind_flags_t::BITS - 2);
#[doc(alias = "HWLOC_MEMBIND_PROCESS")]
const PROCESS = HWLOC_MEMBIND_PROCESS;
#[doc(alias = "HWLOC_MEMBIND_THREAD")]
const THREAD = HWLOC_MEMBIND_THREAD;
#[doc(alias = "HWLOC_MEMBIND_STRICT")]
const STRICT = HWLOC_MEMBIND_STRICT;
#[doc(alias = "HWLOC_MEMBIND_MIGRATE")]
const MIGRATE = HWLOC_MEMBIND_MIGRATE;
#[doc(alias = "HWLOC_MEMBIND_NOCPUBIND")]
const NO_CPU_BINDING = HWLOC_MEMBIND_NOCPUBIND;
#[doc(hidden)]
#[doc(alias = "HWLOC_MEMBIND_BYNODESET")]
const BY_NODE_SET = HWLOC_MEMBIND_BYNODESET;
}
}
impl MemoryBindingFlags {
pub(crate) fn validate(
mut self,
target: MemoryBoundObject,
operation: MemoryBindingOperation,
) -> Option<Self> {
let num_target_flags = (self & (Self::PROCESS | Self::THREAD | Self::ASSUME_SINGLE_THREAD))
.bits()
.count_ones();
let expected_num_target_flags = match target {
MemoryBoundObject::ThisProgram => 1,
MemoryBoundObject::Area => 0,
MemoryBoundObject::Process(_) => match operation {
MemoryBindingOperation::Bind | MemoryBindingOperation::Unbind => 0,
MemoryBindingOperation::GetBinding | MemoryBindingOperation::GetLastLocation => 1,
MemoryBindingOperation::Allocate => unreachable!(),
},
};
if num_target_flags != expected_num_target_flags {
return None;
}
if target != MemoryBoundObject::ThisProgram && self.contains(Self::THREAD) {
return None;
}
match operation {
MemoryBindingOperation::GetLastLocation => {
if self.intersects(Self::STRICT | Self::MIGRATE | Self::NO_CPU_BINDING) {
return None;
}
}
MemoryBindingOperation::GetBinding => {
if self.intersects(Self::MIGRATE | Self::NO_CPU_BINDING) {
return None;
}
match target {
MemoryBoundObject::Area | MemoryBoundObject::Process(_) => {}
MemoryBoundObject::ThisProgram => {
if self.contains(Self::STRICT) && !self.contains(Self::PROCESS) {
return None;
}
}
}
}
MemoryBindingOperation::Unbind => {
if self.intersects(Self::STRICT | Self::MIGRATE) {
return None;
}
}
MemoryBindingOperation::Allocate => {
if self.contains(Self::MIGRATE) {
return None;
}
}
MemoryBindingOperation::Bind => {
if target == MemoryBoundObject::Area && self.contains(Self::MIGRATE) {
return None;
}
}
}
self.remove(Self::ASSUME_SINGLE_THREAD);
Some(self)
}
}
crate::impl_arbitrary_for_bitflags!(MemoryBindingFlags, hwloc_membind_flags_t);
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
pub enum MemoryBoundObject {
Process(ProcessId),
Area,
ThisProgram,
}
#[cfg(any(test, feature = "proptest"))]
impl proptest::prelude::Arbitrary for MemoryBoundObject {
type Parameters = ();
type Strategy = proptest::strategy::TupleUnion<(
proptest::strategy::WA<proptest::strategy::Just<Self>>,
proptest::strategy::WA<proptest::strategy::Just<Self>>,
proptest::strategy::WA<
proptest::strategy::Map<
<ProcessId as proptest::arbitrary::Arbitrary>::Strategy,
fn(ProcessId) -> Self,
>,
>,
)>;
fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
use proptest::prelude::*;
prop_oneof![
1 => Just(Self::ThisProgram),
1 => Just(Self::Area),
3 => any::<ProcessId>().prop_map(Self::Process)
]
}
}
impl Display for MemoryBoundObject {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let display = match self {
Self::Process(pid) => format!("the process with PID {pid}"),
Self::Area => "the target location".to_owned(),
Self::ThisProgram => "the current process/thread".to_owned(),
};
f.pad(&display)
}
}
impl From<ProcessId> for MemoryBoundObject {
fn from(value: ProcessId) -> Self {
Self::Process(value)
}
}
#[derive(Copy, Clone, Debug, Display, EnumCount, EnumIter, Eq, Hash, PartialEq)]
pub(crate) enum MemoryBindingOperation {
Allocate,
Bind,
GetBinding,
GetLastLocation,
Unbind,
}
crate::impl_arbitrary_for_sequence!(MemoryBindingOperation);
#[derive(
Copy, Clone, Debug, Default, Display, EnumCount, EnumIter, Eq, FromRepr, Hash, PartialEq,
)]
#[doc(alias = "hwloc_membind_policy_t")]
#[repr(i32)]
pub enum MemoryBindingPolicy {
#[doc(alias = "HWLOC_MEMBIND_FIRSTTOUCH")]
FirstTouch = HWLOC_MEMBIND_FIRSTTOUCH,
#[default]
#[doc(alias = "HWLOC_MEMBIND_BIND")]
Bind = HWLOC_MEMBIND_BIND,
#[doc(alias = "HWLOC_MEMBIND_INTERLEAVE")]
Interleave = HWLOC_MEMBIND_INTERLEAVE,
#[cfg(feature = "hwloc-2_11_0")]
#[doc(alias = "HWLOC_MEMBIND_WEIGHTED_INTERLEAVE")]
WeightedInterleave = HWLOC_MEMBIND_WEIGHTED_INTERLEAVE,
#[doc(alias = "HWLOC_MEMBIND_NEXTTOUCH")]
NextTouch = HWLOC_MEMBIND_NEXTTOUCH,
#[strum(disabled)]
Unknown(UnknownVariant<hwloc_membind_policy_t>) = hwloc_membind_policy_t::MAX,
}
impl MemoryBindingPolicy {
pub(crate) unsafe fn from_hwloc(value: hwloc_membind_policy_t) -> Self {
Self::from_repr(value).unwrap_or(Self::Unknown(UnknownVariant(value)))
}
}
crate::impl_arbitrary_for_sequence!(MemoryBindingPolicy);
impl From<MemoryBindingPolicy> for hwloc_membind_policy_t {
fn from(value: MemoryBindingPolicy) -> Self {
match value {
MemoryBindingPolicy::FirstTouch => HWLOC_MEMBIND_FIRSTTOUCH,
MemoryBindingPolicy::Bind => HWLOC_MEMBIND_BIND,
MemoryBindingPolicy::Interleave => HWLOC_MEMBIND_INTERLEAVE,
#[cfg(feature = "hwloc-2_11_0")]
MemoryBindingPolicy::WeightedInterleave => HWLOC_MEMBIND_WEIGHTED_INTERLEAVE,
MemoryBindingPolicy::NextTouch => HWLOC_MEMBIND_NEXTTOUCH,
MemoryBindingPolicy::Unknown(unknown) => unknown.get(),
}
}
}
#[derive(Clone, Debug, Error, Eq, Hash, PartialEq)]
pub enum MemoryBindingError<Set: SpecializedBitmap> {
#[error("failed to allocate memory")]
AllocationFailed,
#[error(transparent)]
BadFlags(FlagsError<MemoryBindingFlags>),
#[error("cannot bind memory of {0} to {1}")]
BadSet(MemoryBoundObject, Set),
#[error("cannot query the memory location of a zero-sized target")]
BadArea,
#[error("memory binding varies from one thread of the process to another")]
#[doc(alias = "HWLOC_MEMBIND_MIXED")]
MixedResults,
#[error("requested memory binding action or policy isn't supported")]
Unsupported,
#[cfg(windows)]
#[error("an unknown error occured")]
Unknown,
}
impl<Set: SpecializedBitmap> From<MemoryBindingFlags> for MemoryBindingError<Set> {
fn from(value: MemoryBindingFlags) -> Self {
Self::BadFlags(value.into())
}
}
pub(crate) fn call_hwloc_int<Set: SpecializedBitmap>(
api: &'static str,
object: MemoryBoundObject,
operation: MemoryBindingOperation,
clone_set: &dyn Fn() -> Option<Set>,
ffi: impl FnOnce() -> c_int,
) -> Result<(), HybridError<MemoryBindingError<Set>>> {
match errors::call_hwloc_zero_or_minus1(api, ffi) {
Ok(()) => Ok(()),
Err(e @ RawHwlocError { errno, .. }) => {
Err(decode_errno(object, operation, clone_set, errno)
.map_or_else(|| HybridError::Hwloc(e), HybridError::Rust))
}
}
}
pub type MemoryAllocationError<Set> = MemoryBindingError<Set>;
fn decode_errno<Set: SpecializedBitmap>(
object: MemoryBoundObject,
operation: MemoryBindingOperation,
clone_set: &dyn Fn() -> Option<Set>,
errno: Option<Errno>,
) -> Option<MemoryBindingError<Set>> {
match errno {
Some(Errno(ENOSYS)) => Some(MemoryBindingError::Unsupported),
Some(Errno(EXDEV)) => match operation {
#[cfg(not(tarpaulin_include))]
MemoryBindingOperation::Bind | MemoryBindingOperation::Allocate => {
Some(MemoryBindingError::BadSet(
object,
clone_set()
.expect("This error should only be observed on commands that set bindings"),
))
}
MemoryBindingOperation::GetBinding | MemoryBindingOperation::GetLastLocation => {
Some(MemoryBindingError::MixedResults)
}
MemoryBindingOperation::Unbind => {
unreachable!("The empty set should always be considered valid")
}
},
#[cfg(not(tarpaulin_include))]
Some(Errno(ENOMEM)) => Some(MemoryBindingError::AllocationFailed),
#[cfg(windows)]
None => Some(MemoryBindingError::Unknown),
_ => None,
}
}
pub struct Bytes<'topology> {
topology: &'topology Topology,
data: NonNull<[MaybeUninit<u8>]>,
}
impl<'topology> Bytes<'topology> {
unsafe fn wrap(topology: &'topology Topology, base: NonNull<c_void>, size: usize) -> Self {
isize::try_from(size).expect("Unsupported allocation size");
Self {
topology,
data: NonNull::slice_from_raw_parts(base.cast::<MaybeUninit<u8>>(), size),
}
}
}
impl AsRef<[MaybeUninit<u8>]> for Bytes<'_> {
fn as_ref(&self) -> &[MaybeUninit<u8>] {
unsafe { self.data.as_ref() }
}
}
impl AsMut<[MaybeUninit<u8>]> for Bytes<'_> {
fn as_mut(&mut self) -> &mut [MaybeUninit<u8>] {
unsafe { self.data.as_mut() }
}
}
impl Borrow<[MaybeUninit<u8>]> for Bytes<'_> {
fn borrow(&self) -> &[MaybeUninit<u8>] {
self.as_ref()
}
}
impl BorrowMut<[MaybeUninit<u8>]> for Bytes<'_> {
fn borrow_mut(&mut self) -> &mut [MaybeUninit<u8>] {
self.as_mut()
}
}
impl Debug for Bytes<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
Debug::fmt(self.as_ref(), f)
}
}
impl Deref for Bytes<'_> {
type Target = [MaybeUninit<u8>];
fn deref(&self) -> &Self::Target {
self.as_ref()
}
}
impl DerefMut for Bytes<'_> {
fn deref_mut(&mut self) -> &mut [MaybeUninit<u8>] {
self.as_mut()
}
}
impl Drop for Bytes<'_> {
#[allow(clippy::print_stderr)]
#[doc(alias = "hwloc_free")]
fn drop(&mut self) {
let len = self.data.len();
if len > 0 {
let result = errors::call_hwloc_zero_or_minus1("hwloc_free", || unsafe {
hwlocality_sys::hwloc_free(
self.topology.as_ptr(),
self.data.as_ptr().cast::<c_void>(),
len,
)
});
#[cfg(not(tarpaulin_include))]
if let Err(e) = result {
eprintln!("ERROR: Failed to liberate hwloc allocation ({e}).",);
}
}
}
}
unsafe impl Send for Bytes<'_> {}
unsafe impl Sync for Bytes<'_> {}
#[cfg(test)]
mod tests {
use super::*;
use proptest::prelude::*;
#[allow(unused)]
use similar_asserts::assert_eq;
proptest! {
#[test]
fn display_membound_object(
obj: MemoryBoundObject,
) {
let display = obj.to_string();
match obj {
MemoryBoundObject::Process(pid) => {
let expected_pid = format!("PID {pid}");
prop_assert!(display.contains("process"));
prop_assert!(display.contains(&expected_pid));
}
MemoryBoundObject::ThisProgram => {
prop_assert!(display.contains("current process/thread"));
}
MemoryBoundObject::Area => {}
}
}
#[test]
fn pid_to_membound(pid: ProcessId) {
let obj = MemoryBoundObject::from(pid);
prop_assert_eq!(obj, MemoryBoundObject::Process(pid));
}
}
}