use crate::SimdSieve;
#[cfg(target_arch = "x86_64")]
use crate::avx2::Avx2Filter;
#[cfg(target_arch = "x86_64")]
use crate::avx512::Avx512Filter;
use crate::error::{Result, SimdSieveError};
use crate::fold::{verify_case_insensitive, verify_exact};
#[cfg(target_arch = "aarch64")]
use crate::neon::NeonFilter;
use crate::scalar::ScalarFilter;
use crate::sieve::dispatch::HardwareTier;
impl<'a> SimdSieve<'a> {
#[allow(clippy::missing_errors_doc)]
pub fn new(haystack: &'a [u8], patterns: &[&'a [u8]]) -> Result<Self> {
Self::build(haystack, patterns, false)
}
#[allow(clippy::missing_errors_doc)]
pub fn new_case_insensitive(haystack: &'a [u8], patterns: &[&'a [u8]]) -> Result<Self> {
Self::build(haystack, patterns, true)
}
fn build(haystack: &'a [u8], patterns: &[&'a [u8]], case_insensitive: bool) -> Result<Self> {
if patterns.is_empty() {
return Err(SimdSieveError::EmptyPatternSet);
}
if patterns.len() > crate::MAX_PATTERNS {
return Err(SimdSieveError::PatternLimitExceeded(patterns.len()));
}
let mut max_len = 0;
let mut verify_patterns = [&b""[..]; 16];
let mut count = 0;
for (i, &p) in patterns.iter().enumerate() {
if p.is_empty() {
return Err(SimdSieveError::EmptyPattern { index: i });
}
if verify_patterns[..count].contains(&p) {
continue;
}
let evaluate_len = if p.len() > 4 { 4 } else { p.len() };
if evaluate_len > max_len {
max_len = evaluate_len;
}
verify_patterns[count] = p;
count += 1;
}
let filter_patterns = &verify_patterns[..count];
let verifier = if case_insensitive {
verify_case_insensitive
} else {
verify_exact
};
let tier = Self::select_hardware_tier(filter_patterns, case_insensitive);
Ok(Self {
haystack,
offset: 0,
verification_patterns: verify_patterns,
pattern_count: count,
max_len,
tier: crate::sieve::dispatch::SieveTier::Owned(tier),
current_mask: 0,
next_mask_cache: 0,
mask_base_offset: 0,
verifier,
})
}
pub(crate) fn select_hardware_tier(filter_patterns: &[&[u8]], case_insensitive: bool) -> HardwareTier {
#[cfg(target_arch = "x86_64")]
{
if std::is_x86_feature_detected!("avx512f") && std::is_x86_feature_detected!("avx512bw")
{
return HardwareTier::Avx512(Box::new(unsafe {
Avx512Filter::new(filter_patterns, case_insensitive)
}));
}
if std::is_x86_feature_detected!("avx2") {
return HardwareTier::Avx2(Box::new(unsafe {
Avx2Filter::new(filter_patterns, case_insensitive)
}));
}
}
#[cfg(target_arch = "aarch64")]
{
return HardwareTier::Neon(Box::new(unsafe {
NeonFilter::new(filter_patterns, case_insensitive)
}));
}
#[allow(unreachable_code)]
HardwareTier::Scalar(Box::new(ScalarFilter::new(
filter_patterns,
case_insensitive,
)))
}
}
#[derive(Debug)]
pub struct CompiledSieve {
patterns: Vec<Vec<u8>>,
pattern_count: usize,
max_len: usize,
tier: HardwareTier,
verifier: fn(&[u8], &[u8]) -> bool,
}
impl CompiledSieve {
pub fn new(patterns: &[&[u8]]) -> Result<Self> {
Self::build(patterns, false)
}
pub fn new_case_insensitive(patterns: &[&[u8]]) -> Result<Self> {
Self::build(patterns, true)
}
fn build(patterns: &[&[u8]], case_insensitive: bool) -> Result<Self> {
if patterns.is_empty() {
return Err(SimdSieveError::EmptyPatternSet);
}
if patterns.len() > crate::MAX_PATTERNS {
return Err(SimdSieveError::PatternLimitExceeded(patterns.len()));
}
let mut owned_patterns: Vec<Vec<u8>> = Vec::with_capacity(patterns.len().min(16));
let mut max_len = 0;
for (i, &p) in patterns.iter().enumerate() {
if p.is_empty() {
return Err(SimdSieveError::EmptyPattern { index: i });
}
if owned_patterns.iter().any(|existing| existing.as_slice() == p) {
continue;
}
let eval_len = if p.len() > 4 { 4 } else { p.len() };
if eval_len > max_len {
max_len = eval_len;
}
owned_patterns.push(p.to_vec());
}
let filter_patterns: Vec<&[u8]> = owned_patterns.iter().map(Vec::as_slice).collect();
let verifier = if case_insensitive {
verify_case_insensitive
} else {
verify_exact
};
let tier = SimdSieve::select_hardware_tier(&filter_patterns, case_insensitive);
Ok(Self {
pattern_count: owned_patterns.len(),
patterns: owned_patterns,
max_len,
tier,
verifier,
})
}
pub fn scan<'a>(&'a self, haystack: &'a [u8]) -> SimdSieve<'a> {
let mut verify_patterns = [&b""[..]; 16];
for (i, p) in self.patterns.iter().enumerate() {
verify_patterns[i] = p.as_slice();
}
SimdSieve {
haystack,
offset: 0,
verification_patterns: verify_patterns,
pattern_count: self.pattern_count,
max_len: self.max_len,
tier: crate::sieve::dispatch::SieveTier::Borrowed(&self.tier),
current_mask: 0,
next_mask_cache: 0,
mask_base_offset: 0,
verifier: self.verifier,
}
}
#[must_use]
pub fn estimate_match_count(&self, haystack: &[u8]) -> u64 {
if self.pattern_count == 0 {
return 0;
}
let haystack = &haystack[..haystack.len().min(4096)];
let mut sieve = self.scan(haystack);
let mut global_popcnt: u64 = 0;
while sieve.fetch_next_chunk() {
if sieve.current_mask != 0 {
global_popcnt += u64::from(sieve.current_mask.count_ones());
sieve.current_mask = 0;
}
if sieve.next_mask_cache != 0 {
global_popcnt += u64::from(sieve.next_mask_cache.count_ones());
sieve.next_mask_cache = 0;
}
}
while sieve.offset + sieve.max_len <= haystack.len() {
let current_idx = sieve.offset;
sieve.offset += 1;
for p_idx in 0..sieve.pattern_count {
let vp = sieve.verification_patterns[p_idx];
let prefix_len = vp.len().min(4);
if (sieve.verifier)(
&haystack[current_idx..current_idx + prefix_len],
&vp[..prefix_len],
) {
global_popcnt += 1;
break;
}
}
}
while sieve.offset < haystack.len() {
let current_idx = sieve.offset;
sieve.offset += 1;
for p_idx in 0..sieve.pattern_count {
let vp = sieve.verification_patterns[p_idx];
let prefix_len = vp.len().min(4);
if current_idx + prefix_len <= haystack.len()
&& (sieve.verifier)(
&haystack[current_idx..current_idx + prefix_len],
&vp[..prefix_len],
)
{
global_popcnt += 1;
break;
}
}
}
global_popcnt
}
#[must_use]
pub fn pattern_count(&self) -> usize {
self.pattern_count
}
#[must_use]
pub fn patterns(&self) -> &[Vec<u8>] {
&self.patterns
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_compiled_sieve_exact_parity_with_simdsieve_new() {
let haystack = b"alpha beta gamma GET /admin HTTP/1.1 Host: example\r\nPOST /api/v1\r\n";
let patterns: &[&[u8]] = &[b"GET", b"/admin", b"POST", b"alpha"];
let compiled = CompiledSieve::new(patterns).expect("compilation failed");
assert_eq!(compiled.pattern_count(), 4);
let direct_matches: Vec<usize> = SimdSieve::new(haystack, patterns).unwrap().collect();
let compiled_matches: Vec<usize> = compiled.scan(haystack).collect();
assert_eq!(compiled_matches, direct_matches);
}
#[test]
fn test_compiled_sieve_zero_rebuild_pointer_identity() {
let haystack = b"quick brown fox jumps over the lazy dog";
let patterns: &[&[u8]] = &[b"quick", b"fox", b"dog"];
let compiled = CompiledSieve::new(patterns).unwrap();
for _ in 0..10 {
let sieve = compiled.scan(haystack);
let tier_ref: &HardwareTier = &sieve.tier;
let compiled_tier_ref: &HardwareTier = &compiled.tier;
assert!(
std::ptr::eq(tier_ref, compiled_tier_ref),
"scan must borrow the compiled hardware tier directly without rebuilding"
);
}
}
#[test]
fn test_compiled_sieve_case_insensitive_parity() {
let haystack = b"GeT /AdMiN HTTP/1.1\r\n";
let patterns: &[&[u8]] = &[b"get", b"/admin"];
let compiled = CompiledSieve::new_case_insensitive(patterns).unwrap();
let direct_matches: Vec<usize> = SimdSieve::new_case_insensitive(haystack, patterns)
.unwrap()
.collect();
let compiled_matches: Vec<usize> = compiled.scan(haystack).collect();
assert_eq!(compiled_matches, direct_matches);
}
#[test]
fn test_compiled_sieve_repeated_scans_distinct_haystacks() {
let compiled = CompiledSieve::new(&[b"foo", b"bar"]).unwrap();
let haystacks = [
b"foo bar baz".as_slice(),
b"no match here".as_slice(),
b"bar foo bar".as_slice(),
];
let expected_results = [
vec![0, 4],
vec![],
vec![0, 4, 8],
];
for (haystack, expected) in haystacks.iter().zip(expected_results.iter()) {
let matches: Vec<usize> = compiled.scan(haystack).collect();
assert_eq!(&matches, expected);
}
}
#[test]
fn test_compiled_sieve_deduplication() {
let patterns: &[&[u8]] = &[b"abc", b"def", b"abc", b"def"];
let compiled = CompiledSieve::new(patterns).unwrap();
assert_eq!(compiled.pattern_count(), 2);
}
#[test]
fn test_compiled_sieve_estimate_match_count() {
let haystack = b"GET /index.html HTTP/1.1\r\nHost: test\r\n";
let patterns: &[&[u8]] = &[b"GET", b"HTTP"];
let compiled = CompiledSieve::new(patterns).unwrap();
let direct_estimate = SimdSieve::estimate_match_count(haystack, patterns, false);
let compiled_estimate = compiled.estimate_match_count(haystack);
assert_eq!(compiled_estimate, direct_estimate);
}
}