1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
//! Global compiled-pattern cache — a zshrs-only optimization with NO C
//! counterpart, so it lives here in `src/extensions` rather than in
//! `src/ported`.
//!
//! Why it exists: C zsh recompiles a pattern on every match too, but its C
//! `patcompile` is ~40-75x faster than this Rust port. Shell code re-matches
//! identical patterns constantly — `[[ x == pat ]]`, `${(M)a:#pat}`, `case`,
//! `${x//pat/…}` — and p10k's `_p9k_param` re-matches the SAME
//! `(#b)prompt_([a-z0-9_]#)(*)` thousands of times per precmd. At the port's
//! compile cost that dominated interactive startup (the "hang before prompt").
//! Caching the compiled program turns those repeats into a hash lookup + a
//! cheap clone of the bytecode, and lets zshrs actually beat zsh here.
//!
//! Safety: the compiled `Patprog` is self-contained — matching (`pattry*`)
//! reads `prog.0.flags` / `prog.0.globflags` (never the global compile
//! statics) and borrows the prog by `&` (read-only). So a cached clone is
//! interchangeable with a freshly-compiled one.
//!
//! Correctness of the key: the compiled output depends on the pattern text,
//! `inflags`, and exactly six options — EXTENDEDGLOB/KSHGLOB/SHGLOB (which
//! shape `zpc_special` in `patcompcharsset`) and CASEGLOB/CASEPATHS/MULTIBYTE
//! (which shape `patglobflags` in `patcompstart`). All are folded into the
//! key, so any option change yields a distinct key rather than a stale hit.
//!
//! GLOBAL (not thread-local): the VM dispatches `[[ … ]]`/pattern work across
//! zshrs's worker threads, so a per-thread cache would miss on every worker.
//! A single `RwLock<HashMap>` is shared: reads (the common case, a hit) take
//! the read lock and run concurrently; only a miss's store takes the write
//! lock. The check runs BEFORE `patcompile`'s own compile mutex, so hits never
//! serialise on the compiler.
use cratePatprog;
use crate;
use RefCell;
use HashMap;
use ;
/// (pattern text, inflags, option fingerprint).
type Key = ;
// Two-level cache. L1 is thread-local and LOCK-FREE — the common hit takes
// no lock, so a hot loop (`repeat 20000 { [[ x == pat ]] }`) doesn't pay
// RwLock read-contention across zshrs's worker threads (which otherwise
// dominated the profile). L2 is the shared global that lets a pattern
// compiled on one worker be reused by another; an L2 hit is promoted into
// the reader's L1 so subsequent hits stay lock-free.
thread_local!
static L2: =
new;
/// Bound the cache so a pathological workload (unique patterns forever)
/// can't grow it without limit. On overflow we clear wholesale rather than
/// track LRU — pattern working sets are small and stable (a shell reuses a
/// handful of patterns), so a rare full flush costs one recompile burst,
/// not a steady eviction tax.
const MAX_ENTRIES: usize = 8192;
/// Fold the six compilation-affecting options into a fingerprint byte.
/// Look up a compiled pattern. Returns a clone of the cached `Patprog` on a
/// hit (interchangeable with a fresh compile), `None` on a miss. Checks the
/// lock-free thread-local L1 first, then the shared L2 (promoting an L2 hit
/// into L1).
/// Store a freshly-compiled pattern in both cache levels.
/// Insert into the thread-local L1 (bounded, wholesale-flush on overflow).