use core::sync::atomic::{AtomicU8, Ordering};
#[cfg(target_arch = "aarch64")]
mod neon;
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
mod x86;
#[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
mod wasm128;
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u8)]
pub enum Base64Backend {
Scalar = 0,
Neon = 1,
Ssse3 = 2,
Avx2 = 3,
Wasm128 = 4,
}
impl Base64Backend {
#[cfg(target_arch = "aarch64")]
pub const ALL: &'static [Base64Backend] =
&[Base64Backend::Scalar, Base64Backend::Neon];
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
pub const ALL: &'static [Base64Backend] = &[
Base64Backend::Scalar,
Base64Backend::Ssse3,
Base64Backend::Avx2,
];
#[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
pub const ALL: &'static [Base64Backend] =
&[Base64Backend::Scalar, Base64Backend::Wasm128];
#[cfg(not(any(
target_arch = "aarch64",
target_arch = "x86",
target_arch = "x86_64",
all(target_arch = "wasm32", target_feature = "simd128")
)))]
pub const ALL: &'static [Base64Backend] = &[Base64Backend::Scalar];
#[must_use]
pub const fn name(self) -> &'static str {
match self {
Base64Backend::Scalar => "scalar",
Base64Backend::Neon => "neon",
Base64Backend::Ssse3 => "ssse3",
Base64Backend::Avx2 => "avx2",
Base64Backend::Wasm128 => "wasm128",
}
}
#[must_use]
pub fn is_available(self) -> bool {
match self {
Base64Backend::Scalar => true,
Base64Backend::Neon => have_neon(),
Base64Backend::Ssse3 => have_ssse3(),
Base64Backend::Avx2 => have_avx2() && have_ssse3(),
Base64Backend::Wasm128 => have_wasm_simd128(),
}
}
#[inline]
const fn from_u8(value: u8) -> Base64Backend {
match value {
1 => Base64Backend::Neon,
2 => Base64Backend::Ssse3,
3 => Base64Backend::Avx2,
4 => Base64Backend::Wasm128,
_ => Base64Backend::Scalar,
}
}
}
impl core::fmt::Display for Base64Backend {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(self.name())
}
}
#[cfg(all(feature = "std", any(target_arch = "x86", target_arch = "x86_64")))]
#[inline]
fn have_ssse3() -> bool {
std::arch::is_x86_feature_detected!("ssse3")
}
#[cfg(not(all(feature = "std", any(target_arch = "x86", target_arch = "x86_64"))))]
#[inline]
fn have_ssse3() -> bool {
cfg!(all(
any(target_arch = "x86", target_arch = "x86_64"),
target_feature = "ssse3"
))
}
#[cfg(all(feature = "std", any(target_arch = "x86", target_arch = "x86_64")))]
#[inline]
fn have_avx2() -> bool {
std::arch::is_x86_feature_detected!("avx2")
}
#[cfg(not(all(feature = "std", any(target_arch = "x86", target_arch = "x86_64"))))]
#[inline]
fn have_avx2() -> bool {
cfg!(all(
any(target_arch = "x86", target_arch = "x86_64"),
target_feature = "avx2"
))
}
#[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")
}
}
#[inline]
fn have_wasm_simd128() -> bool {
cfg!(all(target_arch = "wasm32", target_feature = "simd128"))
}
#[cfg(not(all(feature = "std", target_arch = "aarch64")))]
#[inline]
fn have_neon() -> bool {
cfg!(all(target_arch = "aarch64", target_feature = "neon"))
}
const UNINIT: u8 = u8::MAX;
static CACHED_BACKEND: AtomicU8 = AtomicU8::new(UNINIT);
#[must_use]
pub fn detect_base64_backend() -> Base64Backend {
if cfg!(miri) {
Base64Backend::Scalar
} else if have_avx2() && have_ssse3() {
Base64Backend::Avx2
} else if have_ssse3() {
Base64Backend::Ssse3
} else if have_neon() {
Base64Backend::Neon
} else if have_wasm_simd128() {
Base64Backend::Wasm128
} else {
Base64Backend::Scalar
}
}
#[cold]
#[inline(never)]
fn detect_and_cache() -> Base64Backend {
let detected = detect_base64_backend();
CACHED_BACKEND.store(detected as u8, Ordering::Relaxed);
detected
}
#[inline]
#[must_use]
pub fn base64_backend() -> Base64Backend {
let cached = CACHED_BACKEND.load(Ordering::Relaxed);
if cached == UNINIT {
detect_and_cache()
} else {
Base64Backend::from_u8(cached)
}
}
#[cfg(target_arch = "aarch64")]
pub const MIN_ENCODE_LEN: usize = 24;
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
pub const MIN_ENCODE_LEN: usize = 16;
#[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
pub const MIN_ENCODE_LEN: usize = 16;
#[cfg(not(any(
target_arch = "aarch64",
target_arch = "x86",
target_arch = "x86_64",
all(target_arch = "wasm32", target_feature = "simd128")
)))]
pub const MIN_ENCODE_LEN: usize = usize::MAX;
#[cfg(target_arch = "aarch64")]
pub const MIN_DECODE_LEN: usize = 32;
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
pub const MIN_DECODE_LEN: usize = 16;
#[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
pub const MIN_DECODE_LEN: usize = 16;
#[cfg(not(any(
target_arch = "aarch64",
target_arch = "x86",
target_arch = "x86_64",
all(target_arch = "wasm32", target_feature = "simd128")
)))]
pub const MIN_DECODE_LEN: usize = usize::MAX;
#[inline]
pub unsafe fn encode_prefix(
backend: Base64Backend,
dst: *mut u8,
src: &[u8],
) -> (usize, usize) {
#[cfg(not(any(
target_arch = "aarch64",
target_arch = "x86",
target_arch = "x86_64",
all(target_arch = "wasm32", target_feature = "simd128")
)))]
let _ = (dst, src);
match backend {
Base64Backend::Scalar => (0, 0),
#[cfg(target_arch = "aarch64")]
Base64Backend::Neon => unsafe { neon::encode(dst, src.as_ptr(), src.len()) },
#[cfg(not(target_arch = "aarch64"))]
Base64Backend::Neon => (0, 0),
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
Base64Backend::Ssse3 => unsafe { x86::encode_ssse3(dst, src.as_ptr(), src.len()) },
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
Base64Backend::Ssse3 => (0, 0),
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
Base64Backend::Avx2 => unsafe { x86::encode_avx2(dst, src.as_ptr(), src.len()) },
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
Base64Backend::Avx2 => (0, 0),
#[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
Base64Backend::Wasm128 => unsafe { wasm128::encode(dst, src.as_ptr(), src.len()) },
#[cfg(not(all(target_arch = "wasm32", target_feature = "simd128")))]
Base64Backend::Wasm128 => (0, 0),
}
}
#[inline]
pub unsafe fn decode_prefix(
backend: Base64Backend,
dst: *mut u8,
dst_len: usize,
src: &[u8],
) -> (usize, usize) {
#[cfg(not(any(
target_arch = "aarch64",
target_arch = "x86",
target_arch = "x86_64",
all(target_arch = "wasm32", target_feature = "simd128")
)))]
let _ = (dst, dst_len, src);
match backend {
Base64Backend::Scalar => (0, 0),
#[cfg(target_arch = "aarch64")]
Base64Backend::Neon => unsafe { neon::decode(dst, dst_len, src.as_ptr(), src.len()) },
#[cfg(not(target_arch = "aarch64"))]
Base64Backend::Neon => (0, 0),
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
Base64Backend::Ssse3 => unsafe { x86::decode_ssse3(dst, dst_len, src.as_ptr(), src.len()) },
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
Base64Backend::Ssse3 => (0, 0),
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
Base64Backend::Avx2 => unsafe { x86::decode_avx2(dst, dst_len, src.as_ptr(), src.len()) },
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
Base64Backend::Avx2 => (0, 0),
#[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
Base64Backend::Wasm128 => unsafe {
wasm128::decode(dst, dst_len, src.as_ptr(), src.len())
},
#[cfg(not(all(target_arch = "wasm32", target_feature = "simd128")))]
Base64Backend::Wasm128 => (0, 0),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn detection_is_cached_and_executable() {
let detected = detect_base64_backend();
assert!(detected.is_available());
assert_eq!(base64_backend(), detected);
assert_eq!(base64_backend(), detected);
}
#[test]
fn backend_discriminants_round_trip() {
for &backend in Base64Backend::ALL {
assert_eq!(Base64Backend::from_u8(backend as u8), backend);
}
assert_eq!(Base64Backend::from_u8(UNINIT), Base64Backend::Scalar);
assert_eq!(Base64Backend::from_u8(200), Base64Backend::Scalar);
}
}