#![cfg_attr(docsrs, feature(doc_cfg))]
#![cfg_attr(feature = "allocator_api", feature(allocator_api))]
#[cfg(feature = "allocator_api")]
#[cfg_attr(docsrs, doc(cfg(feature = "allocator_api")))]
mod allocator_api;
pub mod alloc;
use std::{
alloc::Layout,
array::from_fn,
cell::Cell,
ptr::{self, NonNull, null_mut},
cmp, hint,
};
use crate::alloc::Allocator;
const BLOCK_CLASS_SKIP: usize = 3;
const ALIGN: usize = 8;
struct BlockHeader{
ptr_offset: u32,
block_class: u32,
}
struct Block{
next_free: *mut Block,
}
#[repr(align(8))]
struct ChunkHeader{
prev_chunk: *mut ChunkHeader,
len: Cell<usize>,
capacity: usize,
}
impl ChunkHeader{
pub fn data_ptr(this: NonNull<Self>) -> *mut u8 {
let ptr: *mut u8 = this.as_ptr().cast();
unsafe{
ptr.add(size_of::<ChunkHeader>())
}
}
pub fn allocate_chunk(
alloc: &impl Allocator, prev_chunk: *mut ChunkHeader, capacity: usize
) -> Option<NonNull<Self>> {
let size = size_of::<ChunkHeader>() + capacity;
let layout = Layout::from_size_align(size, ALIGN).unwrap();
let ptr = unsafe { alloc.allocate_non_zst(layout) };
if let Some(ptr) = ptr {
let this = unsafe{ NonNull::new_unchecked(ptr.as_ptr().cast()) };
unsafe {
this.write(ChunkHeader{
prev_chunk,
len: Cell::new(0),
capacity,
});
}
Some(this)
} else {
None
}
}
#[inline]
pub unsafe fn deallocate_chunk(alloc: &impl Allocator, this: *mut Self){
let capacity = unsafe{ (*this).capacity };
let size = size_of::<ChunkHeader>() + capacity;
let layout = Layout::from_size_align(size, ALIGN).unwrap();
unsafe{
alloc.deallocate_non_zst(NonNull::new_unchecked(this.cast()), layout);
}
}
}
#[repr(transparent)]
struct EmptyChunkHeader(ChunkHeader);
unsafe impl Sync for EmptyChunkHeader {}
static EMPTY_CHUNK: EmptyChunkHeader = EmptyChunkHeader(ChunkHeader {
prev_chunk: null_mut(),
len: Cell::new(0),
capacity: 0
});
pub struct ReBump<Alloc: Allocator = crate::alloc::Global> {
root_chunk : Cell<NonNull<ChunkHeader>>,
free_blocks: [Cell<*mut u8>; 32],
alloc: Alloc
}
unsafe impl<Alloc: Allocator> Send for ReBump<Alloc>{}
impl<Alloc: Default + Allocator> Default for ReBump<Alloc>{
fn default() -> Self {
Self::new()
}
}
impl<Alloc: Allocator> ReBump<Alloc>{
pub fn new() -> Self
where
Alloc: Default
{
Self::new_in(Alloc::default())
}
pub fn new_in(alloc: Alloc) -> Self {
Self {
root_chunk: Cell::new(NonNull::from(&EMPTY_CHUNK.0)),
free_blocks: from_fn(|_| const {
Cell::new(null_mut())
}),
alloc
}
}
fn pop_free_block(root: &Cell<*mut u8>) -> Option<*mut u8>{
let root_ptr = root.get();
if root_ptr.is_null(){
return None;
}
{
let block: *mut Block = root_ptr.cast();
let next_free = unsafe{(*block).next_free};
root.set(next_free.cast());
}
Some(root_ptr)
}
fn push_free_block(root: &Cell<*mut u8>, block_ptr: *mut u8) {
let root_ptr = root.get();
{
let block: *mut Block = block_ptr.cast();
unsafe{(*block).next_free = root_ptr.cast()};
}
root.set(block_ptr);
}
#[inline]
fn correct_layout(layout: std::alloc::Layout) -> std::alloc::Layout{
let align = cmp::max(layout.align(), ALIGN);
unsafe{
Layout::from_size_align_unchecked(layout.size(), align)
}.pad_to_align()
}
#[inline]
fn perfectly_aligned(layout: std::alloc::Layout) -> bool {
layout.align() <= ALIGN
}
pub fn reset(&mut self){
let mut root_chunk = self.root_chunk.get();
if root_chunk.as_ptr().cast_const() == &EMPTY_CHUNK.0{
return;
}
unsafe{
let chunk = (root_chunk.as_mut()).prev_chunk;
self.drop_chunk_chain(chunk);
}
unsafe{
let root_chunk = root_chunk.as_mut();
root_chunk.prev_chunk = &EMPTY_CHUNK.0 as *const ChunkHeader as * mut ChunkHeader;
root_chunk.len.set(0);
}
for free_block_root in &mut self.free_blocks{
free_block_root.set(null_mut());
}
}
#[inline]
pub fn allocate(&self, layout: std::alloc::Layout)
-> Option<std::ptr::NonNull<[u8]>>
{
let layout = Self::correct_layout(layout);
let perfectly_aligned = Self::perfectly_aligned(layout);
let block_size_exp = {
let max_padding_offset = layout.align() - ALIGN;
let block_header_size = if !perfectly_aligned{size_of::<BlockHeader>()} else {0};
let block_size = layout.size() + max_padding_offset + block_header_size;
ilog2_ceil(block_size) as usize };
let block_class_index = block_size_exp - BLOCK_CLASS_SKIP;
if block_class_index >= 32{
return None;
}
let block_ptr = 'out: {
let free_block_root = unsafe{ self.free_blocks.get_unchecked(block_class_index) };
if let Some(ptr) = Self::pop_free_block(free_block_root){
break 'out ptr;
}
let mut chunk_ptr = self.root_chunk.get();
let block_size = 1usize<<block_size_exp;
{
let chunk = unsafe{ self.root_chunk.get().as_ref() };
let requested_len = chunk.len.get() + block_size;
if requested_len > chunk.capacity{
hint::cold_path();
let new_capacity =
cmp::max(
align_up::<2>(requested_len),
chunk.capacity * 2
);
chunk_ptr = ChunkHeader::allocate_chunk(
&self.alloc,
self.root_chunk.get().as_ptr(),
new_capacity
)?;
self.root_chunk.set(chunk_ptr);
}
}
let chunk = unsafe{ self.root_chunk.get().as_mut() };
let start = chunk.len.get();
chunk.len.set(start + block_size);
let data_ptr = ChunkHeader::data_ptr(chunk_ptr);
unsafe{ data_ptr.add(start) }
};
let ptr = if perfectly_aligned{
block_ptr
} else {
let ptr = align_up_ptr(unsafe{ block_ptr.add(size_of::<BlockHeader>()) }, layout.align());
unsafe {
let block_header_ptr: *mut BlockHeader = ptr.sub(size_of::<BlockHeader>()).cast();
block_header_ptr.write(BlockHeader {
ptr_offset : ptr.offset_from(block_ptr) as u32,
block_class: block_class_index as u32
});
}
ptr
};
let slice = ptr::slice_from_raw_parts_mut(ptr, layout.size());
return Some(unsafe{NonNull::new_unchecked(slice)})
}
pub unsafe fn deallocate(
&self, ptr: std::ptr::NonNull<u8>, layout: std::alloc::Layout
) {
let (block_class_index, block_ptr) = if Self::perfectly_aligned(layout){
let layout = Self::correct_layout(layout);
let block_size_exp = {
let block_size = layout.size();
ilog2_ceil(block_size) as usize };
let block_class_index = block_size_exp - BLOCK_CLASS_SKIP;
(block_class_index, ptr.as_ptr())
} else {
unsafe{
let ptr = ptr.as_ptr();
let block_header_ptr: *const BlockHeader = ptr.sub(size_of::<BlockHeader>()).cast();
let block_class_index = (*block_header_ptr).block_class as usize;
let block_ptr = ptr.sub((*block_header_ptr).ptr_offset as usize);
(block_class_index, block_ptr)
}
};
let free_block_root = unsafe{ self.free_blocks.get_unchecked(block_class_index) };
Self::push_free_block(free_block_root, block_ptr);
}
#[inline]
unsafe fn drop_chunk_chain(&mut self, mut chunk_head_ptr: *mut ChunkHeader){
while chunk_head_ptr.cast_const() != &EMPTY_CHUNK.0 {
let next_chunk_head_ptr = {
let chunk = unsafe{ chunk_head_ptr.as_ref_unchecked() };
chunk.prev_chunk
};
unsafe{
ChunkHeader::deallocate_chunk(&self.alloc, chunk_head_ptr);
}
chunk_head_ptr = next_chunk_head_ptr
}
}
}
impl<Alloc: Allocator> Drop for ReBump<Alloc>{
#[inline]
fn drop(&mut self) {
unsafe{
let root_chunk_ptr = self.root_chunk.get().as_ptr();
self.drop_chunk_chain(root_chunk_ptr);
}
}
}
fn align_up<const I: usize>(n: usize) -> usize {
const{ assert!(I.is_power_of_two()) }
(n + (I-1)) & !(I-1)
}
fn align_up_ptr(ptr: *mut u8, align: usize) -> *mut u8 {
let new_addr = align_up_addr(ptr.addr(), align);
ptr.with_addr(new_addr)
}
fn align_up_addr(addr: usize, align: usize) -> usize {
debug_assert!(align.is_power_of_two());
let align_min_1 = align - 1;
(addr + align_min_1) & !(align_min_1)
}
#[inline]
fn ilog2_ceil(x: usize) -> u32 {
debug_assert!(x > 1);
if x <= 1 {
unsafe { std::hint::unreachable_unchecked() }
}
(x - 1).ilog2() + 1
}
#[cfg(feature = "allocator_api")]
#[cfg(test)]
mod tests{
use itertools::assert_equal;
use super::*;
use crate::alloc::AllocatorApiStd;
#[test]
fn test(){
let allocator = ReBump::new();
let mut vec: Vec<i32, ReBump> = Vec::new_in(allocator);
for i in 0..80{
vec.push(i);
}
assert_equal(vec, 0..80);
println!("OK");
}
#[test]
fn test_block_reuse(){
const SIZE: usize = 32_000;
let allocator = ReBump::new();
let mut vec: Vec<_, ReBump> = Vec::new_in(allocator);
for i in 0..SIZE{
vec.push(i);
}
assert_equal(vec.iter().copied(), 0..SIZE);
vec.clear();
vec.shrink_to_fit();
for i in 0..SIZE{
vec.push(i);
}
assert_equal(vec.iter().copied(), 0..SIZE);
}
#[test]
fn test_block_reuse_w_header(){
const SIZE: u128 = 32_000;
let allocator = ReBump::new();
let mut vec: Vec<u128, ReBump> = Vec::new_in(allocator);
for i in 0..SIZE{
vec.push(i);
}
assert_equal(vec.iter().copied(), 0..SIZE);
vec.clear();
vec.shrink_to_fit();
for i in 0..SIZE{
vec.push(i);
}
assert_equal(vec.iter().copied(), 0..SIZE);
}
#[test]
fn test_zst(){
const SIZE: usize = 2_000;
let allocator = ReBump::new();
let mut vec: Vec<(), ReBump> = Vec::new_in(allocator);
for _ in 0..SIZE{
vec.push(());
}
assert_equal(vec.iter().copied(), (0..SIZE).map(|_| ()));
vec.clear();
vec.shrink_to_fit();
for _ in 0..SIZE{
vec.push(());
}
assert_equal(vec.iter().copied(), (0..SIZE).map(|_| ()));
}
#[test]
fn test_reset(){
const SIZE: usize = 3_000;
let mut allocator = ReBump::new();
allocator.reset();
{
let mut vec: Vec<_, &ReBump> = Vec::new_in(&allocator);
for i in 0..SIZE{
vec.push(i.to_string());
}
assert_equal(vec.iter().cloned(), (0..SIZE).map(|i| i.to_string()));
}
allocator.reset();
let mut vec: Vec<_, &ReBump> = Vec::new_in(&allocator);
for i in 0..SIZE{
vec.push(i.to_string());
}
assert_equal(vec.iter().cloned(), (0..SIZE).map(|i| i.to_string()));
}
#[test]
fn test_allocator(){
let bumpalo = bumpalo::Bump::new();
let allocator = ReBump::new_in(AllocatorApiStd(&bumpalo));
const SIZE: usize = 4_000;
let mut vec = Vec::new_in(allocator);
for i in 0..SIZE{
vec.push(i);
}
assert_equal(vec.iter().copied(), 0..SIZE);
vec.clear();
vec.shrink_to_fit();
for i in 0..SIZE{
vec.push(i);
}
assert_equal(vec.iter().copied(), 0..SIZE);
}
}