diff --git a/library/core/src/slice/cmp.rs b/library/core/src/slice/cmp.rs index 5e1b218e507bd..a9b0abae21fa5 100644 --- a/library/core/src/slice/cmp.rs +++ b/library/core/src/slice/cmp.rs @@ -227,34 +227,286 @@ impl_marker_for!(BytewiseEquality, u8 i8 u16 i16 u32 i32 u64 i64 u128 i128 usize isize char bool); pub(super) trait SliceContains: Sized { - fn slice_contains(&self, x: &[Self]) -> bool; + fn slice_contains_element(hs: &[Self], needle: &Self) -> bool; + fn slice_contains_slice(hs: &[Self], needle: &[Self]) -> bool; } impl SliceContains for T where T: PartialEq, { - default fn slice_contains(&self, x: &[Self]) -> bool { - x.iter().any(|y| *y == *self) + default fn slice_contains_element(hs: &[Self], needle: &Self) -> bool { + hs.iter().any(|element| *element == *needle) + } + + default fn slice_contains_slice(hs: &[Self], needle: &[Self]) -> bool { + default_slice_contains_slice(hs, needle) } } impl SliceContains for u8 { #[inline] - fn slice_contains(&self, x: &[Self]) -> bool { - memchr::memchr(*self, x).is_some() + fn slice_contains_element(hs: &[Self], needle: &Self) -> bool { + memchr::memchr(*needle, hs).is_some() + } + + #[inline] + fn slice_contains_slice(hs: &[Self], needle: &[Self]) -> bool { + if needle.len() <= 32 { + if let Some(result) = simd_contains(hs, needle) { + return result; + } + } + default_slice_contains_slice(hs, needle) } } +unsafe fn bytes_of(slice: &[T]) -> &[u8] { + // SAFETY: caller promises that `T` and `u8` have the same memory layout, + // thus casting `x.as_ptr()` as `*const u8` is safe. The `x.as_ptr()` comes + // from a reference and is thus guaranteed to be valid for reads for the + // length of the slice `x.len()`, which cannot be larger than + // `isize::MAX`. The returned slice is never mutated. + unsafe { from_raw_parts(slice.as_ptr() as *const u8, slice.len()) } +} + impl SliceContains for i8 { #[inline] - fn slice_contains(&self, x: &[Self]) -> bool { - let byte = *self as u8; - // SAFETY: `i8` and `u8` have the same memory layout, thus casting `x.as_ptr()` - // as `*const u8` is safe. The `x.as_ptr()` comes from a reference and is thus guaranteed - // to be valid for reads for the length of the slice `x.len()`, which cannot be larger - // than `isize::MAX`. The returned slice is never mutated. - let bytes: &[u8] = unsafe { from_raw_parts(x.as_ptr() as *const u8, x.len()) }; - memchr::memchr(byte, bytes).is_some() + fn slice_contains_element(hs: &[Self], needle: &Self) -> bool { + // SAFETY: i8 and u8 have the same memory layout + u8::slice_contains_element(unsafe { bytes_of(hs) }, &(*needle as u8)) + } + + #[inline] + fn slice_contains_slice(hs: &[Self], needle: &[Self]) -> bool { + // SAFETY: i8 and u8 have the same memory layout + unsafe { u8::slice_contains_slice(bytes_of(hs), bytes_of(needle)) } + } +} + +impl SliceContains for bool { + #[inline] + fn slice_contains_element(hs: &[Self], needle: &Self) -> bool { + // SAFETY: bool and u8 have the same memory layout and all valid bool + // bit patterns are valid u8 bit patterns. + u8::slice_contains_element(unsafe { bytes_of(hs) }, &(*needle as u8)) + } + + #[inline] + fn slice_contains_slice(hs: &[Self], needle: &[Self]) -> bool { + // SAFETY: bool and u8 have the same memory layout and all valid bool + // bit patterns are valid u8 bit patterns. + unsafe { u8::slice_contains_slice(bytes_of(hs), bytes_of(needle)) } + } +} + +fn default_slice_contains_slice(hs: &[T], needle: &[T]) -> bool { + super::pattern::NaiveSearcherState::new(hs.len()) + .next_match(hs, needle) + .is_some() +} + + +/// SIMD search for short needles based on +/// Wojciech Muła's "SIMD-friendly algorithms for substring searching"[0] +/// +/// It skips ahead by the vector width on each iteration (rather than the needle length as two-way +/// does) by probing the first and last byte of the needle for the whole vector width +/// and only doing full needle comparisons when the vectorized probe indicated potential matches. +/// +/// Since the x86_64 baseline only offers SSE2 we only use u8x16 here. +/// If we ever ship std with for x86-64-v3 or adapt this for other platforms then wider vectors +/// should be evaluated. +/// +/// For haystacks smaller than vector-size + needle length it falls back to +/// a naive O(n*m) search so this implementation should not be called on larger needles. +/// +/// [0]: http://0x80.pl/articles/simd-strfind.html#sse-avx2 +#[cfg(all(target_arch = "x86_64", target_feature = "sse2"))] +#[inline] +fn simd_contains(haystack: &[u8], needle: &[u8]) -> Option { + debug_assert!(needle.len() > 1); + + use crate::ops::BitAnd; + use crate::simd::mask8x16 as Mask; + use crate::simd::u8x16 as Block; + use crate::simd::{SimdPartialEq, ToBitMask}; + + let first_probe = needle[0]; + let last_byte_offset = needle.len() - 1; + + // the offset used for the 2nd vector + let second_probe_offset = if needle.len() == 2 { + // never bail out on len=2 needles because the probes will fully cover them and have + // no degenerate cases. + 1 + } else { + // try a few bytes in case first and last byte of the needle are the same + let Some(second_probe_offset) = (needle.len().saturating_sub(4)..needle.len()).rfind(|&idx| needle[idx] != first_probe) else { + // fall back to other search methods if we can't find any different bytes + // since we could otherwise hit some degenerate cases + return None; + }; + second_probe_offset + }; + + // do a naive search if the haystack is too small to fit + if haystack.len() < Block::LANES + last_byte_offset { + return Some(haystack.windows(needle.len()).any(|c| c == needle)); + } + + let first_probe: Block = Block::splat(first_probe); + let second_probe: Block = Block::splat(needle[second_probe_offset]); + // first byte are already checked by the outer loop. to verify a match only the + // remainder has to be compared. + let trimmed_needle = &needle[1..]; + + // this #[cold] is load-bearing, benchmark before removing it... + let check_mask = #[cold] + |idx, mask: u16, skip: bool| -> bool { + if skip { + return false; + } + + // and so is this. optimizations are weird. + let mut mask = mask; + + while mask != 0 { + let trailing = mask.trailing_zeros(); + let offset = idx + trailing as usize + 1; + // SAFETY: mask is between 0 and 15 trailing zeroes, we skip one additional byte that was already compared + // and then take trimmed_needle.len() bytes. This is within the bounds defined by the outer loop + unsafe { + let sub = haystack.get_unchecked(offset..).get_unchecked(..trimmed_needle.len()); + if small_slice_eq(sub, trimmed_needle) { + return true; + } + } + mask &= !(1 << trailing); + } + return false; + }; + + let test_chunk = |idx| -> u16 { + // SAFETY: this requires at least LANES bytes being readable at idx + // that is ensured by the loop ranges (see comments below) + let a: Block = unsafe { haystack.as_ptr().add(idx).cast::().read_unaligned() }; + // SAFETY: this requires LANES + block_offset bytes being readable at idx + let b: Block = unsafe { + haystack.as_ptr().add(idx).add(second_probe_offset).cast::().read_unaligned() + }; + let eq_first: Mask = a.simd_eq(first_probe); + let eq_last: Mask = b.simd_eq(second_probe); + let both = eq_first.bitand(eq_last); + let mask = both.to_bitmask(); + + return mask; + }; + + let mut i = 0; + let mut result = false; + // The loop condition must ensure that there's enough headroom to read LANE bytes, + // and not only at the current index but also at the index shifted by block_offset + const UNROLL: usize = 4; + while i + last_byte_offset + UNROLL * Block::LANES < haystack.len() && !result { + let mut masks = [0u16; UNROLL]; + for j in 0..UNROLL { + masks[j] = test_chunk(i + j * Block::LANES); + } + for j in 0..UNROLL { + let mask = masks[j]; + if mask != 0 { + result |= check_mask(i + j * Block::LANES, mask, result); + } + } + i += UNROLL * Block::LANES; + } + while i + last_byte_offset + Block::LANES < haystack.len() && !result { + let mask = test_chunk(i); + if mask != 0 { + result |= check_mask(i, mask, result); + } + i += Block::LANES; + } + + // Process the tail that didn't fit into LANES-sized steps. + // This simply repeats the same procedure but as right-aligned chunk instead + // of a left-aligned one. The last byte must be exactly flush with the string end so + // we don't miss a single byte or read out of bounds. + let i = haystack.len() - last_byte_offset - Block::LANES; + let mask = test_chunk(i); + if mask != 0 { + result |= check_mask(i, mask, result); + } + + Some(result) +} + +/// Compares short slices for equality. +/// +/// It avoids a call to libc's memcmp which is faster on long slices +/// due to SIMD optimizations but it incurs a function call overhead. +/// +/// # Safety +/// +/// Both slices must have the same length. +#[cfg(all(target_arch = "x86_64", target_feature = "sse2"))] // only called on x86 +#[inline] +unsafe fn small_slice_eq(x: &[u8], y: &[u8]) -> bool { + debug_assert_eq!(x.len(), y.len()); + // This function is adapted from + // https://github.com/BurntSushi/memchr/blob/8037d11b4357b0f07be2bb66dc2659d9cf28ad32/src/memmem/util.rs#L32 + + // If we don't have enough bytes to do 4-byte at a time loads, then + // fall back to the naive slow version. + // + // Potential alternative: We could do a copy_nonoverlapping combined with a mask instead + // of a loop. Benchmark it. + if x.len() < 4 { + for (&b1, &b2) in x.iter().zip(y) { + if b1 != b2 { + return false; + } + } + return true; + } + // When we have 4 or more bytes to compare, then proceed in chunks of 4 at + // a time using unaligned loads. + // + // Also, why do 4 byte loads instead of, say, 8 byte loads? The reason is + // that this particular version of memcmp is likely to be called with tiny + // needles. That means that if we do 8 byte loads, then a higher proportion + // of memcmp calls will use the slower variant above. With that said, this + // is a hypothesis and is only loosely supported by benchmarks. There's + // likely some improvement that could be made here. The main thing here + // though is to optimize for latency, not throughput. + + // SAFETY: Via the conditional above, we know that both `px` and `py` + // have the same length, so `px < pxend` implies that `py < pyend`. + // Thus, derefencing both `px` and `py` in the loop below is safe. + // + // Moreover, we set `pxend` and `pyend` to be 4 bytes before the actual + // end of `px` and `py`. Thus, the final dereference outside of the + // loop is guaranteed to be valid. (The final comparison will overlap with + // the last comparison done in the loop for lengths that aren't multiples + // of four.) + // + // Finally, we needn't worry about alignment here, since we do unaligned + // loads. + unsafe { + let (mut px, mut py) = (x.as_ptr(), y.as_ptr()); + let (pxend, pyend) = (px.add(x.len() - 4), py.add(y.len() - 4)); + while px < pxend { + let vx = (px as *const u32).read_unaligned(); + let vy = (py as *const u32).read_unaligned(); + if vx != vy { + return false; + } + px = px.add(4); + py = py.add(4); + } + let vx = (pxend as *const u32).read_unaligned(); + let vy = (pyend as *const u32).read_unaligned(); + vx == vy } } diff --git a/library/core/src/slice/mod.rs b/library/core/src/slice/mod.rs index 6ea16bf643071..0ffd2eb285384 100644 --- a/library/core/src/slice/mod.rs +++ b/library/core/src/slice/mod.rs @@ -15,6 +15,7 @@ use crate::num::NonZeroUsize; use crate::ops::{Bound, FnMut, OneSidedRange, Range, RangeBounds}; use crate::option::Option; use crate::option::Option::{None, Some}; +use crate::pattern::{DoubleEndedSearcher, Pattern, ReverseSearcher, Searcher}; use crate::ptr; use crate::result::Result; use crate::result::Result::{Err, Ok}; @@ -40,6 +41,7 @@ mod ascii; mod cmp; mod index; mod iter; +mod pattern; mod raw; mod rotate; mod specialize; @@ -2213,11 +2215,14 @@ impl [T] { RSplitNMut::new(self.rsplit_mut(pred), n) } - /// Returns `true` if the slice contains an element with the given value. + /// Returns `true` if the slice contains given pattern; returns `false` + /// otherwise. /// - /// This operation is *O*(*n*). + /// This may be used to look for a single element (in which case the + /// operation is *O*(*n*)) or with more complex patterns. /// - /// Note that if you have a sorted slice, [`binary_search`] may be faster. + /// Note that if you have a sorted slice and are looking for a single + /// element, [`binary_search`] may be faster. /// /// [`binary_search`]: slice::binary_search /// @@ -2227,11 +2232,15 @@ impl [T] { /// let v = [10, 40, 30]; /// assert!(v.contains(&30)); /// assert!(!v.contains(&50)); + /// + /// assert!(v.contains(&[])); + /// assert!(v.contains(&[40, 30])); + /// assert!(!v.contains(&[30, 40])); /// ``` /// - /// If you do not have a `&T`, but some other value that you can compare - /// with one (for example, `String` implements `PartialEq`), you can - /// use `iter().any`: + /// If you’re looking for a single element and don’t have a `&T`, but some + /// other value that you can compare with one (for example, `String` + /// implements `PartialEq`), you can use `iter().any`: /// /// ``` /// let v = [String::from("hello"), String::from("world")]; // slice of `String` @@ -2241,44 +2250,42 @@ impl [T] { #[stable(feature = "rust1", since = "1.0.0")] #[inline] #[must_use] - pub fn contains(&self, x: &T) -> bool - where - T: PartialEq, - { - cmp::SliceContains::slice_contains(x, self) + pub fn contains<'a, P: Pattern<&'a [T]>>(&'a self, pat: P) -> bool { + pat.is_contained_in(self) } - /// Returns `true` if `needle` is a prefix of the slice. + /// Returns `true` if `pattern` matches at the beginning of the slice. /// /// # Examples /// /// ``` /// let v = [10, 40, 30]; + /// + /// assert!(v.starts_with(&[])); /// assert!(v.starts_with(&[10])); /// assert!(v.starts_with(&[10, 40])); /// assert!(!v.starts_with(&[50])); /// assert!(!v.starts_with(&[10, 50])); + /// + /// assert!(v.starts_with(&10)); + /// assert!(!v.starts_with(&30)); /// ``` /// - /// Always returns `true` if `needle` is an empty slice: + /// Always returns `true` if `pattern` is an empty slice: /// /// ``` /// let v = &[10, 40, 30]; - /// assert!(v.starts_with(&[])); + /// assert!(v.ends_with(&[])); /// let v: &[u8] = &[]; - /// assert!(v.starts_with(&[])); + /// assert!(v.ends_with(&[])); /// ``` #[stable(feature = "rust1", since = "1.0.0")] #[must_use] - pub fn starts_with(&self, needle: &[T]) -> bool - where - T: PartialEq, - { - let n = needle.len(); - self.len() >= n && needle == &self[..n] + pub fn starts_with<'a, P: Pattern<&'a [T]>>(&'a self, pattern: P) -> bool { + pattern.is_prefix_of(self) } - /// Returns `true` if `needle` is a suffix of the slice. + /// Returns `true` if `pattern` matches at the end of the slice. /// /// # Examples /// @@ -2288,9 +2295,12 @@ impl [T] { /// assert!(v.ends_with(&[40, 30])); /// assert!(!v.ends_with(&[50])); /// assert!(!v.ends_with(&[50, 30])); + /// + /// assert!(v.ends_with(&30)); + /// assert!(!v.ends_with(&10)); /// ``` /// - /// Always returns `true` if `needle` is an empty slice: + /// Always returns `true` if `pattern` is an empty slice: /// /// ``` /// let v = &[10, 40, 30]; @@ -2300,20 +2310,15 @@ impl [T] { /// ``` #[stable(feature = "rust1", since = "1.0.0")] #[must_use] - pub fn ends_with(&self, needle: &[T]) -> bool - where - T: PartialEq, - { - let (m, n) = (self.len(), needle.len()); - m >= n && needle == &self[m - n..] + pub fn ends_with<'a, P>(&'a self, pattern: P) -> bool + where P: Pattern<&'a [T], Searcher: ReverseSearcher<&'a [T]>> { + pattern.is_suffix_of(self) } /// Returns a subslice with the prefix removed. /// - /// If the slice starts with `prefix`, returns the subslice after the prefix, wrapped in `Some`. - /// If `prefix` is empty, simply returns the original slice. - /// - /// If the slice does not start with `prefix`, returns `None`. + /// If `prefix` matches at the beginning of the slice, returns the subslice + /// after the prefix, wrapped in `Some`. Otherwise returns `None`. /// /// # Examples /// @@ -2324,34 +2329,20 @@ impl [T] { /// assert_eq!(v.strip_prefix(&[50]), None); /// assert_eq!(v.strip_prefix(&[10, 50]), None); /// - /// let prefix : &str = "he"; - /// assert_eq!(b"hello".strip_prefix(prefix.as_bytes()), + /// let prefix: &[u8] = b"he"; + /// assert_eq!(b"hello".strip_prefix(prefix), /// Some(b"llo".as_ref())); /// ``` #[must_use = "returns the subslice without modifying the original"] #[stable(feature = "slice_strip", since = "1.51.0")] - pub fn strip_prefix + ?Sized>(&self, prefix: &P) -> Option<&[T]> - where - T: PartialEq, - { - // This function will need rewriting if and when SlicePattern becomes more sophisticated. - let prefix = prefix.as_slice(); - let n = prefix.len(); - if n <= self.len() { - let (head, tail) = self.split_at(n); - if head == prefix { - return Some(tail); - } - } - None + pub fn strip_prefix<'a, P: Pattern<&'a [T]>>(&'a self, prefix: P) -> Option<&'a [T]> { + prefix.strip_prefix_of(self) } /// Returns a subslice with the suffix removed. /// - /// If the slice ends with `suffix`, returns the subslice before the suffix, wrapped in `Some`. - /// If `suffix` is empty, simply returns the original slice. - /// - /// If the slice does not end with `suffix`, returns `None`. + /// If `suffix` matches at the end of the slice, returns the subslice before + /// the suffix, wrapped in `Some`. Otherwise returns `None`. /// /// # Examples /// @@ -2364,20 +2355,200 @@ impl [T] { /// ``` #[must_use = "returns the subslice without modifying the original"] #[stable(feature = "slice_strip", since = "1.51.0")] - pub fn strip_suffix + ?Sized>(&self, suffix: &P) -> Option<&[T]> + pub fn strip_suffix<'a, P>(&'a self, suffix: P) -> Option<&'a [T]> where - T: PartialEq, + P: Pattern<&'a [T]>, +

>::Searcher: ReverseSearcher<&'a [T]>, { - // This function will need rewriting if and when SlicePattern becomes more sophisticated. - let suffix = suffix.as_slice(); - let (len, n) = (self.len(), suffix.len()); - if n <= len { - let (head, tail) = self.split_at(len - n); - if tail == suffix { - return Some(head); - } + suffix.strip_suffix_of(self) + } + + /// Returns index of the first occurrence of the specified `pattern` in the + /// slice. + /// + /// Returns [`None`] if the pattern doesn't match. + /// + /// # Examples + /// + /// ``` + /// # #![feature(pattern)] + /// + /// let nums = &[10, 40, 30, 40]; + /// assert_eq!(nums.find(&40), Some(1)); + /// assert_eq!(nums.find(&[40, 30]), Some(1)); + /// assert_eq!(nums.find(&42), None); + /// + /// let s = b"The swift brown fox"; + /// + /// assert_eq!(s.find(b"w"), Some(5)); + /// assert_eq!(s.find(&b'w'), Some(5)); + /// assert_eq!(s.find(b"swift"), Some(4)); + /// assert_eq!(s.find(b"slow"), None); + /// ``` + #[unstable(feature = "pattern", issue = "27721")] + pub fn find<'a, P: Pattern<&'a [T]>>(&'a self, pattern: P) -> Option { + pattern.into_searcher(self).next_match().map(|(i, _)| i) + } + + /// Returns index of the last occurrence of the specified `pattern` in the + /// slice. + /// + /// Returns [`None`] if the pattern doesn't match. + /// + /// # Examples + /// + /// ``` + /// # #![feature(pattern)] + /// + /// let nums = &[10, 40, 30, 40]; + /// assert_eq!(nums.find(&40), Some(1)); + /// assert_eq!(nums.find(&[40, 30]), Some(1)); + /// assert_eq!(nums.find(&42), None); + /// + /// let s = b"The swift brown fox"; + /// + /// assert_eq!(s.rfind(b"w"), Some(13)); + /// assert_eq!(s.rfind(&b'w'), Some(13)); + /// assert_eq!(s.rfind(b"swift"), Some(4)); + /// assert_eq!(s.rfind(b"slow"), None); + /// ``` + #[unstable(feature = "pattern", issue = "27721")] + pub fn rfind<'a, P>(&'a self, pat: P) -> Option + where + P: Pattern<&'a [T], Searcher: ReverseSearcher<&'a [T]>>, + { + pat.into_searcher(self).next_match_back().map(|(i, _)| i) + } + + /// Splits the slice on the first occurrence of the specified `delimiter` + /// [pattern] and returns prefix before delimiter and suffix after delimiter. + /// + /// Returns [`None`] if the pattern doesn't match. + /// + /// # Examples + /// + /// ``` + /// # #![feature(pattern)] + /// + /// let s = b"Durarara"; + /// + /// assert_eq!(s.split_once(b"ra"), Some((&b"Du"[..], &b"rara"[..]))); + /// assert_eq!(s.split_once(b"!"), None); + /// ``` + /// + /// [pattern]: crate::slice::pattern + #[unstable(feature = "pattern", issue = "27721")] + pub fn split_once<'a, P: Pattern<&'a [T]>>(&'a self, delimiter: P) -> Option<(&'a [T], &'a [T])> { + let (start, end) = delimiter.into_searcher(self).next_match()?; + // SAFETY: `Searcher` is known to return valid indices. + unsafe { Some((self.get_unchecked(..start), self.get_unchecked(end..))) } + } + + /// Splits the slice on the last occurrence of the specified `delimiter` + /// [pattern] and returns prefix before delimiter and suffix after delimiter. + /// + /// Returns [`None`] if the pattern doesn't match. + /// + /// # Examples + /// + /// Simple patterns: + /// + /// ``` + /// # #![feature(pattern)] + /// + /// let s = b"Durarara"; + /// + /// assert_eq!(s.rsplit_once(b"ra"), Some((&b"Durara"[..], &b""[..]))); + /// assert_eq!(s.rsplit_once(b"!"), None); + /// ``` + /// + /// [pattern]: crate::slice::pattern + #[unstable(feature = "pattern", issue = "27721")] + pub fn rsplit_once<'a, P>(&'a self, delimiter: P) -> Option<(&'a [T], &'a [T])> + where + P: Pattern<&'a [T], Searcher: ReverseSearcher<&'a [T]>>, + { + let (start, end) = delimiter.into_searcher(self).next_match_back()?; + // SAFETY: `Searcher` is known to return valid indices. + unsafe { Some((self.get_unchecked(..start), self.get_unchecked(end..))) } + } + + /// Returns a slice with all prefixes and suffixes that match the `pattern` + /// repeatedly removed. + /// + /// # Examples + /// + /// ``` + /// # #![feature(pattern)] + /// + /// let s = b"111foo1bar111".as_ref(); + /// assert_eq!(s.trim_matches(&b'1'), &b"foo1bar"[..]); + /// ``` + #[unstable(feature = "pattern", issue = "27721")] + pub fn trim_matches<'a, P>(&'a self, pat: P) -> &'a [T] + where + P: Pattern<&'a [T], Searcher: DoubleEndedSearcher<&'a [T]>>, + { + let mut i = 0; + let mut j = 0; + let mut matcher = pat.into_searcher(self); + if let Some((a, b)) = matcher.next_reject() { + i = a; + j = b; // Remember earliest known match, correct it below if + // last match is different + } + if let Some((_, b)) = matcher.next_reject_back() { + j = b; } - None + // SAFETY: `Searcher` is known to return valid indices. + unsafe { self.get_unchecked(i..j) } + } + + /// XXX TODO placeholder + /// + /// # Examples + /// + /// ``` + /// # #![feature(pattern)] + /// + /// let s = b"111foo1bar111".as_ref(); + /// assert_eq!(s.trim_start_matches(&b'1'), &b"foo1bar111"[..]); + /// assert_eq!(s.trim_start_matches(b"11".as_ref()), &b"1foo1bar111"[..]); + /// ``` + #[unstable(feature = "pattern", issue = "27721")] + pub fn trim_start_matches<'a, P: Pattern<&'a [T]>>(&'a self, pat: P) -> &'a [T] { + let mut i = self.len(); + let mut matcher = pat.into_searcher(self); + if let Some((a, _)) = matcher.next_reject() { + i = a; + } + // SAFETY: `Searcher` is known to return valid indices. + unsafe { self.get_unchecked(i..self.len()) } + } + + /// XXX TODO placeholder + /// + /// # Examples + /// + /// ``` + /// # #![feature(pattern)] + /// + /// let s = b"111foo1bar111".as_ref(); + /// assert_eq!(s.trim_end_matches(&b'1'), &b"111foo1bar"[..]); + /// assert_eq!(s.trim_end_matches(b"11".as_ref()), &b"111foo1bar1"[..]); + /// ``` + #[unstable(feature = "pattern", issue = "27721")] + pub fn trim_end_matches<'a, P>(&'a self, pat: P) -> &'a [T] + where + P: Pattern<&'a [T], Searcher: ReverseSearcher<&'a [T]>>, + { + let mut j = 0; + let mut matcher = pat.into_searcher(self); + if let Some((_, b)) = matcher.next_reject_back() { + j = b; + } + // SAFETY: `Searcher` is known to return valid indices. + unsafe { self.get_unchecked(0..j) } } /// Binary searches this slice for a given element. @@ -4407,38 +4578,6 @@ impl const Default for &mut [T] { } } -#[unstable(feature = "slice_pattern", reason = "stopgap trait for slice patterns", issue = "56345")] -/// Patterns in slices - currently, only used by `strip_prefix` and `strip_suffix`. At a future -/// point, we hope to generalise `core::str::Pattern` (which at the time of writing is limited to -/// `str`) to slices, and then this trait will be replaced or abolished. -pub trait SlicePattern { - /// The element type of the slice being matched on. - type Item; - - /// Currently, the consumers of `SlicePattern` need a slice. - fn as_slice(&self) -> &[Self::Item]; -} - -#[stable(feature = "slice_strip", since = "1.51.0")] -impl SlicePattern for [T] { - type Item = T; - - #[inline] - fn as_slice(&self) -> &[Self::Item] { - self - } -} - -#[stable(feature = "slice_strip", since = "1.51.0")] -impl SlicePattern for [T; N] { - type Item = T; - - #[inline] - fn as_slice(&self) -> &[Self::Item] { - self - } -} - /// This checks every index against each other, and against `len`. /// /// This will do `binomial(N + 1, 2) = N * (N + 1) / 2 = 0, 1, 3, 6, 10, ..` diff --git a/library/core/src/slice/pattern.rs b/library/core/src/slice/pattern.rs new file mode 100644 index 0000000000000..b0c4ddd8bbbaa --- /dev/null +++ b/library/core/src/slice/pattern.rs @@ -0,0 +1,777 @@ +#![unstable( + feature = "pattern", + reason = "API not fully fleshed out and ready to be stabilized", + issue = "27721" +)] + +use crate::pattern::{Haystack, Pattern, SearchStep}; +use crate::pattern; + +use super::cmp::SliceContains; + +///////////////////////////////////////////////////////////////////////////// +// Impl for Haystack +///////////////////////////////////////////////////////////////////////////// + +impl<'a, T> Haystack for &'a [T] { + type Cursor = usize; + + fn cursor_at_front(&self) -> usize { 0 } + fn cursor_at_back(&self) -> usize { self.len() } + + unsafe fn split_at_cursor_unchecked(self, pos: usize) -> (Self, Self) { + // SAFETY: Caller promises cursor is valid. + unsafe { (self.get_unchecked(..pos), self.get_unchecked(pos..)) } + } +} + +///////////////////////////////////////////////////////////////////////////// +// Impl Pattern for &T +///////////////////////////////////////////////////////////////////////////// + +/// Pattern implementation for searching for an element in a slice. +/// +/// The pattern matches a single element in a slice. +/// +/// # Examples +/// +/// ``` +/// # #![feature(pattern)] +/// +/// let nums = &[10, 40, 30, 40]; +/// assert_eq!(nums.find(&40), Some(1)); +/// assert_eq!(nums.find(&42), None); +/// ``` +impl<'hs, 'p, T: PartialEq> Pattern<&'hs [T]> for &'p T { + type Searcher = ElementSearcher<'hs, 'p, T>; + + fn into_searcher(self, haystack: &'hs [T]) -> Self::Searcher { + // TODO: We probably should specialise this for u8 and i8 the same way + // we specialise SliceContains + Self::Searcher::new(haystack, self) + } + + fn is_contained_in(self, haystack: &'hs [T]) -> bool { + T::slice_contains_element(haystack, self) + } + + fn is_prefix_of(self, haystack: &'hs [T]) -> bool { + haystack.first() == Some(self) + } + + fn is_suffix_of(self, haystack: &'hs [T]) -> bool { + haystack.last() == Some(self) + } + + fn strip_prefix_of(self, haystack: &'hs [T]) -> Option<&'hs [T]> { + match haystack.split_first() { + Some((first, tail)) if first == self => Some(tail), + _ => None, + } + } + + fn strip_suffix_of(self, haystack: &'hs [T]) -> Option<&'hs [T]> { + match haystack.split_last() { + Some((last, head)) if last == self => Some(head), + _ => None, + } + } +} + +#[derive(Clone, Debug)] +pub struct ElementSearcher<'hs, 'p, T> { + /// Haystack we’re searching in. + haystack: &'hs [T], + /// Element we’re searching for. + needle: &'p T, + /// Internal state of the searcher. + state: PredicateSearchState, +} + +impl<'hs, 'p, T> ElementSearcher<'hs, 'p, T> { + fn new(haystack: &'hs [T], needle: &'p T) -> Self { + Self { + haystack, + needle, + state: PredicateSearchState::new(haystack.len()) + } + } +} + +unsafe impl<'hs, 'p, T: PartialEq> pattern::Searcher<&'hs [T]> for ElementSearcher<'hs, 'p, T> { + fn haystack(&self) -> &'hs [T] { self.haystack } + + fn next(&mut self) -> SearchStep { + self.state.next(self.haystack, &mut |element| element == self.needle) + } + + fn next_match(&mut self) -> Option<(usize, usize)> { + self.state.next_match(self.haystack, &mut |element| element == self.needle) + } + + fn next_reject(&mut self) -> Option<(usize, usize)> { + self.state.next_reject(self.haystack, &mut |element| element == self.needle) + } +} + +unsafe impl<'hs, 'p, T: PartialEq> pattern::ReverseSearcher<&'hs [T]> for ElementSearcher<'hs, 'p, T> { + fn next_back(&mut self) -> SearchStep { + self.state.next_back(self.haystack, &mut |element| element == self.needle) + } + + fn next_match_back(&mut self) -> Option<(usize, usize)> { + self.state.next_match_back(self.haystack, &mut |element| element == self.needle) + } + + fn next_reject_back(&mut self) -> Option<(usize, usize)> { + self.state.next_reject_back(self.haystack, &mut |element| element == self.needle) + } +} + +impl<'hs, 'p, T: PartialEq> pattern::DoubleEndedSearcher<&'hs [T]> for ElementSearcher<'hs, 'p, T> {} + +///////////////////////////////////////////////////////////////////////////// +// Impl Pattern for &mut FnMut(&T) and &mut FnMut(T) +///////////////////////////////////////////////////////////////////////////// + +/* + + XXX TODO those don’t actually work because the implementations conflict with + implementation for &T. This is actually kind of a pain. It may mean that we + will need some kind of core::pattern::Pred wrapper. I think that would work + then. + + +/// Pattern implementation for searching for an element matching given +/// predicate. +/// +/// # Examples +/// +/// ``` +/// # #![feature(pattern)] +/// +/// let nums = &[10, 40, 30, 40]; +/// assert_eq!(nums.find(|n| n % 3 == 0), Some(2)); +/// assert_eq!(nums.find(|n| n % 2 == 1), None); +/// ``` +impl<'hs, 'p, T, F: FnMut(&T) -> bool> Pattern<&'hs [T]> for &'p F { + type Searcher = PredicateSearcher<'hs, 'p, T, F>; + + fn into_searcher(self, haystack: &'hs [T]) -> Self::Searcher { + Self::Searcher::new(haystack, self) + } + + fn is_contained_in(self, haystack: &'hs [T]) -> bool { + haystack.iter().any(self) + } + + fn is_prefix_of(self, haystack: &'hs [T]) -> bool { + haystack.first().filter(self).is_some() + } + + fn is_suffix_of(self, haystack: &'hs [T]) -> bool { + haystack.last().filter(self).is_some() + } + + fn strip_prefix_of(self, haystack: &'hs [T]) -> Option<&'hs [T]> { + match haystack.split_first() { + Some((first, tail)) if self(first) => Some(tail), + _ => None, + } + } + + fn strip_suffix_of(self, haystack: &'hs [T]) -> Option<&'hs [T]> { + match haystack.split_last() { + Some((last, head)) if self(last) => Some(head), + _ => None, + } + } +} + +pub struct PredicateSearcher<'hs, 'p, T, F> { + /// Haystack we’re searching in. + haystack: &'hs [T], + /// Predicate used to match elements. + pred: &'p mut F, + /// Internal state of the searcher. + state: PredicateSearchState, +} + +impl<'hs, 'p, T, F> PredicateSearcher<'hs, 'p, T, F> { + fn new(haystack: &'hs [T], pred: &mut F) -> Self { + let state = PredicateSearchState::new(haystack.len()); + Self { haystack, pred, state } + } +} + +unsafe impl<'hs, 'p, T, F: FnMut(&T) -> bool> pattern::Searcher<&'hs [T]> for PredicateSearcher<'hs, 'p, T, F> { + fn haystack(&self) -> &'hs [T] { self.haystack } + + fn next(&mut self) -> SearchStep { + self.state.next(|idx| self.pred(&self.haystack[idx])) + } + + fn next_match(&mut self) -> Option<(usize, usize)> { + self.state.next_match(|idx| self.pred(&self.haystack[idx])) + } + + fn next_reject(&mut self) -> Option<(usize, usize)> { + self.state.next_reject(|idx| self.pred(&self.haystack[idx])) + } +} + +unsafe impl<'hs, 'p, T, F: FnMut(&T) -> bool> pattern::ReverseSearcher<&'hs [T]> for PredicateSearcher<'hs, 'p, T, F> { + fn next_back(&mut self) -> SearchStep { + self.state.next_back(|idx| self.pred(&self.haystack[idx])) + } + + fn next_match_back(&mut self) -> Option<(usize, usize)> { + self.state.next_match_back(|idx| self.pred(&self.haystack[idx])) + } + + fn next_reject_back(&mut self) -> Option<(usize, usize)> { + self.state.next_reject_back(|idx| self.pred(&self.haystack[idx])) + } +} + +*/ + +///////////////////////////////////////////////////////////////////////////// +// Impl Pattern for &[T] and &[T; N] +///////////////////////////////////////////////////////////////////////////// + +/// Pattern implementation for searching a subslice in a slice. +/// +/// The pattern matches a subslice of a larger slice. An empty pattern matches +/// around every character in a slice. +/// +/// Note: Other than with slice patterns matching `str`, this pattern matches +/// a subslice rather than a single element of haystack being equal to element +/// of the pattern. +/// +/// # Examples +/// +/// ``` +/// # #![feature(pattern)] +/// use core::pattern::{Pattern, Searcher}; +/// +/// // Simple usage +/// let nums: &[i32] = &[10, 40, 30, 40]; +/// assert_eq!(nums.find(&[40]), Some(1)); +/// assert_eq!(nums.find(&[40, 30]), Some(1)); +/// assert_eq!(nums.find(&[42, 30]), None); +/// +/// // Empty pattern +/// let empty: &[i32] = &[]; +/// let mut s = empty.into_searcher(nums); +/// assert_eq!(s.next_match(), Some((0, 0))); +/// assert_eq!(s.next_match(), Some((1, 1))); +/// assert_eq!(s.next_match(), Some((2, 2))); +/// assert_eq!(s.next_match(), Some((3, 3))); +/// assert_eq!(s.next_match(), Some((4, 4))); +/// assert_eq!(s.next_match(), None); +/// +/// // Difference with str patterns. +/// assert_eq!("Foo".find(&['f', 'o']), Some(1)); +/// // -- "Foo" contains letter 'o' at index 1. +/// assert_eq!(b"Foo".find(&[b'f', b'o']), None); +/// // -- b"Foo" doesn’t contain subslice b"fo". +/// ``` +impl<'hs, 'p, T: PartialEq> Pattern<&'hs [T]> for &'p [T] { + type Searcher = Searcher<'hs, 'p, T>; + + fn into_searcher(self, haystack: &'hs [T]) -> Self::Searcher { + Searcher::new(haystack, self) + } + + fn is_contained_in(self, haystack: &'hs [T]) -> bool { + if self.len() == 0 { + true + } else if self.len() == 1 { + T::slice_contains_element(haystack, &self[0]) + } else if self.len() < haystack.len() { + T::slice_contains_slice(haystack, self) + } else if self.len() == haystack.len() { + self == haystack + } else { + false + } + } + #[inline] + fn is_prefix_of(self, haystack: &'hs [T]) -> bool { + haystack.get(..self.len()).map_or(false, |prefix| prefix == self) + } + + + #[inline] + fn is_suffix_of(self, haystack: &'hs [T]) -> bool { + haystack + .len() + .checked_sub(self.len()) + .map_or(false, |n| &haystack[n..] == self) + } + + #[inline] + fn strip_prefix_of(self, haystack: &'hs [T]) -> Option<&'hs [T]> { + self.is_prefix_of(haystack).then(|| { + // SAFETY: prefix was just verified to exist. + unsafe { haystack.get_unchecked(self.len()..) } + }) + } + + #[inline] + fn strip_suffix_of(self, haystack: &'hs [T]) -> Option<&'hs [T]> { + self.is_suffix_of(haystack).then(|| { + let n = haystack.len() - self.len(); + // SAFETY: suffix was just verified to exist. + unsafe { haystack.get_unchecked(..n) } + }) + } +} + +/// Pattern implementation for searching a subslice in a slice. +/// +/// This is identical to a slice pattern: the pattern matches a subslice of +/// a larger slice. An empty array matches around every character in a slice. +/// +/// Note: Other than with slice patterns matching `str`, this pattern matches +/// a subslice rather than a single element of haystack being equal to element +/// of the pattern. +/// +/// # Examples +/// +/// ``` +/// # #![feature(pattern)] +/// +/// let slice: &[u8] = b"The quick brown fox"; +/// assert_eq!(slice.find(b"quick"), Some(4)); +/// assert_eq!(slice.find(b"slow"), None); +/// assert_eq!(slice.find(b""), Some(0)); +/// ``` +impl<'hs, 'p, T: PartialEq, const N: usize> Pattern<&'hs [T]> for &'p [T; N] { + type Searcher = Searcher<'hs, 'p, T>; + + fn into_searcher(self, haystack: &'hs [T]) -> Searcher<'hs, 'p, T> { + Searcher::new(haystack, &self[..]) + } + + #[inline(always)] + fn is_contained_in(self, haystack: &'hs [T]) -> bool { + (&self[..]).is_contained_in(haystack) + } + + #[inline(always)] + fn is_prefix_of(self, haystack: &'hs [T]) -> bool { + (&self[..]).is_prefix_of(haystack) + } + + #[inline(always)] + fn is_suffix_of(self, haystack: &'hs [T]) -> bool { + (&self[..]).is_suffix_of(haystack) + } + + #[inline(always)] + fn strip_prefix_of(self, haystack: &'hs [T]) -> Option<&'hs [T]> { + (&self[..]).strip_prefix_of(haystack) + } + + #[inline(always)] + fn strip_suffix_of(self, haystack: &'hs [T]) -> Option<&'hs [T]> { + (&self[..]).strip_suffix_of(haystack) + } +} + +#[derive(Clone, Debug)] +/// Associated type for `<&'p [T] as Pattern<&'hs [T]>>::Searcher`. +pub struct Searcher<'hs, 'p, T> { + /// Haystack we’re searching in. + haystack: &'hs [T], + /// Subslice we’re searching for. + needle: &'p [T], + /// Internal state of the searcher. + state: SearcherState, +} + +#[derive(Clone, Debug)] +enum SearcherState { + Empty(EmptySearcherState), + Element(PredicateSearchState), + Naive(NaiveSearcherState), +} + +impl<'hs, 'p, T: PartialEq> Searcher<'hs, 'p, T> { + fn new(haystack: &'hs [T], needle: &'p [T]) -> Searcher<'hs, 'p, T> { + let state = match needle.len() { + 0 => SearcherState::Empty(EmptySearcherState::new(haystack.len())), + 1 => SearcherState::Element(PredicateSearchState::new(haystack.len())), + _ => SearcherState::Naive(NaiveSearcherState::new(haystack.len())), + }; + Searcher { haystack, needle, state } + } +} + +macro_rules! delegate { + ($method:ident -> $ret:ty) => { + fn $method(&mut self) -> $ret { + match &mut self.state { + SearcherState::Empty(state) => state.$method(), + SearcherState::Element(state) => state.$method(self.haystack, &mut |element| { + // SAFETY: SearcherState::Element is created if and only if + // needle.len() == 1. + element == unsafe { self.needle.get_unchecked(0) } + }), + SearcherState::Naive(state) => state.$method(self.haystack, self.needle), + } + } + } +} + +unsafe impl<'hs, 'p, T: PartialEq> pattern::Searcher<&'hs [T]> for Searcher<'hs, 'p, T> { + fn haystack(&self) -> &'hs [T] { + self.haystack + } + + delegate!(next -> SearchStep); + delegate!(next_match -> Option<(usize, usize)>); + delegate!(next_reject -> Option<(usize, usize)>); +} + +unsafe impl<'hs, 'p, T: PartialEq> pattern::ReverseSearcher<&'hs [T]> for Searcher<'hs, 'p, T> { + delegate!(next_back -> SearchStep); + delegate!(next_match_back -> Option<(usize, usize)>); + delegate!(next_reject_back -> Option<(usize, usize)>); +} + +///////////////////////////////////////////////////////////////////////////// +// Searching for an empty pattern +///////////////////////////////////////////////////////////////////////////// + +#[derive(Clone, Debug)] +struct EmptySearcherState { + start: usize, + end: usize, + is_match_fw: bool, + is_match_bw: bool, + // Needed in case of an empty haystack, see #85462 + is_finished: bool, +} + +impl EmptySearcherState { + fn new(haystack_length: usize) -> Self { + Self { + start: 0, + end: haystack_length, + is_match_fw: true, + is_match_bw: true, + is_finished: false, + } + } + + fn next(&mut self) -> SearchStep { + if self.is_finished { + return SearchStep::Done; + } + let is_match = self.is_match_fw; + self.is_match_fw = !self.is_match_fw; + let pos = self.start; + if is_match { + SearchStep::Match(pos, pos) + } else if self.start < self.end { + self.start += 1; + SearchStep::Reject(pos, pos + 1) + } else { + self.is_finished = true; + SearchStep::Done + } + } + + fn next_back(&mut self) -> SearchStep { + if self.is_finished { + return SearchStep::Done; + } + let is_match = self.is_match_bw; + self.is_match_bw = !self.is_match_bw; + let end = self.end; + if is_match { + SearchStep::Match(end, end) + } else if self.end <= self.start { + self.is_finished = true; + SearchStep::Done + } else { + self.end -= 1; + SearchStep::Reject(end - 1, end) + } + } + + fn next_match(&mut self) -> Option<(usize, usize)> { + pattern::loop_next::(|| self.next()) + } + + fn next_reject(&mut self) -> Option<(usize, usize)> { + pattern::loop_next::(|| self.next()) + } + + fn next_match_back(&mut self) -> Option<(usize, usize)> { + pattern::loop_next::(|| self.next_back()) + } + + fn next_reject_back(&mut self) -> Option<(usize, usize)> { + pattern::loop_next::(|| self.next_back()) + } +} + +///////////////////////////////////////////////////////////////////////////// +// Searching for a single element +///////////////////////////////////////////////////////////////////////////// + +/// State of a searcher which tests one element at a time using a provided +/// predicate. +/// +/// Matches are always one-element long. Rejects can be arbitrarily long. +#[derive(Clone, Debug)] +struct PredicateSearchState { + /// Position to start searching from. Updated as we find new matches. + start: usize, + /// Position to end searching at. Updated as we find new matches. + end: usize, + /// If true, we’re finished searching or haystack[start] is a match. + is_match_fw: bool, + /// If true, we’re finished searching or haystack[end-1] is a match. + is_match_bw: bool +} + +impl PredicateSearchState { + fn new(haystack_length: usize) -> Self { + Self { + start: 0, + end: haystack_length, + is_match_fw: false, + is_match_bw: false, + } + } + + fn next(&mut self, hs: &[T], pred: &mut F) -> SearchStep + where F: FnMut(&T) -> bool, + { + if self.start >= self.end { + return SearchStep::Done; + } + let count = if self.is_match_fw { + self.is_match_fw = false; + 0 + } else { + self.count(false, hs, pred) + }; + if count == 0 { + self.start += 1; + SearchStep::Match(self.start - 1, self.start) + } else { + self.is_match_fw = true; + let pos = self.start; + self.start += count; + SearchStep::Reject(pos, self.start) + } + } + + fn next_match(&mut self, hs: &[T], pred: &mut F) -> Option<(usize, usize)> + where F: FnMut(&T) -> bool, + { + pattern::loop_next::(|| self.next(hs, pred)) + } + + fn next_reject(&mut self, hs: &[T], pred: &mut F) -> Option<(usize, usize)> + where F: FnMut(&T) -> bool, + { + if self.start >= self.end { + return None; + } + + if self.is_match_fw { + self.start += 1; + } + self.start += self.count(true, hs, pred); + + let count = self.count(false, hs, pred); + if count == 0 { + None + } else { + self.is_match_fw = true; + let pos = self.start; + self.start += count; + Some((pos, self.start)) + } + } + + fn next_back(&mut self, hs: &[T], pred: &mut F) -> SearchStep + where F: FnMut(&T) -> bool + Copy, + { + if self.start >= self.end { + return SearchStep::Done + } + let count = if self.is_match_bw { + self.is_match_bw = false; + 0 + } else { + self.count_back(false, hs, pred) + }; + let pos = self.end; + if count == 0 { + self.end -= 1; + SearchStep::Match(self.end, pos) + } else { + self.is_match_bw = true; + self.end -= count; + SearchStep::Reject(self.end, pos) + } + } + + fn next_match_back(&mut self, hs: &[T], pred: &mut F) -> Option<(usize, usize)> + where F: FnMut(&T) -> bool + Copy, + { + pattern::loop_next::(|| self.next_back(hs, pred)) + } + + fn next_reject_back(&mut self, hs: &[T], pred: &mut F) -> Option<(usize, usize)> + where F: FnMut(&T) -> bool + Copy, + { + if self.start >= self.end { + return None; + } + + if self.is_match_fw { + self.end -= 1; + } + self.end -= self.count_back(true, hs, pred); + + let count = self.count_back(false, hs, pred); + if count == 0 { + None + } else { + self.is_match_bw = true; + let pos = self.end; + self.end -= count; + Some((self.end, pos)) + } + } + + fn count(&self, want: bool, hs: &[T], pred: &mut F) -> usize + where F: FnMut(&T) -> bool, + { + hs[self.start..self.end] + .iter() + .map(pred) + .take_while(|&matches| matches == want) + .count() + } + + fn count_back(&self, want: bool, hs: &[T], pred: &mut F) -> usize + where F: FnMut(&T) -> bool, + { + hs[self.start..self.end] + .iter() + .rev() + .map(pred) + .take_while(|&matches| matches == want) + .count() + } +} + +///////////////////////////////////////////////////////////////////////////// +// Searching for a subslice element +///////////////////////////////////////////////////////////////////////////// + +// TODO: Implement something smarter perhaps? Or have specialisation for +// different T? We’re not using core::str::pattern::TwoWaySearcher because it +// requires PartialOrd elements. Specifically, TwoWaySearcher::maximal_suffix +// and TwoWaySearcher::reverse_maximal_suffix methods compare elements. For the +// time being, use naive O(nk) search. +#[derive(Clone, Debug)] +pub(super) struct NaiveSearcherState { + start: usize, + end: usize, + is_match_fw: bool, + is_match_bw: bool, +} + +impl NaiveSearcherState { + pub(super) fn new(haystack_length: usize) -> Self { + Self { + start: 0, + end: haystack_length, + is_match_fw: false, + is_match_bw: false, + } + } + + pub(super) fn next(&mut self, haystack: &[T], needle: &[T]) -> SearchStep { + if self.end - self.start < needle.len() { + SearchStep::Done + } else if self.is_match_fw { + let pos = self.start; + self.start += needle.len(); + self.is_match_fw = false; + SearchStep::Match(pos, self.start) + } else { + let count = haystack[self.start..self.end] + .windows(needle.len()) + .take_while(|window| *window != needle) + .count(); + let pos = self.start; + if count == 0 { + self.start += needle.len(); + SearchStep::Match(pos, self.start) + } else { + let pos = self.start; + self.start += count; + // We’ve either reached the end of the haystack or start + // where it matches so maker is_match_fw. + self.is_match_fw = true; + SearchStep::Reject(pos, self.start) + } + } + } + + pub(super) fn next_back(&mut self, haystack: &[T], needle: &[T]) -> SearchStep { + if self.end - self.start < needle.len() { + SearchStep::Done + } else if self.is_match_bw { + let pos = self.end; + self.end -= needle.len(); + self.is_match_bw = false; + SearchStep::Match(self.end, pos) + } else { + let count = haystack[self.start..self.end] + .windows(needle.len()) + .rev() + .take_while(|window| *window != needle) + .count(); + let pos = self.end; + if count == 0 { + self.end -= needle.len(); + SearchStep::Match(self.end, pos) + } else { + self.end -= count; + // We’ve either reached the end of the haystack or start + // where it matches so maker is_match_bw. + self.is_match_bw = true; + SearchStep::Reject(self.end, pos) + } + } + } + + pub(super) fn next_match(&mut self, haystack: &[T], needle: &[T]) -> Option<(usize, usize)> { + pattern::loop_next::(|| self.next(haystack, needle)) + } + + pub(super) fn next_reject(&mut self, haystack: &[T], needle: &[T]) -> Option<(usize, usize)> { + pattern::loop_next::(|| self.next(haystack, needle)) + } + + pub(super) fn next_match_back(&mut self, haystack: &[T], needle: &[T]) -> Option<(usize, usize)> { + pattern::loop_next::(|| self.next_back(haystack, needle)) + } + + pub(super) fn next_reject_back(&mut self, haystack: &[T], needle: &[T]) -> Option<(usize, usize)> { + pattern::loop_next::(|| self.next_back(haystack, needle)) + } +} diff --git a/library/core/src/str/pattern.rs b/library/core/src/str/pattern.rs index f12f0c77f2207..d98780f960da1 100644 --- a/library/core/src/str/pattern.rs +++ b/library/core/src/str/pattern.rs @@ -50,7 +50,6 @@ )] use crate::cmp; -use crate::cmp::Ordering; use crate::fmt; use crate::pattern::{DoubleEndedSearcher, Haystack, Pattern, ReverseSearcher, Searcher, SearchStep}; use crate::slice::memchr; @@ -289,34 +288,37 @@ impl<'a> Pattern<&'a str> for char { haystack.as_bytes().contains(&(self as u8)) } else { let mut buffer = [0u8; 4]; - self.encode_utf8(&mut buffer).is_contained_in(haystack) + let chr: &str = self.encode_utf8(&mut buffer); + chr.is_contained_in(haystack) } } #[inline] fn is_prefix_of(self, haystack: &'a str) -> bool { - self.encode_utf8(&mut [0u8; 4]).is_prefix_of(haystack) + let mut buffer = [0u8; 4]; + let chr: &str = self.encode_utf8(&mut buffer); + chr.is_prefix_of(haystack) } #[inline] fn strip_prefix_of(self, haystack: &'a str) -> Option<&'a str> { - self.encode_utf8(&mut [0u8; 4]).strip_prefix_of(haystack) + let mut buffer = [0u8; 4]; + let chr: &str = self.encode_utf8(&mut buffer); + chr.strip_prefix_of(haystack) } #[inline] - fn is_suffix_of(self, haystack: &'a str) -> bool - where - Self::Searcher: ReverseSearcher<&'a str>, - { - self.encode_utf8(&mut [0u8; 4]).is_suffix_of(haystack) + fn is_suffix_of(self, haystack: &'a str) -> bool { + let mut buffer = [0u8; 4]; + let chr: &str = self.encode_utf8(&mut buffer); + chr.is_suffix_of(haystack) } #[inline] - fn strip_suffix_of(self, haystack: &'a str) -> Option<&'a str> - where - Self::Searcher: ReverseSearcher<&'a str>, - { - self.encode_utf8(&mut [0u8; 4]).strip_suffix_of(haystack) + fn strip_suffix_of(self, haystack: &'a str) -> Option<&'a str> { + let mut buffer = [0u8; 4]; + let chr: &str = self.encode_utf8(&mut buffer); + chr.strip_suffix_of(haystack) } } @@ -680,27 +682,7 @@ impl<'a, 'b> Pattern<&'a str> for &'b str { /// Checks whether the pattern matches anywhere in the haystack #[inline] fn is_contained_in(self, haystack: &'a str) -> bool { - if self.len() == 0 { - return true; - } - - match self.len().cmp(&haystack.len()) { - Ordering::Less => { - if self.len() == 1 { - return haystack.as_bytes().contains(&self.as_bytes()[0]); - } - - #[cfg(all(target_arch = "x86_64", target_feature = "sse2"))] - if self.len() <= 32 { - if let Some(result) = simd_contains(self, haystack) { - return result; - } - } - - self.into_searcher(haystack).next_match().is_some() - } - _ => self == haystack, - } + self.as_bytes().is_contained_in(haystack.as_bytes()) } /// Removes the pattern from the front of haystack, if it matches. @@ -1441,210 +1423,3 @@ impl TwoWayStrategy for RejectAndMatch { SearchStep::Match(a, b) } } - -/// SIMD search for short needles based on -/// Wojciech Muła's "SIMD-friendly algorithms for substring searching"[0] -/// -/// It skips ahead by the vector width on each iteration (rather than the needle length as two-way -/// does) by probing the first and last byte of the needle for the whole vector width -/// and only doing full needle comparisons when the vectorized probe indicated potential matches. -/// -/// Since the x86_64 baseline only offers SSE2 we only use u8x16 here. -/// If we ever ship std with for x86-64-v3 or adapt this for other platforms then wider vectors -/// should be evaluated. -/// -/// For haystacks smaller than vector-size + needle length it falls back to -/// a naive O(n*m) search so this implementation should not be called on larger needles. -/// -/// [0]: http://0x80.pl/articles/simd-strfind.html#sse-avx2 -#[cfg(all(target_arch = "x86_64", target_feature = "sse2"))] -#[inline] -fn simd_contains(needle: &str, haystack: &str) -> Option { - let needle = needle.as_bytes(); - let haystack = haystack.as_bytes(); - - debug_assert!(needle.len() > 1); - - use crate::ops::BitAnd; - use crate::simd::mask8x16 as Mask; - use crate::simd::u8x16 as Block; - use crate::simd::{SimdPartialEq, ToBitMask}; - - let first_probe = needle[0]; - let last_byte_offset = needle.len() - 1; - - // the offset used for the 2nd vector - let second_probe_offset = if needle.len() == 2 { - // never bail out on len=2 needles because the probes will fully cover them and have - // no degenerate cases. - 1 - } else { - // try a few bytes in case first and last byte of the needle are the same - let Some(second_probe_offset) = (needle.len().saturating_sub(4)..needle.len()).rfind(|&idx| needle[idx] != first_probe) else { - // fall back to other search methods if we can't find any different bytes - // since we could otherwise hit some degenerate cases - return None; - }; - second_probe_offset - }; - - // do a naive search if the haystack is too small to fit - if haystack.len() < Block::LANES + last_byte_offset { - return Some(haystack.windows(needle.len()).any(|c| c == needle)); - } - - let first_probe: Block = Block::splat(first_probe); - let second_probe: Block = Block::splat(needle[second_probe_offset]); - // first byte are already checked by the outer loop. to verify a match only the - // remainder has to be compared. - let trimmed_needle = &needle[1..]; - - // this #[cold] is load-bearing, benchmark before removing it... - let check_mask = #[cold] - |idx, mask: u16, skip: bool| -> bool { - if skip { - return false; - } - - // and so is this. optimizations are weird. - let mut mask = mask; - - while mask != 0 { - let trailing = mask.trailing_zeros(); - let offset = idx + trailing as usize + 1; - // SAFETY: mask is between 0 and 15 trailing zeroes, we skip one additional byte that was already compared - // and then take trimmed_needle.len() bytes. This is within the bounds defined by the outer loop - unsafe { - let sub = haystack.get_unchecked(offset..).get_unchecked(..trimmed_needle.len()); - if small_slice_eq(sub, trimmed_needle) { - return true; - } - } - mask &= !(1 << trailing); - } - return false; - }; - - let test_chunk = |idx| -> u16 { - // SAFETY: this requires at least LANES bytes being readable at idx - // that is ensured by the loop ranges (see comments below) - let a: Block = unsafe { haystack.as_ptr().add(idx).cast::().read_unaligned() }; - // SAFETY: this requires LANES + block_offset bytes being readable at idx - let b: Block = unsafe { - haystack.as_ptr().add(idx).add(second_probe_offset).cast::().read_unaligned() - }; - let eq_first: Mask = a.simd_eq(first_probe); - let eq_last: Mask = b.simd_eq(second_probe); - let both = eq_first.bitand(eq_last); - let mask = both.to_bitmask(); - - return mask; - }; - - let mut i = 0; - let mut result = false; - // The loop condition must ensure that there's enough headroom to read LANE bytes, - // and not only at the current index but also at the index shifted by block_offset - const UNROLL: usize = 4; - while i + last_byte_offset + UNROLL * Block::LANES < haystack.len() && !result { - let mut masks = [0u16; UNROLL]; - for j in 0..UNROLL { - masks[j] = test_chunk(i + j * Block::LANES); - } - for j in 0..UNROLL { - let mask = masks[j]; - if mask != 0 { - result |= check_mask(i + j * Block::LANES, mask, result); - } - } - i += UNROLL * Block::LANES; - } - while i + last_byte_offset + Block::LANES < haystack.len() && !result { - let mask = test_chunk(i); - if mask != 0 { - result |= check_mask(i, mask, result); - } - i += Block::LANES; - } - - // Process the tail that didn't fit into LANES-sized steps. - // This simply repeats the same procedure but as right-aligned chunk instead - // of a left-aligned one. The last byte must be exactly flush with the string end so - // we don't miss a single byte or read out of bounds. - let i = haystack.len() - last_byte_offset - Block::LANES; - let mask = test_chunk(i); - if mask != 0 { - result |= check_mask(i, mask, result); - } - - Some(result) -} - -/// Compares short slices for equality. -/// -/// It avoids a call to libc's memcmp which is faster on long slices -/// due to SIMD optimizations but it incurs a function call overhead. -/// -/// # Safety -/// -/// Both slices must have the same length. -#[cfg(all(target_arch = "x86_64", target_feature = "sse2"))] // only called on x86 -#[inline] -unsafe fn small_slice_eq(x: &[u8], y: &[u8]) -> bool { - debug_assert_eq!(x.len(), y.len()); - // This function is adapted from - // https://github.com/BurntSushi/memchr/blob/8037d11b4357b0f07be2bb66dc2659d9cf28ad32/src/memmem/util.rs#L32 - - // If we don't have enough bytes to do 4-byte at a time loads, then - // fall back to the naive slow version. - // - // Potential alternative: We could do a copy_nonoverlapping combined with a mask instead - // of a loop. Benchmark it. - if x.len() < 4 { - for (&b1, &b2) in x.iter().zip(y) { - if b1 != b2 { - return false; - } - } - return true; - } - // When we have 4 or more bytes to compare, then proceed in chunks of 4 at - // a time using unaligned loads. - // - // Also, why do 4 byte loads instead of, say, 8 byte loads? The reason is - // that this particular version of memcmp is likely to be called with tiny - // needles. That means that if we do 8 byte loads, then a higher proportion - // of memcmp calls will use the slower variant above. With that said, this - // is a hypothesis and is only loosely supported by benchmarks. There's - // likely some improvement that could be made here. The main thing here - // though is to optimize for latency, not throughput. - - // SAFETY: Via the conditional above, we know that both `px` and `py` - // have the same length, so `px < pxend` implies that `py < pyend`. - // Thus, derefencing both `px` and `py` in the loop below is safe. - // - // Moreover, we set `pxend` and `pyend` to be 4 bytes before the actual - // end of `px` and `py`. Thus, the final dereference outside of the - // loop is guaranteed to be valid. (The final comparison will overlap with - // the last comparison done in the loop for lengths that aren't multiples - // of four.) - // - // Finally, we needn't worry about alignment here, since we do unaligned - // loads. - unsafe { - let (mut px, mut py) = (x.as_ptr(), y.as_ptr()); - let (pxend, pyend) = (px.add(x.len() - 4), py.add(y.len() - 4)); - while px < pxend { - let vx = (px as *const u32).read_unaligned(); - let vy = (py as *const u32).read_unaligned(); - if vx != vy { - return false; - } - px = px.add(4); - py = py.add(4); - } - let vx = (pxend as *const u32).read_unaligned(); - let vy = (pyend as *const u32).read_unaligned(); - vx == vy - } -}