use std::alloc::{alloc, dealloc, Layout};
use std::cell::RefCell;
use std::ptr::NonNull;
struct Slab {
ptr: NonNull<u8>,
size: usize,
position: usize,
}
impl Slab {
fn new(size: usize) -> Self {
let layout = Layout::from_size_align(size, 16).unwrap();
let ptr = unsafe { alloc(layout) };
Self {
ptr: NonNull::new(ptr).expect("allocation failed"),
size,
position: 0,
}
}
fn allocate(&mut self, size: usize, align: usize) -> Option<NonNull<u8>> {
let aligned_pos = (self.position + align - 1) & !(align - 1);
if aligned_pos + size > self.size {
return None;
}
let result = unsafe { NonNull::new_unchecked(self.ptr.as_ptr().add(aligned_pos)) };
self.position = aligned_pos + size;
Some(result)
}
fn remaining(&self) -> usize {
self.size - self.position
}
}
impl Drop for Slab {
fn drop(&mut self) {
let layout = Layout::from_size_align(self.size, 16).unwrap();
unsafe {
dealloc(self.ptr.as_ptr(), layout);
}
}
}
pub struct BumpPtrAllocator {
slabs: Vec<Slab>,
slab_size: usize,
total_allocated: usize,
allocation_count: usize,
}
impl BumpPtrAllocator {
pub fn new() -> Self {
Self::with_slab_size(4096)
}
pub fn with_slab_size(slab_size: usize) -> Self {
Self {
slabs: Vec::new(),
slab_size,
total_allocated: 0,
allocation_count: 0,
}
}
pub fn allocate(&mut self, size: usize, align: usize) -> NonNull<u8> {
if size == 0 {
return NonNull::dangling();
}
if let Some(slab) = self.slabs.last_mut() {
if let Some(ptr) = slab.allocate(size, align) {
self.total_allocated += size;
self.allocation_count += 1;
return ptr;
}
}
let new_slab_size = self.slab_size.max(size + align);
let mut new_slab = Slab::new(new_slab_size);
let ptr = new_slab
.allocate(size, align)
.expect("new slab should have space");
self.slabs.push(new_slab);
self.total_allocated += size;
self.allocation_count += 1;
ptr
}
pub fn allocate_obj<T>(&mut self) -> &mut T {
let size = std::mem::size_of::<T>();
let align = std::mem::align_of::<T>();
let ptr = self.allocate(size, align);
unsafe {
std::ptr::write_bytes(ptr.as_ptr(), 0, size);
&mut *ptr.as_ptr().cast::<T>()
}
}
pub fn allocate_slice<T>(&mut self, count: usize) -> &mut [T] {
let size = std::mem::size_of::<T>() * count;
let align = std::mem::align_of::<T>();
let ptr = self.allocate(size, align);
unsafe {
std::ptr::write_bytes(ptr.as_ptr(), 0, size);
std::slice::from_raw_parts_mut(ptr.as_ptr().cast::<T>(), count)
}
}
pub fn reset(&mut self) {
self.slabs.clear();
self.total_allocated = 0;
self.allocation_count = 0;
}
pub fn total_bytes(&self) -> usize {
self.slabs.iter().map(|s| s.size).sum()
}
pub fn total_allocated(&self) -> usize {
self.total_allocated
}
pub fn allocation_count(&self) -> usize {
self.allocation_count
}
pub fn slab_count(&self) -> usize {
self.slabs.len()
}
}
impl Default for BumpPtrAllocator {
fn default() -> Self {
Self::new()
}
}
impl Drop for BumpPtrAllocator {
fn drop(&mut self) {
self.slabs.clear();
}
}
pub struct SpecificBumpPtrAllocator<T> {
allocator: BumpPtrAllocator,
_phantom: std::marker::PhantomData<T>,
}
impl<T> SpecificBumpPtrAllocator<T> {
pub fn new() -> Self {
Self {
allocator: BumpPtrAllocator::new(),
_phantom: std::marker::PhantomData,
}
}
pub fn allocate(&mut self) -> &mut T {
self.allocator.allocate_obj::<T>()
}
pub fn allocate_array(&mut self, count: usize) -> &mut [T] {
self.allocator.allocate_slice::<T>(count)
}
pub fn reset(&mut self) {
self.allocator.reset();
}
pub fn allocation_count(&self) -> usize {
self.allocator.allocation_count()
}
}
impl<T> Default for SpecificBumpPtrAllocator<T> {
fn default() -> Self {
Self::new()
}
}
impl BumpPtrAllocator {
pub fn with_slab_size_and_alignment(slab_size: usize, _alignment: usize) -> Self {
Self::with_slab_size(slab_size)
}
pub fn allocate_raw(&mut self, layout: std::alloc::Layout) -> NonNull<u8> {
self.allocate(layout.size(), layout.align())
}
pub fn deallocate(&mut self, _ptr: NonNull<u8>) {}
pub fn identify_object(&self, ptr: *const u8) -> bool {
if ptr.is_null() {
return false;
}
for slab in &self.slabs {
let start = slab.ptr.as_ptr() as usize;
let end = start + slab.size;
let addr = ptr as usize;
if addr >= start && addr < end {
return true;
}
}
false
}
pub fn contains_ptr(&self, ptr: *const u8) -> bool {
self.identify_object(ptr)
}
pub fn bytes_remaining(&self) -> usize {
self.slabs.last().map(|s| s.remaining()).unwrap_or(0)
}
pub fn print_stats(&self) {
eprintln!("BumpPtrAllocator stats:");
eprintln!(" Slabs: {}", self.slab_count());
eprintln!(" Total slab bytes: {}", self.total_bytes());
eprintln!(" Total allocated: {}", self.total_allocated);
eprintln!(" Allocations: {}", self.allocation_count);
eprintln!(" Bytes remaining: {}", self.bytes_remaining());
for (i, slab) in self.slabs.iter().enumerate() {
eprintln!(
" Slab {}: size={}, used={}, remaining={}",
i,
slab.size,
slab.position,
slab.remaining()
);
}
}
pub fn reset_keep_first(&mut self) {
if let Some(first) = self.slabs.first_mut() {
first.position = 0;
}
self.slabs.truncate(1);
self.total_allocated = 0;
self.allocation_count = 0;
}
pub fn num_slabs(&self) -> usize {
self.slabs.len()
}
pub fn current_slab_size(&self) -> usize {
self.slabs.last().map(|s| s.size).unwrap_or(0)
}
pub fn current_slab_position(&self) -> usize {
self.slabs.last().map(|s| s.position).unwrap_or(0)
}
pub fn is_empty(&self) -> bool {
self.allocation_count == 0
}
pub fn utilization(&self) -> f64 {
let total = self.total_bytes();
if total == 0 {
return 0.0;
}
self.total_allocated as f64 / total as f64
}
pub fn allocate_obj_with<T: Copy>(&mut self, value: T) -> &mut T {
let obj = self.allocate_obj::<T>();
*obj = value;
obj
}
fn grow(&mut self, min_size: usize) {
let new_size = self.slab_size.max(min_size);
self.slabs.push(Slab::new(new_size));
}
pub fn allocate_zeroed(&mut self, size: usize, align: usize) -> NonNull<u8> {
let ptr = self.allocate(size, align);
unsafe {
std::ptr::write_bytes(ptr.as_ptr(), 0, size);
}
ptr
}
pub fn allocate_copy(&mut self, src: &[u8]) -> &mut [u8] {
let dest = self.allocate_slice::<u8>(src.len());
dest.copy_from_slice(src);
dest
}
}
impl Slab {
pub fn contains(&self, ptr: *const u8) -> bool {
let start = self.ptr.as_ptr() as usize;
let end = start + self.size;
let addr = ptr as usize;
addr >= start && addr < end
}
pub fn start(&self) -> *const u8 {
self.ptr.as_ptr() as *const u8
}
pub fn end(&self) -> *const u8 {
unsafe { (self.ptr.as_ptr() as *const u8).add(self.size) }
}
pub fn allocated(&self) -> usize {
self.position
}
pub fn reset(&mut self) {
self.position = 0;
}
}
impl<T> SpecificBumpPtrAllocator<T> {
pub fn with_slab_size(slab_size: usize) -> Self {
Self {
allocator: BumpPtrAllocator::with_slab_size(slab_size),
_phantom: std::marker::PhantomData,
}
}
pub fn contains(&self, ptr: *const T) -> bool {
self.allocator.identify_object(ptr as *const u8)
}
pub fn reset_keep_first(&mut self) {
self.allocator.reset_keep_first();
}
pub fn print_stats(&self) {
self.allocator.print_stats();
}
pub fn allocate_with(&mut self, value: T) -> &mut T
where
T: Copy,
{
let obj = self.allocate();
*obj = value;
obj
}
pub fn num_allocated(&self) -> usize {
self.allocation_count()
}
pub fn allocate_array_with(&mut self, count: usize, value: T) -> &mut [T]
where
T: Copy,
{
let slice = self.allocate_array(count);
for elem in slice.iter_mut() {
*elem = value;
}
slice
}
}
pub struct SlabPool {
slabs: Vec<Slab>,
free_slabs: Vec<Slab>,
slab_size: usize,
}
impl SlabPool {
pub fn new(slab_size: usize) -> Self {
Self {
slabs: Vec::new(),
free_slabs: Vec::new(),
slab_size,
}
}
pub fn get_slab(&mut self) -> Slab {
match self.free_slabs.pop() { Some(slab) => {
slab
} _ => {
Slab::new(self.slab_size)
}}
}
pub fn return_slab(&mut self, slab: Slab) {
self.free_slabs.push(slab);
}
pub fn free_count(&self) -> usize {
self.free_slabs.len()
}
pub fn active_count(&self) -> usize {
self.slabs.len()
}
}
impl BumpPtrAllocator {
pub fn allocate_sized(&mut self, size: usize, align: usize) -> (NonNull<u8>, usize) {
(self.allocate(size, align), size)
}
pub fn allocate_str(&mut self, s: &str) -> &str {
let bytes = s.as_bytes();
let dest = self.allocate_slice::<u8>(bytes.len());
dest.copy_from_slice(bytes);
unsafe { std::str::from_utf8_unchecked(dest) }
}
pub fn identify_in_first_slab(&self, ptr: *const u8) -> bool {
self.slabs.first().map(|s| s.contains(ptr)).unwrap_or(false)
}
pub fn slabs(&self) -> &[Slab] {
&self.slabs
}
pub fn first_slab_mut(&mut self) -> Option<&mut Slab> {
self.slabs.first_mut()
}
pub fn has_no_slabs(&self) -> bool {
self.slabs.is_empty()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_bump_allocator_basic() {
let mut alloc = BumpPtrAllocator::new();
let ptr = alloc.allocate(100, 8);
assert!(!ptr.as_ptr().is_null());
assert_eq!(alloc.allocation_count(), 1);
}
#[test]
fn test_bump_allocator_multiple() {
let mut alloc = BumpPtrAllocator::new();
let p1 = alloc.allocate(50, 8);
let p2 = alloc.allocate(50, 8);
assert_ne!(p1, p2);
assert_eq!(alloc.allocation_count(), 2);
}
#[test]
fn test_bump_allocator_large_allocation() {
let mut alloc = BumpPtrAllocator::with_slab_size(256);
let ptr = alloc.allocate(1024, 8);
assert!(!ptr.as_ptr().is_null());
assert!(alloc.slab_count() >= 1);
}
#[test]
fn test_bump_allocator_reset() {
let mut alloc = BumpPtrAllocator::new();
alloc.allocate(100, 8);
alloc.allocate(200, 8);
assert_eq!(alloc.allocation_count(), 2);
alloc.reset();
assert_eq!(alloc.allocation_count(), 0);
assert_eq!(alloc.slab_count(), 0);
}
#[test]
fn test_bump_allocator_obj() {
let mut alloc = BumpPtrAllocator::new();
let obj: &mut u64 = alloc.allocate_obj::<u64>();
*obj = 42;
assert_eq!(*obj, 42);
}
#[test]
fn test_bump_allocator_slice() {
let mut alloc = BumpPtrAllocator::new();
let slice = alloc.allocate_slice::<u32>(10);
assert_eq!(slice.len(), 10);
slice[0] = 123;
assert_eq!(slice[0], 123);
}
#[test]
fn test_specific_bump_allocator() {
let mut alloc = SpecificBumpPtrAllocator::<u64>::new();
{
let a = alloc.allocate();
*a = 100;
}
{
let b = alloc.allocate();
*b = 200;
}
assert_eq!(alloc.allocation_count(), 2);
}
#[test]
fn test_bump_allocator_alignment() {
let mut alloc = BumpPtrAllocator::new();
let ptr = alloc.allocate(1, 16);
assert_eq!(ptr.as_ptr() as usize % 16, 0);
}
}