diff --git a/build.rs b/build.rs index 20a8d92..4af8879 100644 --- a/build.rs +++ b/build.rs @@ -11,6 +11,10 @@ fn main() { } fn cfg() { + println!("cargo:rerun-if-changed=build.rs"); + println!("cargo:rerun-if-env-changed=CARGO_CFG_TARGET_ARCH"); + println!("cargo:rerun-if-env-changed=CARGO_CFG_TARGET_FEATURE"); + let target_arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap_or_default(); let target_feature = env::var("CARGO_CFG_TARGET_FEATURE").unwrap_or_default(); diff --git a/src/fallback.rs b/src/fallback.rs index 0f7b21b..807cfcf 100644 --- a/src/fallback.rs +++ b/src/fallback.rs @@ -21,23 +21,26 @@ where } } -pub struct ByteSubstring<'a> { - needle: &'a [u8], +pub struct ByteSubstring { + needle: T, } -impl<'a> ByteSubstring<'a> { - pub /* const */ fn new(needle: &'a[u8]) -> Self { +impl ByteSubstring +where + T: AsRef<[u8]>, +{ + pub /* const */ fn new(needle: T) -> Self { ByteSubstring { needle } } #[cfg(feature = "pattern")] pub fn needle_len(&self) -> usize { - self.needle.len() + self.needle.as_ref().len() } pub fn find(&self, haystack: &[u8]) -> Option { haystack - .windows(self.needle.len()) - .position(|window| window == self.needle) + .windows(self.needle.as_ref().len()) + .position(|window| window == self.needle.as_ref()) } } diff --git a/src/lib.rs b/src/lib.rs index 7c30098..018fba2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -142,7 +142,6 @@ //! | s.find("xyzzy") | 5391 MB/s | #[cfg(test)] -#[macro_use] extern crate lazy_static; #[cfg(test)] extern crate memmap; @@ -151,8 +150,6 @@ extern crate proptest; #[cfg(test)] extern crate region; -use std::marker::PhantomData; - include!(concat!(env!("OUT_DIR"), "/src/macros.rs")); #[cfg(any(jetscii_sse4_2 = "yes", jetscii_sse4_2 = "maybe"))] @@ -164,49 +161,18 @@ mod fallback; #[cfg(feature = "pattern")] mod pattern; -macro_rules! dispatch { - (simd: $simd:expr,fallback: $fallback:expr,) => { - // If we can tell at compile time that we have support, - // call the optimized code directly. - #[cfg(jetscii_sse4_2 = "yes")] - { - $simd - } - - // If we can tell at compile time that we will *never* have - // support, call the fallback directly. - #[cfg(jetscii_sse4_2 = "no")] - { - $fallback - } - - // Otherwise, we will be run on a machine with or without - // support, so we perform runtime detection. - #[cfg(jetscii_sse4_2 = "maybe")] - { - if is_x86_feature_detected!("sse4.2") { - $simd - } else { - $fallback - } - } - }; -} - /// Searches a slice for a set of bytes. Up to 16 bytes may be used. -pub struct Bytes +pub enum Bytes where F: Fn(u8) -> bool, { #[cfg(any(jetscii_sse4_2 = "yes", jetscii_sse4_2 = "maybe"))] - simd: simd::Bytes, + // Since we might not use the fallback implementation, we add + // PhantomData to avoid an unused type parameter + SIMD(simd::Bytes, core::marker::PhantomData), #[cfg(any(jetscii_sse4_2 = "maybe", jetscii_sse4_2 = "no"))] - fallback: fallback::Bytes, - - // Since we might not use the fallback implementation, we add this - // to avoid unused type parameters. - _fallback: PhantomData, + Fallback(fallback::Bytes), } impl Bytes @@ -221,23 +187,34 @@ where /// the same bytes as in the array. #[allow(unused_variables)] pub /* const */ fn new(bytes: [u8; 16], len: i32, fallback: F) -> Self { - Bytes { - #[cfg(any(jetscii_sse4_2 = "yes", jetscii_sse4_2 = "maybe"))] - simd: simd::Bytes::new(bytes, len), + #[cfg(jetscii_sse4_2 = "yes")] + { + Self::SIMD(simd::Bytes::new(bytes, len), Default::default()) + } - #[cfg(any(jetscii_sse4_2 = "maybe", jetscii_sse4_2 = "no"))] - fallback: fallback::Bytes::new(fallback), + #[cfg(jetscii_sse4_2 = "no")] + { + Self::Fallback(fallback::Bytes::new(fallback)) + } - _fallback: PhantomData, + #[cfg(jetscii_sse4_2 = "maybe")] + { + if is_x86_feature_detected!("sse4.2") { + Self::SIMD(simd::Bytes::new(bytes, len), Default::default()) + } else { + Self::Fallback(fallback::Bytes::new(fallback)) + } } } /// Searches the slice for the first matching byte in the set. #[inline] pub fn find(&self, haystack: &[u8]) -> Option { - dispatch! { - simd: unsafe { self.simd.find(haystack) }, - fallback: self.fallback.find(haystack), + match self { + #[cfg(any(jetscii_sse4_2 = "yes", jetscii_sse4_2 = "maybe"))] + Self::SIMD(needle, _) => unsafe { needle.find(haystack) }, + #[cfg(any(jetscii_sse4_2 = "maybe", jetscii_sse4_2 = "no"))] + Self::Fallback(needle) => needle.find(haystack), } } } @@ -282,61 +259,88 @@ where /// A convenience type that can be used in a constant or static. pub type AsciiCharsConst = AsciiChars bool>; -/// Searches a slice for the first occurence of the subslice. -pub struct ByteSubstring<'a> { +/// Searches a slice for the first occurrence of the subslice. +pub enum ByteSubstring { #[cfg(any(jetscii_sse4_2 = "yes", jetscii_sse4_2 = "maybe"))] - simd: simd::ByteSubstring<'a>, + SIMD(simd::ByteSubstring), #[cfg(any(jetscii_sse4_2 = "maybe", jetscii_sse4_2 = "no"))] - fallback: fallback::ByteSubstring<'a>, + Fallback(fallback::ByteSubstring), } -impl<'a> ByteSubstring<'a> { - pub /* const */ fn new(needle: &'a [u8]) -> Self { - ByteSubstring { - #[cfg(any(jetscii_sse4_2 = "yes", jetscii_sse4_2 = "maybe"))] - simd: simd::ByteSubstring::new(needle), +impl ByteSubstring +where + T: AsRef<[u8]>, +{ + pub /* const */ fn new(needle: T) -> Self { + #[cfg(jetscii_sse4_2 = "yes")] + { + Self::SIMD(simd::ByteSubstring::new(needle)) + } - #[cfg(any(jetscii_sse4_2 = "maybe", jetscii_sse4_2 = "no"))] - fallback: fallback::ByteSubstring::new(needle), + #[cfg(jetscii_sse4_2 = "no")] + { + Self::Fallback(fallback::ByteSubstring::new(needle)) + } + + #[cfg(jetscii_sse4_2 = "maybe")] + if is_x86_feature_detected!("sse4.2") { + Self::SIMD(simd::ByteSubstring::new(needle)) + } else { + Self::Fallback(fallback::ByteSubstring::new(needle)) } } #[cfg(feature = "pattern")] fn needle_len(&self) -> usize { - dispatch! { - simd: self.simd.needle_len(), - fallback: self.fallback.needle_len(), + match self { + #[cfg(any(jetscii_sse4_2 = "yes", jetscii_sse4_2 = "maybe"))] + Self::SIMD(needle) => needle.needle_len(), + #[cfg(any(jetscii_sse4_2 = "maybe", jetscii_sse4_2 = "no"))] + Self::Fallback(needle) => needle.needle_len(), } } - /// Searches the slice for the first occurence of the subslice. + /// Searches the slice for the first occurrence of the subslice. #[inline] pub fn find(&self, haystack: &[u8]) -> Option { - dispatch! { - simd: unsafe { self.simd.find(haystack) }, - fallback: self.fallback.find(haystack), + match self { + #[cfg(any(jetscii_sse4_2 = "yes", jetscii_sse4_2 = "maybe"))] + Self::SIMD(needle) => unsafe { needle.find(haystack) }, + #[cfg(any(jetscii_sse4_2 = "maybe", jetscii_sse4_2 = "no"))] + Self::Fallback(needle) => needle.find(haystack), } } } /// A convenience type that can be used in a constant or static. -pub type ByteSubstringConst = ByteSubstring<'static>; +pub type ByteSubstringConst = ByteSubstring<&'static [u8]>; -/// Searches a string for the first occurence of the substring. -pub struct Substring<'a>(ByteSubstring<'a>); +/// Searches a string for the first occurrence of the substring. +pub struct Substring(ByteSubstring); -impl<'a> Substring<'a> { +impl<'a> Substring<&'a [u8]> { pub /* const */ fn new(needle: &'a str) -> Self { Substring(ByteSubstring::new(needle.as_bytes())) } +} + +impl Substring> { + pub fn new_owned(needle: String) -> Self { + Substring(ByteSubstring::new(needle.into_bytes())) + } +} +impl Substring +where + T: AsRef<[u8]>, +{ #[cfg(feature = "pattern")] fn needle_len(&self) -> usize { self.0.needle_len() } - /// Searches the string for the first occurence of the substring. + /// Searches the string for the first occurrence of the substring. #[inline] pub fn find(&self, haystack: &str) -> Option { self.0.find(haystack.as_bytes()) @@ -344,11 +348,16 @@ impl<'a> Substring<'a> { } /// A convenience type that can be used in a constant or static. -pub type SubstringConst = Substring<'static>; +pub type SubstringConst = Substring<&'static [u8]>; +/// A convenience type to save the need to specify that the inner object is a slice. +pub type SubstringRef<'a> = Substring<&'a [u8]>; +/// A convenience type to save the need to specify that the inner object is owned. +pub type SubstringOwned = Substring>; #[cfg(all(test, feature = "benchmarks"))] mod bench { extern crate test; + use lazy_static::lazy_static; use super::*; @@ -476,7 +485,7 @@ mod bench { } lazy_static! { - static ref XYZZY: Substring<'static> = Substring::new("xyzzy"); + static ref XYZZY: SubstringConst = Substring::new("xyzzy"); } fn bench_substring(b: &mut test::Bencher, f: F) diff --git a/src/pattern.rs b/src/pattern.rs index c0bbb20..365bbb0 100644 --- a/src/pattern.rs +++ b/src/pattern.rs @@ -105,8 +105,11 @@ where /// substring to search for is the empty string. It will never /// match. This behavior may change in the future to more closely /// align with the standard library. -impl<'n, 'h> Pattern<'h> for Substring<'n> { - type Searcher = SubstringSearcher<'n, 'h>; +impl<'h, T> Pattern<'h> for Substring +where + T: AsRef<[u8]>, +{ + type Searcher = SubstringSearcher<'h, T>; fn into_searcher(self, haystack: &'h str) -> Self::Searcher { SubstringSearcher { @@ -116,21 +119,31 @@ impl<'n, 'h> Pattern<'h> for Substring<'n> { } } -pub struct SubstringSearcher<'n, 'h> { +pub struct SubstringSearcher<'h, T> +where + T: AsRef<[u8]>, +{ searcher: CoreSearcher<'h>, - finder: Substring<'n>, + finder: Substring, } -impl<'a, 'n> PatternCore for &'a Substring<'n> { +impl<'a, T> PatternCore for &'a Substring +where + T: AsRef<[u8]>, +{ fn find(&self, haystack: &str) -> Option { Substring::find(self, haystack) } + fn len(&self) -> usize { self.needle_len() } } -unsafe impl<'n, 'h> Searcher<'h> for SubstringSearcher<'n, 'h> { +unsafe impl<'h, T> Searcher<'h> for SubstringSearcher<'h, T> +where + T: AsRef<[u8]> +{ fn haystack(&self) -> &'h str { self.searcher.haystack } @@ -225,6 +238,18 @@ mod test { assert_eq!(haystack.find(us), haystack.find(them)); } + + #[test] + fn owned_works_as_find_does_for_substrings( + (needle, haystack) in (any::(), any::()) + ) { + prop_assume!(!needle.is_empty()); + + let us = Substring::new_owned(needle.to_string()); + let them: &str = &needle; + + assert_eq!(haystack.find(us), haystack.find(them)); + } } /// I'm not sure if it's worth it to try to match the standard diff --git a/src/simd.rs b/src/simd.rs index b03a7c0..092506d 100644 --- a/src/simd.rs +++ b/src/simd.rs @@ -260,29 +260,39 @@ impl<'b> PackedCompareControl for &'b Bytes { } } -pub struct ByteSubstring<'a> { - complete_needle: &'a [u8], +struct PackedNeedle { needle: __m128i, needle_len: i32, } -impl<'a> ByteSubstring<'a> { - pub /* const */ fn new(needle: &'a[u8]) -> Self { +pub struct ByteSubstring { + complete_needle: T, + packed_needle: PackedNeedle, +} + +impl ByteSubstring +where + T: AsRef<[u8]>, +{ + pub /* const */ fn new(needle: T) -> Self { use std::cmp; let mut simd_needle = [0; 16]; - let len = cmp::min(simd_needle.len(), needle.len()); - simd_needle[..len].copy_from_slice(&needle[..len]); - ByteSubstring { - complete_needle: needle, + let len = cmp::min(simd_needle.len(), needle.as_ref().len()); + simd_needle[..len].copy_from_slice(&needle.as_ref()[..len]); + let packed_needle = PackedNeedle { needle: unsafe { TransmuteToSimd { bytes: simd_needle }.simd }, needle_len: len as i32, + }; + ByteSubstring { + complete_needle: needle, + packed_needle, } } #[cfg(feature = "pattern")] pub fn needle_len(&self) -> usize { - self.complete_needle.len() + self.complete_needle.as_ref().len() } #[inline] @@ -290,10 +300,13 @@ impl<'a> ByteSubstring<'a> { pub unsafe fn find(&self, haystack: &[u8]) -> Option { let mut offset = 0; - while let Some(idx) = find(PackedCompare::<_, _SIDD_CMP_EQUAL_ORDERED>(self), &haystack[offset..]) { + while let Some(idx) = find( + PackedCompare::<_, _SIDD_CMP_EQUAL_ORDERED>(&self.packed_needle), + &haystack[offset..], + ) { let abs_offset = offset + idx; // Found a match, but is it really? - if haystack[abs_offset..].starts_with(self.complete_needle) { + if haystack[abs_offset..].starts_with(self.complete_needle.as_ref()) { return Some(abs_offset); } @@ -305,10 +318,11 @@ impl<'a> ByteSubstring<'a> { } } -impl<'a, 'b> PackedCompareControl for &'b ByteSubstring<'a> { +impl<'b> PackedCompareControl for &'b PackedNeedle { fn needle(&self) -> __m128i { self.needle } + fn needle_len(&self) -> i32 { self.needle_len } @@ -320,6 +334,7 @@ mod test { use std::{fmt, str}; use memmap::MmapMut; use region::Protection; + use lazy_static::lazy_static; use super::*;