#![forbid(unsafe_code)]
#![warn(clippy::pedantic)]
use std::num::NonZeroU32;
use std::collections::{BTreeMap, BTreeSet};
#[must_use]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "bincode", derive(bincode::Encode, bincode::Decode))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Allocation {
pub chunk_index: u32,
pub offset: u32,
pub len: NonZeroU32,
}
pub struct ChunkedRangeAlloc {
chunk_size: NonZeroU32,
free_ranges: BTreeSet<FreeRange>,
chunks: Vec<Chunk>,
active_chunks: usize,
allocated_memory: u64,
}
#[derive(Default)]
struct Chunk {
free_offsets: BTreeMap<u32, NonZeroU32>,
}
impl Chunk {
#[inline]
fn previous_free_range(&self, offset: u32) -> Option<(u32, NonZeroU32)> {
self.free_offsets
.range(..=offset)
.next_back()
.map(|(&off, &len)| (off, len))
}
#[inline]
fn next_free_range(&self, offset: u32) -> Option<(u32, NonZeroU32)> {
self.free_offsets
.range(offset..)
.next()
.map(|(&off, &len)| (off, len))
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
struct FreeRange {
len: NonZeroU32,
chunk: u32,
offset: u32,
}
struct AlignedFreeRange {
free_range: FreeRange,
end: u32,
aligned_offset: u32,
aligned_end: u32,
}
impl ChunkedRangeAlloc {
#[must_use]
#[inline]
pub fn new(chunk_size: NonZeroU32) -> Self {
ChunkedRangeAlloc {
chunk_size,
free_ranges: BTreeSet::new(),
chunks: Vec::new(),
active_chunks: 0,
allocated_memory: 0,
}
}
#[must_use]
#[inline]
pub fn from_allocations<'a>(
chunk_size: NonZeroU32,
allocations: impl IntoIterator<Item = &'a Allocation>,
) -> Option<Self> {
let mut a = Self::new(chunk_size);
for allocation in allocations {
let chunk_index = usize::try_from(allocation.chunk_index).ok()?;
let chunk = loop {
if let Some(chunk) = a.chunks.get(chunk_index) {
break chunk;
}
let chunk = u32::try_from(a.chunks.len()).ok()?;
a.chunks.push(Chunk::default());
a.insert_free_range(FreeRange {
len: chunk_size,
chunk,
offset: 0,
});
};
let (free_offset, free_len) = chunk.previous_free_range(allocation.offset)?;
let free_end = free_offset + free_len.get();
let allocation_end = allocation.offset + allocation.len.get();
if !(free_offset <= allocation.offset && free_end >= allocation_end) {
return None;
}
a.alloc_from(&AlignedFreeRange {
free_range: FreeRange {
len: free_len,
chunk: allocation.chunk_index,
offset: free_offset,
},
end: free_offset + free_len.get(),
aligned_offset: allocation.offset,
aligned_end: allocation.offset + allocation.len.get(),
});
}
Some(a)
}
#[must_use]
#[inline]
pub fn chunk_size(&self) -> NonZeroU32 {
self.chunk_size
}
#[inline]
pub fn alloc(&mut self, size: NonZeroU32, align: NonZeroU32) -> Allocation {
if let Some(aligned_free_range) = self.find_free_range(size, align) {
let allocation = Allocation {
chunk_index: aligned_free_range.free_range.chunk,
offset: aligned_free_range.aligned_offset,
len: size,
};
self.alloc_from(&aligned_free_range);
return allocation;
}
let len = size;
let size = size.get();
let chunk = u32::try_from(self.chunks.len()).expect("too many chunks");
let tail_len = self
.chunk_size
.get()
.checked_sub(size)
.expect("size exceeds chunk_size");
self.chunks.push(Chunk::default());
if let Some(len) = NonZeroU32::new(tail_len) {
self.insert_free_range(FreeRange {
len,
chunk,
offset: size,
});
}
self.active_chunks += 1;
self.allocated_memory += u64::from(size);
Allocation {
chunk_index: chunk,
offset: 0,
len,
}
}
#[must_use]
#[inline]
pub fn free(&mut self, allocation: Allocation) -> bool {
let chunk_index = usize::try_from(allocation.chunk_index).expect("invalid chunk index");
let chunk = self.chunks.get(chunk_index).expect("invalid chunk index");
let previous_free_range = chunk.previous_free_range(allocation.offset);
let next_free_range = chunk.next_free_range(allocation.offset);
let mut free_range = FreeRange {
len: allocation.len,
chunk: allocation.chunk_index,
offset: allocation.offset,
};
if let Some((offset, len)) = previous_free_range {
let previous_end = offset + len.get();
assert!(previous_end <= free_range.offset, "double free");
if previous_end == free_range.offset {
free_range.offset = offset;
free_range.len = free_range
.len
.checked_add(len.get())
.expect("invalid allocation");
self.remove_free_range(FreeRange {
len,
chunk: free_range.chunk,
offset,
});
}
}
if let Some((offset, len)) = next_free_range {
let end = free_range.offset + free_range.len.get();
assert!(end <= offset, "double free");
if end == offset {
free_range.len = free_range
.len
.checked_add(len.get())
.expect("invalid allocation");
self.remove_free_range(FreeRange {
len,
chunk: free_range.chunk,
offset,
});
}
}
self.insert_free_range(free_range);
self.allocated_memory -= u64::from(allocation.len.get());
let free_chunk = free_range.len >= self.chunk_size;
self.active_chunks -= usize::from(free_chunk);
free_chunk
}
#[must_use]
#[inline]
pub fn chunk_count(&self) -> usize {
self.chunks.len()
}
#[must_use]
#[inline]
pub fn active_chunks(&self) -> usize {
self.active_chunks
}
#[must_use]
#[inline]
pub fn unused_chunks(&self) -> usize {
self.chunk_count() - self.active_chunks
}
#[must_use]
#[inline]
pub fn capacity(&self) -> u64 {
u64::try_from(self.chunk_count()).unwrap_or(u64::MAX) * u64::from(self.chunk_size.get())
}
#[must_use]
#[inline]
pub fn allocated_memory(&self) -> u64 {
self.allocated_memory
}
#[must_use]
#[inline]
pub fn remaining(&self) -> u64 {
self.capacity() - self.allocated_memory
}
#[must_use]
#[inline]
pub fn used_memory(&self) -> u64 {
u64::try_from(self.active_chunks).unwrap_or(u64::MAX) * u64::from(self.chunk_size.get())
}
#[must_use]
#[inline]
pub fn unused_memory(&self) -> u64 {
self.used_memory() - self.allocated_memory
}
#[inline]
fn alloc_from(&mut self, aligned_free_range: &AlignedFreeRange) {
let &AlignedFreeRange {
free_range,
end,
aligned_offset,
aligned_end,
} = aligned_free_range;
self.remove_free_range(free_range);
if let Some(len) = NonZeroU32::new(aligned_offset - free_range.offset) {
self.insert_free_range(FreeRange {
len,
chunk: free_range.chunk,
offset: free_range.offset,
});
}
if let Some(len) = NonZeroU32::new(end - aligned_end) {
self.insert_free_range(FreeRange {
len,
chunk: free_range.chunk,
offset: aligned_end,
});
}
let free_chunk = free_range.len >= self.chunk_size;
self.active_chunks += usize::from(free_chunk);
self.allocated_memory += u64::from(aligned_end - aligned_offset);
}
#[inline]
fn find_free_range(&self, size: NonZeroU32, align: NonZeroU32) -> Option<AlignedFreeRange> {
assert!(size <= self.chunk_size);
assert!(align.is_power_of_two());
let align = align.get();
let start = FreeRange {
len: size,
chunk: 0,
offset: 0,
};
let size = size.get();
self.free_ranges
.range(start..)
.copied()
.find_map(|free_range| {
let end = free_range.offset + free_range.len.get();
let aligned_offset = free_range.offset.checked_next_multiple_of(align)?;
let aligned_end = aligned_offset.checked_add(size)?;
if aligned_end > end {
return None;
}
Some(AlignedFreeRange {
free_range,
end,
aligned_offset,
aligned_end,
})
})
}
#[inline]
fn insert_free_range(&mut self, free_range: FreeRange) {
let chunk_index = usize::try_from(free_range.chunk).expect("invalid chunk index");
let chunk = self
.chunks
.get_mut(chunk_index)
.expect("invalid chunk index");
let inserted = self.free_ranges.insert(free_range);
assert!(inserted);
let last = chunk.free_offsets.insert(free_range.offset, free_range.len);
assert!(last.is_none());
}
#[inline]
fn remove_free_range(&mut self, free_range: FreeRange) {
let chunk_index = usize::try_from(free_range.chunk).expect("invalid chunk index");
let chunk = self
.chunks
.get_mut(chunk_index)
.expect("invalid chunk index");
let removed = self.free_ranges.remove(&free_range);
assert!(removed);
let removed = chunk.free_offsets.remove(&free_range.offset);
assert_eq!(removed, Some(free_range.len));
}
}