#[must_use]
#[inline]
pub fn masks<const N: usize>(block: &[u8; 64], needles: [u8; N]) -> [u64; N] {
#[cfg(target_arch = "x86_64")]
{
sse2(block, needles)
}
#[cfg(not(target_arch = "x86_64"))]
{
portable(block, needles)
}
}
#[cfg(target_arch = "x86_64")]
#[inline]
#[allow(unsafe_code)]
fn sse2<const N: usize>(block: &[u8; 64], needles: [u8; N]) -> [u64; N] {
use std::arch::x86_64::{
__m128i, _mm_cmpeq_epi8, _mm_loadu_si128, _mm_movemask_epi8, _mm_set1_epi8,
};
unsafe {
let base = block.as_ptr().cast::<__m128i>();
let lanes = [
_mm_loadu_si128(base),
_mm_loadu_si128(base.add(1)),
_mm_loadu_si128(base.add(2)),
_mm_loadu_si128(base.add(3)),
];
needles.map(|needle| {
#[allow(clippy::cast_possible_wrap)]
let splat = _mm_set1_epi8(needle as i8);
let mut mask = 0u64;
for (at, lane) in lanes.iter().enumerate() {
#[allow(clippy::cast_sign_loss)]
let bits = _mm_movemask_epi8(_mm_cmpeq_epi8(*lane, splat)) as u32 as u64;
mask |= bits << (16 * at);
}
mask
})
}
}
#[cfg_attr(target_arch = "x86_64", allow(dead_code))]
#[inline]
fn portable<const N: usize>(block: &[u8; 64], needles: [u8; N]) -> [u64; N] {
needles.map(|needle| {
let mut hits = [0u8; 64];
for (hit, &byte) in hits.iter_mut().zip(block) {
*hit = u8::from(byte == needle);
}
let mut mask = 0u64;
for (at, eight) in hits.chunks_exact(8).enumerate() {
let word = u64::from_le_bytes(eight.try_into().expect("eight bytes"));
mask |= (word.wrapping_mul(0x0102_0408_1020_4080) >> 56) << (8 * at);
}
mask
})
}
#[cfg(test)]
mod tests {
use super::*;
fn by_hand(block: &[u8; 64], needle: u8) -> u64 {
let mut mask = 0;
for (at, &byte) in block.iter().enumerate() {
if byte == needle {
mask |= 1 << at;
}
}
mask
}
#[test]
fn every_mask_has_a_bit_for_exactly_the_bytes_that_match() {
let mut seed = 0x9e37_79b9_7f4a_7c15u64;
for round in 0..2000 {
let mut block = [0u8; 64];
for byte in &mut block {
seed ^= seed << 13;
seed ^= seed >> 7;
seed ^= seed << 17;
*byte = [b',', b'"', b'\n', b'\r', b'a', 0x80, 0xff, 0][(seed % 8) as usize];
}
let needles = [b',', b'"', b'\n', b'\r', 0xff, 0, b'z'];
let want = needles.map(|needle| by_hand(&block, needle));
assert_eq!(masks(&block, needles), want, "round {round}");
assert_eq!(portable(&block, needles), want, "round {round}");
}
}
}