use super::{CacheInfo, CacheKind, CoreType, CpuInfo};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Avx512Tier {
Tier1,
Tier2,
Tier3,
Tier4,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Avx10Version {
V10_1,
V10_2,
}
#[cfg(target_arch = "x86")]
use core::arch::x86::{__cpuid_count, _xgetbv, CpuidResult};
#[cfg(target_arch = "x86_64")]
use core::arch::x86_64::{__cpuid_count, _xgetbv, CpuidResult};
#[inline]
fn bit(value: u32, index: u32) -> bool {
(value >> index) & 1 != 0
}
#[inline]
pub(super) fn cpuid(leaf: u32, sub: u32) -> CpuidResult {
__cpuid_count(leaf, sub)
}
#[inline]
fn max_leaves() -> (u32, u32) {
let basic = cpuid(0, 0).eax;
let ext = cpuid(0x8000_0000, 0).eax;
(basic, if ext > 0x8000_0000 { ext } else { 0 })
}
#[inline]
pub(super) fn is_amd_lineage() -> bool {
let r = cpuid(0, 0);
let amd = r.ebx == 0x6874_7541 && r.edx == 0x6974_6e65 && r.ecx == 0x444d_4163; let hygon = r.ebx == 0x6f677948 && r.edx == 0x6e65476e && r.ecx == 0x656e6975; amd || hygon
}
#[inline]
pub(super) fn is_intel() -> bool {
let r = cpuid(0, 0);
r.ebx == 0x756e_6547 && r.edx == 0x4965_6e69 && r.ecx == 0x6c65_746e }
#[inline]
fn family() -> u32 {
family_model().0
}
#[inline]
pub(super) fn family_model() -> (u32, u32) {
let eax = cpuid(1, 0).eax;
let base_family = (eax >> 8) & 0xf;
let base_model = (eax >> 4) & 0xf;
let family = if base_family == 0xf {
base_family + ((eax >> 20) & 0xff)
} else {
base_family
};
let model = if base_family == 0x6 || base_family == 0xf {
(((eax >> 16) & 0xf) << 4) | base_model
} else {
base_model
};
(family, model)
}
fn read_caches(info: &mut CpuInfo, leaf: u32) {
for sub in 0..16 {
let r = cpuid(leaf, sub);
let kind = match r.eax & 0x1f {
0 => break, 1 => CacheKind::Data,
2 => CacheKind::Instruction,
_ => CacheKind::Unified,
};
let level = ((r.eax >> 5) & 0x7) as u8;
let fully_associative = (r.eax >> 9) & 1 != 0;
let shared_by = (((r.eax >> 14) & 0xfff) + 1) as u16;
let line = u64::from(r.ebx & 0xfff) + 1;
let partitions = u64::from((r.ebx >> 12) & 0x3ff) + 1;
let ways = u64::from((r.ebx >> 22) & 0x3ff) + 1;
let sets = u64::from(r.ecx) + 1;
let entry = CacheInfo {
size: (line * partitions * ways * sets) as u32,
line_size: Some(line as u32),
associativity: Some(if fully_associative { 0 } else { ways as u16 }),
shared_by: Some(shared_by),
kind,
};
match (level, kind) {
(1, CacheKind::Data) => info.l1d = Some(entry),
(1, CacheKind::Instruction) => info.l1i = Some(entry),
(1, CacheKind::Unified) => {
info.l1d = Some(entry);
info.l1i = Some(entry);
}
(2, _) => info.l2 = Some(entry),
(3, _) => info.l3 = Some(entry),
_ => {}
}
}
}
fn read_topology(leaf: u32) -> (Option<u16>, Option<u16>) {
const LEVEL_SMT: u32 = 1;
let mut threads_per_core = None;
let mut widest = 0u16;
for sub in 0..8 {
let r = cpuid(leaf, sub);
let level_type = (r.ecx >> 8) & 0xff;
let count = (r.ebx & 0xffff) as u16;
if level_type == 0 {
break; }
if count == 0 {
continue;
}
if level_type == LEVEL_SMT {
threads_per_core = Some(count);
}
widest = widest.max(count);
}
(threads_per_core, (widest > 0).then_some(widest))
}
fn read_topology_amd(max_ext: u32) -> (Option<u16>, Option<u16>) {
let mut threads_per_core = None;
let mut logical = None;
if max_ext >= 0x8000_0008 {
logical = u16::try_from((cpuid(0x8000_0008, 0).ecx & 0xff) + 1).ok();
}
if max_ext >= 0x8000_001E && family() >= 0x17 && bit(cpuid(0x8000_0001, 0).ecx, 22) {
threads_per_core = u16::try_from(((cpuid(0x8000_001E, 0).ebx >> 8) & 0xff) + 1).ok();
}
(threads_per_core, logical)
}
pub fn detect() -> CpuInfo {
let mut info = CpuInfo::UNKNOWN;
let (max_basic, max_ext) = max_leaves();
if max_basic >= 1 {
let line = ((cpuid(1, 0).ebx >> 8) & 0xff) * 8;
if line > 0 {
info.line_size = Some(line);
info.writeback_granule = Some(line);
}
}
let (first, second) = if is_amd_lineage() {
(0x8000_001D, 4)
} else {
(4, 0x8000_001D)
};
for leaf in [first, second] {
let available = if leaf >= 0x8000_0000 {
max_ext >= leaf
} else {
max_basic >= leaf
};
if available {
read_caches(&mut info, leaf);
}
if info.l1d.is_some() {
break;
}
}
if info.line_size.is_none()
&& let Some(l1d) = info.l1d
{
info.line_size = l1d.line_size;
info.writeback_granule = l1d.line_size;
}
let mut topology = (None, None);
if max_basic >= 0x1F {
topology = read_topology(0x1F);
}
if topology.1.is_none() && max_basic >= 0xB {
topology = read_topology(0xB);
}
if topology.1.is_none() {
topology = read_topology_amd(max_ext);
}
let (threads_per_core, logical_per_package) = topology;
info.topology.threads_per_core = threads_per_core;
#[cfg(feature = "std")]
{
info.topology.logical_cores = std::thread::available_parallelism()
.ok()
.and_then(|n| u16::try_from(n.get()).ok());
}
if info.topology.logical_cores.is_none() {
info.topology.logical_cores = logical_per_package;
}
if let (Some(logical), Some(per_core)) = (info.topology.logical_cores, threads_per_core)
&& per_core > 0
{
info.topology.physical_cores = Some(logical / per_core);
}
if max_basic >= 7 {
info.hybrid = (cpuid(7, 0).edx >> 15) & 1 != 0;
}
info
}
pub fn current_core_type() -> CoreType {
let (max_basic, _) = max_leaves();
if max_basic < 0x1A || (cpuid(7, 0).edx >> 15) & 1 == 0 {
return CoreType::Unknown;
}
match cpuid(0x1A, 0).eax >> 24 {
0x20 => CoreType::Efficiency, 0x40 => CoreType::Performance, _ => CoreType::Unknown,
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct Features {
pub sse2: bool,
pub sse42: bool,
pub popcnt: bool,
pub pclmulqdq: bool,
pub avx: bool,
pub avx2: bool,
pub fma: bool,
pub f16c: bool,
pub avx512f: bool,
pub avx512cd: bool,
pub avx512bw: bool,
pub avx512dq: bool,
pub avx512vl: bool,
pub avx512vbmi: bool,
pub avx512vbmi2: bool,
pub avx512vnni: bool,
pub avx512bitalg: bool,
pub avx512vpopcntdq: bool,
pub avx512ifma: bool,
pub avx512bf16: bool,
pub avx512fp16: bool,
pub gfni: bool,
pub vaes: bool,
pub vpclmulqdq: bool,
pub avx10_version: u8,
}
impl Features {
pub fn avx512_tier(&self) -> Option<Avx512Tier> {
if !(self.avx512f && self.avx512cd) {
return None;
}
if !(self.avx512bw && self.avx512dq && self.avx512vl) {
return Some(Avx512Tier::Tier1);
}
let tier3 = self.avx512vbmi
&& self.avx512vbmi2
&& self.avx512vnni
&& self.avx512bitalg
&& self.avx512vpopcntdq
&& self.avx512ifma
&& self.gfni
&& self.vaes
&& self.vpclmulqdq;
if !tier3 {
return Some(Avx512Tier::Tier2);
}
if !self.avx512bf16 {
return Some(Avx512Tier::Tier3);
}
Some(Avx512Tier::Tier4)
}
pub fn avx10(&self) -> Option<Avx10Version> {
match self.avx10_version {
0 => None,
1 => Some(Avx10Version::V10_1),
_ => Some(Avx10Version::V10_2),
}
}
}
pub fn features() -> Features {
let mut f = Features::default();
let (max_basic, _) = max_leaves();
if max_basic < 1 {
return f;
}
let leaf1 = cpuid(1, 0);
f.sse2 = bit(leaf1.edx, 26);
f.sse42 = bit(leaf1.ecx, 20);
f.popcnt = bit(leaf1.ecx, 23);
f.pclmulqdq = bit(leaf1.ecx, 1);
const XCR0_AVX: u64 = 0b110;
const XCR0_AVX512: u64 = 0b1110_0000;
let osxsave = bit(leaf1.ecx, 27);
let xcr0 = if osxsave { unsafe { _xgetbv(0) } } else { 0 };
let os_saves_ymm = osxsave && (xcr0 & XCR0_AVX) == XCR0_AVX;
let os_saves_zmm = os_saves_ymm && (xcr0 & XCR0_AVX512) == XCR0_AVX512;
f.avx = os_saves_ymm && bit(leaf1.ecx, 28);
f.fma = f.avx && bit(leaf1.ecx, 12);
f.f16c = f.avx && bit(leaf1.ecx, 29);
if max_basic >= 7 {
let leaf7 = cpuid(7, 0);
let leaf7_1 = (leaf7.eax >= 1).then(|| cpuid(7, 1));
f.avx2 = f.avx && bit(leaf7.ebx, 5);
f.avx512f = f.avx && os_saves_zmm && bit(leaf7.ebx, 16);
if f.avx512f {
f.avx512dq = bit(leaf7.ebx, 17);
f.avx512ifma = bit(leaf7.ebx, 21);
f.avx512cd = bit(leaf7.ebx, 28);
f.avx512bw = bit(leaf7.ebx, 30);
f.avx512vl = bit(leaf7.ebx, 31);
f.avx512vbmi = bit(leaf7.ecx, 1);
f.avx512vbmi2 = bit(leaf7.ecx, 6);
f.avx512vnni = bit(leaf7.ecx, 11);
f.avx512bitalg = bit(leaf7.ecx, 12);
f.avx512vpopcntdq = bit(leaf7.ecx, 14);
f.avx512fp16 = bit(leaf7.edx, 23);
if let Some(l) = leaf7_1 {
f.avx512bf16 = bit(l.eax, 5);
}
}
f.gfni = bit(leaf7.ecx, 8);
f.vaes = f.avx && bit(leaf7.ecx, 9);
f.vpclmulqdq = f.avx && bit(leaf7.ecx, 10);
if os_saves_zmm
&& f.avx
&& max_basic >= 0x24
&& let Some(l) = leaf7_1
&& bit(l.edx, 19)
{
f.avx10_version = (cpuid(0x24, 0).ebx & 0xff) as u8;
}
if f.avx10_version >= 1 {
f.avx512f = true;
f.avx512cd = true;
f.avx512bw = true;
f.avx512dq = true;
f.avx512vl = true;
f.avx512vbmi = true;
f.avx512vbmi2 = true;
f.avx512vnni = true;
f.avx512bitalg = true;
f.avx512vpopcntdq = true;
f.avx512ifma = true;
f.avx512bf16 = true;
f.avx512fp16 = true;
f.gfni = true;
f.vaes = true;
f.vpclmulqdq = true;
}
}
f
}