use std::sync::OnceLock;
#[allow(clippy::struct_excessive_bools)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CpuFeatures {
pub avx512: bool,
pub avx512bw: bool,
pub avx512vl: bool,
pub avx512vbmi: bool,
pub avx2: bool,
pub neon: bool,
pub cache_line_size: usize,
pub l1_size: usize,
pub l2_size: usize,
pub l3_size: usize,
}
#[must_use]
pub fn detect() -> CpuFeatures {
static FEATURES: OnceLock<CpuFeatures> = OnceLock::new();
*FEATURES.get_or_init(detect_impl)
}
fn detect_impl() -> CpuFeatures {
let (cache_line_size, l1_size, l2_size, l3_size) = detect_cache_sizes();
let avx512 = detect_avx512();
CpuFeatures {
avx512,
avx512bw: avx512 && detect_avx512bw(),
avx512vl: avx512 && detect_avx512vl(),
avx512vbmi: avx512 && detect_avx512vbmi(),
avx2: detect_avx2(),
neon: detect_neon(),
cache_line_size,
l1_size,
l2_size,
l3_size,
}
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
fn detect_avx512() -> bool {
std::arch::is_x86_feature_detected!("avx512f")
}
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
fn detect_avx512() -> bool {
false
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
fn detect_avx512bw() -> bool {
std::arch::is_x86_feature_detected!("avx512bw")
}
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
fn detect_avx512bw() -> bool {
false
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
fn detect_avx512vl() -> bool {
std::arch::is_x86_feature_detected!("avx512vl")
}
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
fn detect_avx512vl() -> bool {
false
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
fn detect_avx512vbmi() -> bool {
std::arch::is_x86_feature_detected!("avx512vbmi")
}
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
fn detect_avx512vbmi() -> bool {
false
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
fn detect_avx2() -> bool {
std::arch::is_x86_feature_detected!("avx2")
}
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
fn detect_avx2() -> bool {
false
}
#[cfg(target_arch = "aarch64")]
fn detect_neon() -> bool {
std::arch::is_aarch64_feature_detected!("neon")
}
#[cfg(target_arch = "arm")]
fn detect_neon() -> bool {
std::arch::is_arm_feature_detected!("neon")
}
#[cfg(not(any(target_arch = "aarch64", target_arch = "arm")))]
fn detect_neon() -> bool {
false
}
#[cfg(target_os = "linux")]
fn detect_cache_sizes() -> (usize, usize, usize, usize) {
(
read_cache_value("/sys/devices/system/cpu/cpu0/cache/index0/coherency_line_size")
.unwrap_or(64),
read_cache_value("/sys/devices/system/cpu/cpu0/cache/index0/size").unwrap_or(32 * 1024),
read_cache_value("/sys/devices/system/cpu/cpu0/cache/index2/size").unwrap_or(256 * 1024),
read_cache_value("/sys/devices/system/cpu/cpu0/cache/index3/size").unwrap_or_default(),
)
}
#[cfg(not(target_os = "linux"))]
fn detect_cache_sizes() -> (usize, usize, usize, usize) {
(64, 32 * 1024, 256 * 1024, 0)
}
#[cfg(target_os = "linux")]
fn read_cache_value(path: &'static str) -> Option<usize> {
let raw = std::fs::read_to_string(path).ok()?;
parse_cache_value(raw.trim()).ok()
}
#[cfg(target_os = "linux")]
fn parse_cache_value(raw: &str) -> Result<usize, ()> {
let uppercase = raw.trim().to_ascii_uppercase();
if let Some(value) = uppercase.strip_suffix('K') {
let size = value.parse::<usize>().map_err(|_| ())?;
return size.checked_mul(1024).ok_or(());
}
if let Some(value) = uppercase.strip_suffix('M') {
let size = value.parse::<usize>().map_err(|_| ())?;
return size
.checked_mul(1024)
.and_then(|v| v.checked_mul(1024))
.ok_or(());
}
uppercase.parse::<usize>().map_err(|_| ())
}
#[cfg(test)]
mod tests {
use super::detect;
#[test]
fn detect_returns_stable_cached_result() {
let first = detect();
let second = detect();
assert_eq!(first, second);
assert!(first.cache_line_size > 0);
assert!(first.l1_size > 0);
}
#[cfg(target_os = "linux")]
#[test]
fn linux_cache_sizes_are_non_zero_for_l1_and_l2() {
let features = detect();
assert!(features.l1_size > 0);
assert!(features.l2_size > 0);
}
}