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
//! Shared non-cryptographic hash + content-keyed memoization primitives.
//!
//! Six byte-identical FNV-1a loops and five verbatim thread-local FNV-keyed
//! caches had accumulated across the crate (decode pipeline dedup, entropy
//! caching, ML-score caching, decode-structure verdict caching). Six copies of
//! one primitive means a hash/seed change silently re-keys only some of the
//! caches, so this module is the single home for both:
//!
//! * [`hash_fast`] - FNV-1a over a byte slice, the one seed every cache keys
//! on (was `decode::pipeline::extractor::hash_fast`).
//! * [`memoize_by_hash`] - the thread-local bounded-cache pattern that every
//! pure content -> value verdict shared, factored to one generic helper.
//!
//! FNV-1a is chosen for the same reason throughout: ~100x faster than SHA-256
//! for the small (<=1KB) credential-sized inputs these caches key on, with
//! collision rates far below the per-scan entry counts.
use RefCell;
use HashMap;
/// FNV-1a offset basis (seed). The ONE place the seed lives, every cache that
/// keys on this hash depends on the value being identical.
pub const FNV_OFFSET_BASIS: u64 = 0xcbf29ce484222325;
/// FNV-1a prime. The ONE place the prime lives.
pub const FNV_PRIME: u64 = 0x100000001b3;
/// FNV-1a hash of `data`. Non-cryptographic; used as a content key for dedup
/// and memoization across the scanner. Keep the seed/prime in sync here only -
/// every cache that keys on this depends on the value being identical.
pub
/// Allocation-free incremental FNV-1a, for content keys composed of MORE THAN
/// ONE slice (e.g. the ML scorer keys on `text` + separator + `context`).
/// Folding the slices into one [`FnvHasher`] is byte-for-byte identical to
/// hashing their concatenation but never allocates the joined buffer, the
/// reason this exists instead of `hash_fast(&[a, b].concat())` on a hot path.
/// Shares the SINGLE seed/prime ([`FNV_OFFSET_BASIS`] / [`FNV_PRIME`]) with
/// [`hash_fast`], so a single-slice `FnvHasher` and `hash_fast` agree (MC-12).
pub
/// Default ceiling for [`memoize_by_hash`] caches: cleared wholesale when this
/// many distinct keys accumulate, bounding memory under adversarial input.
pub const DEFAULT_MAX_CACHE_ENTRIES: usize = 4096;
/// Look up `key` in a thread-local `HashMap<u64, T>`, computing and inserting
/// the value via `compute` on a miss.
///
/// This is the shared form of the bounded-cache idiom that had been copy-pasted
/// across `entropy::shannon_entropy`, `ml_scorer::score_with_config`,
/// and `decode_structure::evidence`. Eviction is wholesale (the whole map is
/// cleared once it reaches `max_entries`) - simple and bounded, matching the
/// prior behavior of every site.
///
/// `cache` must be a distinct thread-local per call site so verdicts of one
/// kind never collide with another. `T: Copy` keeps the value cheap to return
/// without re-borrowing the map.
pub