const MAX_WIDE: usize = 128;
pub struct Skip {
pub resident: u8,
escape: Escape,
leaves: Vec<u8>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Instrument {
Few = 0,
Wide = 1,
}
enum Escape {
Never,
Few([u8; 3], usize),
Wide { lo: [u8; 16], hi: [u8; 16] },
}
impl Skip {
#[must_use]
pub fn of(rows: &[[u8; 16]; 256], block: u8) -> Option<Self> {
let mut escape = Vec::new();
for b in 0..=255u8 {
if rows[usize::from(b)][usize::from(block)] != block {
escape.push(b);
}
}
Some(Self {
resident: block,
escape: Escape::of(&escape)?,
leaves: escape,
})
}
#[must_use]
pub fn leaves(&self) -> &[u8] {
&self.leaves
}
#[must_use]
pub fn instrument(&self) -> Instrument {
match self.escape {
Escape::Wide { .. } => Instrument::Wide,
Escape::Never | Escape::Few(..) => Instrument::Few,
}
}
#[must_use]
pub fn find(&self, hay: &[u8]) -> Option<usize> {
match &self.escape {
Escape::Never => None,
Escape::Few(set, 1) => memchr::memchr(set[0], hay),
Escape::Few(set, 2) => memchr::memchr2(set[0], set[1], hay),
Escape::Few(set, _) => memchr::memchr3(set[0], set[1], set[2], hay),
Escape::Wide { lo, hi } => wide::find(lo, hi, hay),
}
}
#[must_use]
pub fn find_scalar(&self, hay: &[u8]) -> Option<usize> {
match &self.escape {
Escape::Never => None,
Escape::Few(set, n) => hay.iter().position(|b| set[..*n].contains(b)),
Escape::Wide { lo, hi } => hay
.iter()
.position(|&b| lo[usize::from(b & 0xF)] & hi[usize::from(b >> 4)] != 0),
}
}
}
impl Escape {
fn of(escape: &[u8]) -> Option<Self> {
match *escape {
[] => Some(Self::Never),
[a] => Some(Self::Few([a, a, a], 1)),
[a, b] => Some(Self::Few([a, b, b], 2)),
[a, b, c] => Some(Self::Few([a, b, c], 3)),
_ if escape.len() > MAX_WIDE || escape.iter().any(|&b| b >= 0x80) => None,
_ => {
let mut lo = [0u8; 16];
let hi: [u8; 16] = std::array::from_fn(|h| if h < 8 { 1 << h } else { 0 });
for &b in escape {
lo[usize::from(b & 0xF)] |= 1 << (b >> 4);
}
Some(Self::Wide { lo, hi })
},
}
}
}
pub(crate) mod wide {
use crate::arch;
pub fn find(lo: &[u8; 16], hi: &[u8; 16], hay: &[u8]) -> Option<usize> {
#[cfg(target_arch = "aarch64")]
return unsafe { arch::neon::classify(lo, hi, hay) };
#[cfg(target_arch = "x86_64")]
if std::arch::is_x86_feature_detected!("ssse3") {
unsafe { arch::ssse3::classify(lo, hi, hay) }
} else {
scalar(lo, hi, hay)
}
#[cfg(not(any(target_arch = "aarch64", target_arch = "x86_64")))]
scalar(lo, hi, hay)
}
pub fn member(lo: &[u8; 16], hi: &[u8; 16], b: u8) -> bool {
lo[usize::from(b & 0xF)] & hi[usize::from(b >> 4)] != 0
}
pub fn scalar(lo: &[u8; 16], hi: &[u8; 16], hay: &[u8]) -> Option<usize> {
hay.iter().position(|&b| member(lo, hi, b))
}
pub(crate) fn tail(lo: &[u8; 16], hi: &[u8; 16], hay: &[u8]) -> Option<usize> {
let done = hay.len() - hay.len() % arch::STEP;
scalar(lo, hi, &hay[done..]).map(|i| done + i)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn shapes() -> Vec<Vec<u8>> {
vec![
vec![],
vec![b'W'],
vec![b'a', b'b'],
vec![b'a', b'b', b'g'],
(b'0'..=b'9').collect(),
(b'A'..=b'Z').collect(),
(b'0'..=b'9')
.chain(b'a'..=b'f')
.chain(b'A'..=b'F')
.collect(),
vec![0x00, 0x01, 0x7F, b'\n'],
(0..0x80u8).collect(),
]
}
fn skip_over(escape: &[u8]) -> Option<Skip> {
let mut rows = [[0u8; 16]; 256];
for &b in escape {
rows[usize::from(b)][0] = 1;
}
Skip::of(&rows, 0)
}
#[test]
fn every_instrument_agrees_with_the_definition() {
let mut hay = vec![0u8; 512];
for (i, slot) in hay.iter_mut().enumerate() {
*slot = match i % 17 {
0 => b'A' + (i % 26) as u8,
1 => b'0' + (i % 10) as u8,
7 => 0x80 | (i % 128) as u8,
_ => b'a' + (i % 26) as u8,
};
}
for escape in shapes() {
let Some(skip) = skip_over(&escape) else {
continue;
};
for start in 0..64 {
for len in [0usize, 1, 15, 16, 17, 31, 33, 64, 129, 255] {
let end = (start + len).min(hay.len());
let slice = &hay[start.min(hay.len())..end];
assert_eq!(
skip.find(slice),
skip.find_scalar(slice),
"escape={escape:?} start={start} len={len}"
);
}
}
}
}
#[test]
fn the_classifier_admits_exactly_the_set() {
for escape in shapes() {
let Some(skip) = skip_over(&escape) else {
continue;
};
for b in 0..=255u8 {
let found = skip.find(&[b]) == Some(0);
assert_eq!(
found,
escape.contains(&b),
"byte {b:#04x} misclassified for escape={escape:?}"
);
}
}
}
#[test]
fn a_non_ascii_escape_set_is_declined_rather_than_approximated() {
assert!(skip_over(&[b'a', b'b', b'c', 0xC3]).is_none());
assert!(skip_over(&(0..=255u8).collect::<Vec<_>>()).is_none());
}
#[test]
fn a_block_nothing_leaves_never_finds_an_escape() {
let skip = skip_over(&[]).expect("an absorbing block still yields a skip");
assert_eq!(skip.find(&[0u8; 300]), None);
assert_eq!(skip.find_scalar(&[0u8; 300]), None);
}
fn rolls(seed: u64) -> impl FnMut() -> u64 {
let mut s = seed | 1;
move || {
s ^= s << 13;
s ^= s >> 7;
s ^= s << 17;
s
}
}
#[test]
fn the_classifier_is_exact_on_sets_nobody_hand_picked() {
let mut next = rolls(0x5E1F_C0DE_1234_5678);
for trial in 0..2048 {
let width = 1 + (trial % 96);
let mut escape = Vec::with_capacity(width);
while escape.len() < width {
let b = (next() % 128) as u8; if !escape.contains(&b) {
escape.push(b);
}
}
let Some(skip) = skip_over(&escape) else {
continue; };
for b in 0..=255u8 {
assert_eq!(
skip.find(&[b]) == Some(0),
escape.contains(&b),
"trial {trial}: byte {b:#04x} misclassified for {escape:?}"
);
}
}
}
#[test]
fn an_escape_is_found_at_every_offset_of_every_length() {
for escape in shapes() {
let Some(skip) = skip_over(&escape) else {
continue;
};
let Some(&needle) = escape.first() else {
continue; };
let Some(filler) = (0..=255u8).find(|b| !escape.contains(b)) else {
continue;
};
for len in 1..=72usize {
for at in 0..len {
let mut hay = vec![filler; len];
hay[at] = needle;
assert_eq!(
skip.find(&hay),
Some(at),
"escape={escape:?} len={len} planted at {at}"
);
assert_eq!(skip.find(&hay), skip.find_scalar(&hay));
}
}
}
}
}