use core::cell::UnsafeCell;
use core::sync::atomic::{AtomicU8, Ordering};
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
pub mod x86;
#[cfg(target_vendor = "apple")]
pub mod apple;
#[cfg(target_arch = "aarch64")]
mod aarch64;
pub mod quirks;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum CacheKind {
Data,
Instruction,
Unified,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct CacheInfo {
pub size: u32,
pub line_size: Option<u32>,
pub associativity: Option<u16>,
pub shared_by: Option<u16>,
pub kind: CacheKind,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum CoreType {
#[default]
Unknown,
Performance,
Efficiency,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct Topology {
pub logical_cores: Option<u16>,
pub physical_cores: Option<u16>,
pub threads_per_core: Option<u16>,
pub performance_cores: Option<u16>,
pub efficiency_cores: Option<u16>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct CpuInfo {
l1d: Option<CacheInfo>,
l1i: Option<CacheInfo>,
l2: Option<CacheInfo>,
l3: Option<CacheInfo>,
line_size: Option<u32>,
writeback_granule: Option<u32>,
topology: Topology,
hybrid: bool,
}
impl CpuInfo {
pub const UNKNOWN: Self = Self {
l1d: None,
l1i: None,
l2: None,
l3: None,
line_size: None,
writeback_granule: None,
topology: Topology {
logical_cores: None,
physical_cores: None,
threads_per_core: None,
performance_cores: None,
efficiency_cores: None,
},
hybrid: false,
};
#[inline]
pub fn get() -> &'static CpuInfo {
static CACHE: Cache<CpuInfo> = Cache {
state: AtomicU8::new(UNINIT),
value: UnsafeCell::new(CpuInfo::UNKNOWN),
};
CACHE.get(CpuInfo::detect)
}
pub fn detect() -> CpuInfo {
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
{
x86::detect()
}
#[cfg(target_arch = "aarch64")]
{
aarch64::detect()
}
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64")))]
{
CpuInfo::UNKNOWN
}
}
#[inline]
pub fn l1d(&self) -> Option<CacheInfo> {
self.l1d
}
#[inline]
pub fn l1i(&self) -> Option<CacheInfo> {
self.l1i
}
#[inline]
pub fn l2(&self) -> Option<CacheInfo> {
self.l2
}
#[inline]
pub fn l3(&self) -> Option<CacheInfo> {
self.l3
}
#[inline]
pub fn cache_line_size(&self) -> Option<u32> {
self.line_size
}
#[inline]
pub fn writeback_granule(&self) -> Option<u32> {
self.writeback_granule
}
#[inline]
pub fn topology(&self) -> Topology {
self.topology
}
#[inline]
pub fn is_hybrid(&self) -> bool {
self.hybrid
}
}
pub(crate) const UNINIT: u8 = 0;
const BUSY: u8 = 1;
const READY: u8 = 2;
pub(crate) struct Cache<T: 'static> {
pub(crate) state: AtomicU8,
pub(crate) value: UnsafeCell<T>,
}
unsafe impl<T: Send> Sync for Cache<T> {}
impl<T> Cache<T> {
#[inline]
pub(crate) fn get(&'static self, detect: fn() -> T) -> &'static T {
if self.state.load(Ordering::Acquire) != READY {
self.init(detect);
}
unsafe { &*self.value.get() }
}
#[inline(never)]
fn init(&self, detect: fn() -> T) {
match self
.state
.compare_exchange(UNINIT, BUSY, Ordering::AcqRel, Ordering::Acquire)
{
Ok(_) => {
let detected = detect();
unsafe { *self.value.get() = detected };
self.state.store(READY, Ordering::Release);
}
Err(BUSY) => {
while self.state.load(Ordering::Acquire) != READY {
core::hint::spin_loop();
}
}
Err(_) => {}
}
}
}
#[inline]
pub fn current_core_type() -> CoreType {
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
{
x86::current_core_type()
}
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
{
CoreType::Unknown
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn snapshot_is_coherent() {
let info = CpuInfo::get();
for c in [info.l1d(), info.l1i(), info.l2(), info.l3()].into_iter().flatten() {
assert!(c.size > 0, "cache reported with zero size: {c:?}");
if let Some(line) = c.line_size {
assert!(
line.is_power_of_two() && (16..=256).contains(&line),
"implausible line size {line}"
);
}
}
if let (Some(l1d), Some(l2)) = (info.l1d(), info.l2()) {
assert!(l1d.size <= l2.size, "L1d {} > L2 {}", l1d.size, l2.size);
}
if let (Some(l2), Some(l3)) = (info.l2(), info.l3()) {
assert!(l2.size <= l3.size, "L2 {} > L3 {}", l2.size, l3.size);
}
if let Some(line) = info.cache_line_size() {
assert!(
line.is_power_of_two() && (16..=256).contains(&line),
"implausible line size {line}"
);
}
let topo = info.topology();
if let (Some(l), Some(p)) = (topo.logical_cores, topo.physical_cores) {
assert!(l >= p, "logical {l} < physical {p}");
}
if let (Some(p), Some(e), Some(total)) = (topo.performance_cores, topo.efficiency_cores, topo.logical_cores) {
assert!(p + e <= total, "P {p} + E {e} exceeds {total} logical");
}
if let Some(t) = topo.threads_per_core {
assert!((1..=8).contains(&t), "implausible threads/core {t}");
}
}
#[test]
fn snapshot_is_stable() {
let a = *CpuInfo::get();
let b = *CpuInfo::get();
assert_eq!(a, b);
assert!(core::ptr::eq(CpuInfo::get(), CpuInfo::get()));
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
#[test]
fn x86_fills_in_the_basics() {
let info = CpuInfo::get();
assert!(info.cache_line_size().is_some(), "x86 always reports a line size");
assert!(info.l1d().is_some(), "x86 always enumerates L1d");
assert_eq!(info.cache_line_size(), info.writeback_granule());
if !info.is_hybrid() {
assert_eq!(*info, CpuInfo::detect(), "uncached detect disagrees with the snapshot");
}
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
#[test]
fn x86_features_agree_with_build() {
let f = crate::cpu::x86::features();
if cfg!(target_feature = "sse2") {
assert!(f.sse2, "built with sse2 but not detected");
}
if cfg!(target_feature = "avx2") {
assert!(f.avx2, "built with avx2 but not detected");
}
if cfg!(target_feature = "fma") {
assert!(f.fma, "built with fma but not detected");
}
assert!(!f.avx2 || f.avx, "avx2 without avx");
assert!(!f.avx512f || f.avx, "avx512f without avx");
assert!(!f.fma || f.avx, "fma without avx");
assert!(!f.sse42 || f.sse2, "sse4.2 without sse2");
use crate::isa::InstructionSet;
let isa = InstructionSet::get();
match isa {
InstructionSet::X86V3 => assert!(f.avx2 && f.fma && f.popcnt),
InstructionSet::X86V2 => assert!(f.sse42 && f.popcnt),
InstructionSet::X86V1 => assert!(f.sse2),
_ => {}
}
}
#[cfg(all(feature = "std", any(target_arch = "x86", target_arch = "x86_64")))]
#[test]
fn x86_features_match_std_detect() {
let f = crate::cpu::x86::features();
assert_eq!(f.sse2, std::is_x86_feature_detected!("sse2"), "sse2");
assert_eq!(f.sse42, std::is_x86_feature_detected!("sse4.2"), "sse4.2");
assert_eq!(f.popcnt, std::is_x86_feature_detected!("popcnt"), "popcnt");
assert_eq!(f.pclmulqdq, std::is_x86_feature_detected!("pclmulqdq"), "pclmulqdq");
assert_eq!(f.avx, std::is_x86_feature_detected!("avx"), "avx");
assert_eq!(f.avx2, std::is_x86_feature_detected!("avx2"), "avx2");
assert_eq!(f.fma, std::is_x86_feature_detected!("fma"), "fma");
assert_eq!(f.f16c, std::is_x86_feature_detected!("f16c"), "f16c");
assert_eq!(f.avx512f, std::is_x86_feature_detected!("avx512f"), "avx512f");
assert_eq!(f.avx512cd, std::is_x86_feature_detected!("avx512cd"), "avx512cd");
assert_eq!(f.avx512bw, std::is_x86_feature_detected!("avx512bw"), "avx512bw");
assert_eq!(f.avx512dq, std::is_x86_feature_detected!("avx512dq"), "avx512dq");
assert_eq!(f.avx512vl, std::is_x86_feature_detected!("avx512vl"), "avx512vl");
assert_eq!(f.avx512vbmi, std::is_x86_feature_detected!("avx512vbmi"), "avx512vbmi");
assert_eq!(
f.avx512vbmi2,
std::is_x86_feature_detected!("avx512vbmi2"),
"avx512vbmi2"
);
assert_eq!(f.avx512vnni, std::is_x86_feature_detected!("avx512vnni"), "avx512vnni");
assert_eq!(
f.avx512bitalg,
std::is_x86_feature_detected!("avx512bitalg"),
"avx512bitalg"
);
assert_eq!(
f.avx512vpopcntdq,
std::is_x86_feature_detected!("avx512vpopcntdq"),
"avx512vpopcntdq"
);
assert_eq!(f.avx512ifma, std::is_x86_feature_detected!("avx512ifma"), "avx512ifma");
assert_eq!(f.avx512bf16, std::is_x86_feature_detected!("avx512bf16"), "avx512bf16");
assert_eq!(f.avx512fp16, std::is_x86_feature_detected!("avx512fp16"), "avx512fp16");
assert_eq!(f.gfni, std::is_x86_feature_detected!("gfni"), "gfni");
assert_eq!(f.vaes, std::is_x86_feature_detected!("vaes"), "vaes");
assert_eq!(f.vpclmulqdq, std::is_x86_feature_detected!("vpclmulqdq"), "vpclmulqdq");
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
#[test]
fn avx512_tiers_are_monotone() {
use crate::cpu::x86::{Avx512Tier, Features};
let f = crate::cpu::x86::features();
if !f.avx512f {
assert_eq!(f.avx512_tier(), None, "a tier without AVX512F");
assert!(!f.avx512cd && !f.avx512bw && !f.avx512dq && !f.avx512vl);
assert!(!f.avx512vbmi && !f.avx512vbmi2 && !f.avx512vnni && !f.avx512bitalg);
assert!(!f.avx512vpopcntdq && !f.avx512ifma && !f.avx512bf16 && !f.avx512fp16);
assert_eq!(f.avx10_version, 0, "AVX10 without AVX512F");
}
let mut synthetic = Features {
avx512f: true,
avx512cd: true,
..Default::default()
};
assert_eq!(synthetic.avx512_tier(), Some(Avx512Tier::Tier1));
synthetic.avx512bw = true;
synthetic.avx512dq = true;
assert_eq!(synthetic.avx512_tier(), Some(Avx512Tier::Tier1), "promoted without VL");
synthetic.avx512vl = true;
assert_eq!(synthetic.avx512_tier(), Some(Avx512Tier::Tier2));
synthetic.avx512vbmi = true;
synthetic.avx512vnni = true;
assert_eq!(
synthetic.avx512_tier(),
Some(Avx512Tier::Tier2),
"promoted on a partial tier 3"
);
synthetic.avx512vbmi2 = true;
synthetic.avx512bitalg = true;
synthetic.avx512vpopcntdq = true;
synthetic.avx512ifma = true;
synthetic.gfni = true;
synthetic.vaes = true;
synthetic.vpclmulqdq = true;
assert_eq!(synthetic.avx512_tier(), Some(Avx512Tier::Tier3));
synthetic.avx512bf16 = true;
assert_eq!(synthetic.avx512_tier(), Some(Avx512Tier::Tier4));
let mut no_vl = synthetic;
no_vl.avx512vl = false;
assert_eq!(
no_vl.avx512_tier(),
Some(Avx512Tier::Tier1),
"VL must gate tier 2 and up"
);
let knl = Features {
avx512f: true,
avx512cd: true,
..Default::default()
};
assert_eq!(knl.avx512_tier(), Some(Avx512Tier::Tier1));
let f_only = Features {
avx512f: true,
..Default::default()
};
assert_eq!(f_only.avx512_tier(), None);
assert!(Avx512Tier::Tier1 < Avx512Tier::Tier4, "tiers must order");
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
#[test]
fn avx10_implies_the_full_ladder() {
use crate::cpu::x86::{Avx10Version, Avx512Tier, Features};
let f = crate::cpu::x86::features();
if f.avx10().is_some() {
assert_eq!(f.avx512_tier(), Some(Avx512Tier::Tier4));
assert!(f.avx512fp16 && f.avx512vl && f.gfni && f.vaes && f.vpclmulqdq);
}
let mut s = Features::default();
assert_eq!(s.avx10(), None);
s.avx10_version = 1;
assert_eq!(s.avx10(), Some(Avx10Version::V10_1));
s.avx10_version = 2;
assert_eq!(s.avx10(), Some(Avx10Version::V10_2));
s.avx10_version = 9;
assert_eq!(
s.avx10(),
Some(Avx10Version::V10_2),
"future versions are supersets of 10.2"
);
assert!(Avx10Version::V10_1 < Avx10Version::V10_2, "versions must order");
}
#[cfg(target_arch = "aarch64")]
#[test]
fn aarch64_reads_ctr_el0() {
let info = CpuInfo::get();
assert!(info.cache_line_size().is_some(), "CTR_EL0 line size unavailable");
if let Some(cwg) = info.writeback_granule() {
assert!(
cwg.is_power_of_two() && (16..=2048).contains(&cwg),
"implausible CWG {cwg}"
);
}
}
}