use core::sync::atomic::{AtomicU8, Ordering};
use crate::block::{Instance, Position};
pub mod scalar;
#[cfg(target_arch = "aarch64")]
pub mod neon;
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
pub mod sse2;
#[cfg(target_arch = "x86_64")]
pub mod avx2;
#[cfg(target_arch = "x86_64")]
pub mod avx512;
#[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
pub mod wasm128;
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash, PartialOrd, Ord)]
#[repr(u8)]
#[non_exhaustive]
pub enum Backend {
Scalar = 0,
Neon = 1,
Sse2 = 2,
Avx2 = 3,
Avx512 = 4,
Wasm128 = 5,
}
pub type FillSegmentFn = unsafe fn(&Instance, Position);
impl Backend {
pub const ALL: &'static [Backend] = &[
Backend::Scalar,
Backend::Neon,
Backend::Sse2,
Backend::Avx2,
Backend::Avx512,
Backend::Wasm128,
];
#[inline]
#[must_use]
pub const fn name(self) -> &'static str {
match self {
Backend::Scalar => "scalar",
Backend::Neon => "neon",
Backend::Sse2 => "sse2",
Backend::Avx2 => "avx2",
Backend::Avx512 => "avx512",
Backend::Wasm128 => "wasm128",
}
}
#[inline]
#[must_use]
pub fn is_available(self) -> bool {
match self {
Backend::Scalar => true,
Backend::Neon => have_neon(),
Backend::Sse2 => have_sse2(),
Backend::Avx2 => have_avx2(),
Backend::Avx512 => have_avx512f(),
Backend::Wasm128 => have_wasm_simd128(),
}
}
#[inline]
const fn to_u8(self) -> u8 {
self as u8
}
#[inline]
const fn from_u8(value: u8) -> Backend {
match value {
1 => Backend::Neon,
2 => Backend::Sse2,
3 => Backend::Avx2,
4 => Backend::Avx512,
5 => Backend::Wasm128,
_ => Backend::Scalar,
}
}
}
impl core::fmt::Display for Backend {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(self.name())
}
}
#[cfg(all(feature = "std", target_arch = "x86_64"))]
#[inline]
fn have_avx512f() -> bool {
std::arch::is_x86_feature_detected!("avx512f")
}
#[cfg(not(all(feature = "std", target_arch = "x86_64")))]
#[inline]
fn have_avx512f() -> bool {
cfg!(all(target_arch = "x86_64", target_feature = "avx512f"))
}
#[cfg(all(feature = "std", target_arch = "x86_64"))]
#[inline]
fn have_avx2() -> bool {
std::arch::is_x86_feature_detected!("avx2")
}
#[cfg(not(all(feature = "std", target_arch = "x86_64")))]
#[inline]
fn have_avx2() -> bool {
cfg!(all(target_arch = "x86_64", target_feature = "avx2"))
}
#[cfg(all(feature = "std", any(target_arch = "x86", target_arch = "x86_64")))]
#[inline]
fn have_sse2() -> bool {
std::arch::is_x86_feature_detected!("sse2")
}
#[cfg(not(all(feature = "std", any(target_arch = "x86", target_arch = "x86_64"))))]
#[inline]
fn have_sse2() -> bool {
cfg!(all(
any(target_arch = "x86", target_arch = "x86_64"),
target_feature = "sse2"
))
}
#[cfg(all(feature = "std", target_arch = "aarch64"))]
#[inline]
fn have_neon() -> bool {
#[cfg(any(target_vendor = "apple", target_os = "windows"))]
{
true
}
#[cfg(not(any(target_vendor = "apple", target_os = "windows")))]
{
std::arch::is_aarch64_feature_detected!("neon")
}
}
#[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
#[inline]
fn have_wasm_simd128() -> bool {
true
}
#[cfg(not(all(target_arch = "wasm32", target_feature = "simd128")))]
#[inline]
fn have_wasm_simd128() -> bool {
false
}
#[cfg(not(all(feature = "std", target_arch = "aarch64")))]
#[inline]
fn have_neon() -> bool {
cfg!(all(target_arch = "aarch64", target_feature = "neon"))
}
#[cfg(all(
feature = "std",
target_arch = "aarch64",
not(miri),
not(debug_assertions),
not(any(target_vendor = "apple", target_os = "windows"))
))]
fn neon_wins_here() -> bool {
use crate::block::{Instance, Position};
use crate::params::{Algorithm, Memory, Params, TagLen, Version};
use std::time::Instant;
const M_COST: u32 = 1024;
const REPS: usize = 6;
let params = match Params::builder()
.memory(Memory::kib(u64::from(M_COST)))
.passes(1)
.lanes(1)
.tag_len(TagLen::bytes(32))
.build()
{
Ok(params) => params,
Err(_) => return true, };
let blocks = params.memory_layout().0 as usize;
let mut arena = match crate::memory::Arena::new(blocks) {
Ok(arena) => arena,
Err(_) => return true, };
for (i, block) in arena.as_mut_slice().iter_mut().enumerate() {
for (j, w) in block.0.iter_mut().enumerate() {
*w = 0x9E37_79B9_7F4A_7C15u64.wrapping_mul((i * 128 + j) as u64 + 1);
}
}
let mut one_pass = |backend: Backend| -> u128 {
let fill = fill_segment_fn(backend);
let instance = unsafe {
Instance::new(
arena.as_mut_ptr(),
blocks,
Algorithm::Argon2id,
Version::V0x13,
¶ms,
)
};
let t0 = Instant::now();
for slice in 0..crate::params::SYNC_POINTS {
unsafe { fill(&instance, Position::new(0, 0, slice, 0)) };
}
t0.elapsed().as_nanos()
};
let mut scalar_best = u128::MAX;
let mut neon_best = u128::MAX;
for _ in 0..REPS {
scalar_best = scalar_best.min(one_pass(Backend::Scalar));
neon_best = neon_best.min(one_pass(Backend::Neon));
}
core::hint::black_box(arena.as_ptr());
neon_best < scalar_best
}
#[cfg(not(all(
feature = "std",
target_arch = "aarch64",
not(miri),
not(debug_assertions),
not(any(target_vendor = "apple", target_os = "windows"))
)))]
#[inline]
fn neon_wins_here() -> bool {
true
}
const UNINIT: u8 = 0xFF;
static CACHED_BACKEND: AtomicU8 = AtomicU8::new(UNINIT);
#[must_use]
pub fn detect() -> Backend {
if cfg!(miri) {
Backend::Scalar
} else if have_avx512f() {
Backend::Avx512
} else if have_avx2() {
Backend::Avx2
} else if have_sse2() {
Backend::Sse2
} else if have_neon() && neon_wins_here() {
Backend::Neon
} else if have_wasm_simd128() {
Backend::Wasm128
} else {
Backend::Scalar
}
}
#[cold]
#[inline(never)]
fn detect_and_cache() -> Backend {
let detected = detect();
CACHED_BACKEND.store(detected.to_u8(), Ordering::Relaxed);
detected
}
#[inline]
#[must_use]
pub fn backend() -> Backend {
let cached = CACHED_BACKEND.load(Ordering::Relaxed);
if cached == UNINIT {
detect_and_cache()
} else {
Backend::from_u8(cached)
}
}
#[must_use]
pub fn fill_segment_fn(backend: Backend) -> FillSegmentFn {
match backend {
Backend::Scalar => scalar::fill_segment,
#[cfg(target_arch = "aarch64")]
Backend::Neon => neon::fill_segment,
#[cfg(not(target_arch = "aarch64"))]
Backend::Neon => scalar::fill_segment,
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
Backend::Sse2 => sse2::fill_segment,
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
Backend::Sse2 => scalar::fill_segment,
#[cfg(target_arch = "x86_64")]
Backend::Avx2 => avx2::fill_segment,
#[cfg(not(target_arch = "x86_64"))]
Backend::Avx2 => scalar::fill_segment,
#[cfg(target_arch = "x86_64")]
Backend::Avx512 => avx512::fill_segment,
#[cfg(not(target_arch = "x86_64"))]
Backend::Avx512 => scalar::fill_segment,
#[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
Backend::Wasm128 => wasm128::fill_segment,
#[cfg(not(all(target_arch = "wasm32", target_feature = "simd128")))]
Backend::Wasm128 => scalar::fill_segment,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn backend_u8_round_trip() {
for &b in Backend::ALL {
assert_eq!(Backend::from_u8(b.to_u8()), b);
}
assert_eq!(Backend::from_u8(UNINIT), Backend::Scalar);
assert_eq!(Backend::from_u8(200), Backend::Scalar);
}
#[test]
fn cache_agrees_with_detect() {
let first = backend();
assert_eq!(first, detect());
assert_eq!(backend(), first);
assert_ne!(CACHED_BACKEND.load(Ordering::Relaxed), UNINIT);
}
#[test]
fn detected_backend_is_available() {
assert!(detect().is_available());
assert!(Backend::Scalar.is_available());
}
#[test]
fn detection_respects_the_architecture() {
if cfg!(target_arch = "aarch64") {
assert!(!have_sse2());
assert!(!have_avx2());
assert!(!have_avx512f());
if cfg!(target_vendor = "apple") {
assert_eq!(detect(), Backend::Neon);
} else {
assert!(matches!(detect(), Backend::Neon | Backend::Scalar));
}
}
if cfg!(target_arch = "x86_64") {
assert!(have_sse2());
assert!(!have_neon());
assert!(matches!(
detect(),
Backend::Sse2 | Backend::Avx2 | Backend::Avx512
));
}
if cfg!(target_arch = "wasm32") {
assert!(!have_sse2());
assert!(!have_avx2());
assert!(!have_avx512f());
assert!(!have_neon());
if cfg!(target_feature = "simd128") {
assert_eq!(detect(), Backend::Wasm128);
} else {
assert_eq!(detect(), Backend::Scalar);
}
}
}
#[test]
fn every_backend_resolves_to_a_function() {
for &b in Backend::ALL {
let f = fill_segment_fn(b);
let scalar = fill_segment_fn(Backend::Scalar);
if b == Backend::Scalar {
assert!(core::ptr::fn_addr_eq(f, scalar));
}
}
}
}