use crate::arch::*;
use crate::base::block::Block;
use sealed::SimdBuild;
use std::sync::LazyLock;
#[derive(Ord, PartialOrd, Eq, PartialEq, Copy, Clone)]
#[repr(u8)]
pub enum SimdLevel {
#[cfg(not(all(
target_arch = "aarch64",
any(target_os = "macos", target_os = "ios")
)))]
Fallback(Fallback) = 0,
#[cfg(target_arch = "aarch64")]
Neon(Neon) = 1,
#[cfg(target_arch = "x86_64")]
Sse2(Sse2) = 1,
#[cfg(target_arch = "x86_64")]
Avx(Avx) = 2,
#[cfg(target_arch = "x86_64")]
Avx2(Avx2) = 3,
#[cfg(target_arch = "x86_64")]
Avx512(Avx512) = 4, }
cfg_select! {
target_arch = "x86_64" => {
fn highest_level() -> SimdLevel {
use std::arch::is_x86_feature_detected;
let has_avx512f = is_x86_feature_detected!("avx512f");
let has_vaes = is_x86_feature_detected!("vaes");
let has_avx = is_x86_feature_detected!("avx");
let has_aes = is_x86_feature_detected!("aes");
if has_avx512f && has_vaes {
SimdLevel::Avx512(Avx512::new())
} else if has_vaes {
SimdLevel::Avx2(Avx2::new())
} else if has_avx && has_aes {
SimdLevel::Avx(Avx::new())
} else if has_aes {
SimdLevel::Sse2(Sse2::new())
} else {
SimdLevel::Fallback(Fallback::new())
}
}
}
all(target_arch = "aarch64", any(target_os = "macos", target_os = "ios")) => {
fn highest_level() -> SimdLevel {
SimdLevel::Neon(Neon::new())
}
}
target_arch = "aarch64" => {
fn highest_level() -> SimdLevel {
use std::arch::is_aarch64_feature_detected;
if is_aarch64_feature_detected!("aes") {
SimdLevel::Neon(Neon::new())
} else {
SimdLevel::Fallback(Fallback::new())
}
}
}
_ => {
fn highest_level() -> SimdLevel {
SimdLevel::Fallback(Fallback::new())
}
}
}
impl SimdLevel {
#[inline(always)]
pub fn best() -> SimdLevel {
static CPU_INFO: LazyLock<SimdLevel> =
LazyLock::new(|| highest_level().env_bounded());
*CPU_INFO
}
#[cfg(all(test, target_arch = "x86_64"))]
pub fn supported<T: Simd>() -> Option<T> {
(SimdLevel::best() >= T::LEVEL).then(T::new)
}
fn env_bounded(self) -> SimdLevel {
if cfg!(not(feature = "internal_test_only_eats_babies")) {
return self;
}
let backend_str = match std::env::var("PHILBIN_BACKEND") {
Err(_) => return self,
Ok(val) => val.to_lowercase(),
};
let env_level = match backend_str.as_str() {
#[cfg(not(all(
target_arch = "aarch64",
any(target_os = "macos", target_os = "ios")
)))]
"fallback" => SimdLevel::Fallback(Fallback::new()),
#[cfg(target_arch = "aarch64")]
"neon" => SimdLevel::Neon(Neon::new()),
#[cfg(target_arch = "x86_64")]
"sse2" => SimdLevel::Sse2(Sse2::new()),
#[cfg(target_arch = "x86_64")]
"avx" => SimdLevel::Avx(Avx::new()),
#[cfg(target_arch = "x86_64")]
"avx2" => SimdLevel::Avx2(Avx2::new()),
#[cfg(target_arch = "x86_64")]
"avx512" => SimdLevel::Avx512(Avx512::new()),
unknown => panic!(
"Value for PHILBIN_BACKEND is unknown or not supported on this \
platform: {unknown}"
),
};
self.min(env_level)
}
}
#[doc = include_str!("../rustdoc/is_hw_accelerated.md")]
#[must_use]
pub fn is_hw_accelerated() -> bool {
cfg_select! {
all(target_arch = "aarch64",
any(target_os = "macos", target_os = "ios")) => true,
_ => SimdLevel::best() != SimdLevel::Fallback(Fallback::new())
}
}
pub trait Simd: sealed::SimdBuild + Copy {
type Block128: Block<SelfArray = [u8; 16], Simd = Self>;
type Block256: Block<SelfArray = [u8; 32], Simd = Self>;
type Block512: Block<SelfArray = [u8; 64], Simd = Self>;
#[cfg(all(test, target_arch = "x86_64"))]
const LEVEL: SimdLevel;
fn with_target_features<F: FnOnce(Self) -> R, R>(self, func: F) -> R;
}
mod sealed {
pub trait SimdBuild {
fn new() -> Self;
}
}
macro_rules! with_doc {
($doc:literal $( $it:item )+ ) => {
$(
#[doc = $doc]
$it
)+
};
}
with_doc! { r#"
Structs implementing this trait are considered "proof" that the current CPU
supports that instruction set.
Functions that want the caller to prove that the instruction set is available
should take an `impl HasXXX` param.
# Safety
Should only be implemented for types whose creation is guarded by the
corresponding runtime CPU feature check.
"#
#[allow(unused, reason = "We don't actually use `HasFallback`, but various
code is simpler if it exists (avoids a special case).")]
pub unsafe trait HasFallback {}
#[cfg(target_arch = "x86_64")]
pub unsafe trait HasSse2 {}
#[cfg(target_arch = "x86_64")]
pub unsafe trait HasAvx : HasSse2 {}
#[cfg(target_arch = "x86_64")]
pub unsafe trait HasAvx2 : HasAvx {}
#[cfg(target_arch = "x86_64")]
pub unsafe trait HasAvx512 : HasAvx2 {}
#[cfg(target_arch = "aarch64")]
pub unsafe trait HasNeon {}
}
macro_rules! gen_simd_struct {
(
$name:ident =>
$block128:ident, $block256:ident, $block512:ident;
$( $trait:ident ),+ )
=> {
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct $name(());
impl sealed::SimdBuild for $name {
fn new() -> $name {
$name(())
}
}
impl Simd for $name {
type Block128 = $block128::<Self>;
type Block256 = $block256::<Self>;
type Block512 = $block512::<Self>;
#[cfg(all(test, target_arch = "x86_64"))]
const LEVEL: SimdLevel = SimdLevel::$name($name(()));
pastey::paste! {
#[inline(always)]
fn with_target_features<F: FnOnce(Self) -> R, R>(self, func: F) -> R {
annotate_with_target_features! { $name =>
fn [<helper_ $name:lower>]<F: FnOnce($name) -> R, R>(
token: $name,
func: F
) -> R
{
func(token)
}
}
#[allow(unused_unsafe, reason = "HasFallback doesn't need unsafe")]
unsafe { [<helper_ $name:lower>](self, func) }
}
}
}
$(
unsafe impl $trait for $name {}
)+
};
}
cfg_select! {
target_arch = "x86_64" => {
gen_simd_struct!{
Sse2 => Sse1x128, Sse2x128, Sse4x128; HasSse2
}
gen_simd_struct!{
Avx => Sse1x128, Sse2x128, Sse4x128; HasSse2, HasAvx
}
gen_simd_struct!{
Avx2 => Sse1x128, Avx1x256, Avx2x256; HasSse2, HasAvx, HasAvx2
}
gen_simd_struct!{
Avx512 => Sse1x128, Avx1x256, Avx1x512;
HasSse2, HasAvx, HasAvx2, HasAvx512
}
gen_simd_struct!{
Fallback => Fallback128, Fallback256, Fallback512; HasFallback
}
}
all(target_arch = "aarch64", any(target_os = "macos", target_os = "ios")) => {
gen_simd_struct!{
Neon => Neon1x128, Neon2x128, Neon4x128; HasNeon
}
}
target_arch = "aarch64" => {
gen_simd_struct!{
Neon => Neon1x128, Neon2x128, Neon4x128; HasNeon
}
gen_simd_struct!{
Fallback => Fallback128, Fallback256, Fallback512; HasFallback
}
}
_ => {
gen_simd_struct!{
Fallback => Fallback128, Fallback256, Fallback512; HasFallback
}
}
}
macro_rules! annotate_with_target_features {
(Avx512 => $func:item) => {
#[target_feature(enable = "avx512f,vaes")]
$func
};
(Avx2 => $func:item) => {
#[target_feature(enable = "vaes")]
$func
};
(Avx => $func:item) => {
#[target_feature(enable = "aes,avx")]
$func
};
(Sse2 => $func:item) => {
#[target_feature(enable = "aes")]
$func
};
(Neon => $func:item) => {
#[target_feature(enable = "aes")]
$func
};
(Fallback => $func:item) => {
#[cold] #[inline(never)]
$func
};
(HasAvx512 => $func:item) => {
crate::arch::annotate_with_target_features! { Avx512 => $func }
};
(HasAvx2 => $func:item) => {
crate::arch::annotate_with_target_features! { Avx2 => $func }
};
(HasAvx => $func:item) => {
crate::arch::annotate_with_target_features! { Avx => $func }
};
(HasSse2 => $func:item) => {
crate::arch::annotate_with_target_features! { Sse2 => $func }
};
(HasNeon => $func:item) => {
crate::arch::annotate_with_target_features! { Neon => $func }
};
(HasFallback => $func:item) => {
crate::arch::annotate_with_target_features! { Fallback => $func }
};
}
pub(crate) use annotate_with_target_features;
macro_rules! with_simd {
(
fn $name:ident(
$token:ident : impl $token_type:ident,
$( $arg:ident : $arg_type:ty ),* $(,)?
) -> $ret:ty
$body:block
) => {
#[inline(always)]
fn $name(
$token: impl $token_type,
$($arg: $arg_type),*
) -> $ret {
crate::arch::annotate_with_target_features! { $token_type =>
#[inline] fn __real_impl(
$token: impl $token_type,
$($arg: $arg_type),*
) -> $ret {
let _ = $token;
$body
}
}
unsafe { __real_impl( $token, $($arg),* ) }
}
};
}
pub(crate) use with_simd;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn best_simd_basic() {
let _ = SimdLevel::best();
}
}