use std::alloc::{Layout, alloc, dealloc};
use std::slice;
use crate::align_up;
pub struct AlignedBump {
ptr: *mut u8,
layout: Layout,
cursor: usize,
}
impl AlignedBump {
pub fn with_capacity(capacity: usize) -> Self {
let capacity = capacity.max(64);
let layout = Layout::from_size_align(capacity, 64).expect("layout");
let ptr = unsafe { alloc(layout) };
assert!(!ptr.is_null(), "OOM allocating aligned arena chunk");
Self {
ptr,
layout,
cursor: 0,
}
}
pub fn alloc_aligned(&mut self, size: usize, align: usize) -> &mut [u8] {
let cursor = self.cursor;
let cap = self.layout.size();
match self.try_alloc_aligned(size, align) {
Some(s) => s,
None => panic!(
"AlignedBump out of capacity: cursor={cursor} cap={cap} size={size} align={align}",
),
}
}
pub fn try_alloc_aligned(&mut self, size: usize, align: usize) -> Option<&mut [u8]> {
assert!(
align.is_power_of_two(),
"align must be power of two: {align}"
);
let base = self.ptr as usize;
let aligned = align_up(base + self.cursor, align) - base;
let end = aligned.checked_add(size)?;
if end > self.layout.size() {
return None;
}
self.cursor = end;
unsafe {
let p = self.ptr.add(aligned);
Some(slice::from_raw_parts_mut(p, size))
}
}
pub fn reset(&mut self) {
self.cursor = 0;
}
pub fn capacity(&self) -> usize {
self.layout.size()
}
pub fn used(&self) -> usize {
self.cursor
}
}
impl Drop for AlignedBump {
fn drop(&mut self) {
unsafe { dealloc(self.ptr, self.layout) };
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn alignment_one_packs_tightly() {
let mut a = AlignedBump::with_capacity(256);
{
let s1 = a.alloc_aligned(3, 1);
assert_eq!(s1.len(), 3);
}
{
let s2 = a.alloc_aligned(3, 1);
assert_eq!(s2.len(), 3);
}
assert_eq!(a.used(), 6);
}
#[test]
fn alignment_64_byte_cache_line() {
let mut a = AlignedBump::with_capacity(512);
let _ = a.alloc_aligned(1, 1);
let s = a.alloc_aligned(64, 64);
let p = s.as_ptr() as usize;
assert_eq!(p % 64, 0, "cache-line alignment");
}
#[test]
fn alignment_512_byte_page_ish() {
let mut a = AlignedBump::with_capacity(4096);
let _ = a.alloc_aligned(7, 1);
let s = a.alloc_aligned(128, 512);
let p = s.as_ptr() as usize;
assert_eq!(p % 512, 0);
}
#[test]
fn rejects_non_power_of_two_align() {
let mut a = AlignedBump::with_capacity(64);
let r = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
let _ = a.alloc_aligned(8, 3);
}));
assert!(r.is_err());
}
#[test]
fn out_of_capacity_returns_none() {
let mut a = AlignedBump::with_capacity(64);
let _ = a.alloc_aligned(64, 1);
assert!(a.try_alloc_aligned(64, 64).is_none());
}
#[test]
fn reset_rewinds_cursor() {
let mut a = AlignedBump::with_capacity(128);
let _ = a.alloc_aligned(64, 64);
assert_eq!(a.used(), 64);
a.reset();
assert_eq!(a.used(), 0);
let _ = a.alloc_aligned(64, 64);
assert_eq!(a.used(), 64);
}
#[test]
fn slice_is_writable() {
let mut a = AlignedBump::with_capacity(256);
let s = a.alloc_aligned(16, 8);
for (i, b) in s.iter_mut().enumerate() {
*b = i as u8;
}
for (i, b) in s.iter().enumerate() {
assert_eq!(*b, i as u8);
}
}
}