use core::cell::UnsafeCell;
use core::sync::atomic::AtomicU8;
use super::Cache;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct Quirks {
pub fast_gather: bool,
pub fast_scatter: bool,
pub fast_vex_masked_store: bool,
pub fast_compress_store: bool,
pub fast_conflict_detect: bool,
pub fast_pdep_pext: bool,
}
impl Quirks {
pub const CONSERVATIVE: Self = Self {
fast_gather: false,
fast_scatter: false,
fast_vex_masked_store: false,
fast_compress_store: false,
fast_conflict_detect: false,
fast_pdep_pext: false,
};
#[inline]
pub fn get() -> &'static Quirks {
static CACHE: Cache<Quirks> = Cache {
state: AtomicU8::new(super::UNINIT),
value: UnsafeCell::new(Quirks::CONSERVATIVE),
};
CACHE.get(Quirks::detect)
}
pub fn detect() -> Quirks {
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
{
detect_x86()
}
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
{
Quirks::CONSERVATIVE
}
}
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
fn detect_x86() -> Quirks {
use super::x86::{family_model, is_amd_lineage, is_intel};
let (family, model) = family_model();
if is_amd_lineage() {
return amd_quirks(family);
}
if is_intel() && family == 6 {
return intel_quirks(model);
}
Quirks::CONSERVATIVE
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
fn amd_quirks(family: u32) -> Quirks {
match family {
f if f >= 0x1A => Quirks {
fast_gather: false,
fast_scatter: false,
fast_vex_masked_store: true,
fast_compress_store: true,
fast_conflict_detect: true,
fast_pdep_pext: true,
},
0x19 => Quirks {
fast_conflict_detect: true,
fast_pdep_pext: true,
..Quirks::CONSERVATIVE
},
0x17 | 0x18 => Quirks::CONSERVATIVE,
_ => Quirks::CONSERVATIVE,
}
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
fn intel_quirks(model: u32) -> Quirks {
const GDS_AFFECTED: &[u32] = &[
0x4E, 0x5E, 0x55, 0x8E, 0x9E, 0xA5, 0xA6, 0x66, 0x7D, 0x7E, 0x6A, 0x6C, 0x8C, 0x8D, 0xA7, ];
const FAST_GATHER: &[u32] = &[
0x97, 0x9A, 0xB7, 0xBA, 0xBF, 0xAA, 0xAC, 0x8F, 0xCF, 0xAD, 0xAE, ];
const FAST_SCATTER: &[u32] = &[0x7D, 0x7E, 0x6A, 0x6C, 0x8C, 0x8D, 0xA7, 0x8F, 0xCF, 0xAD, 0xAE];
let gds_slowed = GDS_AFFECTED.contains(&model) && gds_mitigated().unwrap_or(true);
Quirks {
fast_gather: FAST_GATHER.contains(&model) || (GDS_AFFECTED.contains(&model) && !gds_slowed),
fast_scatter: FAST_SCATTER.contains(&model),
fast_vex_masked_store: true,
fast_compress_store: true,
fast_conflict_detect: false,
fast_pdep_pext: true,
}
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
fn gds_mitigated() -> Option<bool> {
#[cfg(all(feature = "std", target_os = "linux"))]
{
let status = std::fs::read_to_string("/sys/devices/system/cpu/vulnerabilities/gather_data_sampling").ok()?;
let status = status.trim();
Some(!(status.starts_with("Not affected") || status.starts_with("Vulnerable")))
}
#[cfg(not(all(feature = "std", target_os = "linux")))]
{
None
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn quirks_are_stable() {
let a = *Quirks::get();
let b = *Quirks::get();
assert_eq!(a, b);
assert!(core::ptr::eq(Quirks::get(), Quirks::get()));
assert_eq!(a, Quirks::detect(), "uncached detect disagrees with the cache");
}
#[test]
fn conservative_is_all_false() {
let c = Quirks::CONSERVATIVE;
assert!(!c.fast_gather && !c.fast_scatter && !c.fast_vex_masked_store);
assert!(!c.fast_compress_store && !c.fast_conflict_detect && !c.fast_pdep_pext);
assert_eq!(c, Quirks::default(), "Default must match CONSERVATIVE");
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
#[test]
fn amd_ladder_flips_at_the_right_families() {
let zen2 = amd_quirks(0x17);
assert_eq!(zen2, Quirks::CONSERVATIVE);
assert_eq!(amd_quirks(0x18), Quirks::CONSERVATIVE, "Hygon Dhyana tracks Zen 1");
let zen4 = amd_quirks(0x19);
assert!(zen4.fast_pdep_pext && zen4.fast_conflict_detect);
assert!(!zen4.fast_vex_masked_store, "Zen 4 VEX masked store is 42 uops");
assert!(!zen4.fast_compress_store, "Zen 4 compress-store is 144 uops");
let zen5 = amd_quirks(0x1A);
assert!(zen5.fast_vex_masked_store && zen5.fast_compress_store);
for f in [0x15, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x20] {
let q = amd_quirks(f);
assert!(!q.fast_gather, "family {f:#x} claimed a fast gather");
assert!(!q.fast_scatter, "family {f:#x} claimed a fast scatter");
}
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
#[test]
fn intel_models_land_where_intended() {
assert!(intel_quirks(0x97).fast_gather, "Alder Lake should have a fast gather");
let spr = intel_quirks(0x8F);
assert!(spr.fast_gather && spr.fast_scatter);
assert!(!intel_quirks(0x55).fast_scatter, "Skylake-X scatter is 35 uops / 16.0");
let hsw = intel_quirks(0x3C);
assert!(!hsw.fast_gather && !hsw.fast_scatter);
assert!(hsw.fast_pdep_pext && hsw.fast_vex_masked_store);
for m in [0x55, 0x66, 0x7D, 0x8F, 0xAD, 0x97] {
assert!(
!intel_quirks(m).fast_conflict_detect,
"model {m:#x} claimed a fast vpconflict; none has been measured"
);
}
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
#[test]
fn unknown_intel_model_is_conservative() {
let unknown = intel_quirks(0xFE);
assert!(!unknown.fast_gather && !unknown.fast_scatter);
}
}