use crate::parse::LoopInstruction;
use core::arch::x86_64::*;
const VECTOR_SIZE: usize = std::mem::size_of::<__m128i>();
const fn compute_lookup() -> [u8; 16] {
let mut lookup = [0u8; 16];
lookup[(b'\n' & 0x0f) as usize] |= 1 << (b'\n' >> 4);
lookup[(b'\r' & 0x0f) as usize] |= 1 << (b'\r' >> 4);
lookup[(b'*' & 0x0f) as usize] |= 1 << (b'*' >> 4);
lookup[(b'_' & 0x0f) as usize] |= 1 << (b'_' >> 4);
lookup[(b'~' & 0x0f) as usize] |= 1 << (b'~' >> 4);
lookup[(b'|' & 0x0f) as usize] |= 1 << (b'|' >> 4);
lookup[(b'&' & 0x0f) as usize] |= 1 << (b'&' >> 4);
lookup[(b'\\' & 0x0f) as usize] |= 1 << (b'\\' >> 4);
lookup[(b'[' & 0x0f) as usize] |= 1 << (b'[' >> 4);
lookup[(b']' & 0x0f) as usize] |= 1 << (b']' >> 4);
lookup[(b'<' & 0x0f) as usize] |= 1 << (b'<' >> 4);
lookup[(b'!' & 0x0f) as usize] |= 1 << (b'!' >> 4);
lookup[(b'`' & 0x0f) as usize] |= 1 << (b'`' >> 4);
lookup
}
#[target_feature(enable = "ssse3")]
#[inline]
unsafe fn compute_mask(bytes: &[u8], ix: usize) -> i32 {
debug_assert!(bytes.len() >= ix + VECTOR_SIZE);
let lookup = compute_lookup();
let bitmap = _mm_loadu_si128(lookup.as_ptr() as *const __m128i);
let bitmask_lookup =
_mm_setr_epi8(1, 2, 4, 8, 16, 32, 64, -128, -1, -1, -1, -1, -1, -1, -1, -1);
let raw_ptr = bytes.as_ptr().add(ix) as *const __m128i;
let input = _mm_loadu_si128(raw_ptr);
let bitset = _mm_shuffle_epi8(bitmap, input);
let higher_nibbles = _mm_and_si128(_mm_srli_epi16(input, 4), _mm_set1_epi8(0x0f));
let bitmask = _mm_shuffle_epi8(bitmask_lookup, higher_nibbles);
let tmp = _mm_and_si128(bitset, bitmask);
let result = _mm_cmpeq_epi8(tmp, bitmask);
_mm_movemask_epi8(result)
}
pub(crate) fn iterate_special_bytes<F, T>(
bytes: &[u8],
ix: usize,
callback: F,
) -> (usize, Option<T>)
where
F: FnMut(usize, u8) -> LoopInstruction<Option<T>>,
{
if is_x86_feature_detected!("ssse3") && bytes.len() >= VECTOR_SIZE {
unsafe { simd_iterate_special_bytes(bytes, ix, callback) }
} else {
crate::parse::scalar_iterate_special_bytes(bytes, ix, callback)
}
}
unsafe fn process_mask<F, T>(
mut mask: i32,
bytes: &[u8],
mut offset: usize,
callback: &mut F,
) -> Result<usize, (usize, Option<T>)>
where
F: FnMut(usize, u8) -> LoopInstruction<Option<T>>,
{
while mask != 0 {
let mask_ix = mask.trailing_zeros() as usize;
offset += mask_ix;
match callback(offset, *bytes.get_unchecked(offset)) {
LoopInstruction::ContinueAndSkip(skip) => {
offset += skip + 1;
mask >>= skip + 1 + mask_ix;
}
LoopInstruction::BreakAtWith(ix, val) => return Err((ix, val)),
}
}
Ok(offset)
}
#[target_feature(enable = "ssse3")]
unsafe fn simd_iterate_special_bytes<F, T>(
bytes: &[u8],
mut ix: usize,
mut callback: F,
) -> (usize, Option<T>)
where
F: FnMut(usize, u8) -> LoopInstruction<Option<T>>,
{
debug_assert!(bytes.len() >= VECTOR_SIZE);
let upperbound = bytes.len() - VECTOR_SIZE;
while ix < upperbound {
let mask = compute_mask(bytes, ix);
let block_start = ix;
ix = match process_mask(mask, bytes, ix, &mut callback) {
Ok(ix) => std::cmp::max(ix, VECTOR_SIZE + block_start),
Err((end_ix, val)) => return (end_ix, val),
};
}
if bytes.len() > ix {
let mask = compute_mask(bytes, upperbound) >> ix - upperbound;
if let Err((end_ix, val)) = process_mask(mask, bytes, ix, &mut callback) {
return (end_ix, val);
}
}
(bytes.len(), None)
}
#[cfg(test)]
mod simd_test {
use super::{iterate_special_bytes, LoopInstruction};
fn check_expected_indices(bytes: &[u8], expected: &[usize], skip: usize) {
let mut indices = vec![];
iterate_special_bytes::<_, i32>(bytes, 0, |ix, _byte_ty| {
indices.push(ix);
LoopInstruction::ContinueAndSkip(skip)
});
assert_eq!(&indices[..], expected);
}
#[test]
fn simple_no_match() {
check_expected_indices("abcdef0123456789".as_bytes(), &[], 0);
}
#[test]
fn simple_match() {
check_expected_indices("*bcd&f0123456789".as_bytes(), &[0, 4], 0);
}
#[test]
fn single_open_fish() {
check_expected_indices("<".as_bytes(), &[0], 0);
}
#[test]
fn long_match() {
check_expected_indices("0123456789abcde~*bcd&f0".as_bytes(), &[15, 16, 20], 0);
}
#[test]
fn border_skip() {
check_expected_indices("0123456789abcde~~~~d&f0".as_bytes(), &[15, 20], 3);
}
#[test]
fn exhaustive_search() {
let chars = [
b'\n', b'\r', b'*', b'_', b'~', b'|', b'&', b'\\', b'[', b']', b'<', b'!', b'`',
];
for &c in &chars {
for i in 0u8..=255 {
if !chars.contains(&i) {
let mut buf = [i; 18];
buf[3] = c;
buf[6] = c;
check_expected_indices(&buf[..], &[3, 6], 0);
}
}
}
}
}