const WORDS: usize = 16;
#[must_use = "a mask is built to be applied or inspected; discarding it does nothing"]
#[derive(Clone, Copy, PartialEq, Eq, Hash, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct AffinityMask {
bits: [u64; WORDS],
}
impl AffinityMask {
pub const MAX_LP_COUNT: usize = WORDS * 64;
pub fn empty() -> Self {
Self::default()
}
pub fn single(logical_core_id: usize) -> Self {
let mut mask = Self::empty();
mask.add(logical_core_id);
mask
}
pub fn from_cores(core_ids: &[usize]) -> Self {
let mut mask = Self::empty();
for &id in core_ids {
mask.add(id);
}
mask
}
pub fn add(&mut self, logical_core_id: usize) {
if logical_core_id >= Self::MAX_LP_COUNT {
return;
}
let word_idx = logical_core_id / 64;
let bit_idx = logical_core_id % 64;
self.bits[word_idx] |= 1u64 << bit_idx;
}
pub fn remove(&mut self, logical_core_id: usize) {
if logical_core_id >= Self::MAX_LP_COUNT {
return;
}
let word_idx = logical_core_id / 64;
let bit_idx = logical_core_id % 64;
self.bits[word_idx] &= !(1u64 << bit_idx);
}
#[must_use]
pub fn contains(&self, logical_core_id: usize) -> bool {
if logical_core_id >= Self::MAX_LP_COUNT {
return false;
}
let word_idx = logical_core_id / 64;
let bit_idx = logical_core_id % 64;
(self.bits[word_idx] & (1u64 << bit_idx)) != 0
}
#[must_use]
pub fn count(&self) -> usize {
self.bits.iter().map(|w| w.count_ones() as usize).sum()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.bits.iter().all(|&w| w == 0)
}
pub fn iter(&self) -> impl Iterator<Item = usize> + '_ {
self.bits.iter().enumerate().flat_map(|(word_idx, &word)| {
(0..64).filter_map(move |bit_idx| {
if (word & (1u64 << bit_idx)) != 0 {
Some(word_idx * 64 + bit_idx)
} else {
None
}
})
})
}
pub fn union(&self, other: &AffinityMask) -> AffinityMask {
let mut bits = [0u64; WORDS];
for (i, slot) in bits.iter_mut().enumerate() {
*slot = self.bits[i] | other.bits[i];
}
AffinityMask { bits }
}
pub fn intersection(&self, other: &AffinityMask) -> AffinityMask {
let mut bits = [0u64; WORDS];
for (i, slot) in bits.iter_mut().enumerate() {
*slot = self.bits[i] & other.bits[i];
}
AffinityMask { bits }
}
pub fn as_raw_u64(&self) -> u64 {
self.bits[0]
}
pub fn as_raw_bits(&self) -> &[u64] {
&self.bits
}
}
impl std::fmt::Debug for AffinityMask {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("AffinityMask")
.field("cores", &Ranges(self))
.field("count", &self.count())
.finish()
}
}
impl std::fmt::Display for AffinityMask {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", Ranges(self))
}
}
struct Ranges<'a>(&'a AffinityMask);
impl std::fmt::Debug for Ranges<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut iter = self.0.iter().peekable();
let mut first = true;
write!(f, "[")?;
while let Some(start) = iter.next() {
let mut end = start;
while iter.peek() == Some(&(end + 1)) {
end = iter.next().unwrap();
}
if !first {
write!(f, ", ")?;
}
first = false;
if start == end {
write!(f, "{start}")?;
} else {
write!(f, "{start}-{end}")?;
}
}
write!(f, "]")
}
}
impl FromIterator<usize> for AffinityMask {
fn from_iter<T: IntoIterator<Item = usize>>(iter: T) -> Self {
let mut mask = AffinityMask::empty();
mask.extend(iter);
mask
}
}
impl Extend<usize> for AffinityMask {
fn extend<Iter>(&mut self, iter: Iter)
where
Iter: IntoIterator<Item = usize>,
{
for core in iter {
self.add(core);
}
}
}
impl IntoIterator for &AffinityMask {
type Item = usize;
type IntoIter = std::vec::IntoIter<usize>;
fn into_iter(self) -> Self::IntoIter {
self.iter().collect::<Vec<_>>().into_iter()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_empty_mask() {
let mask = AffinityMask::empty();
assert!(mask.is_empty());
assert_eq!(mask.count(), 0);
assert!(!mask.contains(0));
}
#[test]
fn test_single_core() {
let mask = AffinityMask::single(5);
assert!(!mask.is_empty());
assert_eq!(mask.count(), 1);
assert!(mask.contains(5));
assert!(!mask.contains(4));
}
#[test]
fn test_from_cores() {
let mask = AffinityMask::from_cores(&[0, 2, 4, 6]);
assert_eq!(mask.count(), 4);
assert!(mask.contains(0));
assert!(!mask.contains(1));
assert!(mask.contains(2));
}
#[test]
fn test_add_remove() {
let mut mask = AffinityMask::empty();
mask.add(0);
mask.add(1);
assert_eq!(mask.count(), 2);
mask.remove(0);
assert_eq!(mask.count(), 1);
assert!(!mask.contains(0));
assert!(mask.contains(1));
}
#[test]
fn test_high_core_ids() {
let mut mask = AffinityMask::empty();
mask.add(0);
mask.add(64);
mask.add(128);
assert_eq!(mask.count(), 3);
assert!(mask.contains(0));
assert!(mask.contains(64));
assert!(mask.contains(128));
assert!(!mask.contains(63));
assert!(!mask.contains(65));
}
#[test]
fn test_iter() {
let mask = AffinityMask::from_cores(&[1, 3, 5, 64, 65]);
let cores: Vec<usize> = mask.iter().collect();
assert_eq!(cores, vec![1, 3, 5, 64, 65]);
}
#[test]
fn test_as_raw_u64() {
let mask = AffinityMask::from_cores(&[0, 1, 63]);
assert_eq!(mask.as_raw_u64(), 0x8000_0000_0000_0003);
}
#[test]
fn test_from_iterator() {
let mask: AffinityMask = vec![0, 2, 4].into_iter().collect();
assert_eq!(mask.count(), 3);
assert!(mask.contains(0));
assert!(mask.contains(2));
assert!(mask.contains(4));
}
#[test]
fn test_extend() {
let mut mask = AffinityMask::from_iter([0, 2, 4]);
mask.extend([1, 3, AffinityMask::MAX_LP_COUNT]);
assert_eq!(mask, AffinityMask::from_iter([0, 1, 2, 3, 4]));
}
#[test]
fn test_equality_is_canonical() {
let mut a = AffinityMask::single(64);
a.remove(64);
assert_eq!(a, AffinityMask::empty());
let wide = AffinityMask::from_cores(&[0, 200]);
let narrow = AffinityMask::from_cores(&[0]);
assert_eq!(wide.intersection(&narrow), narrow);
assert_eq!(narrow.union(&AffinityMask::empty()), narrow);
}
#[test]
fn test_capacity_boundary() {
let mut mask = AffinityMask::empty();
mask.add(AffinityMask::MAX_LP_COUNT - 1); assert!(mask.contains(AffinityMask::MAX_LP_COUNT - 1));
assert_eq!(mask.count(), 1);
mask.add(AffinityMask::MAX_LP_COUNT); mask.add(u32::MAX as usize); assert!(!mask.contains(AffinityMask::MAX_LP_COUNT));
assert_eq!(mask.count(), 1);
}
#[test]
fn test_debug_format() {
assert_eq!(
format!("{:?}", AffinityMask::empty()),
"AffinityMask { cores: [], count: 0 }"
);
assert_eq!(
format!("{:?}", AffinityMask::single(5)),
"AffinityMask { cores: [5], count: 1 }"
);
assert_eq!(
format!("{:?}", AffinityMask::from_cores(&[0, 1, 2, 3])),
"AffinityMask { cores: [0-3], count: 4 }"
);
assert_eq!(
format!(
"{:?}",
AffinityMask::from_cores(&[0, 1, 2, 3, 6, 7, 8, 9, 15])
),
"AffinityMask { cores: [0-3, 6-9, 15], count: 9 }"
);
}
#[test]
fn test_debug_format_word_boundary_and_max_id() {
assert_eq!(
format!("{:?}", AffinityMask::from_cores(&[63, 64, 65, 1023])),
"AffinityMask { cores: [63-65, 1023], count: 4 }"
);
}
#[test]
fn test_display_format() {
assert_eq!(
format!(
"{}",
AffinityMask::from_cores(&[0, 1, 2, 3, 6, 7, 8, 9, 15])
),
"[0-3, 6-9, 15]"
);
assert_eq!(format!("{}", AffinityMask::empty()), "[]");
}
}