Skip to main content

jugar_probar/coverage/
superblock.rs

1//! Superblock Tiling (Heijunka)
2//!
3//! Per spec ยง5.4.1: The Granularity Problem
4//!
5//! If a coverage block represents a single basic block (~3 instructions),
6//! the overhead of work-stealing scheduler operations exceeds the work itself.
7//!
8//! Solution: Group localized basic blocks into a single schedulable unit
9//! (Superblock) to amortize scheduling overhead.
10
11use super::{BlockId, FunctionId};
12use std::collections::HashSet;
13use std::hash::{Hash, Hasher};
14
15/// Superblock ID (distinct from BlockId for type safety)
16#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
17pub struct SuperblockId(u32);
18
19impl SuperblockId {
20    /// Create a new superblock ID
21    #[inline]
22    #[must_use]
23    pub const fn new(id: u32) -> Self {
24        Self(id)
25    }
26
27    /// Get the inner value
28    #[inline]
29    #[must_use]
30    pub const fn as_u32(self) -> u32 {
31        self.0
32    }
33}
34
35impl Hash for SuperblockId {
36    fn hash<H: Hasher>(&self, state: &mut H) {
37        self.0.hash(state);
38    }
39}
40
41/// Superblock: A tile of related basic blocks (Kaizen: amortize scheduling)
42///
43/// Inspired by RenderMan's bucket system - group spatially local work
44/// to reduce coordination overhead.
45#[derive(Debug, Clone)]
46pub struct Superblock {
47    /// Superblock ID
48    id: SuperblockId,
49    /// Contained basic blocks
50    blocks: Vec<BlockId>,
51    /// Block set for O(1) lookup
52    block_set: HashSet<BlockId>,
53    /// Estimated execution cost (for load balancing)
54    cost: u64,
55    /// Parent function (for locality)
56    function: FunctionId,
57}
58
59impl Superblock {
60    /// Create a new superblock
61    #[must_use]
62    pub fn new(id: SuperblockId, blocks: Vec<BlockId>, function: FunctionId) -> Self {
63        let block_set: HashSet<BlockId> = blocks.iter().copied().collect();
64        let cost = blocks.len() as u64;
65        Self {
66            id,
67            blocks,
68            block_set,
69            cost,
70            function,
71        }
72    }
73
74    /// Get the superblock ID
75    #[inline]
76    #[must_use]
77    pub fn id(&self) -> SuperblockId {
78        self.id
79    }
80
81    /// Get the number of blocks in this superblock
82    #[inline]
83    #[must_use]
84    pub fn block_count(&self) -> usize {
85        self.blocks.len()
86    }
87
88    /// Check if this superblock contains a specific block
89    #[inline]
90    #[must_use]
91    pub fn contains(&self, block: BlockId) -> bool {
92        self.block_set.contains(&block)
93    }
94
95    /// Get all blocks in this superblock
96    #[must_use]
97    pub fn blocks(&self) -> &[BlockId] {
98        &self.blocks
99    }
100
101    /// Get the estimated execution cost
102    #[inline]
103    #[must_use]
104    pub fn cost_estimate(&self) -> u64 {
105        self.cost
106    }
107
108    /// Set a custom cost estimate (e.g., from profiling)
109    pub fn set_cost_estimate(&mut self, cost: u64) {
110        self.cost = cost;
111    }
112
113    /// Get the parent function
114    #[inline]
115    #[must_use]
116    pub fn function(&self) -> FunctionId {
117        self.function
118    }
119
120    /// Iterate over blocks
121    pub fn iter(&self) -> impl Iterator<Item = &BlockId> {
122        self.blocks.iter()
123    }
124}
125
126/// Superblock builder: Groups blocks by function or loop
127///
128/// Default configuration: 64 blocks per superblock (empirically optimal
129/// for amortizing work-stealing overhead).
130#[derive(Debug, Clone)]
131pub struct SuperblockBuilder {
132    /// Target blocks per superblock (amortization factor)
133    target_size: usize,
134    /// Maximum blocks per superblock (memory bound)
135    max_size: usize,
136    /// Next superblock ID
137    next_id: u32,
138}
139
140impl SuperblockBuilder {
141    /// Create a new builder with default settings
142    ///
143    /// Default: 64 blocks per superblock
144    #[must_use]
145    pub fn new() -> Self {
146        Self {
147            target_size: 64,
148            max_size: 256,
149            next_id: 0,
150        }
151    }
152
153    /// Set the target number of blocks per superblock
154    #[must_use]
155    pub fn with_target_size(mut self, size: usize) -> Self {
156        self.target_size = size;
157        self
158    }
159
160    /// Set the maximum number of blocks per superblock
161    #[must_use]
162    pub fn with_max_size(mut self, size: usize) -> Self {
163        self.max_size = size;
164        self
165    }
166
167    /// Build superblocks from a list of blocks
168    ///
169    /// Groups blocks into superblocks of target_size (capped at max_size).
170    #[must_use]
171    pub fn build_from_blocks(&self, blocks: &[BlockId], function: FunctionId) -> Vec<Superblock> {
172        if blocks.is_empty() {
173            return Vec::new();
174        }
175
176        let effective_size = self.target_size.min(self.max_size);
177        let mut superblocks = Vec::new();
178        let mut next_id = self.next_id;
179
180        for chunk in blocks.chunks(effective_size) {
181            let id = SuperblockId::new(next_id);
182            next_id += 1;
183            superblocks.push(Superblock::new(id, chunk.to_vec(), function));
184        }
185
186        superblocks
187    }
188
189    /// Build superblocks from blocks grouped by function
190    ///
191    /// Each function's blocks are grouped separately, respecting function boundaries.
192    #[must_use]
193    pub fn build_from_function_blocks(
194        &self,
195        function_blocks: &[(FunctionId, Vec<BlockId>)],
196    ) -> Vec<Superblock> {
197        let mut all_superblocks = Vec::new();
198
199        for (function, blocks) in function_blocks {
200            let superblocks = self.build_from_blocks(blocks, *function);
201            all_superblocks.extend(superblocks);
202        }
203
204        all_superblocks
205    }
206
207    /// Get the next superblock ID that would be assigned
208    #[must_use]
209    pub fn next_id(&self) -> u32 {
210        self.next_id
211    }
212}
213
214impl Default for SuperblockBuilder {
215    fn default() -> Self {
216        Self::new()
217    }
218}