#[inline]
pub(crate) fn find_byte(needle: u8, haystack: &[u8]) -> Option<usize> {
#[cfg(feature = "simd")]
{
memchr::memchr(needle, haystack)
}
#[cfg(not(feature = "simd"))]
{
portable(needle, haystack)
}
}
#[inline]
pub(crate) fn find(needle: &[u8], haystack: &[u8]) -> Option<usize> {
let Some((&first, rest)) = needle.split_first() else {
return Some(0);
};
let mut from = 0;
loop {
let at = from + find_byte(first, haystack.get(from..)?)?;
if haystack
.get(at + 1..)
.is_some_and(|after| after.starts_with(rest))
{
return Some(at);
}
from = at + 1;
}
}
#[cfg(any(not(feature = "simd"), test))]
fn portable(needle: u8, haystack: &[u8]) -> Option<usize> {
const LO: u64 = 0x0101_0101_0101_0101;
const HI: u64 = 0x8080_8080_8080_8080;
let splat = LO * u64::from(needle);
let flags = |w: u64| {
let x = w ^ splat;
x.wrapping_sub(LO) & !x & HI
};
let word = |b: Option<&[u8; 8]>| b.map_or(splat ^ HI, |b| u64::from_le_bytes(*b));
let (chunks, tail) = haystack.as_chunks::<16>();
let mut offset = 0;
for chunk in chunks {
let (a, b) = (word(chunk.first_chunk()), word(chunk.last_chunk()));
let (fa, fb) = (flags(a), flags(b));
if fa | fb != 0 {
let bit = if fa != 0 {
fa.trailing_zeros()
} else {
64 + fb.trailing_zeros()
};
return Some(offset + bit as usize / 8);
}
offset += 16;
}
tail.iter().position(|&b| b == needle).map(|i| offset + i)
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::vec::Vec;
fn haystacks() -> Vec<Vec<u8>> {
let mut out = Vec::new();
for len in [
0usize, 1, 2, 7, 15, 16, 17, 31, 32, 33, 63, 64, 65, 100, 257, 1000,
] {
for at in [None, Some(0), Some(len / 2), Some(len.saturating_sub(1))] {
let mut h: Vec<u8> = (0..len).map(|i| b'a' + (i % 23) as u8).collect();
if let Some(at) = at {
if let Some(b) = h.get_mut(at) {
*b = b'<';
}
if let Some(b) = h.get_mut(at + 9) {
*b = b'<';
}
}
out.push(h);
}
}
out
}
fn naive(needle: u8, haystack: &[u8]) -> Option<usize> {
haystack.iter().position(|&b| b == needle)
}
#[test]
fn the_portable_search_finds_the_first_match() {
for h in haystacks() {
for start in 0..h.len().min(40) {
let h = &h[start..];
for needle in [b'<', b'a', b'b', b'&', 0, 0x80, 0xff] {
assert_eq!(portable(needle, h), naive(needle, h), "{needle} in {h:?}");
}
}
}
for needle in 0..=255u8 {
for other in 0..=255u8 {
for lane in 0..17 {
let mut h = [other; 17];
h[lane] = needle;
assert_eq!(portable(needle, &h), naive(needle, &h));
}
}
}
}
#[cfg(feature = "simd")]
#[test]
fn memchr_and_the_portable_search_agree() {
for h in haystacks() {
for start in 0..h.len().min(40) {
let h = &h[start..];
for needle in [b'<', b'a', b'&', b'"', b'\'', 0, 0xff] {
assert_eq!(memchr::memchr(needle, h), portable(needle, h));
assert_eq!(find_byte(needle, h), portable(needle, h));
}
}
}
for needle in 0..=255u8 {
for other in 0..=255u8 {
let mut h = [other; 40];
h[33] = needle;
assert_eq!(find_byte(needle, &h), portable(needle, &h));
}
}
}
#[test]
fn find_finds_the_first_full_match() {
assert_eq!(find(b"-->", b"a - -- --> -->"), Some(7));
assert_eq!(find(b"-->", b"--"), None);
assert_eq!(find(b"-->", b"-->"), Some(0));
assert_eq!(find(b"-->", b"--->"), Some(1));
assert_eq!(find(b"]]>", b"]]]>"), Some(1));
assert_eq!(find(b"", b"x"), Some(0));
assert_eq!(find(b"x", b""), None);
}
}