use aocl_utils_sys as sys;
const AU_CURRENT_CPU_NUM: u32 = u32::MAX;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Cpu {
Current,
Specific(u32),
}
impl Cpu {
fn as_raw(self) -> u32 {
match self {
Cpu::Current => AU_CURRENT_CPU_NUM,
Cpu::Specific(n) => n,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum ZenArch {
Zen,
ZenPlus,
Zen2,
Zen3,
Zen4,
Zen5,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[non_exhaustive]
pub enum X86_64Level {
V2,
V3,
V4,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct VendorInfo {
pub vendor_id: String,
pub family_id: String,
pub model_id: String,
pub stepping_id: String,
pub uarch_id: String,
}
pub fn is_amd(cpu: Cpu) -> bool {
unsafe { sys::au_cpuid_is_amd(cpu.as_raw()) }
}
pub fn is_zen_family(cpu: Cpu) -> bool {
unsafe { sys::au_cpuid_arch_is_zen_family(cpu.as_raw()) }
}
pub fn zen_arch(cpu: Cpu) -> Option<ZenArch> {
let raw = cpu.as_raw();
unsafe {
if sys::au_cpuid_arch_is_zen5(raw) {
Some(ZenArch::Zen5)
} else if sys::au_cpuid_arch_is_zen4(raw) {
Some(ZenArch::Zen4)
} else if sys::au_cpuid_arch_is_zen3(raw) {
Some(ZenArch::Zen3)
} else if sys::au_cpuid_arch_is_zen2(raw) {
Some(ZenArch::Zen2)
} else if sys::au_cpuid_arch_is_zenplus(raw) {
Some(ZenArch::ZenPlus)
} else if sys::au_cpuid_arch_is_zen(raw) {
Some(ZenArch::Zen)
} else {
None
}
}
}
pub fn x86_64_level(cpu: Cpu) -> Option<X86_64Level> {
let raw = cpu.as_raw();
unsafe {
if sys::au_cpuid_arch_is_x86_64v4(raw) {
Some(X86_64Level::V4)
} else if sys::au_cpuid_arch_is_x86_64v3(raw) {
Some(X86_64Level::V3)
} else if sys::au_cpuid_arch_is_x86_64v2(raw) {
Some(X86_64Level::V2)
} else {
None
}
}
}
pub fn has_flag(cpu: Cpu, flag: &str) -> bool {
has_flags_all(cpu, &[flag])
}
pub fn has_flags_all(cpu: Cpu, flags: &[&str]) -> bool {
if flags.is_empty() {
return true;
}
let cstrings: Vec<std::ffi::CString> = flags
.iter()
.filter_map(|s| std::ffi::CString::new(*s).ok())
.collect();
if cstrings.len() != flags.len() {
return false;
}
let pointers: Vec<*const std::os::raw::c_char> = cstrings.iter().map(|c| c.as_ptr()).collect();
unsafe {
sys::au_cpuid_has_flags_all(
cpu.as_raw(),
pointers.as_ptr(),
pointers.len() as std::os::raw::c_int,
)
}
}
pub fn has_flags_any(cpu: Cpu, flags: &[&str]) -> bool {
if flags.is_empty() {
return false;
}
let cstrings: Vec<std::ffi::CString> = flags
.iter()
.filter_map(|s| std::ffi::CString::new(*s).ok())
.collect();
if cstrings.len() != flags.len() {
return false;
}
let pointers: Vec<*const std::os::raw::c_char> = cstrings.iter().map(|c| c.as_ptr()).collect();
unsafe {
sys::au_cpuid_has_flags_any(
cpu.as_raw(),
pointers.as_ptr(),
pointers.len() as std::os::raw::c_int,
)
}
}
pub fn vendor_info(cpu: Cpu) -> VendorInfo {
let mut buf = [0u8; 256];
unsafe {
sys::au_cpuid_get_vendor(
cpu.as_raw(),
buf.as_mut_ptr() as *mut std::os::raw::c_char,
buf.len(),
);
}
let nul = buf.iter().position(|&b| b == 0).unwrap_or(buf.len());
let raw = std::str::from_utf8(&buf[..nul]).unwrap_or("");
let mut it = raw.split('\n');
VendorInfo {
vendor_id: it.next().unwrap_or("").to_string(),
family_id: it.next().unwrap_or("").to_string(),
model_id: it.next().unwrap_or("").to_string(),
stepping_id: it.next().unwrap_or("").to_string(),
uarch_id: it.next().unwrap_or("").to_string(),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn current_cpu_does_not_panic() {
let info = vendor_info(Cpu::Current);
assert!(!info.vendor_id.is_empty(), "vendor_id should be populated");
let _ = is_amd(Cpu::Current);
let _ = is_zen_family(Cpu::Current);
let _ = zen_arch(Cpu::Current);
let _ = x86_64_level(Cpu::Current);
}
#[test]
fn has_flag_queries_run() {
let _ = has_flag(Cpu::Current, "sse2");
let _ = has_flags_all(Cpu::Current, &["sse2", "sse4_2"]);
let _ = has_flags_any(Cpu::Current, &["avx2", "avx512f"]);
assert!(has_flags_all(Cpu::Current, &[]));
assert!(!has_flags_any(Cpu::Current, &[]));
}
}