use core::sync::atomic::{AtomicU32, Ordering};
use crate::types::SEGMENT_SLICE_SIZE;
const SLICE_SHIFT: usize = SEGMENT_SLICE_SIZE.trailing_zeros() as usize;
const _: () = assert!(1 << SLICE_SHIFT == SEGMENT_SLICE_SIZE);
const SLOTS: usize = 1 << (32 - SLICE_SHIFT);
const WORD_BITS: usize = u32::BITS as usize;
const WORDS: usize = SLOTS / WORD_BITS;
static FREE: [AtomicU32; WORDS] = [const { AtomicU32::new(0) }; WORDS];
#[inline]
fn bit(idx: usize) -> (usize, u32) {
(idx / WORD_BITS, 1u32 << (idx % WORD_BITS))
}
pub fn free_range(base: usize, size: usize) -> bool {
if base < SEGMENT_SLICE_SIZE
|| size == 0
|| !base.is_multiple_of(SEGMENT_SLICE_SIZE)
|| !size.is_multiple_of(SEGMENT_SLICE_SIZE)
{
return false;
}
let start = base >> SLICE_SHIFT;
let n = size >> SLICE_SHIFT;
let Some(end) = start.checked_add(n) else {
return false;
};
if end > SLOTS {
return false;
}
for idx in start..end {
let (w, b) = bit(idx);
let prev = FREE[w].fetch_or(b, Ordering::Relaxed);
debug_assert_eq!(prev & b, 0, "slice pool: double free of slice {idx}");
}
true
}
pub fn alloc_run(slices: usize) -> Option<usize> {
if slices == 0 || slices > SLOTS {
return None;
}
let mut run = 0usize;
let mut idx = 0usize;
while idx < SLOTS {
let (w, _) = bit(idx);
let word = FREE[w].load(Ordering::Relaxed);
if word == 0 && idx.is_multiple_of(WORD_BITS) {
run = 0;
idx += WORD_BITS;
continue;
}
if word & (1 << (idx % WORD_BITS)) != 0 {
run += 1;
if run == slices {
let start = idx + 1 - slices;
for j in start..=idx {
let (jw, jb) = bit(j);
FREE[jw].fetch_and(!jb, Ordering::Relaxed);
}
return Some(start << SLICE_SHIFT);
}
} else {
run = 0;
}
idx += 1;
}
None
}
#[cfg(test)]
mod tests {
use super::*;
fn lock() -> std::sync::MutexGuard<'static, ()> {
static LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
LOCK.lock().unwrap_or_else(|e| e.into_inner())
}
const fn sl(n: usize) -> usize {
n * SEGMENT_SLICE_SIZE
}
#[test]
fn round_trips_and_coalesces() {
let _g = lock();
let base = sl(4096);
assert!(free_range(base, sl(32)));
assert!(free_range(base + sl(32), sl(16))); assert_eq!(alloc_run(48), Some(base), "coalesced run");
assert!(alloc_run(1).is_none());
assert!(free_range(base, sl(48)));
assert_eq!(alloc_run(48), Some(base));
}
#[test]
fn first_fit_skips_too_small_holes() {
let _g = lock();
let base = sl(8192);
assert!(free_range(base, sl(16)));
assert!(free_range(base + sl(128), sl(64))); assert_eq!(
alloc_run(32),
Some(base + sl(128)),
"a 32-slice run must skip the 16-slice hole"
);
assert_eq!(alloc_run(16), Some(base));
assert_eq!(alloc_run(32), Some(base + sl(160)));
assert!(alloc_run(1).is_none());
}
#[test]
fn runs_cross_word_boundaries() {
let _g = lock();
let base = sl(1000);
assert!(free_range(base, sl(100)));
assert_eq!(alloc_run(100), Some(base));
assert!(alloc_run(1).is_none());
}
#[test]
fn rejects_what_it_cannot_track() {
let _g = lock();
assert!(!free_range(0, sl(16)), "slice 0 must be refused");
assert!(!free_range(sl(16), 0), "empty range");
assert!(!free_range(sl(256) + 1, sl(16)), "misaligned base");
assert!(!free_range(sl(256), sl(16) + 1), "ragged size");
assert!(
!free_range(sl(SLOTS - 1), sl(2)),
"a run ending past SLOTS is unaddressable"
);
assert!(alloc_run(0).is_none());
assert!(alloc_run(SLOTS + 1).is_none());
assert!(alloc_run(1).is_none(), "a refusal leaves the pool empty");
}
}