pub(crate) use super::phase2_first_bigram::FirstBigramSet;
#[cfg(feature = "simd")]
use super::phase2_hs::Phase2HsEngine;
use crate::types::LazyRegex;
use aho_corasick::AhoCorasick;
use std::cell::RefCell;
use std::sync::atomic::{AtomicU64, Ordering::Relaxed};
use std::sync::OnceLock;
mod mark_stats;
#[cfg(feature = "simd")]
pub(crate) use mark_stats::record_mark_hs_served;
#[cfg(test)]
pub(crate) use mark_stats::take_mark_stats;
pub(crate) use mark_stats::{
format_mark_decomposition, mark_snapshot_from_typed, record_mark_call, record_mark_gate_skip,
record_mark_perpattern_work, record_mark_regexset_served, MarkSnapshot,
};
mod hs_mark_timing;
pub(crate) use hs_mark_timing::{format_hs_mark_split, hs_mark_split_from_typed, HsMarkSplit};
#[cfg(feature = "simd")]
pub(crate) use hs_mark_timing::{hs_mark_dropped_span, hs_mark_scan_span};
pub(crate) use crate::tuning::*;
pub(crate) const MIN_PREFIX_BYTES: usize = 3;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) struct Phase2AlwaysActiveGpuEvidence<'a> {
pub(crate) prefixless_admitted: bool,
pub(crate) prefixless_complete: bool,
pub(crate) prefixless_candidate_bits: Option<&'a [u32]>,
pub(crate) prefixless_candidate_map: Option<&'a [u32]>,
pub(crate) anchor_present: bool,
pub(crate) anchor_literal_matches: Option<&'a [(u32, u32)]>,
}
impl<'a> Phase2AlwaysActiveGpuEvidence<'a> {
#[inline]
pub(crate) const fn exact_absence() -> Phase2AlwaysActiveGpuEvidence<'static> {
Phase2AlwaysActiveGpuEvidence {
prefixless_admitted: false,
prefixless_complete: true,
prefixless_candidate_bits: Some(&[]),
prefixless_candidate_map: Some(&[]),
anchor_present: false,
anchor_literal_matches: Some(&[]),
}
}
#[inline]
pub(crate) fn prefixless_candidates(self) -> Option<(&'a [u32], &'a [u32])> {
let bits = self.prefixless_candidate_bits?;
let map = self.prefixless_candidate_map?;
bits.len()
.checked_mul(u32::BITS as usize)
.filter(|&expected| expected == map.len())
.map(|_| (bits, map))
}
}
pub(crate) fn phase2_pattern_prof_enabled() -> bool {
super::profile::enabled()
}
static PHASE2_PATTERN_NS: OnceLock<Vec<AtomicU64>> = OnceLock::new();
static PHASE2_PATTERN_RUNS: OnceLock<Vec<AtomicU64>> = OnceLock::new();
pub(crate) static GATE_BATCH_SKIPS: AtomicU64 = AtomicU64::new(0);
pub(crate) static GATE_BATCH_RUNS: AtomicU64 = AtomicU64::new(0);
pub(crate) static GATE_CALLS: AtomicU64 = AtomicU64::new(0);
#[cfg(test)]
pub(crate) fn phase2_gate_stats_dump() -> (u64, u64, u64) {
let calls = GATE_CALLS.swap(0, Relaxed);
let skips = GATE_BATCH_SKIPS.swap(0, Relaxed);
let runs = GATE_BATCH_RUNS.swap(0, Relaxed);
eprintln!(
"prefix-gate: calls={calls} gateable_batch_skips={skips} gateable_batch_runs={runs} \
({:.1}% skipped)",
if skips + runs > 0 {
100.0 * skips as f64 / (skips + runs) as f64
} else {
0.0
}
);
(calls, skips, runs)
}
pub(crate) fn phase2_pattern_prof_vecs(len: usize) -> (&'static [AtomicU64], &'static [AtomicU64]) {
let ns = PHASE2_PATTERN_NS.get_or_init(|| (0..len).map(|_| AtomicU64::new(0)).collect());
let runs = PHASE2_PATTERN_RUNS.get_or_init(|| (0..len).map(|_| AtomicU64::new(0)).collect());
(ns.as_slice(), runs.as_slice())
}
pub(crate) fn phase2_pattern_prof_reset(len: usize) {
let (ns, runs) = phase2_pattern_prof_vecs(len);
for n in ns {
n.store(0, Relaxed);
}
for r in runs {
r.store(0, Relaxed);
}
GATE_BATCH_SKIPS.store(0, Relaxed);
GATE_BATCH_RUNS.store(0, Relaxed);
GATE_CALLS.store(0, Relaxed);
}
#[inline]
pub(crate) fn phase2_pattern_prof_record(len: usize, index: usize, nanos: u64) {
let (ns, runs) = phase2_pattern_prof_vecs(len);
if let (Some(n), Some(r)) = (ns.get(index), runs.get(index)) {
n.fetch_add(nanos, Relaxed);
r.fetch_add(1, Relaxed);
}
}
pub(crate) struct ActivePatternsScratch {
pub(crate) active: Vec<usize>,
stamp: Vec<u32>,
generation: u32,
}
impl ActivePatternsScratch {
pub(crate) const fn new() -> Self {
Self {
active: Vec::new(),
stamp: Vec::new(),
generation: 0,
}
}
pub(crate) fn begin(&mut self, len: usize) -> Result<(), crate::error::ScanError> {
self.active.clear();
self.generation = self.generation.wrapping_add(1);
if self.generation == 0 {
self.stamp.iter_mut().for_each(|s| *s = 0);
self.generation = 1;
}
let requested_bytes = len.saturating_mul(
std::mem::size_of::<u32>().saturating_add(std::mem::size_of::<usize>()),
);
crate::enforce_cpu_scratch_ceiling(requested_bytes)?;
if self.stamp.len() < len {
self.stamp.resize(len, 0);
}
Ok(())
}
#[inline]
pub(crate) fn mark(&mut self, index: usize) {
if let Some(slot) = self.stamp.get_mut(index) {
if *slot != self.generation {
*slot = self.generation;
self.active.push(index);
}
}
}
pub(crate) fn remove_indices(&mut self, indices: &[u32]) {
for &index in indices {
if index == u32::MAX {
continue;
}
if let Some(slot) = self.stamp.get_mut(index as usize) {
if *slot == self.generation {
*slot = 0;
}
}
}
let generation = self.generation;
let stamp = &self.stamp;
self.active
.retain(|&index| stamp.get(index) == Some(&generation));
}
#[inline]
pub(crate) fn is_active(&self, index: usize) -> bool {
self.stamp.get(index) == Some(&self.generation)
}
}
pub(crate) struct PrefilterBatch {
pub(crate) phase2_indices: Vec<usize>,
pub(crate) case_insensitive: bool,
pub(crate) gateable: bool,
pub(crate) homoglyph_skippable: bool,
pub(crate) set: std::sync::OnceLock<Option<regex::RegexSet>>,
pub(crate) ascii_set: std::sync::OnceLock<Option<regex::RegexSet>>,
pub(crate) set_trunc: std::sync::OnceLock<Option<regex::RegexSet>>,
pub(crate) ascii_set_trunc: std::sync::OnceLock<Option<regex::RegexSet>>,
}
pub(crate) struct PortablePrefilter {
pub(crate) batches: Vec<PrefilterBatch>,
pub(crate) ci_gate: Option<AhoCorasick>,
pub(crate) plain_gate: Option<AhoCorasick>,
}
pub(crate) struct CombinedNoCandidateGate {
pub(crate) anchor_ac: AhoCorasick,
pub(crate) non_anchorable: Vec<(usize, LazyRegex, bool)>,
pub(crate) anchor_first_bigram: FirstBigramSet,
}
impl CombinedNoCandidateGate {
#[inline]
pub(crate) fn anchor_present(&self, match_text: &str) -> bool {
self.anchor_first_bigram.may_have_match(match_text) && self.anchor_ac.is_match(match_text)
}
#[inline]
pub(crate) fn mark_non_anchorable(
&self,
match_text: &str,
scratch: &mut ActivePatternsScratch,
allowed_indices: &[usize],
skip_homoglyph: bool,
) {
for (idx, re, homoglyph_variant) in &self.non_anchorable {
if !(skip_homoglyph && *homoglyph_variant)
&& allowed_indices.binary_search(idx).is_ok()
&& re.get().is_match(match_text)
{
scratch.mark(*idx);
}
}
}
#[inline]
pub(crate) fn any_non_anchorable_match(&self, match_text: &str, skip_homoglyph: bool) -> bool {
self.non_anchorable
.iter()
.any(|(_, re, homoglyph_variant)| {
!(skip_homoglyph && *homoglyph_variant) && re.get().is_match(match_text)
})
}
}
pub(crate) struct Phase2AlwaysActivePrefilter {
pub(crate) valid_always_active_indices: Vec<usize>,
pub(crate) anchor_residual_indices: Vec<usize>,
pub(crate) localized_residual_indices: Vec<usize>,
pub(crate) portable: OnceLock<PortablePrefilter>,
pub(crate) portable_anchor_residual: OnceLock<PortablePrefilter>,
pub(crate) portable_localized_residual: OnceLock<PortablePrefilter>,
pub(crate) combined_gate: OnceLock<Option<CombinedNoCandidateGate>>,
pub(crate) combined_gate_anchor_residual: OnceLock<Option<CombinedNoCandidateGate>>,
pub(crate) combined_gate_localized_residual: OnceLock<Option<CombinedNoCandidateGate>>,
#[cfg(feature = "simd")]
pub(crate) hs: OnceLock<Option<Phase2HsEngine>>,
#[cfg(feature = "simd")]
pub(crate) packed_hs:
std::sync::Mutex<Option<crate::execution_pack::simd_program::HyperscanPhase2ScopeProgram>>,
#[cfg(feature = "simd")]
pub(crate) hs_anchor_residual: OnceLock<Option<Phase2HsEngine>>,
#[cfg(feature = "simd")]
pub(crate) packed_hs_anchor_residual:
std::sync::Mutex<Option<crate::execution_pack::simd_program::HyperscanPhase2ScopeProgram>>,
#[cfg(feature = "simd")]
pub(crate) hs_localized_residual: OnceLock<Option<Phase2HsEngine>>,
#[cfg(feature = "simd")]
pub(crate) packed_hs_localized_residual:
std::sync::Mutex<Option<crate::execution_pack::simd_program::HyperscanPhase2ScopeProgram>>,
}
pub(crate) const DECODE_FOCUS_MARGIN: usize = 64;
#[inline]
pub(crate) fn homoglyph_skip_applies(text: &str, enabled: bool) -> bool {
enabled && (super::profile::in_decode() || !crate::homoglyph::may_contain_confusable(text))
}
pub(crate) fn gate_prefix_literals(src: &str) -> Option<Vec<Vec<u8>>> {
use regex_syntax::hir::literal::{ExtractKind, Extractor};
let hir = regex_syntax::ParserBuilder::new().build().parse(src).ok()?; let mut ex = Extractor::new();
ex.kind(ExtractKind::Prefix);
let seq = ex.extract(&hir);
if !seq.is_finite() {
return None;
}
let literals = seq.literals()?;
if literals.is_empty() {
return None;
}
let mut out = Vec::with_capacity(literals.len());
for lit in literals {
let bytes = lit.as_bytes();
if bytes.len() < MIN_PREFIX_BYTES || !bytes.is_ascii() {
return None;
}
out.push(bytes.to_vec());
}
Some(out)
}
pub(crate) fn ascii_fold_regex_src(src: &str) -> String {
src.chars().filter(char::is_ascii).collect()
}
thread_local! {
pub(crate) static ACTIVE_PATTERNS_POOL: RefCell<ActivePatternsScratch> =
const { RefCell::new(ActivePatternsScratch::new()) };
}