use std::fmt;
use super::{GROUP, group_bytes, portable};
use crate::sync::OnceLock;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Backend {
Portable,
Sse42,
Avx2,
Avx512,
Neon,
}
const BEST_FIRST: [Backend; 5] = [
Backend::Avx512,
Backend::Avx2,
Backend::Neon,
Backend::Sse42,
Backend::Portable,
];
const COPY_AVX512_GROUPS: usize = 24;
impl Backend {
#[must_use]
pub fn active() -> Self {
static ACTIVE: OnceLock<Backend> = OnceLock::new();
*ACTIVE.get_or_init(|| {
BEST_FIRST
.into_iter()
.find(|b| b.is_available())
.unwrap_or(Self::Portable)
})
}
pub fn available() -> impl Iterator<Item = Self> {
BEST_FIRST.into_iter().filter(|b| b.is_available())
}
#[must_use]
pub fn is_available(self) -> bool {
match self {
Self::Portable => true,
#[cfg(all(target_arch = "x86_64", target_endian = "little"))]
Self::Sse42 => std::arch::is_x86_feature_detected!("sse4.2"),
#[cfg(all(target_arch = "x86_64", target_endian = "little"))]
Self::Avx2 => std::arch::is_x86_feature_detected!("avx2"),
#[cfg(all(target_arch = "x86_64", target_endian = "little"))]
Self::Avx512 => {
std::arch::is_x86_feature_detected!("avx512f")
&& std::arch::is_x86_feature_detected!("avx2")
}
#[cfg(all(target_arch = "aarch64", target_endian = "little"))]
Self::Neon => std::arch::is_aarch64_feature_detected!("neon"),
_ => false,
}
}
pub fn pack_groups(self, src: &[[u32; GROUP]], width: u32, out: &mut [u8]) {
assert!(self.is_available(), "{self} is not available here");
assert!((1..=32).contains(&width), "a group is 1 to 32 bits wide");
let size = group_bytes(width);
assert!(out.len() >= src.len() * size, "a group's bytes");
let numbers = src.as_flattened();
let mut group = 0;
while group < src.len() {
let backend = self.for_pack(width, src.len() - group);
backend.pack_unchecked(&numbers[group * GROUP..], width, &mut out[group * size..]);
group += backend.groups_per_call();
}
}
pub fn unpack_groups(self, src: &[u8], width: u32, out: &mut [[u32; GROUP]]) {
assert!(self.is_available(), "{self} is not available here");
assert!((1..=32).contains(&width), "a group is 1 to 32 bits wide");
let size = group_bytes(width);
assert!(src.len() >= out.len() * size, "a group's bytes");
let count = out.len();
let numbers = out.as_flattened_mut();
let mut group = 0;
while group < count {
let backend = self.for_unpack(width, count - group);
backend.unpack_unchecked(&src[group * size..], width, &mut numbers[group * GROUP..]);
group += backend.groups_per_call();
}
}
fn groups_per_call(self) -> usize {
match self {
Self::Avx512 => 2,
_ => 1,
}
}
fn for_pack(self, width: u32, left: usize) -> Self {
match self {
Self::Avx512 if left < self.groups_per_call() || width == u32::BITS => Self::Avx2,
other => other,
}
}
fn for_unpack(self, width: u32, left: usize) -> Self {
match self {
Self::Avx512
if left < self.groups_per_call()
|| (width == u32::BITS && left > COPY_AVX512_GROUPS) =>
{
Self::Avx2
}
other => other,
}
}
pub(super) fn pack_unchecked(self, src: &[u32], width: u32, out: &mut [u8]) {
match self {
#[cfg(all(target_arch = "x86_64", target_endian = "little"))]
Self::Sse42 => super::sse42::pack(src, width, out),
#[cfg(all(target_arch = "x86_64", target_endian = "little"))]
Self::Avx2 => super::avx2::pack(src, width, out),
#[cfg(all(target_arch = "x86_64", target_endian = "little"))]
Self::Avx512 => super::avx512::pack(src, width, out),
#[cfg(all(target_arch = "aarch64", target_endian = "little"))]
Self::Neon => super::neon::pack(src, width, out),
_ => portable::pack(src, width, out),
}
}
pub(super) fn unpack_unchecked(self, src: &[u8], width: u32, out: &mut [u32]) {
match self {
#[cfg(all(target_arch = "x86_64", target_endian = "little"))]
Self::Sse42 => super::sse42::unpack(src, width, out),
#[cfg(all(target_arch = "x86_64", target_endian = "little"))]
Self::Avx2 => super::avx2::unpack(src, width, out),
#[cfg(all(target_arch = "x86_64", target_endian = "little"))]
Self::Avx512 => super::avx512::unpack(src, width, out),
#[cfg(all(target_arch = "aarch64", target_endian = "little"))]
Self::Neon => super::neon::unpack(src, width, out),
_ => portable::unpack(src, width, out),
}
}
}
impl fmt::Display for Backend {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Self::Portable => "portable",
Self::Sse42 => "SSE4.2",
Self::Avx2 => "AVX2",
Self::Avx512 => "AVX-512",
Self::Neon => "NEON",
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_512_bit_kernels_hand_a_lone_group_to_avx2() {
assert_eq!(Backend::Avx512.for_pack(7, 1), Backend::Avx2);
assert_eq!(Backend::Avx512.for_unpack(7, 1), Backend::Avx2);
assert_eq!(Backend::Avx512.for_pack(7, 2), Backend::Avx512);
assert_eq!(Backend::Avx512.for_unpack(7, 2), Backend::Avx512);
}
#[test]
fn a_full_width_copy_is_packed_by_avx2_and_unpacked_by_avx512_while_it_fits_l1() {
assert_eq!(Backend::Avx512.for_pack(32, 8), Backend::Avx2);
assert_eq!(
Backend::Avx512.for_unpack(32, COPY_AVX512_GROUPS),
Backend::Avx512
);
assert_eq!(
Backend::Avx512.for_unpack(32, COPY_AVX512_GROUPS + 1),
Backend::Avx2
);
}
#[test]
fn narrower_backends_are_never_redirected() {
for backend in [Backend::Portable, Backend::Avx2] {
assert_eq!(backend.for_pack(32, 1), backend);
assert_eq!(backend.for_unpack(32, 100), backend);
}
}
}