#![allow(unsafe_code)]
use super::{
scalar,
set::{ByteSet, Stop},
};
pub(crate) trait Vector: Copy {
const BYTES: usize;
const BITS_PER_BYTE: u32;
unsafe fn load(ptr: *const u8) -> Self;
unsafe fn splat(byte: u8) -> Self;
unsafe fn eq(self, other: Self) -> Self;
unsafe fn and(self, other: Self) -> Self;
unsafe fn or(self, other: Self) -> Self;
unsafe fn and_not(self, other: Self) -> Self;
unsafe fn mask(self) -> u64;
}
pub(crate) trait Table: Vector {
unsafe fn table(entries: &[u8; 16]) -> Self;
unsafe fn lookup_low(self, table: Self) -> Self;
unsafe fn lookup_high(self, table: Self) -> Self;
unsafe fn test_mask(self, mask: Self) -> u64;
}
const UNROLL: usize = 4;
const DASH_LOOKAHEAD: usize = 2;
const FOLD_LOOKAHEAD: usize = 1;
#[inline(always)]
fn first<V: Vector>(mask: u64) -> usize {
(mask.trailing_zeros() / V::BITS_PER_BYTE) as usize
}
#[inline(always)]
pub(crate) unsafe fn dash_line<V: Vector>(tail: &[u8]) -> Option<usize> {
let ptr = tail.as_ptr();
let mut offset = 0;
unsafe {
let newline = V::splat(b'\n');
let dash = V::splat(b'-');
while offset + UNROLL * V::BYTES + DASH_LOOKAHEAD <= tail.len() {
let at = ptr.add(offset + 1);
let dashes = [
V::load(at).eq(dash),
V::load(at.add(V::BYTES)).eq(dash),
V::load(at.add(2 * V::BYTES)).eq(dash),
V::load(at.add(3 * V::BYTES)).eq(dash),
];
let [d0, d1, d2, d3] = dashes;
if d0.or(d1).or(d2.or(d3)).mask() != 0 {
for (index, first_dash) in dashes.into_iter().enumerate() {
let block = offset + index * V::BYTES;
let hits = V::load(ptr.add(block))
.eq(newline)
.and(first_dash)
.and(V::load(ptr.add(block + 2)).eq(dash));
let mask = hits.mask();
if mask != 0 {
return Some(block + first::<V>(mask));
}
}
}
offset += UNROLL * V::BYTES;
}
while offset + V::BYTES + DASH_LOOKAHEAD <= tail.len() {
let at = ptr.add(offset);
let hits = V::load(at)
.eq(newline)
.and(V::load(at.add(1)).eq(dash))
.and(V::load(at.add(2)).eq(dash));
let mask = hits.mask();
if mask != 0 {
return Some(offset + first::<V>(mask));
}
offset += V::BYTES;
}
}
scalar::dash_line(tail, offset)
}
#[inline(always)]
pub(crate) unsafe fn field_end<V: Vector>(tail: &[u8]) -> Option<usize> {
let ptr = tail.as_ptr();
let mut offset = 0;
unsafe {
let newline = V::splat(b'\n');
let space = V::splat(b' ');
let tab = V::splat(b'\t');
while offset + UNROLL * V::BYTES + FOLD_LOOKAHEAD <= tail.len() {
let at = ptr.add(offset);
let lines = [
V::load(at).eq(newline),
V::load(at.add(V::BYTES)).eq(newline),
V::load(at.add(2 * V::BYTES)).eq(newline),
V::load(at.add(3 * V::BYTES)).eq(newline),
];
let [l0, l1, l2, l3] = lines;
if l0.or(l1).or(l2.or(l3)).mask() != 0 {
for (index, lines) in lines.into_iter().enumerate() {
let block = offset + index * V::BYTES;
if lines.mask() == 0 {
continue;
}
let next = V::load(ptr.add(block + 1));
let mask = lines.and_not(next.eq(space).or(next.eq(tab))).mask();
if mask != 0 {
return Some(block + first::<V>(mask));
}
}
}
offset += UNROLL * V::BYTES;
}
while offset + V::BYTES + FOLD_LOOKAHEAD <= tail.len() {
let at = ptr.add(offset);
let lines = V::load(at).eq(newline);
if lines.mask() != 0 {
let next = V::load(at.add(1));
let mask = lines.and_not(next.eq(space).or(next.eq(tab))).mask();
if mask != 0 {
return Some(offset + first::<V>(mask));
}
}
offset += V::BYTES;
}
}
scalar::field_end(tail, offset)
}
impl ByteSet {
#[inline(always)]
pub(super) unsafe fn simd_first_in<V: Vector>(
&self,
hay: &[u8],
from: usize,
end: usize,
mask_at: impl Fn(*const u8) -> u64,
) -> Option<usize> {
let end = end.min(hay.len());
if from >= end {
return None;
}
let ptr = hay.as_ptr();
let first = |offset: usize, mask: u64| {
let hit = offset + (mask.trailing_zeros() / V::BITS_PER_BYTE) as usize;
(hit < end).then_some(hit)
};
let mut offset = from;
while offset + V::BYTES <= hay.len() {
let mask = mask_at(unsafe { ptr.add(offset) });
if mask != 0 {
return first(offset, mask);
}
offset += V::BYTES;
if offset >= end {
return None;
}
}
match hay.len().checked_sub(V::BYTES) {
Some(base) => {
let skipped = (offset - base) as u32 * V::BITS_PER_BYTE;
let mask = mask_at(unsafe { ptr.add(base) }) >> skipped;
if mask != 0 { first(offset, mask) } else { None }
}
None => self.first_in(hay, offset, end),
}
}
#[inline(always)]
pub(super) unsafe fn simd_stop_in<V: Vector>(
&self,
hay: &[u8],
from: usize,
end: usize,
masks_at: impl Fn(*const u8) -> (u64, u64),
) -> Stop {
let end = end.min(hay.len());
let mut marked = false;
let mut offset = from;
while offset < end {
let base = if offset + V::BYTES <= hay.len() {
offset
} else {
match hay.len().checked_sub(V::BYTES) {
Some(base) => base,
None => {
let rest = self.stop_in(hay, offset, end);
return Stop {
at: rest.at,
marked: marked || rest.marked,
};
}
}
};
let skipped = (offset - base) as u32 * V::BITS_PER_BYTE;
let (stops, marks) = masks_at(unsafe { hay.as_ptr().add(base) });
let valid = low_bits(end - offset, V::BITS_PER_BYTE);
let stops = (stops >> skipped) & valid;
let marks = (marks >> skipped) & valid;
if stops != 0 {
let first = stops.trailing_zeros() / V::BITS_PER_BYTE;
return Stop {
at: Some(offset + first as usize),
marked: marked || marks & low_bits(first as usize, V::BITS_PER_BYTE) != 0,
};
}
marked |= marks != 0;
offset = base + V::BYTES;
}
Stop { at: None, marked }
}
}
#[inline(always)]
pub(super) unsafe fn buckets<V: Table>(at: *const u8, low: V, high: V) -> V {
unsafe {
let bytes = V::load(at);
bytes.lookup_low(low).and(bytes.lookup_high(high))
}
}
#[inline(always)]
fn low_bits(bytes: usize, bits_per_byte: u32) -> u64 {
match u32::try_from(bytes).map(|bytes| bytes.saturating_mul(bits_per_byte)) {
Ok(bits) if bits < u64::BITS => (1 << bits) - 1,
_ => u64::MAX,
}
}