Skip to content

Commit 41c4cef

Browse files
committed
perf(regex): cache sparse ASCII fastmap decisions
1 parent 3df0439 commit 41c4cef

1 file changed

Lines changed: 45 additions & 14 deletions

File tree

  • crates/neovm-core/src/emacs_core/text/regex

‎crates/neovm-core/src/emacs_core/text/regex/emacs.rs‎

Lines changed: 45 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,10 @@ pub(crate) struct CompiledPattern {
253253
/// Whether the fastmap is valid (needs recomputation after compile).
254254
pub fastmap_accurate: bool,
255255

256+
/// The tiny ASCII candidate set used by memchr, derived on first use.
257+
/// Rebuilt with the fastmap, including syntax-table recomputation.
258+
sparse_ascii_fastmap: std::cell::OnceCell<Option<SparseAsciiFastmap>>,
259+
256260
/// True if the pattern was compiled for POSIX backtracking.
257261
pub posix: bool,
258262

@@ -391,6 +395,12 @@ pub(crate) struct CompiledPattern {
391395
const PREFILTER_MIN_BUILD_SPAN: usize = 256;
392396

393397
impl CompiledPattern {
398+
fn sparse_ascii_fastmap(&self) -> Option<SparseAsciiFastmap> {
399+
*self
400+
.sparse_ascii_fastmap
401+
.get_or_init(|| sparse_ascii_fastmap(&self.fastmap))
402+
}
403+
394404
/// The multi-literal prefilter, built now if it was not yet.
395405
pub(crate) fn literal_prefilter(&self) -> Option<&LiteralPrefilter> {
396406
self.prefilter
@@ -597,6 +607,7 @@ impl CompiledPattern {
597607
fastmap: [false; 256],
598608
fastmap_translated: [false; 256],
599609
fastmap_accurate: false,
610+
sparse_ascii_fastmap: std::cell::OnceCell::new(),
600611
posix: false,
601612
multibyte: true,
602613
target_multibyte: true,
@@ -7453,6 +7464,9 @@ pub(crate) fn recompute_fastmap(pattern: &mut CompiledPattern, syntax: &dyn Synt
74537464
/// Patterns whose fastmap took that path are flagged `used_syntax` at
74547465
/// compile and must be cache-keyed by syntax table.
74557466
fn compile_fastmap(pattern: &mut CompiledPattern, syntax: &dyn SyntaxLookup) {
7467+
// A cached skip set describes this exact map, including the active syntax
7468+
// table. Leave it empty until a search actually needs the ASCII shortcut.
7469+
pattern.sparse_ascii_fastmap.take();
74567470
let mut folded_multibyte_literal = false;
74577471
compile_fastmap_walk(pattern, syntax, &mut folded_multibyte_literal);
74587472
// `fastmap_translated` is the walk's own map; the byte-indexed `fastmap`
@@ -8080,20 +8094,34 @@ fn fastmap_force_disabled() -> bool {
80808094
false
80818095
}
80828096

8083-
/// Fastmap byte set when it is small (<= 3 bytes) and pure ASCII — the
8084-
/// cases where `memchr`/`memchr2`/`memchr3` can drive the forward skip
8085-
/// loop.
8086-
fn sparse_ascii_fastmap(fastmap: &[bool; 256]) -> Option<SmallVec<[u8; 3]>> {
8087-
let mut bytes: SmallVec<[u8; 3]> = SmallVec::new();
8097+
/// A compact, allocation-free candidate set for the memchr skip loop.
8098+
#[derive(Clone, Copy)]
8099+
enum SparseAsciiFastmap {
8100+
One(u8),
8101+
Two(u8, u8),
8102+
Three(u8, u8, u8),
8103+
}
8104+
8105+
/// Derive a small (1–3 byte), pure ASCII set once per compiled fastmap.
8106+
/// Repeated short searches otherwise scan the same 256 entries on every call.
8107+
fn sparse_ascii_fastmap(fastmap: &[bool; 256]) -> Option<SparseAsciiFastmap> {
8108+
let mut bytes = [0; 3];
8109+
let mut len = 0;
80888110
for (byte, &set) in fastmap.iter().enumerate() {
80898111
if set {
8090-
if byte >= 0x80 || bytes.len() == 3 {
8112+
if byte >= 0x80 || len == bytes.len() {
80918113
return None;
80928114
}
8093-
bytes.push(byte as u8);
8115+
bytes[len] = byte as u8;
8116+
len += 1;
80948117
}
80958118
}
8096-
if bytes.is_empty() { None } else { Some(bytes) }
8119+
match len {
8120+
1 => Some(SparseAsciiFastmap::One(bytes[0])),
8121+
2 => Some(SparseAsciiFastmap::Two(bytes[0], bytes[1])),
8122+
3 => Some(SparseAsciiFastmap::Three(bytes[0], bytes[1], bytes[2])),
8123+
_ => None,
8124+
}
80978125
}
80988126

80998127
/// Search for a match of the compiled pattern in text.
@@ -8367,7 +8395,7 @@ pub(crate) fn re_search(
83678395
}
83688396
pos += 1;
83698397
}
8370-
} else if let Some(bytes) = sparse_ascii_fastmap(&pattern.fastmap) {
8398+
} else if let Some(bytes) = pattern.sparse_ascii_fastmap() {
83718399
// The candidate first-byte set is tiny and pure ASCII
83728400
// (e.g. `{'('}` for the font-lock defun matchers):
83738401
// let memchr's SIMD scan find candidates instead of
@@ -8379,11 +8407,14 @@ pub(crate) fn re_search(
83798407
let hi = if end < text_len { end + 1 } else { text_len };
83808408
while pos <= end {
83818409
if pos < text_len {
8382-
let found = match *bytes.as_slice() {
8383-
[b0] => memchr::memchr(b0, &text[pos..hi]),
8384-
[b0, b1] => memchr::memchr2(b0, b1, &text[pos..hi]),
8385-
[b0, b1, b2] => memchr::memchr3(b0, b1, b2, &text[pos..hi]),
8386-
_ => unreachable!("sparse_ascii_fastmap yields 1..=3 bytes"),
8410+
let found = match bytes {
8411+
SparseAsciiFastmap::One(b0) => memchr::memchr(b0, &text[pos..hi]),
8412+
SparseAsciiFastmap::Two(b0, b1) => {
8413+
memchr::memchr2(b0, b1, &text[pos..hi])
8414+
}
8415+
SparseAsciiFastmap::Three(b0, b1, b2) => {
8416+
memchr::memchr3(b0, b1, b2, &text[pos..hi])
8417+
}
83878418
};
83888419
match found {
83898420
Some(idx) => pos += idx,

0 commit comments

Comments
 (0)