1221 lines
48 KiB
Rust
1221 lines
48 KiB
Rust
/*!
|
|
Generic crate-internal routines for the `memchr` family of functions.
|
|
*/
|
|
|
|
// What follows is a vector algorithm generic over the specific vector
|
|
// type to detect the position of one, two or three needles in a haystack.
|
|
// From what I know, this is a "classic" algorithm, although I don't
|
|
// believe it has been published in any peer reviewed journal. I believe
|
|
// it can be found in places like glibc and Go's standard library. It
|
|
// appears to be well known and is elaborated on in more detail here:
|
|
// https://gms.tf/stdfind-and-memchr-optimizations.html
|
|
//
|
|
// While the routine below is fairly long and perhaps intimidating, the basic
|
|
// idea is actually very simple and can be expressed straight-forwardly in
|
|
// pseudo code. The pseudo code below is written for 128 bit vectors, but the
|
|
// actual code below works for anything that implements the Vector trait.
|
|
//
|
|
// needle = (n1 << 15) | (n1 << 14) | ... | (n1 << 1) | n1
|
|
// // Note: shift amount is in bytes
|
|
//
|
|
// while i <= haystack.len() - 16:
|
|
// // A 16 byte vector. Each byte in chunk corresponds to a byte in
|
|
// // the haystack.
|
|
// chunk = haystack[i:i+16]
|
|
// // Compare bytes in needle with bytes in chunk. The result is a 16
|
|
// // byte chunk where each byte is 0xFF if the corresponding bytes
|
|
// // in needle and chunk were equal, or 0x00 otherwise.
|
|
// eqs = cmpeq(needle, chunk)
|
|
// // Return a 32 bit integer where the most significant 16 bits
|
|
// // are always 0 and the lower 16 bits correspond to whether the
|
|
// // most significant bit in the correspond byte in `eqs` is set.
|
|
// // In other words, `mask as u16` has bit i set if and only if
|
|
// // needle[i] == chunk[i].
|
|
// mask = movemask(eqs)
|
|
//
|
|
// // Mask is 0 if there is no match, and non-zero otherwise.
|
|
// if mask != 0:
|
|
// // trailing_zeros tells us the position of the least significant
|
|
// // bit that is set.
|
|
// return i + trailing_zeros(mask)
|
|
//
|
|
// // haystack length may not be a multiple of 16, so search the rest.
|
|
// while i < haystack.len():
|
|
// if haystack[i] == n1:
|
|
// return i
|
|
//
|
|
// // No match found.
|
|
// return NULL
|
|
//
|
|
// In fact, we could loosely translate the above code to Rust line-for-line
|
|
// and it would be a pretty fast algorithm. But, we pull out all the stops
|
|
// to go as fast as possible:
|
|
//
|
|
// 1. We use aligned loads. That is, we do some finagling to make sure our
|
|
// primary loop not only proceeds in increments of 16 bytes, but that
|
|
// the address of haystack's pointer that we dereference is aligned to
|
|
// 16 bytes. 16 is a magic number here because it is the size of SSE2
|
|
// 128-bit vector. (For the AVX2 algorithm, 32 is the magic number.)
|
|
// Therefore, to get aligned loads, our pointer's address must be evenly
|
|
// divisible by 16.
|
|
// 2. Our primary loop proceeds 64 bytes at a time instead of 16. It's
|
|
// kind of like loop unrolling, but we combine the equality comparisons
|
|
// using a vector OR such that we only need to extract a single mask to
|
|
// determine whether a match exists or not. If so, then we do some
|
|
// book-keeping to determine the precise location but otherwise mush on.
|
|
// 3. We use our "chunk" comparison routine in as many places as possible,
|
|
// even if it means using unaligned loads. In particular, if haystack
|
|
// starts with an unaligned address, then we do an unaligned load to
|
|
// search the first 16 bytes. We then start our primary loop at the
|
|
// smallest subsequent aligned address, which will actually overlap with
|
|
// previously searched bytes. But we're OK with that. We do a similar
|
|
// dance at the end of our primary loop. Finally, to avoid a
|
|
// byte-at-a-time loop at the end, we do a final 16 byte unaligned load
|
|
// that may overlap with a previous load. This is OK because it converts
|
|
// a loop into a small number of very fast vector instructions. The overlap
|
|
// is OK because we know the place where the overlap occurs does not
|
|
// contain a match.
|
|
//
|
|
// And that's pretty all there is to it. Note that since the below is
|
|
// generic and since it's meant to be inlined into routines with a
|
|
// `#[target_feature(enable = "...")]` annotation, we must mark all routines as
|
|
// both unsafe and `#[inline(always)]`.
|
|
//
|
|
// The fact that the code below is generic does somewhat inhibit us. For
|
|
// example, I've noticed that introducing an unlineable `#[cold]` function to
|
|
// handle the match case in the loop generates tighter assembly, but there is
|
|
// no way to do this in the generic code below because the generic code doesn't
|
|
// know what `target_feature` annotation to apply to the unlineable function.
|
|
// We could make such functions part of the `Vector` trait, but we instead live
|
|
// with the slightly sub-optimal codegen for now since it doesn't seem to have
|
|
// a noticeable perf difference.
|
|
|
|
use crate::{
|
|
ext::Pointer,
|
|
vector::{MoveMask, Vector},
|
|
};
|
|
|
|
/// Finds all occurrences of a single byte in a haystack.
|
|
#[derive(Clone, Copy, Debug)]
|
|
pub(crate) struct One<V> {
|
|
s1: u8,
|
|
v1: V,
|
|
}
|
|
|
|
impl<V: Vector> One<V> {
|
|
/// The number of bytes we examine per each iteration of our search loop.
|
|
const LOOP_SIZE: usize = 4 * V::BYTES;
|
|
|
|
/// Create a new searcher that finds occurrences of the byte given.
|
|
#[inline(always)]
|
|
pub(crate) unsafe fn new(needle: u8) -> One<V> {
|
|
One { s1: needle, v1: V::splat(needle) }
|
|
}
|
|
|
|
/// Returns the needle given to `One::new`.
|
|
#[inline(always)]
|
|
pub(crate) fn needle1(&self) -> u8 {
|
|
self.s1
|
|
}
|
|
|
|
/// Return a pointer to the first occurrence of the needle in the given
|
|
/// haystack. If no such occurrence exists, then `None` is returned.
|
|
///
|
|
/// When a match is found, the pointer returned is guaranteed to be
|
|
/// `>= start` and `< end`.
|
|
///
|
|
/// # Safety
|
|
///
|
|
/// * It must be the case that `start < end` and that the distance between
|
|
/// them is at least equal to `V::BYTES`. That is, it must always be valid
|
|
/// to do at least an unaligned load of `V` at `start`.
|
|
/// * Both `start` and `end` must be valid for reads.
|
|
/// * Both `start` and `end` must point to an initialized value.
|
|
/// * Both `start` and `end` must point to the same allocated object and
|
|
/// must either be in bounds or at most one byte past the end of the
|
|
/// allocated object.
|
|
/// * Both `start` and `end` must be _derived from_ a pointer to the same
|
|
/// object.
|
|
/// * The distance between `start` and `end` must not overflow `isize`.
|
|
/// * The distance being in bounds must not rely on "wrapping around" the
|
|
/// address space.
|
|
#[inline(always)]
|
|
pub(crate) unsafe fn find_raw(
|
|
&self,
|
|
start: *const u8,
|
|
end: *const u8,
|
|
) -> Option<*const u8> {
|
|
// If we want to support vectors bigger than 256 bits, we probably
|
|
// need to move up to using a u64 for the masks used below. Currently
|
|
// they are 32 bits, which means we're SOL for vectors that need masks
|
|
// bigger than 32 bits. Overall unclear until there's a use case.
|
|
debug_assert!(V::BYTES <= 32, "vector cannot be bigger than 32 bytes");
|
|
|
|
let topos = V::Mask::first_offset;
|
|
let len = end.distance(start);
|
|
debug_assert!(
|
|
len >= V::BYTES,
|
|
"haystack has length {}, but must be at least {}",
|
|
len,
|
|
V::BYTES
|
|
);
|
|
|
|
// Search a possibly unaligned chunk at `start`. This covers any part
|
|
// of the haystack prior to where aligned loads can start.
|
|
if let Some(cur) = self.search_chunk(start, topos) {
|
|
return Some(cur);
|
|
}
|
|
// Set `cur` to the first V-aligned pointer greater than `start`.
|
|
let mut cur = start.add(V::BYTES - (start.as_usize() & V::ALIGN));
|
|
debug_assert!(cur > start && end.sub(V::BYTES) >= start);
|
|
if len >= Self::LOOP_SIZE {
|
|
while cur <= end.sub(Self::LOOP_SIZE) {
|
|
debug_assert_eq!(0, cur.as_usize() % V::BYTES);
|
|
|
|
let a = V::load_aligned(cur);
|
|
let b = V::load_aligned(cur.add(1 * V::BYTES));
|
|
let c = V::load_aligned(cur.add(2 * V::BYTES));
|
|
let d = V::load_aligned(cur.add(3 * V::BYTES));
|
|
let eqa = self.v1.cmpeq(a);
|
|
let eqb = self.v1.cmpeq(b);
|
|
let eqc = self.v1.cmpeq(c);
|
|
let eqd = self.v1.cmpeq(d);
|
|
let or1 = eqa.or(eqb);
|
|
let or2 = eqc.or(eqd);
|
|
let or3 = or1.or(or2);
|
|
if or3.movemask_will_have_non_zero() {
|
|
let mask = eqa.movemask();
|
|
if mask.has_non_zero() {
|
|
return Some(cur.add(topos(mask)));
|
|
}
|
|
|
|
let mask = eqb.movemask();
|
|
if mask.has_non_zero() {
|
|
return Some(cur.add(1 * V::BYTES).add(topos(mask)));
|
|
}
|
|
|
|
let mask = eqc.movemask();
|
|
if mask.has_non_zero() {
|
|
return Some(cur.add(2 * V::BYTES).add(topos(mask)));
|
|
}
|
|
|
|
let mask = eqd.movemask();
|
|
debug_assert!(mask.has_non_zero());
|
|
return Some(cur.add(3 * V::BYTES).add(topos(mask)));
|
|
}
|
|
cur = cur.add(Self::LOOP_SIZE);
|
|
}
|
|
}
|
|
// Handle any leftovers after the aligned loop above. We use unaligned
|
|
// loads here, but I believe we are guaranteed that they are aligned
|
|
// since `cur` is aligned.
|
|
while cur <= end.sub(V::BYTES) {
|
|
debug_assert!(end.distance(cur) >= V::BYTES);
|
|
if let Some(cur) = self.search_chunk(cur, topos) {
|
|
return Some(cur);
|
|
}
|
|
cur = cur.add(V::BYTES);
|
|
}
|
|
// Finally handle any remaining bytes less than the size of V. In this
|
|
// case, our pointer may indeed be unaligned and the load may overlap
|
|
// with the previous one. But that's okay since we know the previous
|
|
// load didn't lead to a match (otherwise we wouldn't be here).
|
|
if cur < end {
|
|
debug_assert!(end.distance(cur) < V::BYTES);
|
|
cur = cur.sub(V::BYTES - end.distance(cur));
|
|
debug_assert_eq!(end.distance(cur), V::BYTES);
|
|
return self.search_chunk(cur, topos);
|
|
}
|
|
None
|
|
}
|
|
|
|
/// Return a pointer to the last occurrence of the needle in the given
|
|
/// haystack. If no such occurrence exists, then `None` is returned.
|
|
///
|
|
/// When a match is found, the pointer returned is guaranteed to be
|
|
/// `>= start` and `< end`.
|
|
///
|
|
/// # Safety
|
|
///
|
|
/// * It must be the case that `start < end` and that the distance between
|
|
/// them is at least equal to `V::BYTES`. That is, it must always be valid
|
|
/// to do at least an unaligned load of `V` at `start`.
|
|
/// * Both `start` and `end` must be valid for reads.
|
|
/// * Both `start` and `end` must point to an initialized value.
|
|
/// * Both `start` and `end` must point to the same allocated object and
|
|
/// must either be in bounds or at most one byte past the end of the
|
|
/// allocated object.
|
|
/// * Both `start` and `end` must be _derived from_ a pointer to the same
|
|
/// object.
|
|
/// * The distance between `start` and `end` must not overflow `isize`.
|
|
/// * The distance being in bounds must not rely on "wrapping around" the
|
|
/// address space.
|
|
#[inline(always)]
|
|
pub(crate) unsafe fn rfind_raw(
|
|
&self,
|
|
start: *const u8,
|
|
end: *const u8,
|
|
) -> Option<*const u8> {
|
|
// If we want to support vectors bigger than 256 bits, we probably
|
|
// need to move up to using a u64 for the masks used below. Currently
|
|
// they are 32 bits, which means we're SOL for vectors that need masks
|
|
// bigger than 32 bits. Overall unclear until there's a use case.
|
|
debug_assert!(V::BYTES <= 32, "vector cannot be bigger than 32 bytes");
|
|
|
|
let topos = V::Mask::last_offset;
|
|
let len = end.distance(start);
|
|
debug_assert!(
|
|
len >= V::BYTES,
|
|
"haystack has length {}, but must be at least {}",
|
|
len,
|
|
V::BYTES
|
|
);
|
|
|
|
if let Some(cur) = self.search_chunk(end.sub(V::BYTES), topos) {
|
|
return Some(cur);
|
|
}
|
|
let mut cur = end.sub(end.as_usize() & V::ALIGN);
|
|
debug_assert!(start <= cur && cur <= end);
|
|
if len >= Self::LOOP_SIZE {
|
|
while cur >= start.add(Self::LOOP_SIZE) {
|
|
debug_assert_eq!(0, cur.as_usize() % V::BYTES);
|
|
|
|
cur = cur.sub(Self::LOOP_SIZE);
|
|
let a = V::load_aligned(cur);
|
|
let b = V::load_aligned(cur.add(1 * V::BYTES));
|
|
let c = V::load_aligned(cur.add(2 * V::BYTES));
|
|
let d = V::load_aligned(cur.add(3 * V::BYTES));
|
|
let eqa = self.v1.cmpeq(a);
|
|
let eqb = self.v1.cmpeq(b);
|
|
let eqc = self.v1.cmpeq(c);
|
|
let eqd = self.v1.cmpeq(d);
|
|
let or1 = eqa.or(eqb);
|
|
let or2 = eqc.or(eqd);
|
|
let or3 = or1.or(or2);
|
|
if or3.movemask_will_have_non_zero() {
|
|
let mask = eqd.movemask();
|
|
if mask.has_non_zero() {
|
|
return Some(cur.add(3 * V::BYTES).add(topos(mask)));
|
|
}
|
|
|
|
let mask = eqc.movemask();
|
|
if mask.has_non_zero() {
|
|
return Some(cur.add(2 * V::BYTES).add(topos(mask)));
|
|
}
|
|
|
|
let mask = eqb.movemask();
|
|
if mask.has_non_zero() {
|
|
return Some(cur.add(1 * V::BYTES).add(topos(mask)));
|
|
}
|
|
|
|
let mask = eqa.movemask();
|
|
debug_assert!(mask.has_non_zero());
|
|
return Some(cur.add(topos(mask)));
|
|
}
|
|
}
|
|
}
|
|
while cur >= start.add(V::BYTES) {
|
|
debug_assert!(cur.distance(start) >= V::BYTES);
|
|
cur = cur.sub(V::BYTES);
|
|
if let Some(cur) = self.search_chunk(cur, topos) {
|
|
return Some(cur);
|
|
}
|
|
}
|
|
if cur > start {
|
|
debug_assert!(cur.distance(start) < V::BYTES);
|
|
return self.search_chunk(start, topos);
|
|
}
|
|
None
|
|
}
|
|
|
|
/// Return a count of all matching bytes in the given haystack.
|
|
///
|
|
/// # Safety
|
|
///
|
|
/// * It must be the case that `start < end` and that the distance between
|
|
/// them is at least equal to `V::BYTES`. That is, it must always be valid
|
|
/// to do at least an unaligned load of `V` at `start`.
|
|
/// * Both `start` and `end` must be valid for reads.
|
|
/// * Both `start` and `end` must point to an initialized value.
|
|
/// * Both `start` and `end` must point to the same allocated object and
|
|
/// must either be in bounds or at most one byte past the end of the
|
|
/// allocated object.
|
|
/// * Both `start` and `end` must be _derived from_ a pointer to the same
|
|
/// object.
|
|
/// * The distance between `start` and `end` must not overflow `isize`.
|
|
/// * The distance being in bounds must not rely on "wrapping around" the
|
|
/// address space.
|
|
#[inline(always)]
|
|
pub(crate) unsafe fn count_raw(
|
|
&self,
|
|
start: *const u8,
|
|
end: *const u8,
|
|
) -> usize {
|
|
debug_assert!(V::BYTES <= 32, "vector cannot be bigger than 32 bytes");
|
|
|
|
let confirm = |b| b == self.needle1();
|
|
let len = end.distance(start);
|
|
debug_assert!(
|
|
len >= V::BYTES,
|
|
"haystack has length {}, but must be at least {}",
|
|
len,
|
|
V::BYTES
|
|
);
|
|
|
|
// Set `cur` to the first V-aligned pointer greater than `start`.
|
|
let mut cur = start.add(V::BYTES - (start.as_usize() & V::ALIGN));
|
|
// Count any matching bytes before we start our aligned loop.
|
|
let mut count = count_byte_by_byte(start, cur, confirm);
|
|
debug_assert!(cur > start && end.sub(V::BYTES) >= start);
|
|
if len >= Self::LOOP_SIZE {
|
|
while cur <= end.sub(Self::LOOP_SIZE) {
|
|
debug_assert_eq!(0, cur.as_usize() % V::BYTES);
|
|
|
|
let a = V::load_aligned(cur);
|
|
let b = V::load_aligned(cur.add(1 * V::BYTES));
|
|
let c = V::load_aligned(cur.add(2 * V::BYTES));
|
|
let d = V::load_aligned(cur.add(3 * V::BYTES));
|
|
let eqa = self.v1.cmpeq(a);
|
|
let eqb = self.v1.cmpeq(b);
|
|
let eqc = self.v1.cmpeq(c);
|
|
let eqd = self.v1.cmpeq(d);
|
|
count += eqa.movemask().count_ones();
|
|
count += eqb.movemask().count_ones();
|
|
count += eqc.movemask().count_ones();
|
|
count += eqd.movemask().count_ones();
|
|
cur = cur.add(Self::LOOP_SIZE);
|
|
}
|
|
}
|
|
// Handle any leftovers after the aligned loop above. We use unaligned
|
|
// loads here, but I believe we are guaranteed that they are aligned
|
|
// since `cur` is aligned.
|
|
while cur <= end.sub(V::BYTES) {
|
|
debug_assert!(end.distance(cur) >= V::BYTES);
|
|
let chunk = V::load_unaligned(cur);
|
|
count += self.v1.cmpeq(chunk).movemask().count_ones();
|
|
cur = cur.add(V::BYTES);
|
|
}
|
|
// And finally count any leftovers that weren't caught above.
|
|
count += count_byte_by_byte(cur, end, confirm);
|
|
count
|
|
}
|
|
|
|
/// Search `V::BYTES` starting at `cur` via an unaligned load.
|
|
///
|
|
/// `mask_to_offset` should be a function that converts a `movemask` to
|
|
/// an offset such that `cur.add(offset)` corresponds to a pointer to the
|
|
/// match location if one is found. Generally it is expected to use either
|
|
/// `mask_to_first_offset` or `mask_to_last_offset`, depending on whether
|
|
/// one is implementing a forward or reverse search, respectively.
|
|
///
|
|
/// # Safety
|
|
///
|
|
/// `cur` must be a valid pointer and it must be valid to do an unaligned
|
|
/// load of size `V::BYTES` at `cur`.
|
|
#[inline(always)]
|
|
unsafe fn search_chunk(
|
|
&self,
|
|
cur: *const u8,
|
|
mask_to_offset: impl Fn(V::Mask) -> usize,
|
|
) -> Option<*const u8> {
|
|
let chunk = V::load_unaligned(cur);
|
|
let mask = self.v1.cmpeq(chunk).movemask();
|
|
if mask.has_non_zero() {
|
|
Some(cur.add(mask_to_offset(mask)))
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Finds all occurrences of two bytes in a haystack.
|
|
///
|
|
/// That is, this reports matches of one of two possible bytes. For example,
|
|
/// searching for `a` or `b` in `afoobar` would report matches at offsets `0`,
|
|
/// `4` and `5`.
|
|
#[derive(Clone, Copy, Debug)]
|
|
pub(crate) struct Two<V> {
|
|
s1: u8,
|
|
s2: u8,
|
|
v1: V,
|
|
v2: V,
|
|
}
|
|
|
|
impl<V: Vector> Two<V> {
|
|
/// The number of bytes we examine per each iteration of our search loop.
|
|
const LOOP_SIZE: usize = 2 * V::BYTES;
|
|
|
|
/// Create a new searcher that finds occurrences of the byte given.
|
|
#[inline(always)]
|
|
pub(crate) unsafe fn new(needle1: u8, needle2: u8) -> Two<V> {
|
|
Two {
|
|
s1: needle1,
|
|
s2: needle2,
|
|
v1: V::splat(needle1),
|
|
v2: V::splat(needle2),
|
|
}
|
|
}
|
|
|
|
/// Returns the first needle given to `Two::new`.
|
|
#[inline(always)]
|
|
pub(crate) fn needle1(&self) -> u8 {
|
|
self.s1
|
|
}
|
|
|
|
/// Returns the second needle given to `Two::new`.
|
|
#[inline(always)]
|
|
pub(crate) fn needle2(&self) -> u8 {
|
|
self.s2
|
|
}
|
|
|
|
/// Return a pointer to the first occurrence of one of the needles in the
|
|
/// given haystack. If no such occurrence exists, then `None` is returned.
|
|
///
|
|
/// When a match is found, the pointer returned is guaranteed to be
|
|
/// `>= start` and `< end`.
|
|
///
|
|
/// # Safety
|
|
///
|
|
/// * It must be the case that `start < end` and that the distance between
|
|
/// them is at least equal to `V::BYTES`. That is, it must always be valid
|
|
/// to do at least an unaligned load of `V` at `start`.
|
|
/// * Both `start` and `end` must be valid for reads.
|
|
/// * Both `start` and `end` must point to an initialized value.
|
|
/// * Both `start` and `end` must point to the same allocated object and
|
|
/// must either be in bounds or at most one byte past the end of the
|
|
/// allocated object.
|
|
/// * Both `start` and `end` must be _derived from_ a pointer to the same
|
|
/// object.
|
|
/// * The distance between `start` and `end` must not overflow `isize`.
|
|
/// * The distance being in bounds must not rely on "wrapping around" the
|
|
/// address space.
|
|
#[inline(always)]
|
|
pub(crate) unsafe fn find_raw(
|
|
&self,
|
|
start: *const u8,
|
|
end: *const u8,
|
|
) -> Option<*const u8> {
|
|
// If we want to support vectors bigger than 256 bits, we probably
|
|
// need to move up to using a u64 for the masks used below. Currently
|
|
// they are 32 bits, which means we're SOL for vectors that need masks
|
|
// bigger than 32 bits. Overall unclear until there's a use case.
|
|
debug_assert!(V::BYTES <= 32, "vector cannot be bigger than 32 bytes");
|
|
|
|
let topos = V::Mask::first_offset;
|
|
let len = end.distance(start);
|
|
debug_assert!(
|
|
len >= V::BYTES,
|
|
"haystack has length {}, but must be at least {}",
|
|
len,
|
|
V::BYTES
|
|
);
|
|
|
|
// Search a possibly unaligned chunk at `start`. This covers any part
|
|
// of the haystack prior to where aligned loads can start.
|
|
if let Some(cur) = self.search_chunk(start, topos) {
|
|
return Some(cur);
|
|
}
|
|
// Set `cur` to the first V-aligned pointer greater than `start`.
|
|
let mut cur = start.add(V::BYTES - (start.as_usize() & V::ALIGN));
|
|
debug_assert!(cur > start && end.sub(V::BYTES) >= start);
|
|
if len >= Self::LOOP_SIZE {
|
|
while cur <= end.sub(Self::LOOP_SIZE) {
|
|
debug_assert_eq!(0, cur.as_usize() % V::BYTES);
|
|
|
|
let a = V::load_aligned(cur);
|
|
let b = V::load_aligned(cur.add(V::BYTES));
|
|
let eqa1 = self.v1.cmpeq(a);
|
|
let eqb1 = self.v1.cmpeq(b);
|
|
let eqa2 = self.v2.cmpeq(a);
|
|
let eqb2 = self.v2.cmpeq(b);
|
|
let or1 = eqa1.or(eqb1);
|
|
let or2 = eqa2.or(eqb2);
|
|
let or3 = or1.or(or2);
|
|
if or3.movemask_will_have_non_zero() {
|
|
let mask = eqa1.movemask().or(eqa2.movemask());
|
|
if mask.has_non_zero() {
|
|
return Some(cur.add(topos(mask)));
|
|
}
|
|
|
|
let mask = eqb1.movemask().or(eqb2.movemask());
|
|
debug_assert!(mask.has_non_zero());
|
|
return Some(cur.add(V::BYTES).add(topos(mask)));
|
|
}
|
|
cur = cur.add(Self::LOOP_SIZE);
|
|
}
|
|
}
|
|
// Handle any leftovers after the aligned loop above. We use unaligned
|
|
// loads here, but I believe we are guaranteed that they are aligned
|
|
// since `cur` is aligned.
|
|
while cur <= end.sub(V::BYTES) {
|
|
debug_assert!(end.distance(cur) >= V::BYTES);
|
|
if let Some(cur) = self.search_chunk(cur, topos) {
|
|
return Some(cur);
|
|
}
|
|
cur = cur.add(V::BYTES);
|
|
}
|
|
// Finally handle any remaining bytes less than the size of V. In this
|
|
// case, our pointer may indeed be unaligned and the load may overlap
|
|
// with the previous one. But that's okay since we know the previous
|
|
// load didn't lead to a match (otherwise we wouldn't be here).
|
|
if cur < end {
|
|
debug_assert!(end.distance(cur) < V::BYTES);
|
|
cur = cur.sub(V::BYTES - end.distance(cur));
|
|
debug_assert_eq!(end.distance(cur), V::BYTES);
|
|
return self.search_chunk(cur, topos);
|
|
}
|
|
None
|
|
}
|
|
|
|
/// Return a pointer to the last occurrence of the needle in the given
|
|
/// haystack. If no such occurrence exists, then `None` is returned.
|
|
///
|
|
/// When a match is found, the pointer returned is guaranteed to be
|
|
/// `>= start` and `< end`.
|
|
///
|
|
/// # Safety
|
|
///
|
|
/// * It must be the case that `start < end` and that the distance between
|
|
/// them is at least equal to `V::BYTES`. That is, it must always be valid
|
|
/// to do at least an unaligned load of `V` at `start`.
|
|
/// * Both `start` and `end` must be valid for reads.
|
|
/// * Both `start` and `end` must point to an initialized value.
|
|
/// * Both `start` and `end` must point to the same allocated object and
|
|
/// must either be in bounds or at most one byte past the end of the
|
|
/// allocated object.
|
|
/// * Both `start` and `end` must be _derived from_ a pointer to the same
|
|
/// object.
|
|
/// * The distance between `start` and `end` must not overflow `isize`.
|
|
/// * The distance being in bounds must not rely on "wrapping around" the
|
|
/// address space.
|
|
#[inline(always)]
|
|
pub(crate) unsafe fn rfind_raw(
|
|
&self,
|
|
start: *const u8,
|
|
end: *const u8,
|
|
) -> Option<*const u8> {
|
|
// If we want to support vectors bigger than 256 bits, we probably
|
|
// need to move up to using a u64 for the masks used below. Currently
|
|
// they are 32 bits, which means we're SOL for vectors that need masks
|
|
// bigger than 32 bits. Overall unclear until there's a use case.
|
|
debug_assert!(V::BYTES <= 32, "vector cannot be bigger than 32 bytes");
|
|
|
|
let topos = V::Mask::last_offset;
|
|
let len = end.distance(start);
|
|
debug_assert!(
|
|
len >= V::BYTES,
|
|
"haystack has length {}, but must be at least {}",
|
|
len,
|
|
V::BYTES
|
|
);
|
|
|
|
if let Some(cur) = self.search_chunk(end.sub(V::BYTES), topos) {
|
|
return Some(cur);
|
|
}
|
|
let mut cur = end.sub(end.as_usize() & V::ALIGN);
|
|
debug_assert!(start <= cur && cur <= end);
|
|
if len >= Self::LOOP_SIZE {
|
|
while cur >= start.add(Self::LOOP_SIZE) {
|
|
debug_assert_eq!(0, cur.as_usize() % V::BYTES);
|
|
|
|
cur = cur.sub(Self::LOOP_SIZE);
|
|
let a = V::load_aligned(cur);
|
|
let b = V::load_aligned(cur.add(V::BYTES));
|
|
let eqa1 = self.v1.cmpeq(a);
|
|
let eqb1 = self.v1.cmpeq(b);
|
|
let eqa2 = self.v2.cmpeq(a);
|
|
let eqb2 = self.v2.cmpeq(b);
|
|
let or1 = eqa1.or(eqb1);
|
|
let or2 = eqa2.or(eqb2);
|
|
let or3 = or1.or(or2);
|
|
if or3.movemask_will_have_non_zero() {
|
|
let mask = eqb1.movemask().or(eqb2.movemask());
|
|
if mask.has_non_zero() {
|
|
return Some(cur.add(V::BYTES).add(topos(mask)));
|
|
}
|
|
|
|
let mask = eqa1.movemask().or(eqa2.movemask());
|
|
debug_assert!(mask.has_non_zero());
|
|
return Some(cur.add(topos(mask)));
|
|
}
|
|
}
|
|
}
|
|
while cur >= start.add(V::BYTES) {
|
|
debug_assert!(cur.distance(start) >= V::BYTES);
|
|
cur = cur.sub(V::BYTES);
|
|
if let Some(cur) = self.search_chunk(cur, topos) {
|
|
return Some(cur);
|
|
}
|
|
}
|
|
if cur > start {
|
|
debug_assert!(cur.distance(start) < V::BYTES);
|
|
return self.search_chunk(start, topos);
|
|
}
|
|
None
|
|
}
|
|
|
|
/// Search `V::BYTES` starting at `cur` via an unaligned load.
|
|
///
|
|
/// `mask_to_offset` should be a function that converts a `movemask` to
|
|
/// an offset such that `cur.add(offset)` corresponds to a pointer to the
|
|
/// match location if one is found. Generally it is expected to use either
|
|
/// `mask_to_first_offset` or `mask_to_last_offset`, depending on whether
|
|
/// one is implementing a forward or reverse search, respectively.
|
|
///
|
|
/// # Safety
|
|
///
|
|
/// `cur` must be a valid pointer and it must be valid to do an unaligned
|
|
/// load of size `V::BYTES` at `cur`.
|
|
#[inline(always)]
|
|
unsafe fn search_chunk(
|
|
&self,
|
|
cur: *const u8,
|
|
mask_to_offset: impl Fn(V::Mask) -> usize,
|
|
) -> Option<*const u8> {
|
|
let chunk = V::load_unaligned(cur);
|
|
let eq1 = self.v1.cmpeq(chunk);
|
|
let eq2 = self.v2.cmpeq(chunk);
|
|
let mask = eq1.or(eq2).movemask();
|
|
if mask.has_non_zero() {
|
|
let mask1 = eq1.movemask();
|
|
let mask2 = eq2.movemask();
|
|
Some(cur.add(mask_to_offset(mask1.or(mask2))))
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Finds all occurrences of two bytes in a haystack.
|
|
///
|
|
/// That is, this reports matches of one of two possible bytes. For example,
|
|
/// searching for `a` or `b` in `afoobar` would report matches at offsets `0`,
|
|
/// `4` and `5`.
|
|
#[derive(Clone, Copy, Debug)]
|
|
pub(crate) struct Three<V> {
|
|
s1: u8,
|
|
s2: u8,
|
|
s3: u8,
|
|
v1: V,
|
|
v2: V,
|
|
v3: V,
|
|
}
|
|
|
|
impl<V: Vector> Three<V> {
|
|
/// The number of bytes we examine per each iteration of our search loop.
|
|
const LOOP_SIZE: usize = 2 * V::BYTES;
|
|
|
|
/// Create a new searcher that finds occurrences of the byte given.
|
|
#[inline(always)]
|
|
pub(crate) unsafe fn new(
|
|
needle1: u8,
|
|
needle2: u8,
|
|
needle3: u8,
|
|
) -> Three<V> {
|
|
Three {
|
|
s1: needle1,
|
|
s2: needle2,
|
|
s3: needle3,
|
|
v1: V::splat(needle1),
|
|
v2: V::splat(needle2),
|
|
v3: V::splat(needle3),
|
|
}
|
|
}
|
|
|
|
/// Returns the first needle given to `Three::new`.
|
|
#[inline(always)]
|
|
pub(crate) fn needle1(&self) -> u8 {
|
|
self.s1
|
|
}
|
|
|
|
/// Returns the second needle given to `Three::new`.
|
|
#[inline(always)]
|
|
pub(crate) fn needle2(&self) -> u8 {
|
|
self.s2
|
|
}
|
|
|
|
/// Returns the third needle given to `Three::new`.
|
|
#[inline(always)]
|
|
pub(crate) fn needle3(&self) -> u8 {
|
|
self.s3
|
|
}
|
|
|
|
/// Return a pointer to the first occurrence of one of the needles in the
|
|
/// given haystack. If no such occurrence exists, then `None` is returned.
|
|
///
|
|
/// When a match is found, the pointer returned is guaranteed to be
|
|
/// `>= start` and `< end`.
|
|
///
|
|
/// # Safety
|
|
///
|
|
/// * It must be the case that `start < end` and that the distance between
|
|
/// them is at least equal to `V::BYTES`. That is, it must always be valid
|
|
/// to do at least an unaligned load of `V` at `start`.
|
|
/// * Both `start` and `end` must be valid for reads.
|
|
/// * Both `start` and `end` must point to an initialized value.
|
|
/// * Both `start` and `end` must point to the same allocated object and
|
|
/// must either be in bounds or at most one byte past the end of the
|
|
/// allocated object.
|
|
/// * Both `start` and `end` must be _derived from_ a pointer to the same
|
|
/// object.
|
|
/// * The distance between `start` and `end` must not overflow `isize`.
|
|
/// * The distance being in bounds must not rely on "wrapping around" the
|
|
/// address space.
|
|
#[inline(always)]
|
|
pub(crate) unsafe fn find_raw(
|
|
&self,
|
|
start: *const u8,
|
|
end: *const u8,
|
|
) -> Option<*const u8> {
|
|
// If we want to support vectors bigger than 256 bits, we probably
|
|
// need to move up to using a u64 for the masks used below. Currently
|
|
// they are 32 bits, which means we're SOL for vectors that need masks
|
|
// bigger than 32 bits. Overall unclear until there's a use case.
|
|
debug_assert!(V::BYTES <= 32, "vector cannot be bigger than 32 bytes");
|
|
|
|
let topos = V::Mask::first_offset;
|
|
let len = end.distance(start);
|
|
debug_assert!(
|
|
len >= V::BYTES,
|
|
"haystack has length {}, but must be at least {}",
|
|
len,
|
|
V::BYTES
|
|
);
|
|
|
|
// Search a possibly unaligned chunk at `start`. This covers any part
|
|
// of the haystack prior to where aligned loads can start.
|
|
if let Some(cur) = self.search_chunk(start, topos) {
|
|
return Some(cur);
|
|
}
|
|
// Set `cur` to the first V-aligned pointer greater than `start`.
|
|
let mut cur = start.add(V::BYTES - (start.as_usize() & V::ALIGN));
|
|
debug_assert!(cur > start && end.sub(V::BYTES) >= start);
|
|
if len >= Self::LOOP_SIZE {
|
|
while cur <= end.sub(Self::LOOP_SIZE) {
|
|
debug_assert_eq!(0, cur.as_usize() % V::BYTES);
|
|
|
|
let a = V::load_aligned(cur);
|
|
let b = V::load_aligned(cur.add(V::BYTES));
|
|
let eqa1 = self.v1.cmpeq(a);
|
|
let eqb1 = self.v1.cmpeq(b);
|
|
let eqa2 = self.v2.cmpeq(a);
|
|
let eqb2 = self.v2.cmpeq(b);
|
|
let eqa3 = self.v3.cmpeq(a);
|
|
let eqb3 = self.v3.cmpeq(b);
|
|
let or1 = eqa1.or(eqb1);
|
|
let or2 = eqa2.or(eqb2);
|
|
let or3 = eqa3.or(eqb3);
|
|
let or4 = or1.or(or2);
|
|
let or5 = or3.or(or4);
|
|
if or5.movemask_will_have_non_zero() {
|
|
let mask = eqa1
|
|
.movemask()
|
|
.or(eqa2.movemask())
|
|
.or(eqa3.movemask());
|
|
if mask.has_non_zero() {
|
|
return Some(cur.add(topos(mask)));
|
|
}
|
|
|
|
let mask = eqb1
|
|
.movemask()
|
|
.or(eqb2.movemask())
|
|
.or(eqb3.movemask());
|
|
debug_assert!(mask.has_non_zero());
|
|
return Some(cur.add(V::BYTES).add(topos(mask)));
|
|
}
|
|
cur = cur.add(Self::LOOP_SIZE);
|
|
}
|
|
}
|
|
// Handle any leftovers after the aligned loop above. We use unaligned
|
|
// loads here, but I believe we are guaranteed that they are aligned
|
|
// since `cur` is aligned.
|
|
while cur <= end.sub(V::BYTES) {
|
|
debug_assert!(end.distance(cur) >= V::BYTES);
|
|
if let Some(cur) = self.search_chunk(cur, topos) {
|
|
return Some(cur);
|
|
}
|
|
cur = cur.add(V::BYTES);
|
|
}
|
|
// Finally handle any remaining bytes less than the size of V. In this
|
|
// case, our pointer may indeed be unaligned and the load may overlap
|
|
// with the previous one. But that's okay since we know the previous
|
|
// load didn't lead to a match (otherwise we wouldn't be here).
|
|
if cur < end {
|
|
debug_assert!(end.distance(cur) < V::BYTES);
|
|
cur = cur.sub(V::BYTES - end.distance(cur));
|
|
debug_assert_eq!(end.distance(cur), V::BYTES);
|
|
return self.search_chunk(cur, topos);
|
|
}
|
|
None
|
|
}
|
|
|
|
/// Return a pointer to the last occurrence of the needle in the given
|
|
/// haystack. If no such occurrence exists, then `None` is returned.
|
|
///
|
|
/// When a match is found, the pointer returned is guaranteed to be
|
|
/// `>= start` and `< end`.
|
|
///
|
|
/// # Safety
|
|
///
|
|
/// * It must be the case that `start < end` and that the distance between
|
|
/// them is at least equal to `V::BYTES`. That is, it must always be valid
|
|
/// to do at least an unaligned load of `V` at `start`.
|
|
/// * Both `start` and `end` must be valid for reads.
|
|
/// * Both `start` and `end` must point to an initialized value.
|
|
/// * Both `start` and `end` must point to the same allocated object and
|
|
/// must either be in bounds or at most one byte past the end of the
|
|
/// allocated object.
|
|
/// * Both `start` and `end` must be _derived from_ a pointer to the same
|
|
/// object.
|
|
/// * The distance between `start` and `end` must not overflow `isize`.
|
|
/// * The distance being in bounds must not rely on "wrapping around" the
|
|
/// address space.
|
|
#[inline(always)]
|
|
pub(crate) unsafe fn rfind_raw(
|
|
&self,
|
|
start: *const u8,
|
|
end: *const u8,
|
|
) -> Option<*const u8> {
|
|
// If we want to support vectors bigger than 256 bits, we probably
|
|
// need to move up to using a u64 for the masks used below. Currently
|
|
// they are 32 bits, which means we're SOL for vectors that need masks
|
|
// bigger than 32 bits. Overall unclear until there's a use case.
|
|
debug_assert!(V::BYTES <= 32, "vector cannot be bigger than 32 bytes");
|
|
|
|
let topos = V::Mask::last_offset;
|
|
let len = end.distance(start);
|
|
debug_assert!(
|
|
len >= V::BYTES,
|
|
"haystack has length {}, but must be at least {}",
|
|
len,
|
|
V::BYTES
|
|
);
|
|
|
|
if let Some(cur) = self.search_chunk(end.sub(V::BYTES), topos) {
|
|
return Some(cur);
|
|
}
|
|
let mut cur = end.sub(end.as_usize() & V::ALIGN);
|
|
debug_assert!(start <= cur && cur <= end);
|
|
if len >= Self::LOOP_SIZE {
|
|
while cur >= start.add(Self::LOOP_SIZE) {
|
|
debug_assert_eq!(0, cur.as_usize() % V::BYTES);
|
|
|
|
cur = cur.sub(Self::LOOP_SIZE);
|
|
let a = V::load_aligned(cur);
|
|
let b = V::load_aligned(cur.add(V::BYTES));
|
|
let eqa1 = self.v1.cmpeq(a);
|
|
let eqb1 = self.v1.cmpeq(b);
|
|
let eqa2 = self.v2.cmpeq(a);
|
|
let eqb2 = self.v2.cmpeq(b);
|
|
let eqa3 = self.v3.cmpeq(a);
|
|
let eqb3 = self.v3.cmpeq(b);
|
|
let or1 = eqa1.or(eqb1);
|
|
let or2 = eqa2.or(eqb2);
|
|
let or3 = eqa3.or(eqb3);
|
|
let or4 = or1.or(or2);
|
|
let or5 = or3.or(or4);
|
|
if or5.movemask_will_have_non_zero() {
|
|
let mask = eqb1
|
|
.movemask()
|
|
.or(eqb2.movemask())
|
|
.or(eqb3.movemask());
|
|
if mask.has_non_zero() {
|
|
return Some(cur.add(V::BYTES).add(topos(mask)));
|
|
}
|
|
|
|
let mask = eqa1
|
|
.movemask()
|
|
.or(eqa2.movemask())
|
|
.or(eqa3.movemask());
|
|
debug_assert!(mask.has_non_zero());
|
|
return Some(cur.add(topos(mask)));
|
|
}
|
|
}
|
|
}
|
|
while cur >= start.add(V::BYTES) {
|
|
debug_assert!(cur.distance(start) >= V::BYTES);
|
|
cur = cur.sub(V::BYTES);
|
|
if let Some(cur) = self.search_chunk(cur, topos) {
|
|
return Some(cur);
|
|
}
|
|
}
|
|
if cur > start {
|
|
debug_assert!(cur.distance(start) < V::BYTES);
|
|
return self.search_chunk(start, topos);
|
|
}
|
|
None
|
|
}
|
|
|
|
/// Search `V::BYTES` starting at `cur` via an unaligned load.
|
|
///
|
|
/// `mask_to_offset` should be a function that converts a `movemask` to
|
|
/// an offset such that `cur.add(offset)` corresponds to a pointer to the
|
|
/// match location if one is found. Generally it is expected to use either
|
|
/// `mask_to_first_offset` or `mask_to_last_offset`, depending on whether
|
|
/// one is implementing a forward or reverse search, respectively.
|
|
///
|
|
/// # Safety
|
|
///
|
|
/// `cur` must be a valid pointer and it must be valid to do an unaligned
|
|
/// load of size `V::BYTES` at `cur`.
|
|
#[inline(always)]
|
|
unsafe fn search_chunk(
|
|
&self,
|
|
cur: *const u8,
|
|
mask_to_offset: impl Fn(V::Mask) -> usize,
|
|
) -> Option<*const u8> {
|
|
let chunk = V::load_unaligned(cur);
|
|
let eq1 = self.v1.cmpeq(chunk);
|
|
let eq2 = self.v2.cmpeq(chunk);
|
|
let eq3 = self.v3.cmpeq(chunk);
|
|
let mask = eq1.or(eq2).or(eq3).movemask();
|
|
if mask.has_non_zero() {
|
|
let mask1 = eq1.movemask();
|
|
let mask2 = eq2.movemask();
|
|
let mask3 = eq3.movemask();
|
|
Some(cur.add(mask_to_offset(mask1.or(mask2).or(mask3))))
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
}
|
|
|
|
/// An iterator over all occurrences of a set of bytes in a haystack.
|
|
///
|
|
/// This iterator implements the routines necessary to provide a
|
|
/// `DoubleEndedIterator` impl, which means it can also be used to find
|
|
/// occurrences in reverse order.
|
|
///
|
|
/// The lifetime parameters are as follows:
|
|
///
|
|
/// * `'h` refers to the lifetime of the haystack being searched.
|
|
///
|
|
/// This type is intended to be used to implement all iterators for the
|
|
/// `memchr` family of functions. It handles a tiny bit of marginally tricky
|
|
/// raw pointer math, but otherwise expects the caller to provide `find_raw`
|
|
/// and `rfind_raw` routines for each call of `next` and `next_back`,
|
|
/// respectively.
|
|
#[derive(Clone, Debug)]
|
|
pub(crate) struct Iter<'h> {
|
|
/// The original starting point into the haystack. We use this to convert
|
|
/// pointers to offsets.
|
|
original_start: *const u8,
|
|
/// The current starting point into the haystack. That is, where the next
|
|
/// search will begin.
|
|
start: *const u8,
|
|
/// The current ending point into the haystack. That is, where the next
|
|
/// reverse search will begin.
|
|
end: *const u8,
|
|
/// A marker for tracking the lifetime of the start/cur_start/cur_end
|
|
/// pointers above, which all point into the haystack.
|
|
haystack: core::marker::PhantomData<&'h [u8]>,
|
|
}
|
|
|
|
// SAFETY: Iter contains no shared references to anything that performs any
|
|
// interior mutations. Also, the lifetime guarantees that Iter will not outlive
|
|
// the haystack.
|
|
unsafe impl<'h> Send for Iter<'h> {}
|
|
|
|
// SAFETY: Iter perform no interior mutations, therefore no explicit
|
|
// synchronization is necessary. Also, the lifetime guarantees that Iter will
|
|
// not outlive the haystack.
|
|
unsafe impl<'h> Sync for Iter<'h> {}
|
|
|
|
impl<'h> Iter<'h> {
|
|
/// Create a new generic memchr iterator.
|
|
#[inline(always)]
|
|
pub(crate) fn new(haystack: &'h [u8]) -> Iter<'h> {
|
|
Iter {
|
|
original_start: haystack.as_ptr(),
|
|
start: haystack.as_ptr(),
|
|
end: haystack.as_ptr().wrapping_add(haystack.len()),
|
|
haystack: core::marker::PhantomData,
|
|
}
|
|
}
|
|
|
|
/// Returns the next occurrence in the forward direction.
|
|
///
|
|
/// # Safety
|
|
///
|
|
/// Callers must ensure that if a pointer is returned from the closure
|
|
/// provided, then it must be greater than or equal to the start pointer
|
|
/// and less than the end pointer.
|
|
#[inline(always)]
|
|
pub(crate) unsafe fn next(
|
|
&mut self,
|
|
mut find_raw: impl FnMut(*const u8, *const u8) -> Option<*const u8>,
|
|
) -> Option<usize> {
|
|
// SAFETY: Pointers are derived directly from the same &[u8] haystack.
|
|
// We only ever modify start/end corresponding to a matching offset
|
|
// found between start and end. Thus all changes to start/end maintain
|
|
// our safety requirements.
|
|
//
|
|
// The only other assumption we rely on is that the pointer returned
|
|
// by `find_raw` satisfies `self.start <= found < self.end`, and that
|
|
// safety contract is forwarded to the caller.
|
|
let found = find_raw(self.start, self.end)?;
|
|
let result = found.distance(self.original_start);
|
|
self.start = found.add(1);
|
|
Some(result)
|
|
}
|
|
|
|
/// Returns the number of remaining elements in this iterator.
|
|
#[inline(always)]
|
|
pub(crate) fn count(
|
|
self,
|
|
mut count_raw: impl FnMut(*const u8, *const u8) -> usize,
|
|
) -> usize {
|
|
// SAFETY: Pointers are derived directly from the same &[u8] haystack.
|
|
// We only ever modify start/end corresponding to a matching offset
|
|
// found between start and end. Thus all changes to start/end maintain
|
|
// our safety requirements.
|
|
count_raw(self.start, self.end)
|
|
}
|
|
|
|
/// Returns the next occurrence in reverse.
|
|
///
|
|
/// # Safety
|
|
///
|
|
/// Callers must ensure that if a pointer is returned from the closure
|
|
/// provided, then it must be greater than or equal to the start pointer
|
|
/// and less than the end pointer.
|
|
#[inline(always)]
|
|
pub(crate) unsafe fn next_back(
|
|
&mut self,
|
|
mut rfind_raw: impl FnMut(*const u8, *const u8) -> Option<*const u8>,
|
|
) -> Option<usize> {
|
|
// SAFETY: Pointers are derived directly from the same &[u8] haystack.
|
|
// We only ever modify start/end corresponding to a matching offset
|
|
// found between start and end. Thus all changes to start/end maintain
|
|
// our safety requirements.
|
|
//
|
|
// The only other assumption we rely on is that the pointer returned
|
|
// by `rfind_raw` satisfies `self.start <= found < self.end`, and that
|
|
// safety contract is forwarded to the caller.
|
|
let found = rfind_raw(self.start, self.end)?;
|
|
let result = found.distance(self.original_start);
|
|
self.end = found;
|
|
Some(result)
|
|
}
|
|
|
|
/// Provides an implementation of `Iterator::size_hint`.
|
|
#[inline(always)]
|
|
pub(crate) fn size_hint(&self) -> (usize, Option<usize>) {
|
|
(0, Some(self.end.as_usize().saturating_sub(self.start.as_usize())))
|
|
}
|
|
}
|
|
|
|
/// Search a slice using a function that operates on raw pointers.
|
|
///
|
|
/// Given a function to search a contiguous sequence of memory for the location
|
|
/// of a non-empty set of bytes, this will execute that search on a slice of
|
|
/// bytes. The pointer returned by the given function will be converted to an
|
|
/// offset relative to the starting point of the given slice. That is, if a
|
|
/// match is found, the offset returned by this routine is guaranteed to be a
|
|
/// valid index into `haystack`.
|
|
///
|
|
/// Callers may use this for a forward or reverse search.
|
|
///
|
|
/// # Safety
|
|
///
|
|
/// Callers must ensure that if a pointer is returned by `find_raw`, then the
|
|
/// pointer must be greater than or equal to the starting pointer and less than
|
|
/// the end pointer.
|
|
#[inline(always)]
|
|
pub(crate) unsafe fn search_slice_with_raw(
|
|
haystack: &[u8],
|
|
mut find_raw: impl FnMut(*const u8, *const u8) -> Option<*const u8>,
|
|
) -> Option<usize> {
|
|
// SAFETY: We rely on `find_raw` to return a correct and valid pointer, but
|
|
// otherwise, `start` and `end` are valid due to the guarantees provided by
|
|
// a &[u8].
|
|
let start = haystack.as_ptr();
|
|
let end = start.add(haystack.len());
|
|
let found = find_raw(start, end)?;
|
|
let idx = found.distance(start);
|
|
// Required by safety invariant required for find_raw
|
|
// this lets the compiler know the returned index is in bounds for the slice
|
|
if idx >= haystack.len() {
|
|
core::hint::unreachable_unchecked();
|
|
}
|
|
Some(idx)
|
|
}
|
|
|
|
/// Performs a forward byte-at-a-time loop until either `ptr >= end_ptr` or
|
|
/// until `confirm(*ptr)` returns `true`. If the former occurs, then `None` is
|
|
/// returned. If the latter occurs, then the pointer at which `confirm` returns
|
|
/// `true` is returned.
|
|
///
|
|
/// # Safety
|
|
///
|
|
/// Callers must provide valid pointers and they must satisfy `start_ptr <=
|
|
/// ptr` and `ptr <= end_ptr`.
|
|
#[inline(always)]
|
|
pub(crate) unsafe fn fwd_byte_by_byte<F: Fn(u8) -> bool>(
|
|
start: *const u8,
|
|
end: *const u8,
|
|
confirm: F,
|
|
) -> Option<*const u8> {
|
|
debug_assert!(start <= end);
|
|
let mut ptr = start;
|
|
while ptr < end {
|
|
if confirm(*ptr) {
|
|
return Some(ptr);
|
|
}
|
|
ptr = ptr.offset(1);
|
|
}
|
|
None
|
|
}
|
|
|
|
/// Performs a reverse byte-at-a-time loop until either `ptr < start_ptr` or
|
|
/// until `confirm(*ptr)` returns `true`. If the former occurs, then `None` is
|
|
/// returned. If the latter occurs, then the pointer at which `confirm` returns
|
|
/// `true` is returned.
|
|
///
|
|
/// # Safety
|
|
///
|
|
/// Callers must provide valid pointers and they must satisfy `start_ptr <=
|
|
/// ptr` and `ptr <= end_ptr`.
|
|
#[inline(always)]
|
|
pub(crate) unsafe fn rev_byte_by_byte<F: Fn(u8) -> bool>(
|
|
start: *const u8,
|
|
end: *const u8,
|
|
confirm: F,
|
|
) -> Option<*const u8> {
|
|
debug_assert!(start <= end);
|
|
|
|
let mut ptr = end;
|
|
while ptr > start {
|
|
ptr = ptr.offset(-1);
|
|
if confirm(*ptr) {
|
|
return Some(ptr);
|
|
}
|
|
}
|
|
None
|
|
}
|
|
|
|
/// Performs a forward byte-at-a-time loop until `ptr >= end_ptr` and returns
|
|
/// the number of times `confirm(*ptr)` returns `true`.
|
|
///
|
|
/// # Safety
|
|
///
|
|
/// Callers must provide valid pointers and they must satisfy `start_ptr <=
|
|
/// ptr` and `ptr <= end_ptr`.
|
|
#[inline(always)]
|
|
pub(crate) unsafe fn count_byte_by_byte<F: Fn(u8) -> bool>(
|
|
start: *const u8,
|
|
end: *const u8,
|
|
confirm: F,
|
|
) -> usize {
|
|
debug_assert!(start <= end);
|
|
let mut ptr = start;
|
|
let mut count = 0;
|
|
while ptr < end {
|
|
if confirm(*ptr) {
|
|
count += 1;
|
|
}
|
|
ptr = ptr.offset(1);
|
|
}
|
|
count
|
|
}
|