use fearless_simd::{Simd, SimdBase, SimdMask, u8x16, u8x32, u8x64};
const SCALAR_PREFIX_WORDS: usize = 2;
#[inline(always)]
pub(super) fn count<S: Simd>(simd: S, src: &[u8], a: usize, b: usize, limit: usize) -> usize {
let current = &src[a..limit];
let Some(earlier) = src.get(b..b + current.len()) else {
return common_bytes(&src[b..], current);
};
common_run(simd, current, earlier)
}
#[inline(always)]
pub(super) fn common_run<S: Simd>(simd: S, x: &[u8], y: &[u8]) -> usize {
let limit = x.len().min(y.len());
let (x, y) = (&x[..limit], &y[..limit]);
if let (Some(x_word), Some(y_word)) = (x.first_chunk::<8>(), y.first_chunk::<8>()) {
let difference = u64::from_le_bytes(*x_word) ^ u64::from_le_bytes(*y_word);
if difference != 0 {
return (difference.trailing_zeros() >> 3) as usize;
}
return 8 + long_run(simd, &x[8..], &y[8..]);
}
common_bytes(x, y)
}
#[fearless_simd_macros::simd]
#[inline(never)]
fn long_run<S: Simd>(simd: S, x: &[u8], y: &[u8]) -> usize {
let limit = x.len();
let prefix = limit.min(8 * SCALAR_PREFIX_WORDS) & !7;
let mut matched = common_words(&x[..prefix], &y[..prefix]);
if matched < prefix {
return matched;
}
let stride = native_stride::<S>();
let whole_vectors = (limit - matched) - (limit - matched) % stride;
let vectored = common_vectors(simd, &x[matched..], &y[matched..]);
matched += vectored;
if vectored < whole_vectors {
return matched;
}
let whole_words = (limit - matched) & !7;
let words = common_words(
&x[matched..matched + whole_words],
&y[matched..matched + whole_words],
);
matched += words;
if words < whole_words || matched == limit {
return matched;
}
if let (Some(x_last), Some(y_last)) = (x.last_chunk::<8>(), y.last_chunk::<8>()) {
let difference = u64::from_le_bytes(*x_last) ^ u64::from_le_bytes(*y_last);
return if difference == 0 {
limit
} else {
limit - 8 + (difference.trailing_zeros() >> 3) as usize
};
}
matched + common_bytes(&x[matched..], &y[matched..])
}
#[inline(always)]
fn common_bytes(x: &[u8], y: &[u8]) -> usize {
x.iter().zip(y).take_while(|(a, b)| a == b).count()
}
#[inline(always)]
fn common_words(x: &[u8], y: &[u8]) -> usize {
let (x_words, _) = x.as_chunks::<8>();
let (y_words, _) = y.as_chunks::<8>();
let mut matched = 0usize;
for (x_word, y_word) in x_words.iter().zip(y_words) {
let difference = u64::from_le_bytes(*x_word) ^ u64::from_le_bytes(*y_word);
if difference != 0 {
return matched + (difference.trailing_zeros() >> 3) as usize;
}
matched += 8;
}
matched
}
macro_rules! vector_run {
($name:ident, $vector:ident, $lanes:literal) => {
#[inline(always)]
fn $name<S: Simd>(simd: S, x: &[u8], y: &[u8]) -> usize {
let (x_vectors, _) = x.as_chunks::<$lanes>();
let (y_vectors, _) = y.as_chunks::<$lanes>();
let mut matched = 0usize;
for (x_lanes, y_lanes) in x_vectors.iter().zip(y_vectors) {
let equal = $vector::<S>::load_array_ref(simd, x_lanes)
.simd_eq($vector::<S>::load_array_ref(simd, y_lanes));
if !equal.all_true() {
return matched + equal.to_bitmask().trailing_ones() as usize;
}
matched += $lanes;
}
matched
}
};
}
vector_run!(common_vectors_16, u8x16, 16);
vector_run!(common_vectors_32, u8x32, 32);
vector_run!(common_vectors_64, u8x64, 64);
#[inline(always)]
const fn native_stride<S: Simd>() -> usize {
match <S::u8s as SimdBase<S>>::LEN {
16 => 16,
32 => 32,
64 => 64,
_ => 1,
}
}
#[inline(always)]
fn common_vectors<S: Simd>(simd: S, x: &[u8], y: &[u8]) -> usize {
match <S::u8s as SimdBase<S>>::LEN {
16 => common_vectors_16(simd, x, y),
32 => common_vectors_32(simd, x, y),
64 => common_vectors_64(simd, x, y),
_ => common_bytes(x, y),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Backend;
use alloc::vec::Vec;
fn reference(src: &[u8], a: usize, b: usize, limit: usize) -> usize {
let mut n = 0;
while a + n < limit && src[a + n] == src[b + n] {
n += 1;
}
n
}
#[test]
fn counts_equal_the_bytewise_reference_on_every_backend() {
let mut src: Vec<u8> = (0..400u32).map(|i| (i % 7) as u8).collect();
src.extend((0..400u32).map(|i| (i % 7) as u8));
for backend in Backend::available() {
for diff in [
0usize, 1, 7, 8, 9, 15, 16, 17, 31, 32, 33, 63, 64, 65, 130, 399,
] {
let mut data = src.clone();
data[400 + diff] ^= 0x80;
for limit in [400, 401, 407, 408, 409, 424, 433, 464, 528, 799, 800] {
let expected = reference(&data, 400, 0, limit);
let got = fearless_simd::dispatch!(backend.0, simd => count(simd, &data, 400, 0, limit));
assert_eq!(got, expected, "{backend} diff {diff} limit {limit}");
}
}
}
}
#[test]
fn a_short_earlier_window_stops_the_count() {
let src = [5u8; 20];
for backend in Backend::available() {
let got = fearless_simd::dispatch!(backend.0, simd => count(simd, &src, 2, 10, 20));
assert_eq!(got, 10, "{backend}");
let got =
fearless_simd::dispatch!(backend.0, simd => common_run(simd, &src[..3], &src));
assert_eq!(got, 3, "{backend}");
}
}
}