use core::sync::atomic::{AtomicU8, Ordering};
#[doc(hidden)]
pub mod vector;
static OVERRIDE: AtomicU8 = AtomicU8::new(0);
pub fn set_override(isa: Option<SimdIsa>) {
let code = match isa {
Some(isa) if available().contains(&isa) => isa.code(),
Some(_) => SimdIsa::Scalar.code(),
None => 0,
};
OVERRIDE.store(code, Ordering::Relaxed);
}
#[must_use]
pub fn active() -> SimdIsa {
override_isa().unwrap_or_else(detected)
}
#[must_use]
pub fn detected() -> SimdIsa {
detected_isa()
}
#[must_use]
pub fn available() -> Vec<SimdIsa> {
available_isas()
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
pub enum SimdIsa {
Scalar,
Sse41,
Avx2,
Neon,
}
impl SimdIsa {
#[must_use]
pub fn name(self) -> &'static str {
match self {
SimdIsa::Scalar => "scalar",
SimdIsa::Sse41 => "sse4.1",
SimdIsa::Avx2 => "avx2",
SimdIsa::Neon => "neon",
}
}
#[doc(hidden)]
pub fn code(self) -> u8 {
match self {
SimdIsa::Scalar => 1,
SimdIsa::Sse41 => 2,
SimdIsa::Avx2 => 3,
SimdIsa::Neon => 4,
}
}
#[doc(hidden)]
pub fn from_code(code: u8) -> Option<Self> {
match code {
1 => Some(SimdIsa::Scalar),
2 => Some(SimdIsa::Sse41),
3 => Some(SimdIsa::Avx2),
4 => Some(SimdIsa::Neon),
_ => None,
}
}
}
#[must_use]
pub fn lanes(isa: SimdIsa) -> usize {
match isa {
SimdIsa::Scalar => 0,
SimdIsa::Sse41 | SimdIsa::Neon => 4,
SimdIsa::Avx2 => 8,
}
}
#[must_use]
pub fn detected_isa() -> SimdIsa {
#[cfg(target_arch = "x86_64")]
{
if std::is_x86_feature_detected!("avx2") {
return SimdIsa::Avx2;
}
if std::is_x86_feature_detected!("sse4.1") {
return SimdIsa::Sse41;
}
SimdIsa::Scalar
}
#[cfg(target_arch = "aarch64")]
{
SimdIsa::Neon
}
#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
{
SimdIsa::Scalar
}
}
#[must_use]
pub fn available_isas() -> Vec<SimdIsa> {
#[cfg_attr(
not(any(target_arch = "x86_64", target_arch = "aarch64")),
allow(unused_mut)
)]
let mut isas = vec![SimdIsa::Scalar];
#[cfg(target_arch = "x86_64")]
{
if std::is_x86_feature_detected!("sse4.1") {
isas.push(SimdIsa::Sse41);
}
if std::is_x86_feature_detected!("avx2") {
isas.push(SimdIsa::Avx2);
}
}
#[cfg(target_arch = "aarch64")]
{
isas.push(SimdIsa::Neon);
}
isas
}
#[doc(hidden)]
#[inline]
#[must_use]
pub fn override_isa() -> Option<SimdIsa> {
SimdIsa::from_code(OVERRIDE.load(Ordering::Relaxed))
}
#[doc(hidden)]
pub fn test_lock() -> std::sync::MutexGuard<'static, ()> {
static LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
LOCK.lock().unwrap_or_else(|poisoned| poisoned.into_inner())
}
#[cfg(test)]
mod tests {
use super::*;
use super::test_lock as lock;
#[test]
fn available_always_includes_scalar_and_contains_the_detected_set() {
let isas = available();
assert!(isas.contains(&SimdIsa::Scalar));
assert!(isas.contains(&detected()));
}
#[test]
fn override_takes_precedence_over_detection_and_clears() {
let _guard = lock();
set_override(Some(SimdIsa::Scalar));
assert_eq!(active(), SimdIsa::Scalar);
set_override(None);
assert_eq!(active(), detected());
}
#[test]
fn every_available_instruction_set_can_be_pinned() {
let _guard = lock();
for isa in available() {
set_override(Some(isa));
assert_eq!(active(), isa, "{}", isa.name());
}
set_override(None);
}
#[test]
fn an_unsupported_instruction_set_clamps_to_scalar() {
let _guard = lock();
let unsupported = [
SimdIsa::Scalar,
SimdIsa::Sse41,
SimdIsa::Avx2,
SimdIsa::Neon,
]
.into_iter()
.find(|isa| !available().contains(isa));
if let Some(isa) = unsupported {
set_override(Some(isa));
assert_eq!(active(), SimdIsa::Scalar, "{}", isa.name());
}
set_override(None);
}
}