use super::Allocator;
use alloc::vec::Vec;
pub(crate) struct BitmapAllocator {
bitmap: Vec<u64>,
capacity: usize,
allocated: usize,
next_free_hint: usize,
}
impl BitmapAllocator {
const BITS_PER_WORD: usize = 64;
#[allow(dead_code)]
pub fn new(capacity: usize) -> Self {
let num_words = (capacity + Self::BITS_PER_WORD - 1) / Self::BITS_PER_WORD;
let bitmap = alloc::vec![0u64; num_words];
Self {
bitmap,
capacity,
allocated: 0,
next_free_hint: 0,
}
}
#[inline]
fn word_and_bit(index: usize) -> (usize, usize) {
let word_idx = index / Self::BITS_PER_WORD;
let bit_pos = index % Self::BITS_PER_WORD;
(word_idx, bit_pos)
}
#[inline]
fn is_allocated(&self, index: usize) -> bool {
let (word_idx, bit_pos) = Self::word_and_bit(index);
(self.bitmap[word_idx] & (1u64 << bit_pos)) != 0
}
#[inline]
fn mark_allocated(&mut self, index: usize) {
let (word_idx, bit_pos) = Self::word_and_bit(index);
self.bitmap[word_idx] |= 1u64 << bit_pos;
}
#[inline]
fn mark_free(&mut self, index: usize) {
let (word_idx, bit_pos) = Self::word_and_bit(index);
self.bitmap[word_idx] &= !(1u64 << bit_pos);
}
#[inline]
fn find_free_slot(&mut self) -> Option<usize> {
let num_words = self.bitmap.len();
for offset in 0..num_words {
let word_idx = (self.next_free_hint + offset) % num_words;
let word = self.bitmap[word_idx];
if word != u64::MAX {
let bit_pos = (!word).trailing_zeros() as usize;
let index = word_idx * Self::BITS_PER_WORD + bit_pos;
if index < self.capacity {
self.next_free_hint = word_idx;
return Some(index);
}
}
}
None
}
#[allow(dead_code)]
pub fn find_free_slots(&mut self, count: usize) -> Option<alloc::vec::Vec<usize>> {
if count > self.available() {
return None;
}
let mut indices = alloc::vec::Vec::with_capacity(count);
for _ in 0..count {
if let Some(index) = self.find_free_slot() {
indices.push(index);
} else {
return None;
}
}
Some(indices)
}
#[allow(dead_code)]
pub fn extend(&mut self, additional: usize) {
self.capacity += additional;
let new_num_words = (self.capacity + Self::BITS_PER_WORD - 1) / Self::BITS_PER_WORD;
let old_num_words = self.bitmap.len();
if new_num_words > old_num_words {
self.bitmap.resize(new_num_words, 0u64);
}
}
}
impl Allocator for BitmapAllocator {
#[inline]
fn allocate(&mut self) -> Option<usize> {
if self.allocated >= self.capacity {
return None;
}
let index = self.find_free_slot()?;
self.mark_allocated(index);
self.allocated += 1;
Some(index)
}
#[inline]
fn free(&mut self, index: usize) {
debug_assert!(index < self.capacity, "index out of bounds");
debug_assert!(self.is_allocated(index), "double free detected");
self.mark_free(index);
self.allocated -= 1;
let (word_idx, _) = Self::word_and_bit(index);
self.next_free_hint = word_idx;
}
#[inline]
fn available(&self) -> usize {
self.capacity - self.allocated
}
#[inline]
fn capacity(&self) -> usize {
self.capacity
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn new_allocator_is_empty() {
let allocator = BitmapAllocator::new(100);
assert_eq!(allocator.available(), 100);
assert_eq!(allocator.capacity(), 100);
assert!(allocator.is_empty());
}
#[test]
fn allocate_and_free() {
let mut allocator = BitmapAllocator::new(10);
let idx0 = allocator.allocate().unwrap();
let idx1 = allocator.allocate().unwrap();
assert_eq!(allocator.available(), 8);
assert_eq!(allocator.allocated, 2);
allocator.free(idx0);
assert_eq!(allocator.available(), 9);
assert_eq!(allocator.allocated, 1);
allocator.free(idx1);
assert!(allocator.is_empty());
}
#[test]
fn allocate_until_full() {
let mut allocator = BitmapAllocator::new(5);
for _ in 0..5 {
assert!(allocator.allocate().is_some());
}
assert!(allocator.is_full());
assert!(allocator.allocate().is_none());
}
#[test]
fn extend_capacity() {
let mut allocator = BitmapAllocator::new(2);
allocator.allocate();
allocator.allocate();
assert!(allocator.is_full());
allocator.extend(3);
assert_eq!(allocator.capacity(), 5);
assert_eq!(allocator.available(), 3);
assert!(allocator.allocate().is_some());
assert!(allocator.allocate().is_some());
assert!(allocator.allocate().is_some());
assert!(allocator.is_full());
}
#[test]
fn large_capacity() {
let mut allocator = BitmapAllocator::new(1000);
let mut indices = Vec::new();
for _ in 0..500 {
indices.push(allocator.allocate().unwrap());
}
assert_eq!(allocator.available(), 500);
for idx in indices {
allocator.free(idx);
}
assert!(allocator.is_empty());
}
#[test]
fn reuse_freed_slots() {
let mut allocator = BitmapAllocator::new(10);
let idx = allocator.allocate().unwrap();
allocator.free(idx);
let reused_idx = allocator.allocate().unwrap();
assert_eq!(reused_idx, idx);
}
}