1use crate::class::{self, SPAN_BYTES};
25use crate::os::PAGE;
26
27pub const PAGES_PER_SPAN: usize = SPAN_BYTES / PAGE;
29
30pub const BITMAP_WORDS: usize = SPAN_BYTES / 16 / 64;
32
33pub const NO_CLASS: u8 = 0xFF;
35
36#[derive(Clone, Copy)]
40pub struct SpanMeta {
41 pub class: u8,
43 hint: u8,
47 pub live: u16,
49 pub high_water: u16,
52 pub discarded: u16,
55 bitmap: [u64; BITMAP_WORDS],
58}
59
60impl SpanMeta {
61 pub(crate) const fn new() -> Self {
62 Self {
63 class: NO_CLASS,
64 hint: 0,
65 live: 0,
66 high_water: 0,
67 discarded: 0,
68 bitmap: [0; BITMAP_WORDS],
69 }
70 }
71
72 pub fn reset(&mut self, class: u8) {
75 debug_assert_eq!(self.live, 0, "resetting a span with live slots");
76 *self = Self { class, ..Self::new() };
77 }
78
79 #[must_use]
81 pub fn capacity(&self) -> u32 {
82 if self.class == NO_CLASS {
83 return 0;
84 }
85 class::slots_per_span(self.class as usize) as u32
86 }
87
88 pub fn alloc_slot(&mut self) -> Option<u32> {
90 let n = self.capacity();
91 let words = (n as usize).div_ceil(64);
92 for w in (self.hint as usize)..words {
93 let holes = !self.bitmap[w];
94 if holes == 0 {
95 continue;
96 }
97 let i = (w as u32) * 64 + holes.trailing_zeros();
98 if i >= n {
99 return None;
102 }
103 self.bitmap[w] |= 1u64 << (i % 64);
104 self.hint = w as u8;
105 self.live += 1;
106 if i as u16 >= self.high_water {
107 self.high_water = i as u16 + 1;
108 }
109 return Some(i);
110 }
111 None
112 }
113
114 pub fn claim_word(&mut self) -> Option<(u8, u64)> {
128 let n = self.capacity();
129 let words = (n as usize).div_ceil(64);
130 for w in (self.hint as usize)..words {
131 let valid = if (w + 1) * 64 <= n as usize {
132 !0u64
133 } else {
134 (1u64 << (n as usize - w * 64)) - 1
135 };
136 let holes = !self.bitmap[w] & valid;
137 if holes == 0 {
138 continue;
139 }
140 self.bitmap[w] |= holes;
141 self.live += holes.count_ones() as u16;
142 let hi = (w as u32) * 64 + (63 - holes.leading_zeros());
143 if hi as u16 >= self.high_water {
144 self.high_water = hi as u16 + 1;
145 }
146 self.hint = w as u8;
147 return Some((w as u8, holes));
148 }
149 None
150 }
151
152 pub fn retire_word(&mut self, w: u8, unused: u64) {
157 debug_assert_eq!(
158 self.bitmap[w as usize] & unused,
159 unused,
160 "retiring bits that were not claimed"
161 );
162 self.bitmap[w as usize] &= !unused;
163 self.live -= unused.count_ones() as u16;
164 if w < self.hint {
165 self.hint = w;
166 }
167 }
168
169 pub fn free_slot(&mut self, i: u32) {
171 let w = (i / 64) as usize;
172 let m = 1u64 << (i % 64);
173 debug_assert!(self.bitmap[w] & m != 0, "double free of slot {i}");
174 self.bitmap[w] &= !m;
175 self.live -= 1;
176 if (w as u8) < self.hint {
177 self.hint = w as u8;
178 }
179 }
180
181 #[must_use]
184 pub fn is_live(&self, i: u32) -> bool {
185 self.bitmap[(i / 64) as usize] & (1u64 << (i % 64)) != 0
186 }
187
188 #[must_use]
190 pub fn range_has_live(&self, first: u32, last: u32) -> bool {
191 let (fw, lw) = ((first / 64) as usize, (last / 64) as usize);
192 for w in fw..=lw {
193 let mut mask = !0u64;
194 if w == fw {
195 mask &= !0u64 << (first % 64);
196 }
197 if w == lw {
198 mask &= !0u64 >> (63 - (last % 64));
199 }
200 if self.bitmap[w] & mask != 0 {
201 return true;
202 }
203 }
204 false
205 }
206}
207
208#[must_use]
210pub fn pages_of_slot(i: u32, slot_size: usize) -> (usize, usize) {
211 let start = i as usize * slot_size;
212 let end = start + slot_size - 1;
213 (start / PAGE, end / PAGE)
214}
215
216#[must_use]
219pub fn slots_of_page(p: usize, slot_size: usize, nslots: u32) -> (u32, u32) {
220 let first = (p * PAGE / slot_size) as u32;
221 let last = (((p + 1) * PAGE - 1) / slot_size) as u32;
222 (first.min(nslots - 1), last.min(nslots - 1))
223}
224
225#[cfg(test)]
226mod tests {
227 use super::*;
228
229 fn meta_for(size: usize) -> SpanMeta {
230 let mut m = SpanMeta::new();
231 m.reset(class::index_of(size, 8).unwrap() as u8);
232 m
233 }
234
235 #[test]
236 fn allocation_is_lowest_first_and_exhausts_exactly() {
237 let mut m = meta_for(8192);
238 let cap = m.capacity();
239 for expect in 0..cap {
240 assert_eq!(m.alloc_slot(), Some(expect), "not lowest-first");
241 }
242 assert_eq!(m.alloc_slot(), None, "over-handed past capacity");
243 assert_eq!(m.live as u32, cap);
244 }
245
246 #[test]
247 fn a_freed_low_slot_is_taken_before_a_higher_hole() {
248 let mut m = meta_for(400);
249 for _ in 0..100 {
250 m.alloc_slot();
251 }
252 m.free_slot(3);
253 m.free_slot(97);
254 assert_eq!(m.alloc_slot(), Some(3), "densification broken");
255 assert_eq!(m.alloc_slot(), Some(97));
256 }
257
258 #[test]
259 fn range_has_live_sees_across_word_boundaries() {
260 let mut m = meta_for(16); for _ in 0..=130 {
262 m.alloc_slot();
263 }
264 for i in 0..=129 {
265 m.free_slot(i);
266 }
267 assert!(m.range_has_live(0, 200));
269 assert!(m.range_has_live(130, 130));
270 assert!(!m.range_has_live(0, 129));
271 assert!(!m.range_has_live(131, 300));
272 }
273
274 #[test]
275 fn claim_takes_the_lowest_holed_word_and_retire_reverses_it() {
276 let mut m = meta_for(400); for _ in 0..64 {
278 m.alloc_slot(); }
280 let (w, mask) = m.claim_word().expect("word 1 has holes");
281 assert_eq!(w, 1, "lowest holed word");
282 assert_eq!(mask, !0u64, "all 64 bits were free");
283 assert_eq!(m.live, 128);
284 assert_eq!(m.alloc_slot(), Some(128), "next span alloc lands in word 2");
286 m.free_slot(128);
287 m.retire_word(w, 0xFFFF_FFFF);
289 assert_eq!(m.live, 96);
290 assert_eq!(m.alloc_slot(), Some(64), "retired bit is the lowest hole");
291 }
292
293 #[test]
294 fn claim_respects_the_capacity_edge() {
295 let mut m = meta_for(400); for _ in 0..128 {
297 m.alloc_slot();
298 }
299 let (w, mask) = m.claim_word().expect("partial last word");
300 assert_eq!(w, 2);
301 assert_eq!(mask.count_ones(), 157 - 128, "only valid bits claimed");
302 assert_eq!(m.claim_word(), None, "span exhausted");
303 assert_eq!(m.live as u32, m.capacity());
304 }
305
306 #[test]
307 fn page_and_slot_maps_are_inverses() {
308 for size in [16usize, 400, 416, 4096, 8192] {
309 let slot = class::size_of(class::index_of(size, 8).unwrap());
310 let n = (SPAN_BYTES / slot) as u32;
311 for p in 0..PAGES_PER_SPAN {
312 let (a, b) = slots_of_page(p, slot, n);
313 for i in a..=b {
314 let (pa, pb) = pages_of_slot(i, slot);
315 assert!(
316 pa <= p && p <= pb,
317 "slot {i} of {slot}B claims pages {pa}..={pb}, not {p}"
318 );
319 }
320 }
321 }
322 }
323}