foundation: slab allocator
This commit is contained in:
parent
67551afc56
commit
86d80132be
|
|
@ -1,8 +1,8 @@
|
||||||
//! A slab allocator
|
//! A slab allocator
|
||||||
|
|
||||||
use core::{alloc::Layout, cell::Cell, hint::unlikely, mem::offset_of, num::NonZero, ptr::NonNull};
|
use core::{
|
||||||
|
alloc::Layout, cell::Cell, fmt::Debug, hint::unlikely, num::NonZero, pin::Pin, ptr::NonNull,
|
||||||
const UNLINKED: NonNull<()> = unsafe { NonNull::new_unchecked(!0 as *mut ()) };
|
};
|
||||||
|
|
||||||
use liballoc::alloc::Allocator;
|
use liballoc::alloc::Allocator;
|
||||||
|
|
||||||
|
|
@ -15,7 +15,20 @@ pub struct Slab<A: Allocator + Clone> {
|
||||||
element_size: usize,
|
element_size: usize,
|
||||||
/// Pointer to the first chunk in the slab.
|
/// Pointer to the first chunk in the slab.
|
||||||
head: Option<NonNull<SlabChunk<A>>>,
|
head: Option<NonNull<SlabChunk<A>>>,
|
||||||
|
/// Pointer to the first chunk in the slab that is full.
|
||||||
|
full_head: Option<NonNull<SlabChunk<A>>>,
|
||||||
alloc: A,
|
alloc: A,
|
||||||
|
_pd: core::marker::PhantomPinned,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A: Allocator + Clone> Debug for Slab<A> {
|
||||||
|
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||||
|
f.debug_struct("Slab")
|
||||||
|
.field("element_size", &self.element_size)
|
||||||
|
.field("head", &self.head)
|
||||||
|
.field("full_head", &self.full_head)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct SlabChunk<A: Allocator + Clone> {
|
struct SlabChunk<A: Allocator + Clone> {
|
||||||
|
|
@ -39,7 +52,18 @@ enum SlotResult {
|
||||||
None,
|
None,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum FreeSlotResult {
|
||||||
|
Empty,
|
||||||
|
WasFull,
|
||||||
|
NotEmpty,
|
||||||
|
}
|
||||||
|
|
||||||
impl<A: Allocator + Clone> SlabChunk<A> {
|
impl<A: Allocator + Clone> SlabChunk<A> {
|
||||||
|
unsafe fn slab_pinned_mut<'a>(&self) -> Pin<&'a mut Slab<A>> {
|
||||||
|
// SAFETY: `slab` is created from a `Pin<&mut Slab<A>>`
|
||||||
|
unsafe { Pin::new_unchecked(self.slab.as_ptr().as_mut_unchecked()) }
|
||||||
|
}
|
||||||
|
|
||||||
fn pop_free_slot(&self) -> SlotResult {
|
fn pop_free_slot(&self) -> SlotResult {
|
||||||
let Some(slot) = self.free.get() else {
|
let Some(slot) = self.free.get() else {
|
||||||
return SlotResult::None;
|
return SlotResult::None;
|
||||||
|
|
@ -55,14 +79,18 @@ impl<A: Allocator + Clone> SlabChunk<A> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// returns `true` if the slab is now empty and can be freed
|
/// returns `true` if the slab is now empty and can be freed
|
||||||
fn push_free_slot(&self, slot: NonNull<u8>) -> bool {
|
fn push_free_slot(&self, slot: NonNull<u8>) -> FreeSlotResult {
|
||||||
let slot = slot.cast::<ChunkSlot>();
|
let slot = slot.cast::<ChunkSlot>();
|
||||||
let next = self.free.get();
|
let next = self.free.get();
|
||||||
unsafe { slot.as_ptr().write(ChunkSlot(next)) };
|
unsafe { slot.as_ptr().write(ChunkSlot(next)) };
|
||||||
self.free.set(Some(slot));
|
self.free.set(Some(slot));
|
||||||
self.count.update(|count| count - 1);
|
self.count.update(|count| count - 1);
|
||||||
|
|
||||||
self.count.get() == 0
|
match (next, self.count.get()) {
|
||||||
|
(None, _) => FreeSlotResult::WasFull,
|
||||||
|
(_, 0) => FreeSlotResult::Empty,
|
||||||
|
_ => FreeSlotResult::NotEmpty,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -79,57 +107,72 @@ impl<A: Allocator + Clone> Slab<A> {
|
||||||
"element_size must be a power of two"
|
"element_size must be a power of two"
|
||||||
);
|
);
|
||||||
|
|
||||||
Self {
|
let slab = Self {
|
||||||
element_size,
|
element_size,
|
||||||
head: None,
|
head: None,
|
||||||
alloc,
|
full_head: None,
|
||||||
}
|
alloc: alloc.clone(),
|
||||||
|
_pd: core::marker::PhantomPinned,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
std::eprintln!("Slab::new({element_size}) -> {slab:#?}",);
|
||||||
|
|
||||||
|
slab
|
||||||
}
|
}
|
||||||
|
|
||||||
fn first_slot_offset(&self) -> usize {
|
fn first_slot_offset(element_size: usize) -> usize {
|
||||||
mem::align_up(core::mem::size_of::<SlabChunk<A>>(), self.element_size)
|
mem::align_up(core::mem::size_of::<SlabChunk<A>>(), element_size)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn count_and_layout(&self) -> (usize, Layout) {
|
fn count_and_layout(element_size: usize) -> (usize, Layout) {
|
||||||
let (count, size, align) = {
|
let (count, size, align) = {
|
||||||
let one_page_count = (PAGE_SIZE - self.first_slot_offset()) / self.element_size;
|
let one_page_count = (PAGE_SIZE - Self::first_slot_offset(element_size)) / element_size;
|
||||||
|
|
||||||
if one_page_count < 3 {
|
if one_page_count < 3 {
|
||||||
let count = 3;
|
let count = 3;
|
||||||
let size =
|
let size = (Self::first_slot_offset(element_size) + count * element_size)
|
||||||
(self.first_slot_offset() + count * self.element_size).next_power_of_two();
|
.next_power_of_two();
|
||||||
|
|
||||||
assert!(size.is_multiple_of(PAGE_SIZE));
|
assert!(size.is_multiple_of(PAGE_SIZE));
|
||||||
assert!(size >= PAGE_SIZE);
|
assert!(size >= PAGE_SIZE);
|
||||||
assert!(size.is_multiple_of(self.element_size));
|
//assert!(size.is_multiple_of(self.element_size));
|
||||||
|
|
||||||
(count, size, size)
|
(count, size, size)
|
||||||
} else {
|
} else {
|
||||||
let count = one_page_count;
|
let count = one_page_count;
|
||||||
let size = PAGE_SIZE;
|
let size = PAGE_SIZE;
|
||||||
(count, size, self.element_size)
|
let align = element_size.max(PAGE_SIZE);
|
||||||
|
(count, size, align)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
std::eprintln!("element_size: {element_size} count: {count}, size: {size}, align: {align}");
|
||||||
|
|
||||||
(count, unsafe {
|
(count, unsafe {
|
||||||
Layout::from_size_align_unchecked(size, align)
|
Layout::from_size_align_unchecked(size, align)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn alloc_chunk(&mut self) -> NonNull<SlabChunk<A>> {
|
fn alloc_chunk(self: Pin<&mut Self>) -> NonNull<SlabChunk<A>> {
|
||||||
// we want to limit chunks to 1 page unless the element size is so
|
// we want to limit chunks to 1 page unless the element size is so
|
||||||
// large that we can fit fewer than 3 elements in a page.
|
// large that we can fit fewer than 3 elements in a page.
|
||||||
let (count, layout) = self.count_and_layout();
|
let (count, layout) = Self::count_and_layout(self.element_size);
|
||||||
|
|
||||||
let Some(bytes) = self.alloc.allocate(layout).ok() else {
|
let Some(bytes) = self.alloc.allocate(layout).ok() else {
|
||||||
panic!()
|
panic!()
|
||||||
};
|
};
|
||||||
|
|
||||||
let chunk = bytes.as_non_null_ptr().cast::<SlabChunk<A>>();
|
let chunk = bytes.as_non_null_ptr().cast::<SlabChunk<A>>();
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
std::eprintln!("Slab::alloc_chunk(chunk: {chunk:#?}, layout: {layout:?})");
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
let first_slot = chunk
|
let first_slot = chunk
|
||||||
.as_ptr()
|
.as_ptr()
|
||||||
.byte_add(self.first_slot_offset())
|
.byte_add(Self::first_slot_offset(self.element_size))
|
||||||
.cast::<ChunkSlot>();
|
.cast::<ChunkSlot>();
|
||||||
|
|
||||||
for i in 0..(count - 1) {
|
for i in 0..(count - 1) {
|
||||||
|
|
@ -143,7 +186,8 @@ impl<A: Allocator + Clone> Slab<A> {
|
||||||
|
|
||||||
chunk.write(SlabChunk {
|
chunk.write(SlabChunk {
|
||||||
next: self.head,
|
next: self.head,
|
||||||
slab: NonNull::from(self),
|
// SAFETY: we only access `slab` via `SlabChunk::slab_pinned_mut`
|
||||||
|
slab: NonNull::from(Pin::into_inner_unchecked(self)),
|
||||||
free: Cell::new(Some(NonNull::new_unchecked(first_slot))),
|
free: Cell::new(Some(NonNull::new_unchecked(first_slot))),
|
||||||
count: Cell::new(0),
|
count: Cell::new(0),
|
||||||
});
|
});
|
||||||
|
|
@ -153,26 +197,37 @@ impl<A: Allocator + Clone> Slab<A> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cold]
|
#[cold]
|
||||||
fn alloc_chunk_cold(&mut self) -> NonNull<SlabChunk<A>> {
|
fn alloc_chunk_cold(self: Pin<&mut Self>) -> NonNull<SlabChunk<A>> {
|
||||||
self.alloc_chunk()
|
self.alloc_chunk()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn alloc_slot(&mut self) -> NonNull<[u8]> {
|
fn alloc_slot(mut self: Pin<&mut Self>) -> NonNull<[u8]> {
|
||||||
let mut chunk = match self.head {
|
#[cfg(test)]
|
||||||
|
std::eprintln!("Slab::alloc_slot({self:?})");
|
||||||
|
|
||||||
|
let chunk = match self.head {
|
||||||
Some(chunk) => chunk,
|
Some(chunk) => chunk,
|
||||||
None => {
|
None => {
|
||||||
let chunk = self.alloc_chunk_cold();
|
let chunk = self.as_mut().alloc_chunk_cold();
|
||||||
self.head = Some(chunk);
|
unsafe {
|
||||||
|
self.as_mut().get_unchecked_mut().head = Some(chunk);
|
||||||
|
}
|
||||||
chunk
|
chunk
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let chunk = unsafe { chunk.as_mut() };
|
let chunk = unsafe { chunk.as_ptr().as_mut_unchecked() };
|
||||||
|
|
||||||
let ptr = match chunk.pop_free_slot() {
|
let ptr = match chunk.pop_free_slot() {
|
||||||
SlotResult::Some(non_null) => non_null,
|
SlotResult::Some(non_null) => non_null,
|
||||||
SlotResult::Last(non_null) => {
|
SlotResult::Last(non_null) => {
|
||||||
self.head = chunk.next.replace(UNLINKED.cast());
|
unsafe {
|
||||||
|
let mut_ref = self.as_mut().get_unchecked_mut();
|
||||||
|
mut_ref.head = chunk.next.take();
|
||||||
|
chunk.next = mut_ref.full_head;
|
||||||
|
mut_ref.full_head = Some(chunk.into());
|
||||||
|
}
|
||||||
|
|
||||||
non_null
|
non_null
|
||||||
}
|
}
|
||||||
SlotResult::None => {
|
SlotResult::None => {
|
||||||
|
|
@ -183,49 +238,97 @@ impl<A: Allocator + Clone> Slab<A> {
|
||||||
ptr.cast_slice(self.element_size)
|
ptr.cast_slice(self.element_size)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn free_slot(&mut self, slot: NonNull<u8>) {
|
fn free_slot(element_size: usize, slot: NonNull<u8>) {
|
||||||
let (_, layout) = self.count_and_layout();
|
let (_, layout) = Self::count_and_layout(element_size);
|
||||||
let mut chunk = slot
|
|
||||||
|
let chunk_ptr = slot
|
||||||
.map_addr(|addr| unsafe {
|
.map_addr(|addr| unsafe {
|
||||||
NonZero::new_unchecked(mem::align_down(addr.get(), layout.align()))
|
NonZero::new_unchecked(mem::align_down(addr.get(), layout.align()))
|
||||||
})
|
})
|
||||||
.cast::<SlabChunk<A>>();
|
.cast::<SlabChunk<A>>();
|
||||||
|
|
||||||
let chunk = unsafe { chunk.as_mut() };
|
#[cfg(test)]
|
||||||
let linked = chunk.next != Some(UNLINKED.cast());
|
std::eprintln!("Slab::free_slot(chunk: {chunk_ptr:#?})");
|
||||||
|
|
||||||
if chunk.push_free_slot(slot) {
|
let chunk = unsafe { chunk_ptr.as_ptr().as_mut_unchecked() };
|
||||||
if linked {
|
|
||||||
let mut head = self.head.expect("chunk is linked, so head exists");
|
let mut slab = unsafe { chunk.slab_pinned_mut() };
|
||||||
while let Some(next) = unsafe { head.as_ref().next } {
|
|
||||||
if next == chunk.into() {
|
match chunk.push_free_slot(slot) {
|
||||||
unsafe { head.as_mut().next = chunk.next };
|
FreeSlotResult::Empty => {
|
||||||
break;
|
// chunk is empty:
|
||||||
|
// first unlink the chunk from the slab..
|
||||||
|
if slab.head == Some(chunk.into()) {
|
||||||
|
unsafe { slab.as_mut().get_unchecked_mut().head = chunk.next };
|
||||||
|
} else {
|
||||||
|
let mut head = slab.head.expect("chunk is linked, so head exists");
|
||||||
|
while let Some(next) = unsafe { (&raw const (*head.as_ptr()).next).read() } {
|
||||||
|
if next == chunk.into() {
|
||||||
|
unsafe { (&raw mut (*head.as_ptr()).next).write(chunk.next) };
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
head = next;
|
||||||
}
|
}
|
||||||
head = next;
|
}
|
||||||
|
|
||||||
|
// ..then free it.
|
||||||
|
#[cfg(test)]
|
||||||
|
std::eprintln!("free_slot::drop({chunk_ptr:?}, layout: {layout:?})");
|
||||||
|
|
||||||
|
unsafe { slab.alloc.deallocate(chunk_ptr.cast(), layout) };
|
||||||
|
}
|
||||||
|
FreeSlotResult::WasFull => {
|
||||||
|
// chunk was full:
|
||||||
|
|
||||||
|
// unlink it from the full list
|
||||||
|
if slab.full_head == Some(chunk.into()) {
|
||||||
|
unsafe { slab.as_mut().get_unchecked_mut().full_head = chunk.next };
|
||||||
|
} else {
|
||||||
|
let mut head = slab.full_head.expect("chunk is linked, so head exists");
|
||||||
|
while let Some(next) = unsafe { (&raw const (*head.as_ptr()).next).read() } {
|
||||||
|
if next == chunk.into() {
|
||||||
|
unsafe { (&raw mut (*head.as_ptr()).next).write(chunk.next) };
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
head = next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// link it to the head of the slab
|
||||||
|
chunk.next = slab.head;
|
||||||
|
unsafe {
|
||||||
|
slab.as_mut().get_unchecked_mut().head = Some(chunk.into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
FreeSlotResult::NotEmpty => {
|
||||||
unsafe {
|
// chunk is not empty, and was not full, so nothing to do
|
||||||
self.alloc
|
}
|
||||||
.deallocate(NonNull::from_mut(chunk).cast(), layout)
|
|
||||||
};
|
|
||||||
} else if !linked {
|
|
||||||
chunk.next = self.head;
|
|
||||||
self.head = Some(chunk.into());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<A: Allocator + Clone> Drop for Slab<A> {
|
impl<A: Allocator + Clone> Drop for Slab<A> {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
let (_, layout) = self.count_and_layout();
|
let (_, layout) = Self::count_and_layout(self.element_size);
|
||||||
|
|
||||||
let mut chunk = self.head.take();
|
let mut chunk = self.head.take();
|
||||||
while let Some(mut chunk_ptr) = chunk {
|
while let Some(mut chunk_ptr) = chunk {
|
||||||
let chunk_ref = unsafe { chunk_ptr.as_mut() };
|
let chunk_ref = unsafe { chunk_ptr.as_mut() };
|
||||||
chunk = chunk_ref.next.take();
|
chunk = chunk_ref.next.take();
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
std::eprintln!("Slab::drop({chunk_ptr:?}, layout: {layout:?})");
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
self.alloc.deallocate(chunk_ptr.cast(), layout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut chunk = self.full_head.take();
|
||||||
|
while let Some(mut chunk_ptr) = chunk {
|
||||||
|
let chunk_ref = unsafe { chunk_ptr.as_mut() };
|
||||||
|
chunk = chunk_ref.next.take();
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
self.alloc.deallocate(chunk_ptr.cast(), layout);
|
self.alloc.deallocate(chunk_ptr.cast(), layout);
|
||||||
}
|
}
|
||||||
|
|
@ -242,18 +345,19 @@ pub struct SlabAllocator<A: Allocator + Clone> {
|
||||||
|
|
||||||
impl<A: Allocator + Clone> SlabAllocator<A> {
|
impl<A: Allocator + Clone> SlabAllocator<A> {
|
||||||
pub fn new(alloc: A) -> Self {
|
pub fn new(alloc: A) -> Self {
|
||||||
let slabs = [
|
Self {
|
||||||
Slab::new(16, alloc.clone()),
|
slabs: [
|
||||||
Slab::new(32, alloc.clone()),
|
Slab::new(16, alloc.clone()),
|
||||||
Slab::new(64, alloc.clone()),
|
Slab::new(32, alloc.clone()),
|
||||||
Slab::new(128, alloc.clone()),
|
Slab::new(64, alloc.clone()),
|
||||||
Slab::new(256, alloc.clone()),
|
Slab::new(128, alloc.clone()),
|
||||||
Slab::new(512, alloc.clone()),
|
Slab::new(256, alloc.clone()),
|
||||||
Slab::new(1024, alloc.clone()),
|
Slab::new(512, alloc.clone()),
|
||||||
Slab::new(2048, alloc.clone()),
|
Slab::new(1024, alloc.clone()),
|
||||||
];
|
Slab::new(2048, alloc.clone()),
|
||||||
|
],
|
||||||
Self { slabs, alloc }
|
alloc,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn slab_index_for_size(size: usize) -> Option<usize> {
|
fn slab_index_for_size(size: usize) -> Option<usize> {
|
||||||
|
|
@ -268,6 +372,14 @@ impl<A: Allocator + Clone> SlabAllocator<A> {
|
||||||
// subtracting 4 (since 2^4 = 16)
|
// subtracting 4 (since 2^4 = 16)
|
||||||
let index = (size.next_power_of_two().trailing_zeros() - 16usize.trailing_zeros()) as usize;
|
let index = (size.next_power_of_two().trailing_zeros() - 16usize.trailing_zeros()) as usize;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
std::eprintln!(
|
||||||
|
"slab_index_for_size: size: {size}, next_power_of_two: {}, trailing_zeros: {}, index: {}",
|
||||||
|
size.next_power_of_two(),
|
||||||
|
size.next_power_of_two().trailing_zeros(),
|
||||||
|
index,
|
||||||
|
);
|
||||||
|
|
||||||
// we have 8 slabs, so the index must be less than 8
|
// we have 8 slabs, so the index must be less than 8
|
||||||
if index < SLAB_ALLOCATOR_BUCKETS {
|
if index < SLAB_ALLOCATOR_BUCKETS {
|
||||||
Some(index)
|
Some(index)
|
||||||
|
|
@ -276,7 +388,10 @@ impl<A: Allocator + Clone> SlabAllocator<A> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn alloc(&mut self, layout: Layout) -> Option<NonNull<[u8]>> {
|
pub fn alloc(self: Pin<&mut Self>, layout: Layout) -> Option<NonNull<[u8]>> {
|
||||||
|
#[cfg(test)]
|
||||||
|
std::eprintln!("SlabAllocator::alloc({:?})", layout);
|
||||||
|
|
||||||
if unlikely(layout.size() == 0) {
|
if unlikely(layout.size() == 0) {
|
||||||
return Some(NonNull::dangling().cast_slice(0));
|
return Some(NonNull::dangling().cast_slice(0));
|
||||||
}
|
}
|
||||||
|
|
@ -284,9 +399,12 @@ impl<A: Allocator + Clone> SlabAllocator<A> {
|
||||||
let size = layout.size().max(layout.align());
|
let size = layout.size().max(layout.align());
|
||||||
|
|
||||||
match Self::slab_index_for_size(size) {
|
match Self::slab_index_for_size(size) {
|
||||||
Some(slab_index) => {
|
Some(slab_index) => unsafe {
|
||||||
Some(unsafe { self.slabs.get_unchecked_mut(slab_index).alloc_slot() })
|
Some(
|
||||||
}
|
self.map_unchecked_mut(|alloc| alloc.slabs.get_unchecked_mut(slab_index))
|
||||||
|
.alloc_slot(),
|
||||||
|
)
|
||||||
|
},
|
||||||
None => {
|
None => {
|
||||||
// allocate directly from the backing allocator
|
// allocate directly from the backing allocator
|
||||||
self.alloc.allocate(layout).ok()
|
self.alloc.allocate(layout).ok()
|
||||||
|
|
@ -294,7 +412,10 @@ impl<A: Allocator + Clone> SlabAllocator<A> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn dealloc(&mut self, ptr: NonNull<u8>, layout: Layout) {
|
pub fn dealloc(self: Pin<&mut Self>, ptr: NonNull<u8>, layout: Layout) {
|
||||||
|
#[cfg(test)]
|
||||||
|
std::eprintln!("SlabAllocator::dealloc({:?})", layout);
|
||||||
|
|
||||||
if unlikely(layout.size() == 0) {
|
if unlikely(layout.size() == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -302,15 +423,8 @@ impl<A: Allocator + Clone> SlabAllocator<A> {
|
||||||
let size = layout.size().max(layout.align());
|
let size = layout.size().max(layout.align());
|
||||||
|
|
||||||
match Self::slab_index_for_size(size) {
|
match Self::slab_index_for_size(size) {
|
||||||
Some(_) => unsafe {
|
Some(slab_index) => unsafe {
|
||||||
let chunk = ptr
|
Slab::<A>::free_slot(self.slabs.get_unchecked(slab_index).element_size, ptr);
|
||||||
.map_addr(|addr| {
|
|
||||||
NonZero::new_unchecked(mem::align_down(addr.get(), layout.align()))
|
|
||||||
})
|
|
||||||
.cast::<SlabChunk<A>>();
|
|
||||||
|
|
||||||
let mut slab = (&raw const (*chunk.as_ptr()).slab).read();
|
|
||||||
slab.as_mut().free_slot(ptr);
|
|
||||||
},
|
},
|
||||||
None => {
|
None => {
|
||||||
// deallocate directly to the backing allocator
|
// deallocate directly to the backing allocator
|
||||||
|
|
@ -320,30 +434,16 @@ impl<A: Allocator + Clone> SlabAllocator<A> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<A: Allocator + Clone> Drop for SlabAllocator<A> {
|
|
||||||
fn drop(&mut self) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
use core::{mem::forget, pin};
|
||||||
use std::{prelude::rust_2024::*, sync::Mutex};
|
use std::{prelude::rust_2024::*, sync::Mutex};
|
||||||
|
|
||||||
struct SlabAllocatorWrapper<A: Allocator + Clone>(Mutex<SlabAllocator<A>>);
|
struct SlabAllocatorWrapper<'a, A: Allocator + Clone>(Mutex<Pin<&'a mut SlabAllocator<A>>>);
|
||||||
|
|
||||||
impl<A: Allocator + Clone> SlabAllocatorWrapper<A> {
|
unsafe impl<'a, A: Allocator + Clone> Allocator for SlabAllocatorWrapper<'a, A> {
|
||||||
fn new(alloc: A) -> Self {
|
|
||||||
Self(Mutex::new(SlabAllocator::new(alloc)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl SlabAllocatorWrapper<std::alloc::System> {
|
|
||||||
fn new_system() -> SlabAllocatorWrapper<std::alloc::System> {
|
|
||||||
SlabAllocatorWrapper::new(std::alloc::System)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe impl<A: Allocator + Clone> Allocator for SlabAllocatorWrapper<A> {
|
|
||||||
fn allocate(
|
fn allocate(
|
||||||
&self,
|
&self,
|
||||||
layout: core::alloc::Layout,
|
layout: core::alloc::Layout,
|
||||||
|
|
@ -351,29 +451,33 @@ mod tests {
|
||||||
self.0
|
self.0
|
||||||
.lock()
|
.lock()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
.as_mut()
|
||||||
.alloc(layout)
|
.alloc(layout)
|
||||||
.ok_or(core::alloc::AllocError)
|
.ok_or(core::alloc::AllocError)
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: core::alloc::Layout) {
|
unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: core::alloc::Layout) {
|
||||||
self.0.lock().unwrap().dealloc(ptr, layout)
|
self.0.lock().unwrap().as_mut().dealloc(ptr, layout)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_slab_allocator() {
|
fn test_slab_allocator() {
|
||||||
let alloc = SlabAllocatorWrapper::new_system();
|
let mut slab = pin::pin!(SlabAllocator::new(std::alloc::Global));
|
||||||
|
let alloc = SlabAllocatorWrapper(Mutex::new(slab.as_mut()));
|
||||||
|
|
||||||
let layout = Layout::from_size_align(16, 8).unwrap();
|
let layout = Layout::from_size_align(4, 4).unwrap();
|
||||||
let ptr = alloc.allocate(layout).unwrap();
|
let ptr = alloc.allocate(layout).unwrap();
|
||||||
assert_eq!(ptr.len(), 16);
|
assert_eq!(ptr.len(), 16);
|
||||||
assert_eq!(ptr.addr().get() % 8, 0);
|
assert_eq!(ptr.addr().get() % 8, 0);
|
||||||
|
|
||||||
let mut boxed = Box::new_in(42u32, &alloc);
|
{
|
||||||
assert_eq!(*boxed, 42);
|
let mut boxed = Box::new_in(42u32, &alloc);
|
||||||
assert_ne!(Box::as_non_null(&mut boxed), ptr.cast());
|
assert_eq!(*boxed, 42);
|
||||||
|
assert_ne!(Box::as_non_null(&mut boxed), ptr.cast());
|
||||||
unsafe { alloc.deallocate(ptr.cast(), layout) };
|
unsafe { alloc.deallocate(ptr.cast(), layout) };
|
||||||
_ = boxed;
|
drop(boxed);
|
||||||
|
std::eprintln!("test_slab_allocator: dropped boxed");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue