use core::fmt;
use core::str::FromStr;
#[cfg(feature = "std")]
use archmage::{
NeonAesToken, NeonToken, ScalarToken, SimdToken, Wasm128Token, X64V1Token, X64V2Token,
X64V3GfniCryptoToken, X64V3Token,
};
#[cfg(all(feature = "std", feature = "avx512"))]
use archmage::{X64V4Token, X64V4xToken};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[non_exhaustive]
pub enum Backend {
V4x,
V4,
V3GfniCrypto,
V3,
V2,
V1,
NeonAes,
Neon,
Wasm128,
Scalar,
}
impl Backend {
pub const ALL: &'static [Backend] = &[
Backend::V4x,
Backend::V4,
Backend::V3GfniCrypto,
Backend::V3,
Backend::V2,
Backend::V1,
Backend::NeonAes,
Backend::Neon,
Backend::Wasm128,
Backend::Scalar,
];
#[must_use]
pub const fn name(self) -> &'static str {
match self {
Backend::V4x => "v4x",
Backend::V4 => "v4",
Backend::V3GfniCrypto => "v3_gfni_crypto",
Backend::V3 => "v3",
Backend::V2 => "v2",
Backend::V1 => "v1",
Backend::NeonAes => "neon_aes",
Backend::Neon => "neon",
Backend::Wasm128 => "wasm128",
Backend::Scalar => "scalar",
}
}
#[must_use]
pub fn from_name(name: &str) -> Option<Backend> {
Some(match name {
"v4x" => Backend::V4x,
"v4" => Backend::V4,
"v3_gfni_crypto" => Backend::V3GfniCrypto,
"v3" => Backend::V3,
"v2" => Backend::V2,
"v1" => Backend::V1,
"neon_aes" => Backend::NeonAes,
"neon" => Backend::Neon,
"wasm128" => Backend::Wasm128,
"scalar" => Backend::Scalar,
_ => return None,
})
}
#[must_use]
pub const fn lane_bytes(self) -> usize {
match self {
Backend::V4x | Backend::V4 => 64,
Backend::V3GfniCrypto | Backend::V3 => 32,
Backend::V2 | Backend::V1 | Backend::NeonAes | Backend::Neon | Backend::Wasm128 => 16,
Backend::Scalar => 8,
}
}
#[cfg(feature = "std")]
#[must_use]
pub(crate) fn probes_on_host(self) -> bool {
match self {
#[cfg(feature = "avx512")]
Backend::V4x => X64V4xToken::summon().is_some(),
#[cfg(not(feature = "avx512"))]
Backend::V4x => false,
#[cfg(feature = "avx512")]
Backend::V4 => X64V4Token::summon().is_some(),
#[cfg(not(feature = "avx512"))]
Backend::V4 => false,
Backend::V3GfniCrypto => X64V3GfniCryptoToken::summon().is_some(),
Backend::V3 => X64V3Token::summon().is_some(),
Backend::V2 => X64V2Token::summon().is_some(),
Backend::V1 => X64V1Token::summon().is_some(),
Backend::NeonAes => NeonAesToken::summon().is_some(),
Backend::Neon => NeonToken::summon().is_some(),
Backend::Wasm128 => Wasm128Token::summon().is_some(),
Backend::Scalar => ScalarToken::summon().is_some(),
}
}
}
impl fmt::Display for Backend {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.name())
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ParseBackendError;
impl fmt::Display for ParseBackendError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("unknown SIMD backend name")
}
}
impl core::error::Error for ParseBackendError {}
impl FromStr for Backend {
type Err = ParseBackendError;
fn from_str(name: &str) -> Result<Self, Self::Err> {
Backend::from_name(name).ok_or(ParseBackendError)
}
}
#[cfg(test)]
mod tests {
use super::*;
const TIERS_RS_MIRROR: &[(Backend, u32)] = &[
(Backend::V4x, 50),
(Backend::V4, 40),
(Backend::V3GfniCrypto, 37),
(Backend::V3, 30),
(Backend::V2, 20),
(Backend::V1, 10),
(Backend::NeonAes, 30),
(Backend::Neon, 20),
(Backend::Wasm128, 20),
(Backend::Scalar, 0),
];
#[test]
fn all_pins_variant_order_to_tiers_rs() {
let mut mirrored_order = [Backend::Scalar; 10];
for (i, &(backend, _)) in TIERS_RS_MIRROR.iter().enumerate() {
mirrored_order[i] = backend;
}
assert_eq!(Backend::ALL, mirrored_order.as_slice());
}
#[test]
fn order_encodes_capability_within_family() {
assert!(Backend::V4x < Backend::V4);
assert!(Backend::V4 < Backend::V3GfniCrypto);
assert!(Backend::V3GfniCrypto < Backend::V3);
assert!(Backend::V3 < Backend::V2);
assert!(Backend::V2 < Backend::V1);
assert!(Backend::NeonAes < Backend::Neon);
assert!(Backend::Wasm128 < Backend::Scalar);
assert!(Backend::Neon < Backend::Wasm128);
assert!(Backend::V3GfniCrypto < Backend::Scalar);
assert!(Backend::V1 < Backend::NeonAes);
}
#[test]
fn lane_bytes_are_architectural() {
assert_eq!(Backend::V4x.lane_bytes(), 64);
assert_eq!(Backend::V4.lane_bytes(), 64);
assert_eq!(Backend::V3GfniCrypto.lane_bytes(), 32);
assert_eq!(Backend::V3.lane_bytes(), 32);
assert_eq!(Backend::V2.lane_bytes(), 16);
assert_eq!(Backend::V1.lane_bytes(), 16);
assert_eq!(Backend::NeonAes.lane_bytes(), 16);
assert_eq!(Backend::Neon.lane_bytes(), 16);
assert_eq!(Backend::Wasm128.lane_bytes(), 16);
assert_eq!(Backend::Scalar.lane_bytes(), 8);
}
#[test]
fn names_roundtrip() {
for &backend in Backend::ALL {
assert_eq!(Backend::from_name(backend.name()), Some(backend));
assert_eq!(backend.name().parse::<Backend>(), Ok(backend));
}
}
struct FmtBuf {
bytes: [u8; 24],
len: usize,
}
impl FmtBuf {
fn new() -> Self {
FmtBuf {
bytes: [0; 24],
len: 0,
}
}
}
impl core::fmt::Write for FmtBuf {
fn write_str(&mut self, s: &str) -> core::fmt::Result {
for byte in s.bytes() {
self.bytes[self.len] = byte;
self.len += 1;
}
Ok(())
}
}
#[test]
fn display_matches_name() {
for &backend in Backend::ALL {
let mut buf = FmtBuf::new();
use core::fmt::Write;
write!(&mut buf, "{backend}").unwrap();
let rendered = core::str::from_utf8(&buf.bytes[..buf.len]).unwrap();
assert_eq!(rendered, backend.name());
}
}
#[test]
fn unknown_and_historical_names_are_rejected() {
assert_eq!(Backend::from_name("avx2"), None);
assert_eq!(Backend::from_name("gfni"), None);
assert_eq!(Backend::from_name("pmull"), None);
assert_eq!(Backend::from_name("neon_aesx"), None);
assert_eq!("bogus".parse::<Backend>(), Err(ParseBackendError));
}
#[test]
#[cfg(all(feature = "std", target_arch = "x86_64"))]
fn v1_always_summons_on_x86_64() {
assert!(Backend::V1.probes_on_host());
}
#[test]
#[cfg(all(feature = "std", not(feature = "avx512")))]
fn avx512_tiers_do_not_summon_without_avx512() {
assert!(!Backend::V4x.probes_on_host());
assert!(!Backend::V4.probes_on_host());
}
#[test]
#[cfg(all(feature = "std", feature = "avx512"))]
fn avx512_probes_match_archmage_tokens() {
assert_eq!(
Backend::V4x.probes_on_host(),
X64V4xToken::summon().is_some()
);
assert_eq!(Backend::V4.probes_on_host(), X64V4Token::summon().is_some());
}
#[test]
#[cfg(all(feature = "std", target_arch = "aarch64"))]
fn neon_always_summons_on_aarch64() {
assert!(Backend::Neon.probes_on_host());
}
}