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 = [0x7ff6_0000_0000usize | word, 0x7ff6_ffff_ffffusize];
227 let shard = stack_interner_shard(&stack);
228 if frames[..found].iter().all(|(seen, _)| *seen != shard) {
229 frames[found] = (shard, stack);
230 found += 1;
231 if found == N {
232 return frames;
233 }
234 }
235 }
236 panic!("invariant: deterministic stack hash did not cover {N} distinct shards");
237 }
238
239 fn distinct_frames_for_shard(shard: usize, excluded: &[usize]) -> [usize; 2] {
240 for word in 1..16_384usize {
241 let stack = [0x7ff6_1000_0000usize | word, 0x7ff6_ffff_ffffusize];
242 if stack_interner_shard(&stack) == shard && stack.as_slice() != excluded {
243 return stack;
244 }
245 }
246 panic!("invariant: deterministic stack hash did not find a distinct same-shard stack");
247 }
248
249 #[test]
250 fn stack_interner_hash_covers_all_shards() {
251 let frames = frames_for_shards::<STACK_INTERNER_SHARDS>();
252 let mut seen = [false; STACK_INTERNER_SHARDS];
253 for (shard, stack) in frames {
254 assert_eq!(
255 stack_interner_shard(&stack),
256 shard,
257 "fixture must route to its recorded shard"
258 );
259 seen[shard] = true;
260 }
261 assert!(
262 seen.into_iter().all(|covered| covered),
263 "deterministic stack fixtures must cover every interner shard"
264 );
265 }
266
267 #[test]
268 fn stack_interner_encodes_shard_and_local_id() {
269 crate::reset_profiler_for_testing();
270
271 let [(first_shard, first_stack), (second_shard, second_stack)] = frames_for_shards::<2>();
272 let first = intern_stack(&first_stack);
273 let second = intern_stack(&second_stack);
274
275 assert_eq!(first.shard(), first_shard);
276 assert_eq!(second.shard(), second_shard);
277 assert_eq!(first.local_id(), 0);
278 assert_eq!(second.local_id(), 0);
279 assert_ne!(
280 first, second,
281 "equal local ids in distinct shards must still form distinct StackIds"
282 );
283
284 release_stack(first);
285 release_stack(second);
286 crate::reset_profiler_for_testing();
287 }
288
289 #[test]
290 fn stack_interner_reuses_ids_and_releases_last_reference() {
291 crate::reset_profiler_for_testing();
292
293 let first = intern_stack(&[1, 2, 3]);
294 let repeat = intern_stack(&[1, 2, 3]);
295 assert_eq!(first, repeat);
296
297 {
298 let guard = STACK_INTERNER[first.shard()]
299 .mutex
300 .lock()
301 .unwrap_or_else(std::sync::PoisonError::into_inner);
302 let interner = guard.as_ref().expect("stack interner must be initialized");
303 let entry = interner.entries[first.local_index()]
304 .as_ref()
305 .expect("interned stack id must point to a live entry");
306 assert_eq!(entry.refs, 2);
307 assert_eq!(interner.forward.len(), 1);
308 }
309
310 release_stack(first);
311 {
312 let guard = STACK_INTERNER[first.shard()]
313 .mutex
314 .lock()
315 .unwrap_or_else(std::sync::PoisonError::into_inner);
316 let interner = guard
317 .as_ref()
318 .expect("stack interner must stay initialized");
319 let entry = interner.entries[first.local_index()]
320 .as_ref()
321 .expect("one remaining reference must keep the entry live");
322 assert_eq!(entry.refs, 1);
323 assert_eq!(interner.forward.len(), 1);
324 }
325
326 release_stack(repeat);
327 {
328 let guard = STACK_INTERNER[first.shard()]
329 .mutex
330 .lock()
331 .unwrap_or_else(std::sync::PoisonError::into_inner);
332 let interner = guard
333 .as_ref()
334 .expect("stack interner must stay initialized");
335 assert!(interner.entries[first.local_index()].is_none());
336 assert!(interner.forward.is_empty());
337 assert_eq!(interner.free_ids.as_slice(), &[first.local_id()]);
338 }
339
340 let same_shard = distinct_frames_for_shard(first.shard(), &[1, 2, 3]);
341 let reused = intern_stack(&same_shard);
342 assert_eq!(
343 reused, first,
344 "released same-shard stack ids should be recycled instead of growing the table"
345 );
346
347 crate::reset_profiler_for_testing();
348 }
349
350 #[test]
351 fn stack_interner_interns_distinct_shards_concurrently() {
352 crate::reset_profiler_for_testing();
353
354 let frames = frames_for_shards::<STACK_INTERNER_SHARDS>();
355 let barrier = Arc::new(std::sync::Barrier::new(STACK_INTERNER_SHARDS));
356 let mut workers = Vec::with_capacity(STACK_INTERNER_SHARDS);
357 for (expected_shard, stack) in frames {
358 let barrier = Arc::clone(&barrier);
359 workers.push(std::thread::spawn(move || {
360 barrier.wait();
361 let id = intern_stack(&stack);
362 assert_eq!(id.shard(), expected_shard);
363 id
364 }));
365 }
366
367 let ids: Vec<_> = workers
368 .into_iter()
369 .map(|worker| worker.join().expect("interner worker must not panic"))
370 .collect();
371 assert_eq!(ids.len(), STACK_INTERNER_SHARDS);
372 for id in &ids {
373 let guard = STACK_INTERNER[id.shard()]
374 .mutex
375 .lock()
376 .unwrap_or_else(std::sync::PoisonError::into_inner);
377 let interner = guard.as_ref().expect("stack interner must be initialized");
378 let entry = interner.entries[id.local_index()]
379 .as_ref()
380 .expect("worker interned stack id must point to a live entry");
381 assert_eq!(entry.refs, 1);
382 }
383 for id in ids {
384 release_stack(id);
385 }
386
387 crate::reset_profiler_for_testing();
388 }
389}