use std::sync::atomic::{AtomicU8, Ordering};
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
pub enum Level {
Baseline = 0,
V2 = 1,
V3 = 2,
V4 = 3,
}
impl Level {
pub fn name(self) -> &'static str {
match self {
Level::Baseline => "baseline",
Level::V2 => "v2",
Level::V3 => "v3",
Level::V4 => "v4",
}
}
fn from_u8(v: u8) -> Level {
match v {
1 => Level::V2,
2 => Level::V3,
3 => Level::V4,
_ => Level::Baseline,
}
}
fn from_name(s: &str) -> Option<Level> {
match s.trim().to_ascii_lowercase().as_str() {
"baseline" | "v1" | "none" => Some(Level::Baseline),
"v2" => Some(Level::V2),
"v3" => Some(Level::V3),
"v4" => Some(Level::V4),
"native" | "auto" | "max" => Some(detected()),
_ => None,
}
}
}
#[cfg(target_arch = "x86_64")]
pub fn detected() -> Level {
let v2 = is_x86_feature_detected!("sse4.2") && is_x86_feature_detected!("popcnt");
let v3 = v2
&& is_x86_feature_detected!("avx2")
&& is_x86_feature_detected!("fma")
&& is_x86_feature_detected!("bmi1")
&& is_x86_feature_detected!("bmi2")
&& is_x86_feature_detected!("f16c")
&& is_x86_feature_detected!("lzcnt");
let v4 = v3
&& is_x86_feature_detected!("avx512f")
&& is_x86_feature_detected!("avx512bw")
&& is_x86_feature_detected!("avx512cd")
&& is_x86_feature_detected!("avx512dq")
&& is_x86_feature_detected!("avx512vl");
if v4 {
Level::V4
} else if v3 {
Level::V3
} else if v2 {
Level::V2
} else {
Level::Baseline
}
}
#[cfg(target_arch = "aarch64")]
pub fn detected() -> Level {
if std::arch::is_aarch64_feature_detected!("neon") { Level::V3 } else { Level::Baseline }
}
#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
pub fn detected() -> Level {
Level::Baseline
}
pub fn available() -> Vec<Level> {
let top = detected();
[Level::Baseline, Level::V2, Level::V3, Level::V4].into_iter().filter(|&l| l <= top).collect()
}
const UNSET: u8 = u8::MAX;
static LEVEL: AtomicU8 = AtomicU8::new(UNSET);
fn from_env() -> Level {
let asked = std::env::var("LIBJAY_CPU_LEVEL").ok().and_then(|v| Level::from_name(&v));
match asked {
Some(l) => l.min(detected()),
None => detected(),
}
}
#[inline]
pub fn level() -> Level {
let v = LEVEL.load(Ordering::Relaxed);
if v != UNSET {
return Level::from_u8(v);
}
let l = from_env();
LEVEL.store(l as u8, Ordering::Relaxed);
l
}
pub fn set_level(l: Level) -> Level {
let l = l.min(detected());
LEVEL.store(l as u8, Ordering::Relaxed);
l
}
macro_rules! multiversioned {
(
$(#[$attr:meta])*
fn $name:ident $([$($gen:tt)*])? ($($arg:ident: $ty:ty),* $(,)?) -> $ret:ty = $body:ident;
) => {
mod $name {
#![allow(clippy::too_many_arguments)]
#[allow(unused_imports)]
use super::*;
pub(super) fn baseline $(<$($gen)*>)? ($($arg: $ty),*) -> $ret {
$body($($arg),*)
}
#[::multiversion::multiversion(targets("x86_64+sse3+ssse3+sse4.1+sse4.2+popcnt"))]
pub(super) fn v2 $(<$($gen)*>)? ($($arg: $ty),*) -> $ret {
$body($($arg),*)
}
#[::multiversion::multiversion(targets(
"x86_64+avx+avx2+fma+bmi1+bmi2+lzcnt+f16c",
"aarch64+neon",
))]
pub(super) fn v3 $(<$($gen)*>)? ($($arg: $ty),*) -> $ret {
$body($($arg),*)
}
#[::multiversion::multiversion(targets(
"x86_64+avx512f+avx512bw+avx512cd+avx512dq+avx512vl",
))]
pub(super) fn v4 $(<$($gen)*>)? ($($arg: $ty),*) -> $ret {
$body($($arg),*)
}
}
$(#[$attr])*
#[inline]
fn $name $(<$($gen)*>)? ($($arg: $ty),*) -> $ret {
match $crate::simd::level() {
$crate::simd::Level::Baseline => $name::baseline($($arg),*),
$crate::simd::Level::V2 => $name::v2($($arg),*),
$crate::simd::Level::V3 => $name::v3($($arg),*),
$crate::simd::Level::V4 => $name::v4($($arg),*),
}
}
};
}
pub(crate) use multiversioned;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_machines_own_level_is_available() {
let all = available();
assert_eq!(all.last().copied(), Some(detected()));
assert!(all.contains(&Level::Baseline));
}
#[test]
fn a_level_the_machine_lacks_clamps_to_one_it_has() {
assert!(set_level(Level::V4) <= detected());
assert_eq!(set_level(Level::Baseline), Level::Baseline);
assert_eq!(level(), Level::Baseline);
set_level(detected());
}
#[test]
fn every_level_has_a_name_and_reads_back() {
for l in [Level::Baseline, Level::V2, Level::V3, Level::V4] {
assert_eq!(Level::from_name(l.name()), Some(l));
}
assert_eq!(Level::from_name("nonsense"), None);
assert_eq!(Level::from_name("native"), Some(detected()));
}
}