diff --git a/Cargo.toml b/Cargo.toml index 36bbdf3..b081acb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,10 @@ [package] -authors = ["Andre Bogus ", "Joshua Landau "] +authors = [ + "Andre Bogus ", + "Joshua Landau ", +] description = "count occurrences of a given byte, or the number of UTF-8 code points, in a byte slice, fast" -edition = "2018" +edition = "2021" name = "bytecount" version = "0.6.3" license = "Apache-2.0/MIT" @@ -28,7 +31,7 @@ packed_simd = { version = "0.3.8", package = "packed_simd_2", optional = true } [dev-dependencies] quickcheck = "1.0" rand = "0.8" -criterion = { version = "0.4", default-features = false } +criterion = { version = "0.5.1", default-features = false } [[bench]] name = "bench" diff --git a/README.md b/README.md index 62e1633..78e68d6 100644 --- a/README.md +++ b/README.md @@ -15,11 +15,11 @@ To use bytecount in your crate, if you have [cargo-edit](https://github.com/kill `Cargo.toml` to add `bytecount = 0.6.3` to your `[dependencies]` section. In your crate root (`lib.rs` or `main.rs`, depending on if you are writing a -library or application), add `extern crate bytecount;`. Now you can simply use +library or application), add `use bytecount;`. Now you can simply use `bytecount::count` as follows: ```Rust -extern crate bytecount; +use bytecount; fn main() { let mytext = "some potentially large text, perhaps read from disk?"; @@ -31,7 +31,7 @@ fn main() { bytecount supports two features to make use of modern CPU's features to speed up counting considerably. To allow your users to use them, add the following to your `Cargo.toml`: -``` +```toml [features] runtime-dispatch-simd = ["bytecount/runtime-dispatch-simd"] generic-simd = ["bytecount/generic-simd"] diff --git a/benches/bench.rs b/benches/bench.rs index 2e091fc..6691bcf 100644 --- a/benches/bench.rs +++ b/benches/bench.rs @@ -1,14 +1,9 @@ -#[macro_use] -extern crate criterion; -extern crate bytecount; -extern crate rand; - -use criterion::{Bencher, BenchmarkId, Criterion}; +use criterion::{criterion_group, criterion_main, Bencher, BenchmarkId, Criterion}; use rand::RngCore; use std::env; use std::time::Duration; -use bytecount::{count, naive_count, naive_count_32, naive_num_chars, num_chars}; +use bytecount::{self, count, naive_count, naive_count_32, naive_num_chars, num_chars}; fn random_bytes(len: usize) -> Vec { let mut result = vec![0; len]; diff --git a/src/integer_simd.rs b/src/integer_simd.rs index 48f2ee8..0604194 100644 --- a/src/integer_simd.rs +++ b/src/integer_simd.rs @@ -13,7 +13,7 @@ unsafe fn usize_load_unchecked(bytes: &[u8], offset: usize) -> usize { ptr::copy_nonoverlapping( bytes.as_ptr().add(offset), &mut output as *mut usize as *mut u8, - mem::size_of::() + mem::size_of::(), ); output } @@ -65,11 +65,17 @@ pub fn chunk_count(haystack: &[u8], needle: u8) -> usize { // 8 let mut counts = 0; for i in 0..(haystack.len() - offset) / chunksize { - counts += bytewise_equal(usize_load_unchecked(haystack, offset + i * chunksize), needles); + counts += bytewise_equal( + usize_load_unchecked(haystack, offset + i * chunksize), + needles, + ); } if haystack.len() % 8 != 0 { let mask = usize::from_le(!(!0 >> ((haystack.len() % chunksize) * 8))); - counts += bytewise_equal(usize_load_unchecked(haystack, haystack.len() - chunksize), needles) & mask; + counts += bytewise_equal( + usize_load_unchecked(haystack, haystack.len() - chunksize), + needles, + ) & mask; } count += sum_usize(counts); @@ -98,11 +104,15 @@ pub fn chunk_num_chars(utf8_chars: &[u8]) -> usize { // 8 let mut counts = 0; for i in 0..(utf8_chars.len() - offset) / chunksize { - counts += is_leading_utf8_byte(usize_load_unchecked(utf8_chars, offset + i * chunksize)); + counts += + is_leading_utf8_byte(usize_load_unchecked(utf8_chars, offset + i * chunksize)); } if utf8_chars.len() % 8 != 0 { let mask = usize::from_le(!(!0 >> ((utf8_chars.len() % chunksize) * 8))); - counts += is_leading_utf8_byte(usize_load_unchecked(utf8_chars, utf8_chars.len() - chunksize)) & mask; + counts += is_leading_utf8_byte(usize_load_unchecked( + utf8_chars, + utf8_chars.len() - chunksize, + )) & mask; } count += sum_usize(counts); diff --git a/src/lib.rs b/src/lib.rs index ef4235c..f2baac5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -32,7 +32,6 @@ //! still on small strings. #![deny(missing_docs)] - #![cfg_attr(not(feature = "runtime-dispatch-simd"), no_std)] #[cfg(not(feature = "runtime-dispatch-simd"))] @@ -45,7 +44,10 @@ pub use naive::*; mod integer_simd; #[cfg(any( - all(feature = "runtime-dispatch-simd", any(target_arch = "x86", target_arch = "x86_64")), + all( + feature = "runtime-dispatch-simd", + any(target_arch = "x86", target_arch = "x86_64") + ), feature = "generic-simd" ))] mod simd; @@ -64,7 +66,9 @@ pub fn count(haystack: &[u8], needle: u8) -> usize { #[cfg(all(feature = "runtime-dispatch-simd", target_arch = "x86_64"))] { if is_x86_feature_detected!("avx2") { - unsafe { return simd::x86_avx2::chunk_count(haystack, needle); } + unsafe { + return simd::x86_avx2::chunk_count(haystack, needle); + } } } @@ -80,7 +84,9 @@ pub fn count(haystack: &[u8], needle: u8) -> usize { ))] { if is_x86_feature_detected!("sse2") { - unsafe { return simd::x86_sse2::chunk_count(haystack, needle); } + unsafe { + return simd::x86_sse2::chunk_count(haystack, needle); + } } } } @@ -109,7 +115,9 @@ pub fn num_chars(utf8_chars: &[u8]) -> usize { #[cfg(all(feature = "runtime-dispatch-simd", target_arch = "x86_64"))] { if is_x86_feature_detected!("avx2") { - unsafe { return simd::x86_avx2::chunk_num_chars(utf8_chars); } + unsafe { + return simd::x86_avx2::chunk_num_chars(utf8_chars); + } } } @@ -125,7 +133,9 @@ pub fn num_chars(utf8_chars: &[u8]) -> usize { ))] { if is_x86_feature_detected!("sse2") { - unsafe { return simd::x86_sse2::chunk_num_chars(utf8_chars); } + unsafe { + return simd::x86_sse2::chunk_num_chars(utf8_chars); + } } } } diff --git a/src/naive.rs b/src/naive.rs index 315c4b6..e3f6cf6 100644 --- a/src/naive.rs +++ b/src/naive.rs @@ -22,7 +22,9 @@ pub fn naive_count_32(haystack: &[u8], needle: u8) -> usize { /// assert_eq!(number_of_spaces, 6); /// ``` pub fn naive_count(utf8_chars: &[u8], needle: u8) -> usize { - utf8_chars.iter().fold(0, |n, c| n + (*c == needle) as usize) + utf8_chars + .iter() + .fold(0, |n, c| n + (*c == needle) as usize) } /// Count the number of UTF-8 encoded Unicode codepoints in a slice of bytes, simple @@ -38,5 +40,8 @@ pub fn naive_count(utf8_chars: &[u8], needle: u8) -> usize { /// assert_eq!(char_count, 4); /// ``` pub fn naive_num_chars(utf8_chars: &[u8]) -> usize { - utf8_chars.iter().filter(|&&byte| (byte >> 6) != 0b10).count() + utf8_chars + .iter() + .filter(|&&byte| (byte >> 6) != 0b10) + .count() } diff --git a/src/simd/generic.rs b/src/simd/generic.rs index 2031e73..87c8d5f 100644 --- a/src/simd/generic.rs +++ b/src/simd/generic.rs @@ -1,4 +1,4 @@ -extern crate packed_simd; +use packed_simd; #[cfg(not(feature = "runtime-dispatch-simd"))] use core::mem; @@ -8,10 +8,9 @@ use std::mem; use self::packed_simd::{u8x32, u8x64, FromCast}; const MASK: [u8; 64] = [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, ]; unsafe fn u8x64_from_offset(slice: &[u8], offset: usize) -> u8x64 { @@ -66,15 +65,17 @@ pub fn chunk_count(haystack: &[u8], needle: u8) -> usize { // 32 let mut counts = u8x32::splat(0); for i in 0..(haystack.len() - offset) / 32 { - counts -= u8x32::from_cast(u8x32_from_offset(haystack, offset + i * 32).eq(needles_x32)); + counts -= + u8x32::from_cast(u8x32_from_offset(haystack, offset + i * 32).eq(needles_x32)); } count += sum_x32(&counts); // Straggler; need to reset counts because prior loop can run 255 times counts = u8x32::splat(0); if haystack.len() % 32 != 0 { - counts -= u8x32::from_cast(u8x32_from_offset(haystack, haystack.len() - 32).eq(needles_x32)) & - u8x32_from_offset(&MASK, haystack.len() % 32); + counts -= + u8x32::from_cast(u8x32_from_offset(haystack, haystack.len() - 32).eq(needles_x32)) + & u8x32_from_offset(&MASK, haystack.len() % 32); } count += sum_x32(&counts); @@ -127,8 +128,9 @@ pub fn chunk_num_chars(utf8_chars: &[u8]) -> usize { // Straggler; need to reset counts because prior loop can run 255 times counts = u8x32::splat(0); if utf8_chars.len() % 32 != 0 { - counts -= is_leading_utf8_byte_x32(u8x32_from_offset(utf8_chars, utf8_chars.len() - 32)) & - u8x32_from_offset(&MASK, utf8_chars.len() % 32); + counts -= + is_leading_utf8_byte_x32(u8x32_from_offset(utf8_chars, utf8_chars.len() - 32)) + & u8x32_from_offset(&MASK, utf8_chars.len() % 32); } count += sum_x32(&counts); diff --git a/src/simd/x86_avx2.rs b/src/simd/x86_avx2.rs index 90a55c0..ea191e2 100644 --- a/src/simd/x86_avx2.rs +++ b/src/simd/x86_avx2.rs @@ -1,14 +1,6 @@ use std::arch::x86_64::{ - __m256i, - _mm256_and_si256, - _mm256_cmpeq_epi8, - _mm256_extract_epi64, - _mm256_loadu_si256, - _mm256_sad_epu8, - _mm256_set1_epi8, - _mm256_setzero_si256, - _mm256_sub_epi8, - _mm256_xor_si256, + __m256i, _mm256_and_si256, _mm256_cmpeq_epi8, _mm256_extract_epi64, _mm256_loadu_si256, + _mm256_sad_epu8, _mm256_set1_epi8, _mm256_setzero_si256, _mm256_sub_epi8, _mm256_xor_si256, }; #[target_feature(enable = "avx2")] @@ -22,10 +14,9 @@ pub unsafe fn mm256_cmpneq_epi8(a: __m256i, b: __m256i) -> __m256i { } const MASK: [u8; 64] = [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, ]; #[target_feature(enable = "avx2")] @@ -36,10 +27,10 @@ unsafe fn mm256_from_offset(slice: &[u8], offset: usize) -> __m256i { #[target_feature(enable = "avx2")] unsafe fn sum(u8s: &__m256i) -> usize { let sums = _mm256_sad_epu8(*u8s, _mm256_setzero_si256()); - ( - _mm256_extract_epi64(sums, 0) + _mm256_extract_epi64(sums, 1) + - _mm256_extract_epi64(sums, 2) + _mm256_extract_epi64(sums, 3) - ) as usize + (_mm256_extract_epi64(sums, 0) + + _mm256_extract_epi64(sums, 1) + + _mm256_extract_epi64(sums, 2) + + _mm256_extract_epi64(sums, 3)) as usize } #[target_feature(enable = "avx2")] @@ -57,7 +48,7 @@ pub unsafe fn chunk_count(haystack: &[u8], needle: u8) -> usize { for _ in 0..255 { counts = _mm256_sub_epi8( counts, - _mm256_cmpeq_epi8(mm256_from_offset(haystack, offset), needles) + _mm256_cmpeq_epi8(mm256_from_offset(haystack, offset), needles), ); offset += 32; } @@ -70,7 +61,7 @@ pub unsafe fn chunk_count(haystack: &[u8], needle: u8) -> usize { for _ in 0..128 { counts = _mm256_sub_epi8( counts, - _mm256_cmpeq_epi8(mm256_from_offset(haystack, offset), needles) + _mm256_cmpeq_epi8(mm256_from_offset(haystack, offset), needles), ); offset += 32; } @@ -82,7 +73,7 @@ pub unsafe fn chunk_count(haystack: &[u8], needle: u8) -> usize { for i in 0..(haystack.len() - offset) / 32 { counts = _mm256_sub_epi8( counts, - _mm256_cmpeq_epi8(mm256_from_offset(haystack, offset + i * 32), needles) + _mm256_cmpeq_epi8(mm256_from_offset(haystack, offset + i * 32), needles), ); } if haystack.len() % 32 != 0 { @@ -90,8 +81,8 @@ pub unsafe fn chunk_count(haystack: &[u8], needle: u8) -> usize { counts, _mm256_and_si256( _mm256_cmpeq_epi8(mm256_from_offset(haystack, haystack.len() - 32), needles), - mm256_from_offset(&MASK, haystack.len() % 32) - ) + mm256_from_offset(&MASK, haystack.len() % 32), + ), ); } count += sum(&counts); @@ -101,7 +92,10 @@ pub unsafe fn chunk_count(haystack: &[u8], needle: u8) -> usize { #[target_feature(enable = "avx2")] unsafe fn is_leading_utf8_byte(u8s: __m256i) -> __m256i { - mm256_cmpneq_epi8(_mm256_and_si256(u8s, _mm256_set1_epu8(0b1100_0000)), _mm256_set1_epu8(0b1000_0000)) + mm256_cmpneq_epi8( + _mm256_and_si256(u8s, _mm256_set1_epu8(0b1100_0000)), + _mm256_set1_epu8(0b1000_0000), + ) } #[target_feature(enable = "avx2")] @@ -118,7 +112,7 @@ pub unsafe fn chunk_num_chars(utf8_chars: &[u8]) -> usize { for _ in 0..255 { counts = _mm256_sub_epi8( counts, - is_leading_utf8_byte(mm256_from_offset(utf8_chars, offset)) + is_leading_utf8_byte(mm256_from_offset(utf8_chars, offset)), ); offset += 32; } @@ -131,7 +125,7 @@ pub unsafe fn chunk_num_chars(utf8_chars: &[u8]) -> usize { for _ in 0..128 { counts = _mm256_sub_epi8( counts, - is_leading_utf8_byte(mm256_from_offset(utf8_chars, offset)) + is_leading_utf8_byte(mm256_from_offset(utf8_chars, offset)), ); offset += 32; } @@ -143,7 +137,7 @@ pub unsafe fn chunk_num_chars(utf8_chars: &[u8]) -> usize { for i in 0..(utf8_chars.len() - offset) / 32 { counts = _mm256_sub_epi8( counts, - is_leading_utf8_byte(mm256_from_offset(utf8_chars, offset + i * 32)) + is_leading_utf8_byte(mm256_from_offset(utf8_chars, offset + i * 32)), ); } if utf8_chars.len() % 32 != 0 { @@ -151,8 +145,8 @@ pub unsafe fn chunk_num_chars(utf8_chars: &[u8]) -> usize { counts, _mm256_and_si256( is_leading_utf8_byte(mm256_from_offset(utf8_chars, utf8_chars.len() - 32)), - mm256_from_offset(&MASK, utf8_chars.len() % 32) - ) + mm256_from_offset(&MASK, utf8_chars.len() % 32), + ), ); } count += sum(&counts); diff --git a/src/simd/x86_sse2.rs b/src/simd/x86_sse2.rs index 63d295e..a5e96e5 100644 --- a/src/simd/x86_sse2.rs +++ b/src/simd/x86_sse2.rs @@ -1,29 +1,13 @@ #[cfg(target_arch = "x86")] use std::arch::x86::{ - __m128i, - _mm_and_si128, - _mm_cmpeq_epi8, - _mm_extract_epi32, - _mm_loadu_si128, - _mm_sad_epu8, - _mm_set1_epi8, - _mm_setzero_si128, - _mm_sub_epi8, - _mm_xor_si128, + __m128i, _mm_and_si128, _mm_cmpeq_epi8, _mm_extract_epi32, _mm_loadu_si128, _mm_sad_epu8, + _mm_set1_epi8, _mm_setzero_si128, _mm_sub_epi8, _mm_xor_si128, }; #[cfg(target_arch = "x86_64")] use std::arch::x86_64::{ - __m128i, - _mm_and_si128, - _mm_cmpeq_epi8, - _mm_extract_epi32, - _mm_loadu_si128, - _mm_sad_epu8, - _mm_set1_epi8, - _mm_setzero_si128, - _mm_sub_epi8, - _mm_xor_si128, + __m128i, _mm_and_si128, _mm_cmpeq_epi8, _mm_extract_epi32, _mm_loadu_si128, _mm_sad_epu8, + _mm_set1_epi8, _mm_setzero_si128, _mm_sub_epi8, _mm_xor_si128, }; #[target_feature(enable = "sse2")] @@ -37,8 +21,8 @@ pub unsafe fn mm_cmpneq_epi8(a: __m128i, b: __m128i) -> __m128i { } const MASK: [u8; 32] = [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, ]; #[target_feature(enable = "sse2")] @@ -67,7 +51,7 @@ pub unsafe fn chunk_count(haystack: &[u8], needle: u8) -> usize { for _ in 0..255 { counts = _mm_sub_epi8( counts, - _mm_cmpeq_epi8(mm_from_offset(haystack, offset), needles) + _mm_cmpeq_epi8(mm_from_offset(haystack, offset), needles), ); offset += 16; } @@ -80,7 +64,7 @@ pub unsafe fn chunk_count(haystack: &[u8], needle: u8) -> usize { for _ in 0..128 { counts = _mm_sub_epi8( counts, - _mm_cmpeq_epi8(mm_from_offset(haystack, offset), needles) + _mm_cmpeq_epi8(mm_from_offset(haystack, offset), needles), ); offset += 16; } @@ -92,7 +76,7 @@ pub unsafe fn chunk_count(haystack: &[u8], needle: u8) -> usize { for i in 0..(haystack.len() - offset) / 16 { counts = _mm_sub_epi8( counts, - _mm_cmpeq_epi8(mm_from_offset(haystack, offset + i * 16), needles) + _mm_cmpeq_epi8(mm_from_offset(haystack, offset + i * 16), needles), ); } if haystack.len() % 16 != 0 { @@ -100,8 +84,8 @@ pub unsafe fn chunk_count(haystack: &[u8], needle: u8) -> usize { counts, _mm_and_si128( _mm_cmpeq_epi8(mm_from_offset(haystack, haystack.len() - 16), needles), - mm_from_offset(&MASK, haystack.len() % 16) - ) + mm_from_offset(&MASK, haystack.len() % 16), + ), ); } count += sum(&counts); @@ -111,7 +95,10 @@ pub unsafe fn chunk_count(haystack: &[u8], needle: u8) -> usize { #[target_feature(enable = "sse2")] unsafe fn is_leading_utf8_byte(u8s: __m128i) -> __m128i { - mm_cmpneq_epi8(_mm_and_si128(u8s, _mm_set1_epu8(0b1100_0000)), _mm_set1_epu8(0b1000_0000)) + mm_cmpneq_epi8( + _mm_and_si128(u8s, _mm_set1_epu8(0b1100_0000)), + _mm_set1_epu8(0b1000_0000), + ) } #[target_feature(enable = "sse2")] @@ -128,7 +115,7 @@ pub unsafe fn chunk_num_chars(utf8_chars: &[u8]) -> usize { for _ in 0..255 { counts = _mm_sub_epi8( counts, - is_leading_utf8_byte(mm_from_offset(utf8_chars, offset)) + is_leading_utf8_byte(mm_from_offset(utf8_chars, offset)), ); offset += 16; } @@ -141,7 +128,7 @@ pub unsafe fn chunk_num_chars(utf8_chars: &[u8]) -> usize { for _ in 0..128 { counts = _mm_sub_epi8( counts, - is_leading_utf8_byte(mm_from_offset(utf8_chars, offset)) + is_leading_utf8_byte(mm_from_offset(utf8_chars, offset)), ); offset += 16; } @@ -153,7 +140,7 @@ pub unsafe fn chunk_num_chars(utf8_chars: &[u8]) -> usize { for i in 0..(utf8_chars.len() - offset) / 16 { counts = _mm_sub_epi8( counts, - is_leading_utf8_byte(mm_from_offset(utf8_chars, offset + i * 16)) + is_leading_utf8_byte(mm_from_offset(utf8_chars, offset + i * 16)), ); } if utf8_chars.len() % 16 != 0 { @@ -161,8 +148,8 @@ pub unsafe fn chunk_num_chars(utf8_chars: &[u8]) -> usize { counts, _mm_and_si128( is_leading_utf8_byte(mm_from_offset(utf8_chars, utf8_chars.len() - 16)), - mm_from_offset(&MASK, utf8_chars.len() % 16) - ) + mm_from_offset(&MASK, utf8_chars.len() % 16), + ), ); } count += sum(&counts); diff --git a/tests/check.rs b/tests/check.rs index 147b466..0a19e19 100644 --- a/tests/check.rs +++ b/tests/check.rs @@ -1,13 +1,7 @@ -extern crate bytecount; -#[macro_use] -extern crate quickcheck; -extern crate rand; +use quickcheck::quickcheck; -use bytecount::{ - count, naive_count, - num_chars, naive_num_chars, -}; -use rand::RngCore; +use bytecount::{self, count, naive_count, naive_num_chars, num_chars}; +use rand::{self, RngCore}; fn random_bytes(len: usize) -> Vec { let mut result = vec![0; len]; @@ -59,8 +53,6 @@ fn check_count_overflow_many() { } } - - quickcheck! { fn check_num_chars_correct(haystack: Vec) -> bool { num_chars(&haystack) == naive_num_chars(&haystack)