Skip to main content

jugar_probar/coverage/
thread_local.rs

1//! Thread-Local Counter Buffering (Muda Elimination)
2//!
3//! Per spec ยง5.3.1: Workers increment local registers and flush to global
4//! counters only upon block exit or checkpoint. This reduces bus contention
5//! from O(N) to O(B) where N is instructions and B is block transitions.
6
7use super::BlockId;
8
9/// Thread-local counter buffer (Kaizen: eliminates atomic contention)
10///
11/// Instead of atomic increments on every block hit, counters are buffered
12/// locally and flushed periodically. This dramatically reduces cache
13/// coherence traffic in parallel coverage collection.
14#[derive(Debug)]
15pub struct ThreadLocalCounters {
16    /// Local counter buffer (one per block)
17    local: Vec<u64>,
18    /// Flush threshold (blocks executed before sync)
19    flush_threshold: usize,
20    /// Blocks since last flush
21    blocks_since_flush: usize,
22    /// Number of flushes performed
23    flush_count: usize,
24}
25
26impl ThreadLocalCounters {
27    /// Create new thread-local counters for the given number of blocks
28    ///
29    /// Uses a default flush threshold of 1000 blocks.
30    #[must_use]
31    pub fn new(block_count: usize) -> Self {
32        Self {
33            local: vec![0; block_count],
34            flush_threshold: 1000,
35            blocks_since_flush: 0,
36            flush_count: 0,
37        }
38    }
39
40    /// Create with a custom flush threshold
41    ///
42    /// Lower thresholds mean more frequent flushes (more accurate but slower).
43    /// Higher thresholds mean fewer flushes (faster but less accurate intermediate state).
44    #[must_use]
45    pub fn with_flush_threshold(block_count: usize, threshold: usize) -> Self {
46        Self {
47            local: vec![0; block_count],
48            flush_threshold: threshold,
49            blocks_since_flush: 0,
50            flush_count: 0,
51        }
52    }
53
54    /// Increment counter locally (no atomic, no cache coherence traffic)
55    ///
56    /// This is the hot path - must be as fast as possible.
57    #[inline(always)]
58    pub fn increment(&mut self, block: BlockId) {
59        let idx = block.as_u32() as usize;
60        if idx < self.local.len() {
61            self.local[idx] += 1;
62            self.blocks_since_flush += 1;
63
64            // Amortize flush cost over many increments
65            if self.blocks_since_flush >= self.flush_threshold {
66                self.internal_flush();
67            }
68        }
69    }
70
71    /// Get the current local count for a block (before flush)
72    #[inline]
73    #[must_use]
74    pub fn get(&self, block: BlockId) -> u64 {
75        let idx = block.as_u32() as usize;
76        self.local.get(idx).copied().unwrap_or(0)
77    }
78
79    /// Flush local counters and return the counts
80    ///
81    /// Returns the accumulated counts and resets the local buffer.
82    #[must_use]
83    pub fn flush(&mut self) -> Vec<u64> {
84        let result = self.local.clone();
85        self.local.fill(0);
86        self.blocks_since_flush = 0;
87        self.flush_count += 1;
88        result
89    }
90
91    /// Internal flush that just resets state (for automatic threshold flush)
92    fn internal_flush(&mut self) {
93        // In a real implementation, this would atomically add to global counters
94        // For now, we just track that a flush occurred
95        self.blocks_since_flush = 0;
96        self.flush_count += 1;
97    }
98
99    /// Get the number of times flush has been called
100    #[must_use]
101    pub fn flush_count(&self) -> usize {
102        self.flush_count
103    }
104
105    /// Get the number of blocks being tracked
106    #[must_use]
107    pub fn block_count(&self) -> usize {
108        self.local.len()
109    }
110
111    /// Check if any blocks have been hit since last flush
112    #[must_use]
113    pub fn has_pending(&self) -> bool {
114        self.blocks_since_flush > 0
115    }
116
117    /// Get the flush threshold
118    #[must_use]
119    pub fn flush_threshold(&self) -> usize {
120        self.flush_threshold
121    }
122}
123
124impl Default for ThreadLocalCounters {
125    fn default() -> Self {
126        Self::new(0)
127    }
128}