use crate::topology::cpu::MAX_PROCESSOR_ID;
pub(super) const RELATION_PROCESSOR_CORE: u32 = 0;
const RECORD_HEADER_BYTES: usize = 8;
const EFFICIENCY_CLASS_OFFSET: usize = 9;
const GROUP_COUNT_OFFSET: usize = 30;
const GROUP_MASK_OFFSET: usize = 32;
pub(super) const GROUP_AFFINITY_BYTES: usize = 16;
const PROCESSORS_PER_GROUP: usize = 64;
const MAX_GROUPS_PER_CORE: usize = 64;
const MAX_BUFFER_BYTES: usize = 1024 * 1024;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(in crate::topology::cpu) struct ProcessorClass {
pub(in crate::topology::cpu) processor: u32,
pub(in crate::topology::cpu) raw_class: u8,
pub(in crate::topology::cpu) core: u32,
}
pub(in crate::topology::cpu) fn parse_processor_cores(bytes: &[u8]) -> Option<Vec<ProcessorClass>> {
let mut offset = 0usize;
let mut entries = Vec::with_capacity(bytes.len() / 64);
let mut core = 0u32;
while offset < bytes.len() {
let relationship = u32::from_ne_bytes(field::<4>(bytes, offset)?);
let record_size =
usize::try_from(u32::from_ne_bytes(field::<4>(bytes, offset + 4)?)).ok()?;
if !(RECORD_HEADER_BYTES..=MAX_BUFFER_BYTES).contains(&record_size)
|| record_size > bytes.len().saturating_sub(offset)
{
return None;
}
if relationship == RELATION_PROCESSOR_CORE {
let record = bytes.get(offset..offset.checked_add(record_size)?)?;
parse_core_record(record, core, &mut entries)?;
core = core.checked_add(1)?;
}
offset = offset.checked_add(record_size)?;
}
(!entries.is_empty()).then_some(entries)
}
fn parse_core_record(record: &[u8], core: u32, entries: &mut Vec<ProcessorClass>) -> Option<()> {
let raw_class = *record.get(EFFICIENCY_CLASS_OFFSET)?;
let group_count = usize::from(u16::from_ne_bytes(field::<2>(record, GROUP_COUNT_OFFSET)?));
if !(1..=MAX_GROUPS_PER_CORE).contains(&group_count) {
return None;
}
let group_bytes = group_count.checked_mul(GROUP_AFFINITY_BYTES)?;
if GROUP_MASK_OFFSET.checked_add(group_bytes)? > record.len() {
return None;
}
for group_index in 0..group_count {
let group_offset = GROUP_MASK_OFFSET + group_index * GROUP_AFFINITY_BYTES;
let mask = u64::from_ne_bytes(field::<8>(record, group_offset)?);
let group = usize::from(u16::from_ne_bytes(field::<2>(record, group_offset + 8)?));
for bit in 0..PROCESSORS_PER_GROUP {
if mask & (1u64 << bit) == 0 {
continue;
}
let processor = group.checked_mul(PROCESSORS_PER_GROUP)?.checked_add(bit)?;
if processor >= MAX_PROCESSOR_ID {
return None;
}
entries.push(ProcessorClass {
processor: u32::try_from(processor).ok()?,
raw_class,
core,
});
}
}
Some(())
}
fn field<const N: usize>(bytes: &[u8], offset: usize) -> Option<[u8; N]> {
bytes.get(offset..offset.checked_add(N)?)?.try_into().ok()
}
#[cfg(test)]
pub(in crate::topology::cpu) mod fixtures {
use super::{GROUP_AFFINITY_BYTES, RELATION_PROCESSOR_CORE};
const RELATION_CACHE: u32 = 2;
pub(in crate::topology::cpu) const PERFORMANCE_MASK: u64 = 0xc0_3c03;
pub(in crate::topology::cpu) fn core_record(raw_class: u8, groups: &[(u16, u64)]) -> Vec<u8> {
let size = 32 + groups.len() * GROUP_AFFINITY_BYTES;
let mut record = vec![0u8; size];
record[0..4].copy_from_slice(&RELATION_PROCESSOR_CORE.to_ne_bytes());
record[4..8].copy_from_slice(
&u32::try_from(size)
.expect("fixture size fits")
.to_ne_bytes(),
);
record[8] = 1; record[9] = raw_class;
record[30..32].copy_from_slice(
&u16::try_from(groups.len())
.expect("fixture fits")
.to_ne_bytes(),
);
for (index, (group, mask)) in groups.iter().enumerate() {
let base = 32 + index * GROUP_AFFINITY_BYTES;
record[base..base + 8].copy_from_slice(&mask.to_ne_bytes());
record[base + 8..base + 10].copy_from_slice(&group.to_ne_bytes());
}
record
}
pub(in crate::topology::cpu) fn cache_record() -> Vec<u8> {
let mut record = vec![0u8; 48];
record[0..4].copy_from_slice(&RELATION_CACHE.to_ne_bytes());
record[4..8].copy_from_slice(&48u32.to_ne_bytes());
record[8] = 1; record
}
pub(in crate::topology::cpu) fn hybrid_host_buffer() -> Vec<u8> {
(0..24u32)
.map(|processor| {
let is_performance = PERFORMANCE_MASK & (1u64 << processor) != 0;
core_record(u8::from(is_performance), &[(0, 1u64 << processor)])
})
.collect::<Vec<_>>()
.concat()
}
pub(in crate::topology::cpu) fn homogeneous_host_buffer() -> Vec<u8> {
(0..8u32)
.map(|core| core_record(0, &[(0, 0b11 << (core * 2))]))
.collect::<Vec<_>>()
.concat()
}
}
#[cfg(test)]
mod tests {
use super::fixtures::{
cache_record, core_record, homogeneous_host_buffer, hybrid_host_buffer, PERFORMANCE_MASK,
};
use super::{parse_processor_cores, ProcessorClass};
fn parsed(bytes: &[u8]) -> Vec<ProcessorClass> {
parse_processor_cores(bytes).expect("fixture buffer is well formed")
}
#[test]
fn hybrid_host_classes_follow_the_recorded_performance_mask() {
let entries = parsed(&hybrid_host_buffer());
assert_eq!(entries.len(), 24);
for entry in &entries {
let expected = u8::from(PERFORMANCE_MASK & (1u64 << entry.processor) != 0);
assert_eq!(
entry.raw_class, expected,
"processor {} class",
entry.processor
);
}
let performance: Vec<u32> = entries
.iter()
.filter(|entry| entry.raw_class == 1)
.map(|entry| entry.processor)
.collect();
assert_eq!(performance, vec![0, 1, 10, 11, 12, 13, 22, 23]);
assert!(
!performance.contains(&2),
"processor 2 is an efficiency core on this host"
);
}
#[test]
fn homogeneous_host_reports_one_class_for_every_processor() {
let entries = parsed(&homogeneous_host_buffer());
assert_eq!(entries.len(), 16);
assert!(entries.iter().all(|entry| entry.raw_class == 0));
let processors: Vec<u32> = entries.iter().map(|entry| entry.processor).collect();
assert_eq!(processors, (0..16).collect::<Vec<u32>>());
}
#[test]
fn three_tier_host_preserves_every_distinct_class_byte() {
let records = [
core_record(0, &[(0, 0b0001)]), core_record(1, &[(0, 0b0010)]), core_record(2, &[(0, 0b1100)]), ]
.concat();
let entries = parsed(&records);
assert_eq!(
entries.iter().map(|e| e.raw_class).collect::<Vec<u8>>(),
vec![0, 1, 2, 2]
);
}
#[test]
fn multi_group_host_numbers_processors_by_group_and_bit() {
let records = [
core_record(1, &[(0, 1u64 << 63)]),
core_record(0, &[(1, 0b101)]),
core_record(0, &[(2, 1u64 << 7)]),
]
.concat();
let entries = parsed(&records);
assert_eq!(
entries.iter().map(|e| e.processor).collect::<Vec<u32>>(),
vec![63, 64, 66, 135]
);
}
#[test]
fn processors_of_one_record_share_a_core_ordinal_in_walk_order() {
let records = [
core_record(0, &[(0, 0b0011)]), cache_record(), core_record(0, &[(0, 0b1100)]), core_record(1, &[(1, 0b0001)]), ]
.concat();
let entries = parsed(&records);
assert_eq!(
entries
.iter()
.map(|e| (e.processor, e.core))
.collect::<Vec<(u32, u32)>>(),
vec![(0, 0), (1, 0), (2, 1), (3, 1), (64, 2)]
);
}
#[test]
fn foreign_records_are_stepped_over_not_interpreted() {
let records = [
cache_record(),
core_record(1, &[(0, 0b01)]),
cache_record(),
core_record(0, &[(0, 0b10)]),
]
.concat();
let entries = parsed(&records);
assert_eq!(
entries
.iter()
.map(|e| (e.processor, e.raw_class))
.collect::<Vec<(u32, u8)>>(),
vec![(0, 1), (1, 0)]
);
}
#[test]
fn empty_and_relationless_buffers_are_absent() {
assert_eq!(parse_processor_cores(&[]), None);
assert_eq!(parse_processor_cores(&cache_record()), None);
}
#[test]
fn malformed_buffers_are_absent_rather_than_partially_parsed() {
let mut oversized = core_record(1, &[(0, 0b1)]);
oversized[4..8].copy_from_slice(&4096u32.to_ne_bytes());
assert_eq!(parse_processor_cores(&oversized), None);
let mut zero_size = core_record(1, &[(0, 0b1)]);
zero_size[4..8].copy_from_slice(&0u32.to_ne_bytes());
assert_eq!(parse_processor_cores(&zero_size), None);
let mut lying_group_count = core_record(1, &[(0, 0b1)]);
lying_group_count[30..32].copy_from_slice(&4u16.to_ne_bytes());
assert_eq!(parse_processor_cores(&lying_group_count), None);
let mut no_groups = core_record(1, &[(0, 0b1)]);
no_groups[30..32].copy_from_slice(&0u16.to_ne_bytes());
assert_eq!(parse_processor_cores(&no_groups), None);
let mut truncated = core_record(1, &[(0, 0b1)]);
truncated.extend_from_slice(&[0u8; 5]);
assert_eq!(parse_processor_cores(&truncated), None);
let mut mixed = core_record(1, &[(0, 0b1)]);
let mut bad = core_record(0, &[(0, 0b10)]);
bad[30..32].copy_from_slice(&9u16.to_ne_bytes());
mixed.extend_from_slice(&bad);
assert_eq!(parse_processor_cores(&mixed), None);
}
#[test]
fn processor_ids_beyond_the_crate_bound_are_absent() {
let record = core_record(0, &[(512, 0b1)]);
assert_eq!(parse_processor_cores(&record), None);
}
}