@@ -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 {
391395const PREFILTER_MIN_BUILD_SPAN : usize = 256 ;
392396
393397impl 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.
74557466fn 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