240 lines
6.8 KiB
Rust
240 lines
6.8 KiB
Rust
use core::{alloc::Layout, ops::Index, ptr::NonNull};
|
|
|
|
use crate::{
|
|
bucket::Bucket,
|
|
sys::{self, PageProvider},
|
|
util::{Assert, IsTrue},
|
|
};
|
|
|
|
struct BucketList<P: PageProvider, const N: usize, const M: usize>
|
|
where
|
|
[u64; M / 64]: Sized,
|
|
Assert<{ M % 64 == 0 }>: IsTrue,
|
|
{
|
|
head: Option<NonNull<Bucket<P, N, M>>>,
|
|
}
|
|
|
|
pub struct BucketAllocator<P: PageProvider> {
|
|
// 1024x64 = 16 pages
|
|
buckets_1024: BucketList<P, 1024, 64>,
|
|
// 512x64 = 8 pages
|
|
buckets_512: BucketList<P, 512, 64>,
|
|
// 256x128 = 8 pages
|
|
buckets_256: BucketList<P, 256, 128>,
|
|
// 128x128 = 4 pages
|
|
buckets_128: BucketList<P, 128, 128>,
|
|
// 64x128 = 2 pages
|
|
buckets_64: BucketList<P, 128, 128>,
|
|
// 32x256 = 2 pages
|
|
buckets_32: BucketList<P, 128, 128>,
|
|
}
|
|
|
|
impl<P: PageProvider, const N: usize, const M: usize> BucketList<P, N, M>
|
|
where
|
|
[u64; M / 64]: Sized,
|
|
Assert<{ M % 64 == 0 }>: IsTrue,
|
|
{
|
|
const fn new() -> Self {
|
|
Self { head: None }
|
|
}
|
|
|
|
fn allocate(&mut self) -> Option<NonNull<u8>> {
|
|
let mut node = self.head;
|
|
while let Some(mut bucket) = node {
|
|
let bucket = unsafe { bucket.as_mut() };
|
|
|
|
if let Some(ptr) = bucket.allocate() {
|
|
return Some(ptr);
|
|
}
|
|
|
|
node = bucket.next;
|
|
}
|
|
|
|
// No usable bucket found
|
|
let mut node = Bucket::new()?;
|
|
let bucket = unsafe { node.as_mut() };
|
|
bucket.next = self.head;
|
|
self.head = Some(node);
|
|
|
|
bucket.allocate()
|
|
}
|
|
|
|
unsafe fn free(&mut self, ptr: NonNull<u8>) {
|
|
let mut node = self.head;
|
|
while let Some(mut bucket) = node {
|
|
let bucket = bucket.as_mut();
|
|
|
|
if let (true, _last) = bucket.free(ptr) {
|
|
// TODO free the node if last?
|
|
return;
|
|
}
|
|
|
|
node = bucket.next;
|
|
}
|
|
|
|
panic!("Possible double free detected: pointer {:p} from bucket list {}x{}B, no corresponding bucket found", ptr, N, M);
|
|
}
|
|
}
|
|
|
|
impl<P: PageProvider, const N: usize, const M: usize> Index<usize> for BucketList<P, N, M>
|
|
where
|
|
[u64; M / 64]: Sized,
|
|
Assert<{ M % 64 == 0 }>: IsTrue,
|
|
{
|
|
type Output = Bucket<P, N, M>;
|
|
|
|
fn index(&self, index: usize) -> &Self::Output {
|
|
let mut current = 0;
|
|
let mut node = self.head;
|
|
while let Some(bucket) = node {
|
|
let bucket = unsafe { bucket.as_ref() };
|
|
if current == index {
|
|
return bucket;
|
|
}
|
|
current += 1;
|
|
node = bucket.next;
|
|
}
|
|
|
|
panic!(
|
|
"BucketList index out of range: contains {} buckets, tried to index {}",
|
|
current, index
|
|
);
|
|
}
|
|
}
|
|
|
|
impl<P: PageProvider> BucketAllocator<P> {
|
|
pub const fn new() -> Self {
|
|
Self {
|
|
buckets_1024: BucketList::new(),
|
|
buckets_512: BucketList::new(),
|
|
buckets_256: BucketList::new(),
|
|
buckets_128: BucketList::new(),
|
|
buckets_64: BucketList::new(),
|
|
buckets_32: BucketList::new(),
|
|
}
|
|
}
|
|
|
|
pub fn allocate(&mut self, layout: Layout) -> Option<NonNull<u8>> {
|
|
let aligned = layout.pad_to_align();
|
|
|
|
match aligned.size() {
|
|
size if size <= 32 => self.buckets_32.allocate(),
|
|
size if size <= 64 => self.buckets_64.allocate(),
|
|
size if size <= 128 => self.buckets_128.allocate(),
|
|
size if size <= 256 => self.buckets_256.allocate(),
|
|
size if size <= 512 => self.buckets_512.allocate(),
|
|
size if size <= 1024 => self.buckets_1024.allocate(),
|
|
size => P::map_pages(size.div_ceil(sys::PAGE_SIZE)),
|
|
}
|
|
}
|
|
|
|
/// # Safety
|
|
///
|
|
/// Unsafe: may cause double-free problem and panic
|
|
pub unsafe fn free(&mut self, ptr: NonNull<u8>, layout: Layout) {
|
|
let aligned = layout.pad_to_align();
|
|
|
|
match aligned.size() {
|
|
size if size <= 32 => self.buckets_32.free(ptr),
|
|
size if size <= 64 => self.buckets_64.free(ptr),
|
|
size if size <= 128 => self.buckets_128.free(ptr),
|
|
size if size <= 256 => self.buckets_256.free(ptr),
|
|
size if size <= 512 => self.buckets_512.free(ptr),
|
|
size if size <= 1024 => self.buckets_1024.free(ptr),
|
|
size => {
|
|
assert_eq!(usize::from(ptr.addr()) % sys::PAGE_SIZE, 0);
|
|
P::unmap_pages(ptr, size.div_ceil(sys::PAGE_SIZE));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
unsafe impl<P: PageProvider> Sync for BucketAllocator<P> {}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use core::{alloc::Layout, ptr::NonNull};
|
|
|
|
use super::{BucketAllocator, BucketList};
|
|
use crate::sys::OsPageProvider;
|
|
|
|
#[test]
|
|
fn single_list_allocation() {
|
|
let mut list = BucketList::<OsPageProvider, 32, 64>::new();
|
|
let mut vec = vec![];
|
|
|
|
for _ in 0..4 * 64 + 3 {
|
|
let ptr = list.allocate().unwrap();
|
|
vec.push(ptr);
|
|
}
|
|
|
|
for ptr in vec {
|
|
unsafe {
|
|
list.free(ptr);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn multi_list_allocation() {
|
|
const SIZES: &[usize] = &[1, 3, 7, 15, 16, 24, 33, 65, 126, 255, 500, 1000];
|
|
|
|
let mut allocator = BucketAllocator::<OsPageProvider>::new();
|
|
let mut vec = vec![];
|
|
|
|
for _ in 0..65 {
|
|
for &size in SIZES {
|
|
let layout = Layout::from_size_align(size, 16).unwrap();
|
|
let ptr = allocator.allocate(layout).unwrap();
|
|
assert_eq!(usize::from(ptr.addr()) % 16, 0);
|
|
let mut slice = NonNull::slice_from_raw_parts(ptr, size);
|
|
unsafe {
|
|
slice.as_mut().fill(123);
|
|
}
|
|
vec.push((ptr, layout));
|
|
}
|
|
}
|
|
|
|
for (ptr, layout) in vec {
|
|
unsafe {
|
|
allocator.free(ptr, layout);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
#[should_panic]
|
|
fn double_free() {
|
|
let mut allocator = BucketAllocator::<OsPageProvider>::new();
|
|
let layout = Layout::from_size_align(63, 32).unwrap();
|
|
let ptr = allocator.allocate(layout).unwrap();
|
|
|
|
unsafe {
|
|
allocator.free(ptr, layout);
|
|
allocator.free(ptr, layout);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn large_alloc() {
|
|
const SIZES: &[usize] = &[2000, 2048, 4000, 4096, 8192];
|
|
|
|
let mut allocator = BucketAllocator::<OsPageProvider>::new();
|
|
let mut vec = vec![];
|
|
|
|
for &size in SIZES {
|
|
let layout = Layout::from_size_align(size, 32).unwrap();
|
|
let ptr = allocator.allocate(layout).unwrap();
|
|
vec.push((ptr, layout));
|
|
}
|
|
|
|
for (ptr, layout) in vec {
|
|
assert_eq!(usize::from(ptr.addr()) % 0x1000, 0);
|
|
|
|
unsafe {
|
|
allocator.free(ptr, layout);
|
|
}
|
|
}
|
|
}
|
|
}
|