const NUM_BINS: usize = 13;
const SIZE_CLASSES: [usize; NUM_BINS] = [
16, 32, 48, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 65536,
];
fn bin_for_size(size: usize) -> Option<usize> {
for (i, &class_size) in SIZE_CLASSES.iter().enumerate() {
if size <= class_size {
return Some(i);
}
}
None
}
struct Bin {
size_class: usize,
free_list: Vec<usize>,
total_allocated: usize,
}
impl Bin {
fn new(size_class: usize) -> Self {
Bin {
size_class,
free_list: Vec::new(),
total_allocated: 0,
}
}
}
struct Block {
offset: usize,
size: usize,
in_use: bool,
}
pub struct BinnedAllocator {
bins: Vec<Bin>,
blocks: Vec<Block>,
storage: Vec<u8>,
overflow_free: Vec<usize>,
pub alloc_count: usize,
pub free_count: usize,
}
impl BinnedAllocator {
pub fn new() -> Self {
let bins = SIZE_CLASSES.iter().map(|&s| Bin::new(s)).collect();
BinnedAllocator {
bins,
blocks: Vec::new(),
storage: Vec::new(),
overflow_free: Vec::new(),
alloc_count: 0,
free_count: 0,
}
}
pub fn alloc(&mut self, size: usize) -> usize {
self.alloc_count += 1;
if let Some(bin_idx) = bin_for_size(size) {
let bin = &mut self.bins[bin_idx];
if let Some(block_idx) = bin.free_list.pop() {
self.blocks[block_idx].in_use = true;
return block_idx;
}
let class_size = bin.size_class;
bin.total_allocated += 1;
self.alloc_new_block(class_size)
} else {
if let Some(block_idx) = self.overflow_free.pop() {
if self.blocks[block_idx].size >= size {
self.blocks[block_idx].in_use = true;
return block_idx;
}
self.overflow_free.push(block_idx);
}
self.alloc_new_block(size)
}
}
pub fn free(&mut self, block_idx: usize) {
if block_idx >= self.blocks.len() {
return;
}
let block = &mut self.blocks[block_idx];
if !block.in_use {
return; }
block.in_use = false;
self.free_count += 1;
let size = block.size;
if let Some(bin_idx) = bin_for_size(size) {
self.bins[bin_idx].free_list.push(block_idx);
} else {
self.overflow_free.push(block_idx);
}
}
pub fn get(&self, block_idx: usize) -> Option<&[u8]> {
self.blocks.get(block_idx).and_then(|b| {
if b.in_use {
self.storage.get(b.offset..b.offset + b.size)
} else {
None
}
})
}
pub fn get_mut(&mut self, block_idx: usize) -> Option<&mut [u8]> {
if let Some(b) = self.blocks.get(block_idx) {
if b.in_use {
let offset = b.offset;
let size = b.size;
return self.storage.get_mut(offset..offset + size);
}
}
None
}
pub fn write(&mut self, block_idx: usize, data: &[u8]) -> bool {
if let Some(slice) = self.get_mut(block_idx) {
let len = data.len().min(slice.len());
slice[..len].copy_from_slice(&data[..len]);
true
} else {
false
}
}
pub fn live_count(&self) -> usize {
self.blocks.iter().filter(|b| b.in_use).count()
}
pub fn total_blocks(&self) -> usize {
self.blocks.len()
}
pub fn storage_bytes(&self) -> usize {
self.storage.len()
}
pub fn free_block_count(&self) -> usize {
self.bins.iter().map(|b| b.free_list.len()).sum::<usize>()
+ self.overflow_free.len()
}
fn alloc_new_block(&mut self, size: usize) -> usize {
let offset = self.storage.len();
let aligned = (size + 7) & !7;
self.storage.resize(self.storage.len() + aligned, 0);
let idx = self.blocks.len();
self.blocks.push(Block {
offset,
size: aligned,
in_use: true,
});
idx
}
}
impl Default for BinnedAllocator {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_bin_selection() {
assert_eq!(bin_for_size(1), Some(0)); assert_eq!(bin_for_size(16), Some(0)); assert_eq!(bin_for_size(17), Some(1)); assert_eq!(bin_for_size(48), Some(2)); assert_eq!(bin_for_size(65), Some(4)); assert_eq!(bin_for_size(65536), Some(12)); assert_eq!(bin_for_size(65537), None); }
#[test]
fn test_alloc_and_read() {
let mut alloc = BinnedAllocator::new();
let b = alloc.alloc(8);
assert!(alloc.write(b, &[1, 2, 3, 4, 5, 6, 7, 8]));
assert_eq!(&alloc.get(b).unwrap()[..8], &[1, 2, 3, 4, 5, 6, 7, 8]);
}
#[test]
fn test_free_and_reuse() {
let mut alloc = BinnedAllocator::new();
let b1 = alloc.alloc(16);
alloc.free(b1);
let b2 = alloc.alloc(16);
assert_eq!(b1, b2, "LIFO reuse");
}
#[test]
fn test_double_free_harmless() {
let mut alloc = BinnedAllocator::new();
let b = alloc.alloc(16);
alloc.free(b);
alloc.free(b); assert_eq!(alloc.free_count, 1, "second free should be no-op");
}
#[test]
fn test_storage_never_shrinks() {
let mut alloc = BinnedAllocator::new();
for _ in 0..100 {
let b = alloc.alloc(64);
alloc.free(b);
}
let bytes = alloc.storage_bytes();
assert!(bytes > 0);
for _ in 0..100 {
let b = alloc.alloc(64);
alloc.free(b);
}
assert_eq!(alloc.storage_bytes(), bytes, "storage must not shrink");
}
#[test]
fn test_deterministic_alloc_order() {
let mut a1 = BinnedAllocator::new();
let mut a2 = BinnedAllocator::new();
let seq1: Vec<usize> = (0..10).map(|_| a1.alloc(32)).collect();
let seq2: Vec<usize> = (0..10).map(|_| a2.alloc(32)).collect();
assert_eq!(seq1, seq2, "same alloc sequence → same block indices");
}
#[test]
fn test_lifo_free_order() {
let mut alloc = BinnedAllocator::new();
let b1 = alloc.alloc(32);
let b2 = alloc.alloc(32);
let b3 = alloc.alloc(32);
alloc.free(b1);
alloc.free(b2);
alloc.free(b3);
assert_eq!(alloc.alloc(32), b3);
assert_eq!(alloc.alloc(32), b2);
assert_eq!(alloc.alloc(32), b1);
}
#[test]
fn test_overflow_alloc() {
let mut alloc = BinnedAllocator::new();
let b = alloc.alloc(100_000); assert!(alloc.write(b, &[0xAB; 100]));
assert_eq!(alloc.get(b).unwrap()[0], 0xAB);
}
#[test]
fn test_live_count() {
let mut alloc = BinnedAllocator::new();
let b1 = alloc.alloc(16);
let b2 = alloc.alloc(32);
let _b3 = alloc.alloc(64);
assert_eq!(alloc.live_count(), 3);
alloc.free(b1);
assert_eq!(alloc.live_count(), 2);
alloc.free(b2);
assert_eq!(alloc.live_count(), 1);
}
#[test]
fn test_freed_block_not_readable() {
let mut alloc = BinnedAllocator::new();
let b = alloc.alloc(16);
alloc.free(b);
assert!(alloc.get(b).is_none(), "freed block should not be readable");
}
#[test]
fn test_mixed_size_classes() {
let mut alloc = BinnedAllocator::new();
let small = alloc.alloc(8);
let medium = alloc.alloc(256);
let large = alloc.alloc(4096);
alloc.free(small);
alloc.free(medium);
alloc.free(large);
let s2 = alloc.alloc(8);
let m2 = alloc.alloc(256);
let l2 = alloc.alloc(4096);
assert_eq!(s2, small);
assert_eq!(m2, medium);
assert_eq!(l2, large);
}
}