use super::CpuInfo;
#[inline]
fn ctr_el0() -> u64 {
let value: u64;
unsafe {
core::arch::asm!("mrs {}, ctr_el0", out(reg) value, options(nostack, nomem, preserves_flags, pure));
}
value
}
pub fn detect() -> CpuInfo {
let mut info = CpuInfo::UNKNOWN;
let ctr = ctr_el0();
info.line_size = Some(4 << ((ctr >> 16) & 0xf));
let cwg = (ctr >> 24) & 0xf;
info.writeback_granule = (cwg != 0).then(|| 4u32 << cwg);
#[cfg(target_vendor = "apple")]
apple::fill(&mut info);
#[cfg(all(any(target_os = "linux", target_os = "android"), feature = "std"))]
linux::fill(&mut info);
#[cfg(feature = "std")]
if info.topology.logical_cores.is_none() {
info.topology.logical_cores = std::thread::available_parallelism()
.ok()
.and_then(|n| u16::try_from(n.get()).ok());
}
info
}
#[cfg(target_vendor = "apple")]
mod apple {
use crate::cpu::{CacheInfo, CacheKind, CpuInfo};
use core::ffi::{c_char, c_int, c_void};
#[link(name = "System")]
unsafe extern "C" {
fn sysctlbyname(
name: *const c_char,
oldp: *mut c_void,
oldlenp: *mut usize,
newp: *mut c_void,
newlen: usize,
) -> c_int;
}
fn sysctl(name: &[u8]) -> Option<u64> {
debug_assert_eq!(name.last(), Some(&0), "sysctl name must be NUL-terminated");
let mut value = 0u64;
let mut len = size_of::<u64>();
let rc = unsafe {
sysctlbyname(
name.as_ptr().cast::<c_char>(),
(&raw mut value).cast::<c_void>(),
&raw mut len,
core::ptr::null_mut(),
0,
)
};
(rc == 0 && value != 0).then_some(value)
}
fn cache(size: Option<u64>, line: Option<u32>, kind: CacheKind) -> Option<CacheInfo> {
Some(CacheInfo {
size: u32::try_from(size?).ok()?,
line_size: line,
associativity: None, shared_by: None,
kind,
})
}
pub fn fill(info: &mut CpuInfo) {
if let Some(line) = sysctl(b"hw.cachelinesize\0").and_then(|v| u32::try_from(v).ok()) {
info.line_size = Some(line);
info.writeback_granule.get_or_insert(line);
}
let line = info.line_size;
info.l1d = cache(sysctl(b"hw.l1dcachesize\0"), line, CacheKind::Data);
info.l1i = cache(sysctl(b"hw.l1icachesize\0"), line, CacheKind::Instruction);
info.l2 = cache(sysctl(b"hw.l2cachesize\0"), line, CacheKind::Unified);
info.l3 = cache(sysctl(b"hw.l3cachesize\0"), line, CacheKind::Unified);
let u16_of = |key: &[u8]| sysctl(key).and_then(|v| u16::try_from(v).ok());
info.topology.logical_cores = u16_of(b"hw.logicalcpu\0");
info.topology.physical_cores = u16_of(b"hw.physicalcpu\0");
if let (Some(logical), Some(physical)) = (info.topology.logical_cores, info.topology.physical_cores)
&& physical > 0
{
info.topology.threads_per_core = Some(logical / physical);
}
if sysctl(b"hw.nperflevels\0").unwrap_or(1) > 1 {
info.hybrid = true;
info.topology.performance_cores = u16_of(b"hw.perflevel0.logicalcpu\0");
info.topology.efficiency_cores = u16_of(b"hw.perflevel1.logicalcpu\0");
}
}
}
#[cfg(all(any(target_os = "linux", target_os = "android"), feature = "std"))]
mod linux {
use crate::cpu::{CacheInfo, CacheKind, CpuInfo};
fn read(path: &str) -> Option<std::string::String> {
std::fs::read_to_string(path).ok().map(|s| s.trim().into())
}
fn parse_size(text: &str) -> Option<u32> {
let (digits, scale) = match text.as_bytes().last()? {
b'K' => (&text[..text.len() - 1], 1024),
b'M' => (&text[..text.len() - 1], 1024 * 1024),
b'G' => (&text[..text.len() - 1], 1024 * 1024 * 1024),
_ => (text, 1),
};
digits.parse::<u32>().ok()?.checked_mul(scale)
}
fn count_cpu_list(text: &str) -> u16 {
text.split(',')
.filter_map(|part| match part.split_once('-') {
Some((lo, hi)) => Some(hi.parse::<u16>().ok()? - lo.parse::<u16>().ok()? + 1),
None => part.parse::<u16>().ok().map(|_| 1),
})
.sum()
}
pub fn fill(info: &mut CpuInfo) {
for index in 0..10 {
let dir = std::format!("/sys/devices/system/cpu/cpu0/cache/index{index}");
let Some(level) = read(&std::format!("{dir}/level")).and_then(|s| s.parse::<u8>().ok()) else {
break; };
let Some(size) = read(&std::format!("{dir}/size")).and_then(|s| parse_size(&s)) else {
continue;
};
let kind = match read(&std::format!("{dir}/type")).as_deref() {
Some("Data") => CacheKind::Data,
Some("Instruction") => CacheKind::Instruction,
_ => CacheKind::Unified,
};
let entry = CacheInfo {
size,
line_size: read(&std::format!("{dir}/coherency_line_size")).and_then(|s| s.parse().ok()),
associativity: read(&std::format!("{dir}/ways_of_associativity")).and_then(|s| s.parse().ok()),
shared_by: read(&std::format!("{dir}/shared_cpu_list")).map(|s| count_cpu_list(&s)),
kind,
};
match (level, kind) {
(1, CacheKind::Instruction) => info.l1i = Some(entry),
(1, _) => info.l1d = Some(entry),
(2, _) => info.l2 = Some(entry),
(3, _) => info.l3 = Some(entry),
_ => {}
}
}
if let Some(siblings) = read("/sys/devices/system/cpu/cpu0/topology/thread_siblings_list") {
let threads = count_cpu_list(&siblings);
if threads > 0 {
info.topology.threads_per_core = Some(threads);
}
}
let logical = std::thread::available_parallelism()
.ok()
.and_then(|n| u16::try_from(n.get()).ok());
info.topology.logical_cores = logical;
if let (Some(logical), Some(per_core)) = (logical, info.topology.threads_per_core)
&& per_core > 0
{
info.topology.physical_cores = Some(logical / per_core);
}
if let Some(count) = logical {
let mut capacities = std::vec::Vec::with_capacity(usize::from(count));
for cpu in 0..count {
let path = std::format!("/sys/devices/system/cpu/cpu{cpu}/cpu_capacity");
match read(&path).and_then(|s| s.parse::<u32>().ok()) {
Some(capacity) => capacities.push(capacity),
None => {
capacities.clear();
break;
}
}
}
if let Some(&max) = capacities.iter().max()
&& capacities.iter().any(|&c| c != max)
{
let big = capacities.iter().filter(|&&c| c == max).count() as u16;
info.hybrid = true;
info.topology.performance_cores = Some(big);
info.topology.efficiency_cores = Some(count - big);
}
}
}
}