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 {
let cores: Vec<usize> = self.iter().collect();
f.debug_struct("AffinityMask")
.field("cores", &cores)
.field("count", &cores.len())
.finish()
}
}
impl std::fmt::Display for AffinityMask {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let cores: Vec<usize> = self.iter().collect();
if cores.is_empty() {
write!(f, "AffinityMask(empty)")
} else {
write!(f, "AffinityMask({:?})", cores)
}
}
}
impl FromIterator<usize> for AffinityMask {
fn from_iter<T: IntoIterator<Item = usize>>(iter: T) -> Self {
let mut mask = AffinityMask::empty();
for id in iter {
mask.add(id);
}
mask
}
}
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_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);
}
}