#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum SimdBackend {
Scalar,
Sse2,
Avx2,
Neon,
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct HardwareCapabilities {
pub sse2: bool,
pub avx: bool,
pub avx2: bool,
pub avx512f: bool,
pub avx512bw: bool,
pub neon: bool,
pub sve: bool,
pub sme: bool,
}
pub fn hardware_capabilities() -> HardwareCapabilities {
let mut capabilities = HardwareCapabilities::default();
#[cfg(target_arch = "x86_64")]
{
capabilities.sse2 = std::arch::is_x86_feature_detected!("sse2");
capabilities.avx = std::arch::is_x86_feature_detected!("avx");
capabilities.avx2 = std::arch::is_x86_feature_detected!("avx2");
capabilities.avx512f = std::arch::is_x86_feature_detected!("avx512f");
capabilities.avx512bw = std::arch::is_x86_feature_detected!("avx512bw");
}
#[cfg(target_arch = "aarch64")]
{
capabilities.neon = std::arch::is_aarch64_feature_detected!("neon");
capabilities.sve = std::arch::is_aarch64_feature_detected!("sve");
capabilities.sme = detect_sme();
}
capabilities
}
#[cfg(all(
target_arch = "aarch64",
any(target_os = "linux", target_os = "android")
))]
fn detect_sme() -> bool {
use std::ffi::c_ulong;
const AT_HWCAP2: c_ulong = 26;
const HWCAP2_SME: c_ulong = 1 << 23;
unsafe extern "C" {
fn getauxval(kind: c_ulong) -> c_ulong;
}
unsafe { getauxval(AT_HWCAP2) & HWCAP2_SME != 0 }
}
#[cfg(all(
target_arch = "aarch64",
not(any(target_os = "linux", target_os = "android"))
))]
const fn detect_sme() -> bool {
false
}
pub fn simd_backend() -> SimdBackend {
let capabilities = hardware_capabilities();
if capabilities.avx2 {
SimdBackend::Avx2
} else if capabilities.sse2 {
SimdBackend::Sse2
} else if capabilities.neon {
SimdBackend::Neon
} else {
SimdBackend::Scalar
}
}
#[cfg(feature = "adaptive")]
pub(crate) fn is_ascii(bytes: &[u8]) -> bool {
first_non_ascii(bytes) == bytes.len()
}
#[cfg(feature = "adaptive")]
pub(crate) fn plain_varint_prefix(bytes: &[u8]) -> usize {
match simd_backend() {
#[cfg(target_arch = "x86_64")]
SimdBackend::Avx2 => {
unsafe { x86::plain_prefix_avx2(bytes) }
}
#[cfg(target_arch = "x86_64")]
SimdBackend::Sse2 => {
unsafe { x86::plain_prefix_sse2(bytes) }
}
#[cfg(target_arch = "aarch64")]
SimdBackend::Neon => {
unsafe { arm::plain_prefix_neon(bytes) }
}
_ => scalar_plain_prefix(bytes),
}
}
#[cfg(feature = "adaptive")]
fn first_non_ascii(bytes: &[u8]) -> usize {
match simd_backend() {
#[cfg(target_arch = "x86_64")]
SimdBackend::Avx2 => {
unsafe { x86::ascii_prefix_avx2(bytes) }
}
#[cfg(target_arch = "x86_64")]
SimdBackend::Sse2 => {
unsafe { x86::ascii_prefix_sse2(bytes) }
}
#[cfg(target_arch = "aarch64")]
SimdBackend::Neon => {
unsafe { arm::ascii_prefix_neon(bytes) }
}
_ => scalar_ascii_prefix(bytes),
}
}
#[cfg(feature = "adaptive")]
fn scalar_ascii_prefix(bytes: &[u8]) -> usize {
bytes
.iter()
.position(|byte| !byte.is_ascii())
.unwrap_or(bytes.len())
}
#[cfg(feature = "adaptive")]
fn scalar_plain_prefix(bytes: &[u8]) -> usize {
bytes
.iter()
.position(|byte| *byte > 250)
.unwrap_or(bytes.len())
}
#[cfg(all(feature = "adaptive", target_arch = "x86_64"))]
mod x86 {
use std::arch::x86_64::*;
#[target_feature(enable = "avx2")]
pub(super) unsafe fn ascii_prefix_avx2(bytes: &[u8]) -> usize {
let mut offset = 0;
while offset + 32 <= bytes.len() {
let vector = unsafe { _mm256_loadu_si256(bytes.as_ptr().add(offset).cast()) };
let mask = _mm256_movemask_epi8(vector) as u32;
if mask != 0 {
return offset + mask.trailing_zeros() as usize;
}
offset += 32;
}
offset + super::scalar_ascii_prefix(&bytes[offset..])
}
#[target_feature(enable = "sse2")]
pub(super) unsafe fn ascii_prefix_sse2(bytes: &[u8]) -> usize {
let mut offset = 0;
while offset + 16 <= bytes.len() {
let vector = unsafe { _mm_loadu_si128(bytes.as_ptr().add(offset).cast()) };
let mask = _mm_movemask_epi8(vector) as u32;
if mask != 0 {
return offset + mask.trailing_zeros() as usize;
}
offset += 16;
}
offset + super::scalar_ascii_prefix(&bytes[offset..])
}
#[target_feature(enable = "avx2")]
pub(super) unsafe fn plain_prefix_avx2(bytes: &[u8]) -> usize {
let mut offset = 0;
let sign = _mm256_set1_epi8(i8::MIN);
let threshold = _mm256_set1_epi8(122);
while offset + 32 <= bytes.len() {
let vector = unsafe { _mm256_loadu_si256(bytes.as_ptr().add(offset).cast()) };
let unsigned_order = _mm256_xor_si256(vector, sign);
let markers = _mm256_cmpgt_epi8(unsigned_order, threshold);
let mask = _mm256_movemask_epi8(markers) as u32;
if mask != 0 {
return offset + mask.trailing_zeros() as usize;
}
offset += 32;
}
offset + super::scalar_plain_prefix(&bytes[offset..])
}
#[target_feature(enable = "sse2")]
pub(super) unsafe fn plain_prefix_sse2(bytes: &[u8]) -> usize {
let mut offset = 0;
let sign = _mm_set1_epi8(i8::MIN);
let threshold = _mm_set1_epi8(122);
while offset + 16 <= bytes.len() {
let vector = unsafe { _mm_loadu_si128(bytes.as_ptr().add(offset).cast()) };
let unsigned_order = _mm_xor_si128(vector, sign);
let markers = _mm_cmpgt_epi8(unsigned_order, threshold);
let mask = _mm_movemask_epi8(markers) as u32;
if mask != 0 {
return offset + mask.trailing_zeros() as usize;
}
offset += 16;
}
offset + super::scalar_plain_prefix(&bytes[offset..])
}
}
#[cfg(all(feature = "adaptive", target_arch = "aarch64"))]
mod arm {
use std::arch::aarch64::*;
#[target_feature(enable = "neon")]
pub(super) unsafe fn ascii_prefix_neon(bytes: &[u8]) -> usize {
let mut offset = 0;
let threshold = vdupq_n_u8(127);
while offset + 16 <= bytes.len() {
let vector = unsafe { vld1q_u8(bytes.as_ptr().add(offset)) };
let compared = vcgtq_u8(vector, threshold);
let mut lanes = [0u8; 16];
unsafe { vst1q_u8(lanes.as_mut_ptr(), compared) };
if let Some(index) = lanes.iter().position(|lane| *lane != 0) {
return offset + index;
}
offset += 16;
}
offset + super::scalar_ascii_prefix(&bytes[offset..])
}
#[target_feature(enable = "neon")]
pub(super) unsafe fn plain_prefix_neon(bytes: &[u8]) -> usize {
let mut offset = 0;
let threshold = vdupq_n_u8(250);
while offset + 16 <= bytes.len() {
let vector = unsafe { vld1q_u8(bytes.as_ptr().add(offset)) };
let compared = vcgtq_u8(vector, threshold);
let mut lanes = [0u8; 16];
unsafe { vst1q_u8(lanes.as_mut_ptr(), compared) };
if let Some(index) = lanes.iter().position(|lane| *lane != 0) {
return offset + index;
}
offset += 16;
}
offset + super::scalar_plain_prefix(&bytes[offset..])
}
}
#[cfg(all(test, feature = "adaptive"))]
mod tests {
use super::*;
#[test]
fn dispatched_scanners_match_scalar_at_vector_boundaries() {
for length in 0..96 {
let ascii = vec![b'a'; length];
assert_eq!(first_non_ascii(&ascii), scalar_ascii_prefix(&ascii));
assert_eq!(plain_varint_prefix(&ascii), scalar_plain_prefix(&ascii));
for position in 0..length {
let mut non_ascii = ascii.clone();
non_ascii[position] = 0x80;
assert_eq!(first_non_ascii(&non_ascii), position);
let mut marker = ascii.clone();
marker[position] = 251;
assert_eq!(plain_varint_prefix(&marker), position);
}
}
}
#[test]
fn plain_varint_scan_accepts_entire_single_byte_domain() {
let values = (0..=250).map(|value| value as u8).collect::<Vec<_>>();
assert_eq!(plain_varint_prefix(&values), values.len());
for marker in 251..=255 {
let mut input = values.clone();
input.push(marker);
assert_eq!(plain_varint_prefix(&input), values.len());
}
}
}