extern crate alloc;
use alloc::{boxed::Box, vec};
#[repr(align(32))]
#[derive(Copy)]
#[allow(dead_code)]
struct Align32([u8; 32]);
impl Clone for Align32 {
fn clone(&self) -> Self {
*self
}
}
use core::{
alloc::{GlobalAlloc, Layout},
sync::atomic::{AtomicUsize, Ordering},
};
pub struct BumpAllocator {
heap: Box<[Align32]>,
offset: AtomicUsize,
heap_size: usize,
}
impl BumpAllocator {
pub fn new(heap_size: usize) -> Self {
let elem_size = 32;
let n_elems = heap_size.div_ceil(elem_size);
let heap = vec![Align32([0u8; 32]); n_elems].into_boxed_slice();
Self {
heap,
offset: AtomicUsize::new(0),
heap_size,
}
}
fn heap_start(&self) -> *mut u8 {
self.heap.as_ptr() as *mut u8
}
pub fn reset(&self) {
self.offset.store(0, Ordering::Release);
}
}
unsafe impl Sync for BumpAllocator {}
unsafe impl GlobalAlloc for BumpAllocator {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
let size = layout.size();
let align = layout.align();
let heap_start = self.heap_start();
let heap_alignment = 1 << (heap_start as usize).trailing_zeros();
if align > heap_alignment {
return core::ptr::null_mut();
}
if size == 0 {
if heap_start.align_offset(align) == usize::MAX {
return core::ptr::null_mut();
}
return heap_start;
}
loop {
let orig_offset = self.offset.load(Ordering::Acquire);
if orig_offset > self.heap_size {
return core::ptr::null_mut();
}
let ptr = unsafe { heap_start.add(orig_offset) };
let offset = ptr.align_offset(align);
if offset == usize::MAX || orig_offset.checked_add(offset).is_none() {
return core::ptr::null_mut();
}
let aligned_offset = orig_offset + offset;
let new_offset = aligned_offset.checked_add(size);
if new_offset.is_none() || new_offset.unwrap() > self.heap_size {
return core::ptr::null_mut();
}
if self
.offset
.compare_exchange(
orig_offset,
new_offset.unwrap(),
Ordering::AcqRel,
Ordering::Acquire,
)
.is_ok()
{
return unsafe { heap_start.add(aligned_offset) };
}
}
}
unsafe fn dealloc(&self, _ptr: *mut u8, _layout: Layout) {
}
}
pub struct StaticBumpAllocator {
heap_start: *mut u8,
heap_size: usize,
offset: AtomicUsize,
}
impl StaticBumpAllocator {
pub unsafe fn new(heap_start: *mut u8, heap_size: usize) -> Self {
Self {
heap_start,
heap_size,
offset: AtomicUsize::new(0),
}
}
pub fn reset(&self) {
self.offset.store(0, Ordering::Release);
}
}
unsafe impl Sync for StaticBumpAllocator {}
unsafe impl GlobalAlloc for StaticBumpAllocator {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
let size = layout.size();
let align = layout.align();
if size == 0 {
let ptr = self.heap_start;
return if (ptr as usize) % align == 0 {
ptr
} else {
let rounded = (ptr as usize + (align - 1)) & !(align - 1);
rounded as *mut u8
};
}
let heap_start = self.heap_start;
let heap_alignment = 1 << (heap_start as usize).trailing_zeros();
if align > heap_alignment {
return core::ptr::null_mut();
}
loop {
let orig = self.offset.load(Ordering::Acquire);
let aligned_off = (orig + (align - 1)) & !(align - 1);
let new_off = match aligned_off.checked_add(size) {
Some(no) if no <= self.heap_size => no,
_ => return core::ptr::null_mut(),
};
if self
.offset
.compare_exchange(orig, new_off, Ordering::AcqRel, Ordering::Acquire)
.is_ok()
{
return unsafe { heap_start.add(aligned_off) };
}
}
}
unsafe fn dealloc(&self, _ptr: *mut u8, _layout: Layout) {
}
}
#[cfg(test)]
mod tests {
use super::*;
use core::alloc::Layout;
fn test_allocator(size: usize) -> BumpAllocator {
BumpAllocator::new(size)
}
#[test]
fn alloc_basic() {
let heap_size = 128;
let alloc = test_allocator(heap_size);
let layout = Layout::from_size_align(8, 4).unwrap();
let ptr = unsafe { alloc.alloc(layout) };
assert!(!ptr.is_null(), "Allocation should succeed");
assert_eq!(ptr as usize % 4, 0, "Pointer should be 4-byte aligned");
}
#[test]
fn alloc_multiple() {
let heap_size = 64;
let alloc = test_allocator(heap_size);
let layout = Layout::from_size_align(16, 8).unwrap();
let ptr1 = unsafe { alloc.alloc(layout) };
let ptr2 = unsafe { alloc.alloc(layout) };
assert!(
!ptr1.is_null() && !ptr2.is_null(),
"Both allocations should succeed"
);
assert_ne!(ptr1, ptr2, "Pointers should be different");
assert_eq!(ptr1 as usize % 8, 0);
assert_eq!(ptr2 as usize % 8, 0);
}
#[test]
fn alloc_out_of_memory() {
let heap_size = 32;
let alloc = test_allocator(heap_size);
let layout = Layout::from_size_align(32, 1).unwrap();
let ptr1 = unsafe { alloc.alloc(layout) };
let ptr2 = unsafe { alloc.alloc(layout) };
assert!(!ptr1.is_null(), "First allocation should succeed");
assert!(ptr2.is_null(), "Second allocation should fail (OOM)");
}
#[test]
fn alloc_zero_size() {
let heap_size = 16;
let alloc = test_allocator(heap_size);
let layout = Layout::from_size_align(0, 1).unwrap();
let ptr = unsafe { alloc.alloc(layout) };
let _ = ptr;
}
#[test]
fn alloc_max_size() {
let heap_size = 128;
let alloc = test_allocator(heap_size);
let layout = Layout::from_size_align(heap_size, 1).unwrap();
let ptr = unsafe { alloc.alloc(layout) };
assert!(!ptr.is_null(), "Should allocate entire heap");
let layout2 = Layout::from_size_align(1, 1).unwrap();
let ptr2 = unsafe { alloc.alloc(layout2) };
assert!(ptr2.is_null(), "Should be OOM after full allocation");
}
#[test]
fn dealloc_does_not_reuse() {
let heap_size = 32;
let alloc = test_allocator(heap_size);
let layout = Layout::from_size_align(16, 1).unwrap();
let ptr1 = unsafe { alloc.alloc(layout) };
unsafe { alloc.dealloc(ptr1, layout) };
let ptr2 = unsafe { alloc.alloc(layout) };
assert_ne!(ptr1, ptr2, "Bump allocator should not reuse freed space");
}
#[test]
fn alloc_alignment() {
let heap_size = 128;
let alloc = test_allocator(heap_size);
let alignments = [1, 2, 4, 8, 16, 32];
for &align in &alignments {
let layout = Layout::from_size_align(8, align).unwrap();
let ptr = unsafe { alloc.alloc(layout) };
assert!(
!ptr.is_null(),
"Allocation with alignment {align} should succeed"
);
assert_eq!(
ptr as usize % align,
0,
"Pointer should be {align}-byte aligned"
);
}
}
}
#[cfg(test)]
mod static_tests {
use super::*;
use core::alloc::Layout;
use core::mem::MaybeUninit;
#[test]
fn static_alloc_basic() {
let mut heap: [MaybeUninit<u8>; 256] = [MaybeUninit::uninit(); 256];
let heap_ptr = heap.as_mut_ptr() as *mut u8;
let alloc = unsafe { StaticBumpAllocator::new(heap_ptr, 256) };
let layout = Layout::from_size_align(8, 4).unwrap();
let ptr = unsafe { alloc.alloc(layout) };
assert!(!ptr.is_null(), "Allocation should succeed");
assert_eq!(ptr as usize % 4, 0, "Pointer should be 4-byte aligned");
}
}