#[cfg(target_arch = "x86")]
use core::arch::x86::__cpuid_count;
#[cfg(target_arch = "x86_64")]
use core::arch::x86_64::__cpuid_count;
use super::{ArchInfo, tables::x86_uarch};
#[derive(Clone, Copy, Default)]
struct Regs {
eax: u32,
ebx: u32,
ecx: u32,
edx: u32,
}
fn cpuid(leaf: u32, subleaf: u32) -> Regs {
let r = __cpuid_count(leaf, subleaf);
Regs {
eax: r.eax,
ebx: r.ebx,
ecx: r.ecx,
edx: r.edx,
}
}
fn max_leaf() -> u32 {
cpuid(0, 0).eax
}
fn max_ext_leaf() -> u32 {
cpuid(0x8000_0000, 0).eax
}
fn vendor_string() -> String {
let r = cpuid(0, 0);
let bytes: [u32; 3] = [r.ebx, r.edx, r.ecx];
let mut s = String::with_capacity(12);
for word in bytes {
for shift in [0, 8, 16, 24] {
s.push(((word >> shift) & 0xFF) as u8 as char);
}
}
s
}
pub fn brand_string() -> Option<String> {
if max_ext_leaf() < 0x8000_0004 {
return None;
}
let mut bytes = Vec::with_capacity(48);
for leaf in [0x8000_0002u32, 0x8000_0003, 0x8000_0004] {
let r = cpuid(leaf, 0);
for word in [r.eax, r.ebx, r.ecx, r.edx] {
bytes.extend_from_slice(&word.to_le_bytes());
}
}
let s = String::from_utf8_lossy(&bytes)
.trim_matches(char::from(0))
.trim()
.to_string();
if s.is_empty() { None } else { Some(s) }
}
struct Fms {
ext_family: u32,
family: u32,
ext_model: u32,
model: u32,
stepping: u32,
}
fn family_model_stepping() -> Fms {
let eax = cpuid(1, 0).eax;
let stepping = eax & 0xF;
let base_model = (eax >> 4) & 0xF;
let base_family = (eax >> 8) & 0xF;
let ext_model = (eax >> 16) & 0xF;
let ext_family = (eax >> 20) & 0xFF;
let family = if base_family == 0xF {
base_family + ext_family
} else {
base_family
};
let model = if base_family == 0x6 || base_family == 0xF {
(ext_model << 4) + base_model
} else {
base_model
};
Fms {
ext_family,
family,
ext_model,
model,
stepping,
}
}
fn lookup_uarch(table: &[x86_uarch::Entry], fms: &Fms) -> Option<(&'static str, Option<u32>)> {
table.iter().find_map(|&(ef, f, em, m, s, name, proc)| {
let ok = ef == fms.ext_family
&& f == fms.family
&& em.is_none_or(|v| v == fms.ext_model)
&& m.is_none_or(|v| v == fms.model)
&& s.is_none_or(|v| v == fms.stepping);
ok.then_some((name, proc))
})
}
fn features() -> Vec<String> {
let mut flags = Vec::new();
let l1 = cpuid(1, 0);
if l1.edx & (1 << 25) != 0 {
flags.push("sse".to_string());
}
if l1.edx & (1 << 26) != 0 {
flags.push("sse2".to_string());
}
if l1.ecx & (1 << 0) != 0 {
flags.push("sse3".to_string());
}
if l1.ecx & (1 << 9) != 0 {
flags.push("ssse3".to_string());
}
if l1.ecx & (1 << 19) != 0 {
flags.push("sse4_1".to_string());
}
if l1.ecx & (1 << 20) != 0 {
flags.push("sse4_2".to_string());
}
if l1.ecx & (1 << 12) != 0 {
flags.push("fma".to_string());
}
if l1.ecx & (1 << 25) != 0 {
flags.push("aes".to_string());
}
if l1.ecx & (1 << 28) != 0 {
flags.push("avx".to_string());
}
if max_leaf() >= 7 {
let l7 = cpuid(7, 0);
if l7.ebx & (1 << 5) != 0 {
flags.push("avx2".to_string());
}
if l7.ebx & (1 << 16) != 0 {
flags.push("avx512f".to_string());
}
if l7.ebx & (1 << 29) != 0 {
flags.push("sha_ni".to_string());
}
}
flags
}
pub fn detect() -> ArchInfo {
let vendor_raw = vendor_string();
let vendor = match vendor_raw.as_str() {
"GenuineIntel" => "Intel",
"AuthenticAMD" => "AMD",
"HygonGenuine" => "Hygon",
other => other,
};
let fms = family_model_stepping();
let table: &[x86_uarch::Entry] = match vendor {
"Intel" => x86_uarch::INTEL,
"AMD" => x86_uarch::AMD,
"Hygon" => x86_uarch::HYGON,
_ => &[],
};
let (uarch, process_nm) = match lookup_uarch(table, &fms) {
Some((name, proc)) => (Some(name.to_string()), proc),
None => (None, None),
};
ArchInfo {
vendor: Some(vendor.to_string()),
uarch,
soc: None,
process_nm,
features: features(),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn zen3_matches_via_model_wildcard() {
let fms = Fms {
ext_family: 10,
family: 15,
ext_model: 3,
model: 99,
stepping: 5,
};
let (name, proc) = lookup_uarch(x86_uarch::AMD, &fms).expect("should match Zen 3");
assert_eq!(name, "Zen 3");
assert_eq!(proc, Some(7));
}
#[test]
fn exact_model_match_required_when_table_specifies_one() {
let fms = Fms {
ext_family: 0,
family: 4,
ext_model: 0,
model: 0,
stepping: 0,
};
assert_eq!(
lookup_uarch(x86_uarch::INTEL, &fms).map(|(n, _)| n),
Some("i80486DX")
);
let fms_wrong_model = Fms {
ext_family: 0,
family: 4,
ext_model: 0,
model: 99,
stepping: 0,
};
assert_ne!(
lookup_uarch(x86_uarch::INTEL, &fms_wrong_model).map(|(n, _)| n),
Some("i80486DX")
);
}
#[test]
fn unknown_fms_returns_none() {
let fms = Fms {
ext_family: 255,
family: 255,
ext_model: 255,
model: 255,
stepping: 255,
};
assert!(lookup_uarch(x86_uarch::INTEL, &fms).is_none());
assert!(lookup_uarch(x86_uarch::AMD, &fms).is_none());
}
}