use std::fmt::Debug;
use std::sync::atomic::{AtomicU8, Ordering};
pub(crate) mod sealed {
pub trait Sealed {}
}
pub trait Simd: Copy + Send + Sync + Debug + 'static + sealed::Sealed {
const NAME: &'static str;
type R: Copy + Send + Sync + Debug;
type M: Copy + Send + Sync + Debug;
fn splat(x: f64) -> Self::R;
fn zero() -> Self::R;
fn load(s: &[f64]) -> Self::R;
fn store(a: Self::R, s: &mut [f64]);
fn add(a: Self::R, b: Self::R) -> Self::R;
fn sub(a: Self::R, b: Self::R) -> Self::R;
fn mul(a: Self::R, b: Self::R) -> Self::R;
fn div(a: Self::R, b: Self::R) -> Self::R;
type D: Copy + Send + Sync + Debug;
fn divisor(b: Self::R) -> Self::D;
fn div_by(a: Self::R, d: Self::D) -> Self::R;
fn fma(a: Self::R, b: Self::R, c: Self::R) -> Self::R;
fn fms(a: Self::R, b: Self::R, c: Self::R) -> Self::R;
fn neg(a: Self::R) -> Self::R;
fn abs(a: Self::R) -> Self::R;
fn max(a: Self::R, b: Self::R) -> Self::R;
fn bcast(a: Self::R, j: usize) -> Self::R;
fn sqrt(a: Self::R) -> Self::R;
fn pow2_floor(a: Self::R) -> Self::R;
fn ne(a: Self::R, b: Self::R) -> Self::M;
fn eq(a: Self::R, b: Self::R) -> Self::M;
fn gt(a: Self::R, b: Self::R) -> Self::M;
fn ge(a: Self::R, b: Self::R) -> Self::M;
fn and(m: Self::M, n: Self::M) -> Self::M;
fn or(m: Self::M, n: Self::M) -> Self::M;
fn not(m: Self::M) -> Self::M;
fn sel(m: Self::M, t: Self::R, f: Self::R) -> Self::R;
#[inline(always)]
fn add_where(m: Self::M, a: Self::R, b: Self::R) -> Self::R {
Self::sel(m, Self::add(a, b), a)
}
#[inline(always)]
fn and_eq(m: Self::M, a: Self::R, b: Self::R) -> Self::M {
Self::and(m, Self::eq(a, b))
}
#[inline(always)]
fn and_ne(m: Self::M, a: Self::R, b: Self::R) -> Self::M {
Self::and(m, Self::ne(a, b))
}
#[doc(hidden)]
#[inline(always)]
fn opaque(m: Self::M) -> Self::M {
m
}
fn bits(m: Self::M) -> u8;
fn vectorize<T>(f: impl FnOnce() -> T) -> T;
fn hot<T>(f: impl FnOnce() -> T) -> T;
fn outline<T>(f: impl FnOnce() -> T) -> T;
}
pub(crate) mod backend {
#[cfg(target_arch = "x86_64")]
#[derive(Clone, Copy, Debug)]
pub struct Avx512;
#[cfg(target_arch = "x86_64")]
#[derive(Clone, Copy, Debug)]
pub struct Avx2;
#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), target_feature = "sse2"))]
#[derive(Clone, Copy, Debug)]
pub struct Sse2;
#[cfg(all(target_arch = "aarch64", target_feature = "neon"))]
#[derive(Clone, Copy, Debug)]
pub struct Neon;
#[derive(Clone, Copy, Debug)]
pub struct Portable;
}
#[cfg(all(not(feature = "portable"), target_arch = "x86_64", target_feature = "avx512f"))]
pub type Native = backend::Avx512;
#[cfg(all(
not(feature = "portable"),
target_arch = "x86_64",
not(target_feature = "avx512f"),
target_feature = "avx2",
target_feature = "fma"
))]
pub type Native = backend::Avx2;
#[cfg(all(
not(feature = "portable"),
any(target_arch = "x86", target_arch = "x86_64"),
target_feature = "sse2",
not(all(target_arch = "x86_64", target_feature = "avx512f")),
not(all(target_arch = "x86_64", target_feature = "avx2", target_feature = "fma"))
))]
pub type Native = backend::Sse2;
#[cfg(all(not(feature = "portable"), target_arch = "aarch64", target_feature = "neon"))]
pub type Native = backend::Neon;
#[cfg(any(
feature = "portable",
not(any(
all(any(target_arch = "x86", target_arch = "x86_64"), target_feature = "sse2"),
all(target_arch = "aarch64", target_feature = "neon")
))
))]
pub type Native = backend::Portable;
pub trait Kernel {
type Output;
fn run<S: Simd>(self) -> Self::Output;
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u8)]
enum Level {
Portable = 1,
Sse2,
Neon,
Avx2,
Avx512,
}
impl Level {
fn name(self) -> &'static str {
match self {
Level::Portable => "portable",
Level::Sse2 => "sse2",
Level::Neon => "neon",
Level::Avx2 => "avx2",
Level::Avx512 => "avx512",
}
}
}
fn levels() -> Vec<Level> {
[Level::Avx512, Level::Avx2, Level::Sse2, Level::Neon, Level::Portable]
.into_iter()
.filter(|&l| runs(l))
.collect()
}
fn runs(l: Level) -> bool {
if cfg!(feature = "portable") {
return l == Level::Portable;
}
match l {
#[cfg(target_arch = "x86_64")]
Level::Avx512 => {
std::arch::is_x86_feature_detected!("avx512f")
&& std::arch::is_x86_feature_detected!("avx2")
&& std::arch::is_x86_feature_detected!("fma")
}
#[cfg(target_arch = "x86_64")]
Level::Avx2 => std::arch::is_x86_feature_detected!("avx2") && std::arch::is_x86_feature_detected!("fma"),
Level::Sse2 => cfg!(all(
any(target_arch = "x86", target_arch = "x86_64"),
target_feature = "sse2"
)),
Level::Neon => cfg!(all(target_arch = "aarch64", target_feature = "neon")),
Level::Portable => true,
#[allow(unreachable_patterns)]
_ => false,
}
}
static BEST: AtomicU8 = AtomicU8::new(0);
fn best() -> Level {
let all = [Level::Portable, Level::Sse2, Level::Neon, Level::Avx2, Level::Avx512];
if let Some(&l) = all.iter().find(|l| **l as u8 == BEST.load(Ordering::Relaxed)) {
return l;
}
let l = levels()[0];
BEST.store(l as u8, Ordering::Relaxed);
l
}
#[inline(always)]
fn run<S: Simd, K: Kernel>(k: K) -> K::Output {
S::vectorize(
#[inline(always)]
|| k.run::<S>(),
)
}
fn run_on<K: Kernel>(l: Level, k: K) -> K::Output {
match l {
#[cfg(target_arch = "x86_64")]
Level::Avx512 => run::<backend::Avx512, K>(k),
#[cfg(target_arch = "x86_64")]
Level::Avx2 => run::<backend::Avx2, K>(k),
#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), target_feature = "sse2"))]
Level::Sse2 => run::<backend::Sse2, K>(k),
#[cfg(all(target_arch = "aarch64", target_feature = "neon"))]
Level::Neon => run::<backend::Neon, K>(k),
Level::Portable => run::<backend::Portable, K>(k),
#[allow(unreachable_patterns)]
_ => unreachable!("backend {} not built for this target", l.name()),
}
}
pub fn dispatch<K: Kernel>(k: K) -> K::Output {
run_on(best(), k)
}
pub fn for_each_available<K: Kernel + Clone>(k: K) -> Vec<(&'static str, K::Output)> {
levels().into_iter().map(|l| (l.name(), run_on(l, k.clone()))).collect()
}
pub fn isa() -> &'static str {
best().name()
}
pub fn available() -> Vec<&'static str> {
levels().into_iter().map(Level::name).collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Clone)]
struct Name;
impl Kernel for Name {
type Output = &'static str;
#[inline(always)]
fn run<S: Simd>(self) -> &'static str {
S::NAME
}
}
#[test]
fn dispatch_runs_the_backends_it_names() {
assert_eq!(dispatch(Name), isa());
let ran = for_each_available(Name);
assert_eq!(ran.iter().map(|r| r.0).collect::<Vec<_>>(), available());
assert!(ran.iter().all(|(name, ran_on)| name == ran_on));
assert_eq!(ran.last().map(|r| r.0), Some("portable"));
assert!(available().contains(&Native::NAME));
println!("simd: {} of {:?}, native {}", isa(), available(), Native::NAME);
}
}