mnemosyne_prof/sampler/
stack_interner.rs1use std::collections::HashMap;
2use std::sync::{Arc, Mutex};
3
4use super::hasher::FastBuildHasher;
5
6#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
14pub struct StackId(u32);
15
16const STACK_INTERNER_SHARDS: usize = 64;
17const STACK_INTERNER_SHARD_BITS: u32 = STACK_INTERNER_SHARDS.trailing_zeros();
18const STACK_ID_LOCAL_BITS: u32 = u32::BITS - STACK_INTERNER_SHARD_BITS;
19const STACK_ID_LOCAL_MASK: u32 = (1u32 << STACK_ID_LOCAL_BITS) - 1;
20const _: () = assert!(STACK_INTERNER_SHARDS.is_power_of_two());
21
22impl StackId {
23 #[inline]
24 fn new(shard: usize, local_id: u32) -> Self {
25 debug_assert!(shard < STACK_INTERNER_SHARDS);
26 debug_assert!(local_id <= STACK_ID_LOCAL_MASK);
27 Self(((shard as u32) << STACK_ID_LOCAL_BITS) | local_id)
28 }
29
30 #[inline]
31 fn shard(self) -> usize {
32 (self.0 >> STACK_ID_LOCAL_BITS) as usize
33 }
34
35 #[inline]
36 fn local_index(self) -> usize {
37 (self.0 & STACK_ID_LOCAL_MASK) as usize
38 }
39
40 #[inline]
41 fn local_id(self) -> u32 {
42 self.0 & STACK_ID_LOCAL_MASK
43 }
44}
45
46struct StackInternerShard {
52 forward: HashMap<Arc<[usize]>, StackId, FastBuildHasher>,
53 entries: Vec<Option<StackEntry>>,
54 free_ids: Vec<u32>,
55}
56
57struct StackEntry {
58 frames: Arc<[usize]>,
59 refs: usize,
60}
61
62type RetiredStack = (Arc<[usize]>, Arc<[usize]>);
63
64#[repr(align(64))]
65struct InternerShard {
66 mutex: Mutex<Option<StackInternerShard>>,
67}
68
69static STACK_INTERNER: [InternerShard; STACK_INTERNER_SHARDS] = [const {
70 InternerShard {
71 mutex: Mutex::new(None),
72 }
73}; STACK_INTERNER_SHARDS];
74
75fn stack_interner_shard(frames: &[usize]) -> usize {
76 let mut hasher = <FastBuildHasher as std::hash::BuildHasher>::build_hasher(&FastBuildHasher);
77 std::hash::Hash::hash(&frames, &mut hasher);
78 (std::hash::Hasher::finish(&hasher) as usize) & (STACK_INTERNER_SHARDS - 1)
79}
80
81fn get_stack_interner(shard: usize) -> std::sync::MutexGuard<'static, Option<StackInternerShard>> {
82 let mut lock = STACK_INTERNER[shard]
83 .mutex
84 .lock()
85 .unwrap_or_else(std::sync::PoisonError::into_inner);
86 if lock.is_none() {
87 *lock = Some(StackInternerShard {
88 forward: HashMap::with_hasher(FastBuildHasher),
89 entries: Vec::new(),
90 free_ids: Vec::new(),
91 });
92 }
93 lock
94}
95
96pub(super) fn resolve_stack(id: StackId) -> Option<Arc<[usize]>> {
97 let guard = STACK_INTERNER[id.shard()]
98 .mutex
99 .lock()
100 .unwrap_or_else(std::sync::PoisonError::into_inner);
101 guard.as_ref().and_then(|interner| interner.resolve(id))
102}
103
104pub(super) fn intern_stack(frames: &[usize]) -> StackId {
107 let shard = stack_interner_shard(frames);
108 {
109 let mut guard = get_stack_interner(shard);
110 let interner = guard
111 .as_mut()
112 .expect("stack interner shard must be initialized");
113 if let Some(&id) = interner.forward.get(frames) {
114 return interner.retain(id);
115 }
116 }
117
118 let arc: Arc<[usize]> = Arc::from(frames);
119 let mut guard = get_stack_interner(shard);
120 let interner = guard
121 .as_mut()
122 .expect("stack interner shard must be initialized");
123 if let Some(&id) = interner.forward.get(arc.as_ref()) {
124 return interner.retain(id);
125 }
126 let id = if let Some(local_id) = interner.free_ids.pop() {
127 let id = StackId::new(shard, local_id);
128 interner.entries[local_id as usize] = Some(StackEntry {
129 frames: Arc::clone(&arc),
130 refs: 1,
131 });
132 id
133 } else {
134 assert!(
135 interner.entries.len() <= STACK_ID_LOCAL_MASK as usize,
136 "invariant: stack interner shard id count exceeds its bit budget"
137 );
138 let local_id = u32::try_from(interner.entries.len())
139 .expect("invariant: stack interner shard id count exceeds u32::MAX");
140 let id = StackId::new(shard, local_id);
141 interner.entries.push(Some(StackEntry {
142 frames: Arc::clone(&arc),
143 refs: 1,
144 }));
145 id
146 };
147 interner.forward.insert(arc, id);
148 id
149}
150
151pub(super) fn release_stack(id: StackId) {
152 let mut guard = STACK_INTERNER[id.shard()]
153 .mutex
154 .lock()
155 .unwrap_or_else(std::sync::PoisonError::into_inner);
156 let retired = guard.as_mut().and_then(|interner| interner.release(id));
157 drop(guard);
158 drop(retired);
162}
163
164pub(super) fn reset_stack_interner_state() {
165 for shard in &STACK_INTERNER {
166 let mut lock = shard
167 .mutex
168 .lock()
169 .unwrap_or_else(std::sync::PoisonError::into_inner);
170 *lock = None;
171 }
172}
173
174impl StackInternerShard {
175 fn retain(&mut self, id: StackId) -> StackId {
176 let entry = self
177 .entries
178 .get_mut(id.local_index())
179 .and_then(Option::as_mut)
180 .expect("invariant: stack interner forward map points at a live entry");
181 entry.refs = entry
182 .refs
183 .checked_add(1)
184 .expect("invariant: stack interner reference count overflow");
185 id
186 }
187
188 fn resolve(&self, id: StackId) -> Option<Arc<[usize]>> {
189 self.entries
190 .get(id.local_index())
191 .and_then(Option::as_ref)
192 .map(|entry| Arc::clone(&entry.frames))
193 }
194
195 fn release(&mut self, id: StackId) -> Option<RetiredStack> {
196 let entry_slot = self.entries.get_mut(id.local_index())?;
197 if entry_slot.as_ref()?.refs > 1 {
198 entry_slot.as_mut()?.refs -= 1;
199 return None;
200 }
201
202 let entry = entry_slot
203 .take()
204 .expect("invariant: checked live stack entry must remain present");
205 let (key, removed_id) = self
206 .forward
207 .remove_entry(entry.frames.as_ref())
208 .expect("invariant: live stack entry must have a forward-map key");
209 assert_eq!(
210 removed_id, id,
211 "invariant: stack interner forward map points at a different id"
212 );
213 self.free_ids.push(id.local_id());
214 Some((entry.frames, key))
215 }
216}
217
218#[cfg(test)]
219mod tests {
220 use super::*;
221
222 fn frames_for_shards<const N: usize>() -> [(usize, [usize; 2]); N] {
223 let mut frames = [(usize::MAX, [0usize; 2]); N];
224 let mut found = 0usize;
225 for word in 1..16_384usize {
226 let stack = [0x1000usize | word, usize::MAX - 0x1000];
229 let shard = stack_interner_shard(&stack);
230 if frames[..found].iter().all(|(seen, _)| *seen != shard) {
231 frames[found] = (shard, stack);
232 found += 1;
233 if found == N {
234 return frames;
235 }
236 }
237 }
238 panic!("invariant: deterministic stack hash did not cover {N} distinct shards");
239 }
240
241 fn distinct_frames_for_shard(shard: usize, excluded: &[usize]) -> [usize; 2] {
242 for word in 1..16_384usize {
243 let stack = [0x0010_0000usize | word, usize::MAX - 0x1000];
244 if stack_interner_shard(&stack) == shard && stack.as_slice() != excluded {
245 return stack;
246 }
247 }
248 panic!("invariant: deterministic stack hash did not find a distinct same-shard stack");
249 }
250
251 #[test]
252 fn stack_interner_hash_covers_all_shards() {
253 let frames = frames_for_shards::<STACK_INTERNER_SHARDS>();
254 let mut seen = [false; STACK_INTERNER_SHARDS];
255 for (shard, stack) in frames {
256 assert_eq!(
257 stack_interner_shard(&stack),
258 shard,
259 "fixture must route to its recorded shard"
260 );
261 seen[shard] = true;
262 }
263 assert!(
264 seen.into_iter().all(|covered| covered),
265 "deterministic stack fixtures must cover every interner shard"
266 );
267 }
268
269 #[test]
270 fn stack_interner_encodes_shard_and_local_id() {
271 crate::reset_profiler_for_testing();
272
273 let [(first_shard, first_stack), (second_shard, second_stack)] = frames_for_shards::<2>();
274 let first = intern_stack(&first_stack);
275 let second = intern_stack(&second_stack);
276
277 assert_eq!(first.shard(), first_shard);
278 assert_eq!(second.shard(), second_shard);
279 assert_eq!(first.local_id(), 0);
280 assert_eq!(second.local_id(), 0);
281 assert_ne!(
282 first, second,
283 "equal local ids in distinct shards must still form distinct StackIds"
284 );
285
286 release_stack(first);
287 release_stack(second);
288 crate::reset_profiler_for_testing();
289 }
290
291 #[test]
292 fn stack_interner_reuses_ids_and_releases_last_reference() {
293 crate::reset_profiler_for_testing();
294
295 let first = intern_stack(&[1, 2, 3]);
296 let repeat = intern_stack(&[1, 2, 3]);
297 assert_eq!(first, repeat);
298
299 {
300 let guard = STACK_INTERNER[first.shard()]
301 .mutex
302 .lock()
303 .unwrap_or_else(std::sync::PoisonError::into_inner);
304 let interner = guard.as_ref().expect("stack interner must be initialized");
305 let entry = interner.entries[first.local_index()]
306 .as_ref()
307 .expect("interned stack id must point to a live entry");
308 assert_eq!(entry.refs, 2);
309 assert_eq!(interner.forward.len(), 1);
310 }
311
312 release_stack(first);
313 {
314 let guard = STACK_INTERNER[first.shard()]
315 .mutex
316 .lock()
317 .unwrap_or_else(std::sync::PoisonError::into_inner);
318 let interner = guard
319 .as_ref()
320 .expect("stack interner must stay initialized");
321 let entry = interner.entries[first.local_index()]
322 .as_ref()
323 .expect("one remaining reference must keep the entry live");
324 assert_eq!(entry.refs, 1);
325 assert_eq!(interner.forward.len(), 1);
326 }
327
328 release_stack(repeat);
329 {
330 let guard = STACK_INTERNER[first.shard()]
331 .mutex
332 .lock()
333 .unwrap_or_else(std::sync::PoisonError::into_inner);
334 let interner = guard
335 .as_ref()
336 .expect("stack interner must stay initialized");
337 assert!(interner.entries[first.local_index()].is_none());
338 assert!(interner.forward.is_empty());
339 assert_eq!(interner.free_ids.as_slice(), &[first.local_id()]);
340 }
341
342 let same_shard = distinct_frames_for_shard(first.shard(), &[1, 2, 3]);
343 let reused = intern_stack(&same_shard);
344 assert_eq!(
345 reused, first,
346 "released same-shard stack ids should be recycled instead of growing the table"
347 );
348
349 crate::reset_profiler_for_testing();
350 }
351
352 #[test]
353 fn stack_interner_interns_distinct_shards_concurrently() {
354 crate::reset_profiler_for_testing();
355
356 let frames = frames_for_shards::<STACK_INTERNER_SHARDS>();
357 let barrier = Arc::new(std::sync::Barrier::new(STACK_INTERNER_SHARDS));
358 let mut workers = Vec::with_capacity(STACK_INTERNER_SHARDS);
359 for (expected_shard, stack) in frames {
360 let barrier = Arc::clone(&barrier);
361 workers.push(std::thread::spawn(move || {
362 barrier.wait();
363 let id = intern_stack(&stack);
364 assert_eq!(id.shard(), expected_shard);
365 id
366 }));
367 }
368
369 let ids: Vec<_> = workers
370 .into_iter()
371 .map(|worker| worker.join().expect("interner worker must not panic"))
372 .collect();
373 assert_eq!(ids.len(), STACK_INTERNER_SHARDS);
374 for id in &ids {
375 let guard = STACK_INTERNER[id.shard()]
376 .mutex
377 .lock()
378 .unwrap_or_else(std::sync::PoisonError::into_inner);
379 let interner = guard.as_ref().expect("stack interner must be initialized");
380 let entry = interner.entries[id.local_index()]
381 .as_ref()
382 .expect("worker interned stack id must point to a live entry");
383 assert_eq!(entry.refs, 1);
384 }
385 for id in ids {
386 release_stack(id);
387 }
388
389 crate::reset_profiler_for_testing();
390 }
391}