1pub const PAGE_BYTES: usize = 2048;
17
18#[derive(Copy, Clone, Debug, PartialEq, Eq)]
20pub struct PageRun {
21 pub first_page: u32,
23 pub page_count: u32,
25}
26
27impl PageRun {
28 pub const fn end_page(self) -> u32 {
30 self.first_page + self.page_count
31 }
32
33 pub const fn capacity_bytes(self) -> usize {
35 self.page_count as usize * PAGE_BYTES
36 }
37}
38
39#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
42pub struct Handle {
43 pool: u8,
44 slot: u16,
45 generation: u32,
46}
47
48impl Handle {
49 pub const NONE: Handle = Handle {
51 pool: u8::MAX,
52 slot: u16::MAX,
53 generation: 0,
54 };
55
56 pub const fn pool(self) -> u8 {
58 self.pool
59 }
60
61 pub const fn slot(self) -> u16 {
63 self.slot
64 }
65
66 pub const fn generation(self) -> u32 {
68 self.generation
69 }
70}
71
72#[derive(Copy, Clone, Debug, PartialEq, Eq)]
74pub enum Placement {
75 FirstFit,
77 BestFit,
79}
80
81#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
83pub struct Fragmentation {
84 pub free_pages: u32,
86 pub largest_free_run: u32,
88 pub free_run_count: u32,
90}
91
92impl Fragmentation {
93 pub const fn permille(self) -> u32 {
96 if self.free_pages == 0 {
97 return 0;
98 }
99 let mut free = self.free_pages;
101 let mut largest = self.largest_free_run;
102 while free > u32::MAX / 1000 {
103 free >>= 1;
104 largest >>= 1;
105 }
106 1000 - largest * 1000 / free
107 }
108}
109
110#[derive(Copy, Clone, Debug, PartialEq, Eq)]
112pub struct Moved {
113 pub handle: Handle,
115 pub from: PageRun,
117 pub to_page: u32,
119}
120
121#[derive(Copy, Clone)]
122struct Run {
123 first: u32,
124 count: u32,
126 generation: u32,
127 locked: bool,
128}
129
130const FREE_RUN: Run = Run {
131 first: 0,
132 count: 0,
133 generation: 0,
134 locked: false,
135};
136
137pub struct PagePool<const SLOTS: usize> {
142 pool: u8,
143 capacity_pages: u32,
144 placement: Placement,
145 runs: [Run; SLOTS],
146 order: [u16; SLOTS],
148 live: usize,
149 used_pages: u32,
150 layout_generation: u32,
151}
152
153impl<const SLOTS: usize> PagePool<SLOTS> {
154 pub const fn new(pool: u8, capacity_pages: u32, placement: Placement) -> Self {
157 Self {
158 pool,
159 capacity_pages,
160 placement,
161 runs: [FREE_RUN; SLOTS],
162 order: [0; SLOTS],
163 live: 0,
164 used_pages: 0,
165 layout_generation: 0,
166 }
167 }
168
169 pub const fn capacity_pages(&self) -> u32 {
171 self.capacity_pages
172 }
173
174 pub const fn used_pages(&self) -> u32 {
176 self.used_pages
177 }
178
179 pub const fn free_pages(&self) -> u32 {
181 self.capacity_pages - self.used_pages
182 }
183
184 pub const fn live_count(&self) -> usize {
186 self.live
187 }
188
189 pub const fn placement(&self) -> Placement {
191 self.placement
192 }
193
194 pub fn set_placement(&mut self, placement: Placement) {
196 self.placement = placement;
197 }
198
199 pub const fn layout_generation(&self) -> u32 {
202 self.layout_generation
203 }
204
205 pub fn allocate(&mut self, page_count: u32) -> Option<(Handle, PageRun)> {
208 if page_count == 0 {
209 return None;
210 }
211 let slot = self.runs.iter().position(|run| run.count == 0)?;
212 let first = self.find_fit(page_count)?;
213 let generation = self.runs[slot].generation.wrapping_add(1);
214 self.runs[slot] = Run {
215 first,
216 count: page_count,
217 generation,
218 locked: false,
219 };
220 let mut position = self.live;
221 while position > 0 && self.runs[self.order[position - 1] as usize].first > first {
222 self.order[position] = self.order[position - 1];
223 position -= 1;
224 }
225 self.order[position] = slot as u16;
226 self.live += 1;
227 self.used_pages += page_count;
228 Some((
229 Handle {
230 pool: self.pool,
231 slot: slot as u16,
232 generation,
233 },
234 PageRun {
235 first_page: first,
236 page_count,
237 },
238 ))
239 }
240
241 pub fn find_fit(&self, page_count: u32) -> Option<u32> {
243 if page_count == 0 || page_count > self.free_pages() {
244 return None;
245 }
246 let mut best: Option<(u32, u32)> = None;
247 for (start, length) in self.gaps() {
248 if length < page_count {
249 continue;
250 }
251 match self.placement {
252 Placement::FirstFit => return Some(start),
253 Placement::BestFit => {
254 if best.is_none_or(|(_, best_length)| length < best_length) {
255 best = Some((start, length));
256 }
257 }
258 }
259 }
260 best.map(|(start, _)| start)
261 }
262
263 pub fn free(&mut self, handle: Handle) -> bool {
265 if !self.is_current(handle) {
266 return false;
267 }
268 let slot = handle.slot as usize;
269 let position = self.order[..self.live]
270 .iter()
271 .position(|&s| s as usize == slot)
272 .expect("a live run is always in the order table");
273 self.order.copy_within(position + 1..self.live, position);
274 self.live -= 1;
275 self.used_pages -= self.runs[slot].count;
276 self.runs[slot] = Run {
277 generation: self.runs[slot].generation.wrapping_add(1),
278 ..FREE_RUN
279 };
280 true
281 }
282
283 pub fn is_current(&self, handle: Handle) -> bool {
285 handle.pool == self.pool
286 && self
287 .runs
288 .get(handle.slot as usize)
289 .is_some_and(|run| run.count > 0 && run.generation == handle.generation)
290 }
291
292 pub fn try_run(&self, handle: Handle) -> Option<PageRun> {
294 if !self.is_current(handle) {
295 return None;
296 }
297 let run = self.runs[handle.slot as usize];
298 Some(PageRun {
299 first_page: run.first,
300 page_count: run.count,
301 })
302 }
303
304 pub fn set_locked(&mut self, handle: Handle, locked: bool) -> bool {
308 if !self.is_current(handle) {
309 return false;
310 }
311 self.runs[handle.slot as usize].locked = locked;
312 true
313 }
314
315 pub fn is_locked(&self, handle: Handle) -> bool {
317 self.is_current(handle) && self.runs[handle.slot as usize].locked
318 }
319
320 pub fn iter_runs(&self) -> impl Iterator<Item = (u16, PageRun, bool)> + '_ {
322 self.order[..self.live].iter().map(move |&slot| {
323 let run = self.runs[slot as usize];
324 (
325 slot,
326 PageRun {
327 first_page: run.first,
328 page_count: run.count,
329 },
330 run.locked,
331 )
332 })
333 }
334
335 pub fn handle_of_slot(&self, slot: u16) -> Option<Handle> {
337 let run = self.runs.get(slot as usize)?;
338 (run.count > 0).then_some(Handle {
339 pool: self.pool,
340 slot,
341 generation: run.generation,
342 })
343 }
344
345 pub fn gaps(&self) -> impl Iterator<Item = (u32, u32)> + '_ {
347 let mut cursor = 0u32;
348 let mut position = 0usize;
349 let mut done = false;
350 core::iter::from_fn(move || {
351 while !done {
352 let (end, next_cursor) = if position < self.live {
353 let run = self.runs[self.order[position] as usize];
354 position += 1;
355 (run.first, run.first + run.count)
356 } else {
357 done = true;
358 (self.capacity_pages, self.capacity_pages)
359 };
360 let start = cursor;
361 cursor = next_cursor;
362 if end > start {
363 return Some((start, end - start));
364 }
365 }
366 None
367 })
368 }
369
370 pub fn fragmentation(&self) -> Fragmentation {
372 let mut result = Fragmentation::default();
373 for (_, length) in self.gaps() {
374 result.free_pages += length;
375 result.free_run_count += 1;
376 result.largest_free_run = result.largest_free_run.max(length);
377 }
378 result
379 }
380
381 pub fn compact_step(&mut self, mut copy: impl FnMut(PageRun, u32)) -> Option<Moved> {
387 let mut cursor = 0u32;
388 for position in 0..self.live {
389 let slot = self.order[position] as usize;
390 let run = self.runs[slot];
391 if !run.locked && run.first > cursor {
392 let from = PageRun {
393 first_page: run.first,
394 page_count: run.count,
395 };
396 copy(from, cursor);
397 self.runs[slot].first = cursor;
398 self.layout_generation = self.layout_generation.wrapping_add(1);
399 return Some(Moved {
400 handle: Handle {
401 pool: self.pool,
402 slot: slot as u16,
403 generation: run.generation,
404 },
405 from,
406 to_page: cursor,
407 });
408 }
409 cursor = run.first + run.count;
410 }
411 None
412 }
413
414 #[cfg(any(test, debug_assertions))]
416 pub fn check_invariants(&self) {
417 let mut used = 0u32;
418 let mut previous_end = 0u32;
419 for position in 0..self.live {
420 let run = self.runs[self.order[position] as usize];
421 assert!(run.count > 0, "order lists a free slot");
422 assert!(run.first >= previous_end, "runs overlap or are unsorted");
423 previous_end = run.first + run.count;
424 assert!(previous_end <= self.capacity_pages, "run past capacity");
425 used += run.count;
426 }
427 assert_eq!(used, self.used_pages, "used_pages out of step");
428 let live = self.runs.iter().filter(|run| run.count > 0).count();
429 assert_eq!(live, self.live, "live count out of step");
430 }
431}
432
433#[cfg(test)]
434mod tests {
435 use super::*;
436
437 #[test]
438 fn charges_exact_page_count() {
439 let mut pool = PagePool::<3>::new(0, 8, Placement::FirstFit);
440 let (_, a) = pool.allocate(1).unwrap();
441 let (_, b) = pool.allocate(2).unwrap();
442 assert_eq!(a.page_count, 1);
443 assert_eq!(b.page_count, 2);
444 assert_eq!(pool.free_pages(), 5);
445 pool.check_invariants();
446 }
447
448 #[test]
449 fn released_or_reused_slot_invalidates_old_handle() {
450 let mut pool = PagePool::<1>::new(0, 4, Placement::FirstFit);
451 let (first, _) = pool.allocate(1).unwrap();
452 assert!(pool.try_run(first).is_some());
453 assert!(pool.free(first));
454 assert!(pool.try_run(first).is_none());
455 assert!(!pool.free(first), "double free is refused");
456 let (second, _) = pool.allocate(1).unwrap();
457 assert_eq!(first.slot(), second.slot());
458 assert_ne!(first.generation(), second.generation());
459 assert!(pool.try_run(first).is_none(), "reuse must not revive it");
460 }
461
462 #[test]
463 fn handle_from_another_pool_is_stale() {
464 let mut a = PagePool::<2>::new(0, 4, Placement::FirstFit);
465 let b = PagePool::<2>::new(1, 4, Placement::FirstFit);
466 let (handle, _) = a.allocate(1).unwrap();
467 assert!(!b.is_current(handle));
468 }
469
470 #[test]
471 fn placement_policies_choose_different_gaps() {
472 let build = |placement| {
474 let mut pool = PagePool::<8>::new(0, 10, placement);
475 let (a, _) = pool.allocate(2).unwrap();
476 let (gap1, _) = pool.allocate(4).unwrap();
477 let (b, _) = pool.allocate(1).unwrap();
478 let (gap2, _) = pool.allocate(2).unwrap();
479 let (c, _) = pool.allocate(1).unwrap();
480 pool.free(gap1);
481 pool.free(gap2);
482 (pool, [a, b, c])
483 };
484 let (first_fit, _) = build(Placement::FirstFit);
485 let (best_fit, _) = build(Placement::BestFit);
486 assert_eq!(first_fit.find_fit(2), Some(2));
487 assert_eq!(best_fit.find_fit(2), Some(7));
488 assert_eq!(best_fit.find_fit(4), Some(2));
489 assert_eq!(best_fit.find_fit(5), None);
490 }
491
492 #[test]
493 fn fragmented_pool_compacts_and_keeps_handles() {
494 let mut pool = PagePool::<4>::new(0, 8, Placement::FirstFit);
495 let (a, _) = pool.allocate(2).unwrap();
496 let (b, _) = pool.allocate(2).unwrap();
497 let (c, _) = pool.allocate(2).unwrap();
498 pool.free(b);
499 assert_eq!(pool.find_fit(4), None);
500 assert_eq!(pool.fragmentation().free_run_count, 2);
501 let before = pool.layout_generation();
502 let mut copies = [None; 2];
503 let mut count = 0;
504 while let Some(moved) = pool.compact_step(|from, to| {
505 copies[count] = Some((from.first_page, to));
506 }) {
507 assert_eq!(moved.handle, c);
508 count += 1;
509 }
510 assert_eq!(copies[0], Some((4, 2)));
511 assert_ne!(pool.layout_generation(), before);
512 assert_eq!(pool.try_run(a).unwrap().first_page, 0);
513 assert_eq!(pool.try_run(c).unwrap().first_page, 2);
514 assert_eq!(pool.find_fit(4), Some(4));
515 pool.check_invariants();
516 }
517
518 #[test]
519 fn locked_run_is_a_compaction_barrier() {
520 let mut pool = PagePool::<5>::new(0, 12, Placement::FirstFit);
521 let (a, _) = pool.allocate(2).unwrap();
522 let (b, _) = pool.allocate(2).unwrap();
523 let (c, _) = pool.allocate(2).unwrap();
524 let (d, _) = pool.allocate(2).unwrap();
525 pool.free(a);
526 pool.free(c);
527 assert!(pool.set_locked(b, true));
528 assert!(pool.is_locked(b));
529 let moved = pool.compact_step(|_, _| {}).unwrap();
532 assert_eq!(moved.handle, d);
533 assert_eq!(moved.to_page, 4);
534 assert!(pool.compact_step(|_, _| {}).is_none());
535 assert_eq!(pool.try_run(b).unwrap().first_page, 2);
536 assert_eq!(pool.try_run(d).unwrap().first_page, 4);
537 pool.check_invariants();
538 }
539
540 #[test]
541 fn free_space_shape_is_reported() {
542 let mut pool = PagePool::<4>::new(0, 10, Placement::FirstFit);
543 let (a, _) = pool.allocate(3).unwrap();
544 let (_b, _) = pool.allocate(3).unwrap();
545 pool.free(a);
546 let shape = pool.fragmentation();
547 assert_eq!(shape.free_pages, 7);
548 assert_eq!(shape.largest_free_run, 4);
549 assert_eq!(shape.free_run_count, 2);
550 assert_eq!(shape.permille(), 1000 - 4 * 1000 / 7);
551 }
552
553 #[test]
554 fn full_slot_table_refuses_even_when_pages_are_free() {
555 let mut pool = PagePool::<2>::new(0, 100, Placement::FirstFit);
556 pool.allocate(1).unwrap();
557 pool.allocate(1).unwrap();
558 assert!(pool.allocate(1).is_none());
559 assert!(pool.allocate(0).is_none());
560 }
561}