#![doc = include_str!("../README.md")]
#![no_std]
extern crate alloc;
use {
::alloc::collections::{BTreeMap, BTreeSet},
::core::{cmp::Ordering, error::Error, fmt, num::NonZero, ops::Range},
};
type Size = u32;
type Location = Size;
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
pub struct Allocation {
pub offset: Location,
pub size: NonZero<Size>,
}
impl Allocation {
pub fn offset(&self) -> Location {
self.offset
}
pub fn size(&self) -> Size {
self.size.get()
}
pub fn range(&self) -> Range<usize> {
(self.offset as usize)..((self.offset + self.size.get()) as usize)
}
}
#[derive(Clone)]
pub struct Allocator {
free: BTreeSet<FreeRegion>,
location_map: BTreeMap<Location, NonZero<Size>>,
capacity: NonZero<Size>,
available: Size,
}
#[derive(PartialEq, Eq, Copy, Clone, Debug)]
struct FreeRegion {
location: Location,
size: NonZero<Size>,
}
impl PartialOrd for FreeRegion {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for FreeRegion {
fn cmp(&self, other: &Self) -> Ordering {
use Ordering as O;
match (
self.size.cmp(&other.size),
self.location.cmp(&other.location),
) {
(O::Equal, O::Equal) => O::Equal,
(O::Equal, O::Less) | (O::Less, _) => O::Less,
(O::Equal, O::Greater) | (O::Greater, _) => O::Greater,
}
}
}
impl Allocator {
pub fn new(capacity: Size) -> Self {
let capacity = NonZero::new(capacity).expect("`capacity == 0`");
let mut allocator = Allocator {
free: BTreeSet::new(),
location_map: BTreeMap::new(),
capacity,
available: capacity.get(),
};
allocator.reset();
allocator
}
pub fn alloc(&mut self, size: Size) -> Option<Allocation> {
self.alloc_with_align(size, 1)
}
pub fn alloc_with_align(
&mut self,
size: Size,
align: Size,
) -> Option<Allocation> {
let size = NonZero::new(size)?;
let align = NonZero::new(align)?;
let FreeRegion {
location: mut free_region_location,
size: free_region_size,
} = self.find_free_region(size.checked_add(align.get() - 1)?)?;
self.remove_free_region(free_region_location, free_region_size);
let mut free_region_size = free_region_size.get();
if let Some(misalignment) =
NonZero::new((align.get() - (free_region_location % align)) % align)
{
self.insert_free_region(free_region_location, misalignment);
free_region_location += misalignment.get();
free_region_size -= misalignment.get();
}
if let Some(size_leftover) = NonZero::new(free_region_size - size.get()) {
self
.insert_free_region(free_region_location + size.get(), size_leftover);
}
self.available -= size.get();
Some(Allocation {
size,
offset: free_region_location,
})
}
pub fn free(&mut self, alloc: Allocation) {
let mut free_region = FreeRegion {
location: alloc.offset,
size: alloc.size,
};
{
if let Some(FreeRegion { location, size }) =
self.previous_free_region(alloc.offset)
{
if location + size.get() == free_region.location {
self.remove_free_region(location, size);
free_region.location = location;
free_region.size = free_region.size.checked_add(size.get()).unwrap();
}
};
if let Some(FreeRegion { location, size }) =
self.following_free_region(alloc.offset)
{
if free_region.location + free_region.size.get() == location {
self.remove_free_region(location, size);
free_region.size = free_region.size.checked_add(size.get()).unwrap();
}
}
}
self.insert_free_region(free_region.location, free_region.size);
self.available += alloc.size.get();
}
pub fn reset(&mut self) {
self.free.clear();
self.location_map.clear();
self.available = self.capacity.get();
self.insert_free_region(0, self.capacity);
}
pub fn grow_capacity(&mut self, additional: Size) -> Result<(), Overflow> {
let Some(additional) = NonZero::new(additional) else {
return Ok(()); };
let current_capacity = self.capacity;
let Some(new_capacity) = current_capacity.checked_add(additional.get())
else {
return Err(Overflow {
current_capacity,
additional,
});
};
self.capacity = new_capacity;
self.free(Allocation {
offset: current_capacity.get(),
size: additional,
});
Ok(())
}
pub fn try_reallocate(
&mut self,
alloc: Allocation,
new_size: Size,
) -> Result<Allocation, ReallocateError> {
let Some(new_size) = NonZero::new(new_size) else {
return Err(ReallocateError::Invalid);
};
match new_size.cmp(&alloc.size) {
Ordering::Greater => {
let required_additional = NonZero::new(new_size.get() - alloc.size())
.unwrap_or_else(|| unreachable!());
let Some(next_free) = self.following_free_region(alloc.offset) else {
return Err(ReallocateError::InsufficientSpace {
required_additional,
available: 0,
});
};
if next_free.location != alloc.offset + alloc.size() {
return Err(ReallocateError::InsufficientSpace {
required_additional,
available: 0,
});
}
if next_free.size < required_additional {
return Err(ReallocateError::InsufficientSpace {
required_additional,
available: next_free.size.get(),
});
}
let new_alloc = Allocation {
offset: alloc.offset,
size: new_size,
};
self.remove_free_region(next_free.location, next_free.size);
if let Some(new_free_region_size) =
NonZero::new(next_free.size.get() - required_additional.get())
{
self.insert_free_region(
new_alloc.offset + new_alloc.size(),
new_free_region_size,
);
}
self.available -= required_additional.get();
Ok(new_alloc)
},
Ordering::Less => {
let additional = NonZero::new(alloc.size() - new_size.get())
.unwrap_or_else(|| unreachable!());
self.free(Allocation {
offset: alloc.offset + alloc.size() - additional.get(),
size: additional,
});
Ok(Allocation {
offset: alloc.offset,
size: new_size,
})
},
Ordering::Equal => {
Ok(alloc)
},
}
}
pub fn capacity(&self) -> Size {
self.capacity.get()
}
pub fn total_available(&self) -> Size {
self.available
}
pub fn largest_available(&self) -> Size {
self.free.last().map_or(0, |region| region.size.get())
}
pub fn is_empty(&self) -> bool {
self.capacity.get() == self.available
}
pub fn report_free_regions(
&self,
) -> impl Iterator<Item = Allocation> + use<'_> {
self.free.iter().map(|free_region| Allocation {
offset: free_region.location,
size: free_region.size,
})
}
fn find_free_region(&mut self, size: NonZero<Size>) -> Option<FreeRegion> {
self
.free
.range(FreeRegion { size, location: 0 }..)
.copied()
.next()
}
fn previous_free_region(&self, location: Location) -> Option<FreeRegion> {
self
.location_map
.range(..location)
.next_back()
.map(|(&location, &size)| FreeRegion { location, size })
}
fn following_free_region(&self, location: Location) -> Option<FreeRegion> {
use ::core::ops::Bound as B;
self
.location_map
.range((B::Excluded(location), B::Unbounded))
.next()
.map(|(&location, &size)| FreeRegion { location, size })
}
fn remove_free_region(&mut self, location: Location, size: NonZero<Size>) {
self.location_map.remove(&location);
let region_existed = self.free.remove(&FreeRegion { location, size });
assert!(
region_existed,
"tried to remove a FreeRegion which did not exist: {:?}",
FreeRegion { location, size }
);
}
fn insert_free_region(&mut self, location: Location, size: NonZero<Size>) {
self.free.insert(FreeRegion { location, size });
let existing_size = self.location_map.insert(location, size);
assert!(
existing_size.is_none(),
"Double free. Tried to add {new:?}, but {existing:?} was already there",
new = FreeRegion { location, size },
existing = FreeRegion {
location,
size: existing_size.unwrap_or_else(|| unreachable!())
}
)
}
}
impl fmt::Debug for Allocator {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Allocator")
.field("capacity", &self.capacity)
.field("total_available", &self.available)
.field("largest_available", &self.largest_available())
.finish()
}
}
#[derive(Debug, Copy, Clone)]
pub struct Overflow {
pub current_capacity: NonZero<Size>,
pub additional: NonZero<Size>,
}
impl Error for Overflow {}
impl fmt::Display for Overflow {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_fmt(format_args!(
"Overflow Error: Allocator with capacity {} could not grow by additional {}.",
self.current_capacity, self.additional
))
}
}
#[derive(Debug, Copy, Clone)]
pub enum ReallocateError {
InsufficientSpace {
required_additional: NonZero<Size>,
available: Size,
},
Invalid,
}
impl Error for ReallocateError {}
impl fmt::Display for ReallocateError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ReallocateError::InsufficientSpace {
required_additional,
available,
} => f.write_fmt(format_args!(
"InsufficientSpace Error: Unable to expand allocation: \
required_additional:{required_additional}, available:{available}."
)),
ReallocateError::Invalid => {
f.write_str("Invalid allocation or `new_size` was 0")
},
}
}
}