diff --git a/notes/coll_alloc_supertrait/arena2_vs_arena3.md b/notes/coll_alloc_supertrait/arena2_vs_arena3.md index d8383d6..7e1b449 100644 --- a/notes/coll_alloc_supertrait/arena2_vs_arena3.md +++ b/notes/coll_alloc_supertrait/arena2_vs_arena3.md @@ -17,7 +17,6 @@ but arena3 fits more objects into the same amount of memory arena2 is faster at every size and the gap grows as object count goes up | objects | arena3 | arena2 | -|---------|---------|---------| | 100 | 1.02 µs | 643 ns | | 500 | 4.15 µs | 1.83 µs | | 1000 | 8.36 µs | 2.77 µs | @@ -34,7 +33,6 @@ header on every object. But this bench is measuring allocation time, not memory writing the header is cheap, what costs time in arena3 is the size class routing. | objects | arena3 (0-byte header) | arena2 (8-byte header) | -|---------|---------|---------| | 100 | 781 ns | 257 ns | | 500 | 3.56 µs | 1.08 µs | | 1000 | 7.02 µs | 2.15 µs | @@ -91,7 +89,6 @@ the whole suite time to free all objects and reclaim dead arenas: | objects | arena3 | arena2 | -|---------|---------|---------| | 100 | 951 ns | 665 ns | | 500 | 2.57 µs | 2.11 µs | | 1000 | 4.65 µs | 4.97 µs | diff --git a/oscars/Cargo.toml b/oscars/Cargo.toml index c5d5513..abd88be 100644 --- a/oscars/Cargo.toml +++ b/oscars/Cargo.toml @@ -20,7 +20,7 @@ harness = false required-features = ["gc_allocator"] [[bench]] -name = "arena2_vs_arena3" +name = "arena2_vs_mempool3" harness = false [features] diff --git a/oscars/benches/arena2_vs_arena3.rs b/oscars/benches/arena2_vs_mempool3.rs similarity index 86% rename from oscars/benches/arena2_vs_arena3.rs rename to oscars/benches/arena2_vs_mempool3.rs index e77598a..f1b28eb 100644 --- a/oscars/benches/arena2_vs_arena3.rs +++ b/oscars/benches/arena2_vs_mempool3.rs @@ -6,12 +6,12 @@ fn bench_alloc_speed(c: &mut Criterion) { for num_objects in [100, 500, 1000].iter() { group.bench_with_input( - BenchmarkId::new("arena3", num_objects), + BenchmarkId::new("mempool3", num_objects), num_objects, |b, &num_objects| { b.iter(|| { let mut allocator = - oscars::alloc::arena3::ArenaAllocator::default().with_arena_size(65536); + oscars::alloc::mempool3::PoolAllocator::default().with_page_size(65536); let mut ptrs = Vec::new(); for i in 0..num_objects { @@ -19,7 +19,7 @@ fn bench_alloc_speed(c: &mut Criterion) { ptrs.push(ptr); } - black_box((ptrs.len(), allocator.arenas_len())) + black_box((ptrs.len(), allocator.pools_len())) }); }, ); @@ -34,6 +34,7 @@ fn bench_alloc_speed(c: &mut Criterion) { let mut ptrs = Vec::new(); for i in 0..num_objects { + let i: usize = i; let ptr = allocator.try_alloc(i).expect("allocation failed"); ptrs.push(ptr); } @@ -59,12 +60,12 @@ fn bench_small_objects(c: &mut Criterion) { for num_objects in [100, 500, 1000].iter() { group.bench_with_input( - BenchmarkId::new("arena3", num_objects), + BenchmarkId::new("mempool3", num_objects), num_objects, |b, &num_objects| { b.iter(|| { let mut allocator = - oscars::alloc::arena3::ArenaAllocator::default().with_arena_size(32768); + oscars::alloc::mempool3::PoolAllocator::default().with_page_size(32768); for i in 0..num_objects { let obj = SmallObject { @@ -74,7 +75,7 @@ fn bench_small_objects(c: &mut Criterion) { let _ = allocator.try_alloc(obj).expect("allocation failed"); } - black_box(allocator.arenas_len()) + black_box(allocator.pools_len()) }); }, ); @@ -88,6 +89,7 @@ fn bench_small_objects(c: &mut Criterion) { oscars::alloc::arena2::ArenaAllocator::default().with_arena_size(32768); for i in 0..num_objects { + let i: usize = i; let obj = SmallObject { _a: i as u64, _b: i as u64 * 2, @@ -107,10 +109,10 @@ fn bench_small_objects(c: &mut Criterion) { fn bench_mixed(c: &mut Criterion) { let mut group = c.benchmark_group("3_mixed_sizes"); - group.bench_function("arena3", |b| { + group.bench_function("mempool3", |b| { b.iter(|| { let mut allocator = - oscars::alloc::arena3::ArenaAllocator::default().with_arena_size(65536); + oscars::alloc::mempool3::PoolAllocator::default().with_page_size(65536); for _ in 0..50 { let _ = allocator.try_alloc([0u8; 16]); @@ -119,7 +121,7 @@ fn bench_mixed(c: &mut Criterion) { let _ = allocator.try_alloc([0u8; 128]); } - black_box(allocator.arenas_len()) + black_box(allocator.pools_len()) }); }); @@ -148,20 +150,20 @@ fn bench_density(c: &mut Criterion) { const PAGE_SIZE: usize = 4096; - group.bench_function("arena3", |b| { + group.bench_function("mempool3", |b| { b.iter(|| { let mut allocator = - oscars::alloc::arena3::ArenaAllocator::default().with_arena_size(PAGE_SIZE); + oscars::alloc::mempool3::PoolAllocator::default().with_page_size(PAGE_SIZE); let mut count = 0; while allocator.try_alloc([0u64; 2]).is_ok() { count += 1; - if allocator.arenas_len() > 1 { + if allocator.pools_len() > 1 { break; } } - black_box((count, allocator.arenas_len())) + black_box((count, allocator.pools_len())) }); }); @@ -189,10 +191,10 @@ fn bench_density(c: &mut Criterion) { fn bench_vec_growth(c: &mut Criterion) { let mut group = c.benchmark_group("5_vec_growth"); - group.bench_function("arena3", |b| { + group.bench_function("mempool3", |b| { b.iter(|| { let mut allocator = - oscars::alloc::arena3::ArenaAllocator::default().with_arena_size(32768); + oscars::alloc::mempool3::PoolAllocator::default().with_page_size(32768); let mut cap = 1; while cap <= 1024 { @@ -235,7 +237,7 @@ fn bench_vec_growth(c: &mut Criterion) { cap *= 2; } - black_box(allocator.arenas_len()) + black_box(allocator.pools_len()) }); }); @@ -296,16 +298,16 @@ fn bench_throughput(c: &mut Criterion) { let mut group = c.benchmark_group("6_sustained_throughput"); group.throughput(criterion::Throughput::Elements(10000)); - group.bench_function("arena3", |b| { + group.bench_function("mempool3", |b| { b.iter(|| { let mut allocator = - oscars::alloc::arena3::ArenaAllocator::default().with_arena_size(131072); + oscars::alloc::mempool3::PoolAllocator::default().with_page_size(131072); for i in 0..10000 { let _ = allocator.try_alloc(i); } - black_box(allocator.arenas_len()) + black_box(allocator.pools_len()) }); }); @@ -332,13 +334,13 @@ fn bench_dealloc_speed(c: &mut Criterion) { // using `iter_batched` ensures we only measure the deallocation phase for num_objects in [100, 500, 1000].iter() { group.bench_with_input( - BenchmarkId::new("arena3", num_objects), + BenchmarkId::new("mempool3", num_objects), num_objects, |b, &num_objects| { b.iter_batched( || { let mut allocator = - oscars::alloc::arena3::ArenaAllocator::default().with_arena_size(65536); + oscars::alloc::mempool3::PoolAllocator::default().with_page_size(65536); let mut ptrs = Vec::new(); for i in 0..num_objects { @@ -347,12 +349,14 @@ fn bench_dealloc_speed(c: &mut Criterion) { } (allocator, ptrs) }, - |(mut allocator, ptrs)| { + |(mut allocator, ptrs): (_, _)| { for ptr in ptrs { - allocator.free_slot(ptr.as_ptr().cast::()); + unsafe { + allocator.free_slot_typed(ptr.as_ptr()); + } } - allocator.drop_dead_arenas(); - black_box(allocator.arenas_len()) + allocator.drop_empty_pools(); + black_box(allocator.pools_len()) }, criterion::BatchSize::SmallInput, ); @@ -370,12 +374,13 @@ fn bench_dealloc_speed(c: &mut Criterion) { let mut ptrs = Vec::new(); for i in 0..num_objects { + let i: usize = i; let ptr = allocator.try_alloc(i).expect("allocation failed"); ptrs.push(ptr); } (allocator, ptrs) }, - |(mut allocator, ptrs)| { + |(mut allocator, ptrs): (_, _)| { for ptr in ptrs { let mut heap_item_ptr = ptr.as_ptr(); unsafe { diff --git a/oscars/benches/oscars_vs_boa_gc.rs b/oscars/benches/oscars_vs_boa_gc.rs index 6610f8f..63edffc 100644 --- a/oscars/benches/oscars_vs_boa_gc.rs +++ b/oscars/benches/oscars_vs_boa_gc.rs @@ -11,7 +11,7 @@ fn bench_alloc(c: &mut Criterion) { for size in [10, 100, 1000].iter() { group.bench_with_input(BenchmarkId::new("oscars", size), size, |b, &size| { let collector = MarkSweepGarbageCollector::default() - .with_arena_size(65536) + .with_page_size(65536) .with_heap_threshold(262144); b.iter(|| { @@ -56,7 +56,7 @@ fn bench_collection(c: &mut Criterion) { b.iter_batched( || { let collector = MarkSweepGarbageCollector::default() - .with_arena_size(65536) + .with_page_size(65536) .with_heap_threshold(262144); let mut roots = Vec::new(); for i in 0..num_objects { @@ -110,7 +110,7 @@ fn bench_vec_create(c: &mut Criterion) { size, |b, &size| { let collector = MarkSweepGarbageCollector::default() - .with_arena_size(65536) + .with_page_size(65536) .with_heap_threshold(262144); b.iter(|| { @@ -153,7 +153,7 @@ fn bench_vec_ptrs(c: &mut Criterion) { num_elements, |b, &num_elements| { let collector = MarkSweepGarbageCollector::default() - .with_arena_size(65536) + .with_page_size(65536) .with_heap_threshold(262144); b.iter(|| { @@ -199,7 +199,7 @@ fn bench_mixed(c: &mut Criterion) { group.bench_function("oscars", |b| { let collector = MarkSweepGarbageCollector::default() - .with_arena_size(65536) + .with_page_size(65536) .with_heap_threshold(131072); b.iter(|| { @@ -249,7 +249,7 @@ fn bench_pressure(c: &mut Criterion) { group.bench_function("oscars", |b| { let collector = MarkSweepGarbageCollector::default() - .with_arena_size(32768) + .with_page_size(32768) .with_heap_threshold(65536); b.iter(|| { @@ -316,7 +316,7 @@ fn bench_deep(c: &mut Criterion) { group.bench_function("oscars", |b| { let collector = MarkSweepGarbageCollector::default() - .with_arena_size(131072) + .with_page_size(131072) .with_heap_threshold(262144); b.iter(|| { diff --git a/oscars/src/alloc/arena2/mod.rs b/oscars/src/alloc/arena2/mod.rs index 470d4d3..91b6a0a 100644 --- a/oscars/src/alloc/arena2/mod.rs +++ b/oscars/src/alloc/arena2/mod.rs @@ -37,8 +37,6 @@ impl From for ArenaAllocError { // This may also point to a different problem which is that the arena's as they // currently exist do not have a lifetime, their lifetime is derived from the // ArenaAllocator. -// -// But this may all be something to work on in arena3 // NOTE: Vec may actually be better here over link list. diff --git a/oscars/src/alloc/arena3/alloc.rs b/oscars/src/alloc/mempool3/alloc.rs similarity index 70% rename from oscars/src/alloc/arena3/alloc.rs rename to oscars/src/alloc/mempool3/alloc.rs index b2687a2..d47c269 100644 --- a/oscars/src/alloc/arena3/alloc.rs +++ b/oscars/src/alloc/mempool3/alloc.rs @@ -2,22 +2,22 @@ use core::{cell::Cell, marker::PhantomData, ptr::NonNull}; use rust_alloc::alloc::{Layout, alloc, dealloc, handle_alloc_error}; -use crate::alloc::arena3::ArenaAllocError; +use crate::alloc::mempool3::PoolAllocError; -// ree slot pointing to the next free slot -// `repr(C)` puts `next` exactly at the start of the slot +/// free slot pointing to the next free slot +/// `repr(C)` puts `next` exactly at the start of the slot #[repr(C)] pub(crate) struct FreeSlot { next: *mut FreeSlot, } -// transparent wrapper around a GC value -// liveness is tracked by the pool bitmap +/// transparent wrapper around a GC value +/// liveness is tracked by the pool bitmap #[derive(Debug)] #[repr(transparent)] -pub struct ArenaHeapItem(pub T); +pub struct PoolItem(pub T); -impl ArenaHeapItem { +impl PoolItem { pub fn value(&self) -> &T { &self.0 } @@ -27,93 +27,88 @@ impl ArenaHeapItem { } } -// type erased pointer into a pool slot -// `'arena` prevents outliving the allocator +/// type erased pointer into a pool slot +/// `'pool` prevents outliving the allocator #[derive(Debug, Clone, Copy)] #[repr(transparent)] -pub struct ErasedArenaPointer<'arena>(NonNull, PhantomData<&'arena ()>); +pub struct ErasedPoolPointer<'pool>(NonNull, PhantomData<&'pool ()>); -impl<'arena> ErasedArenaPointer<'arena> { +impl<'pool> ErasedPoolPointer<'pool> { pub fn as_ptr(&self) -> NonNull { self.0 } - /// Retype this pointer + /// retype this pointer /// /// # Safety - /// - /// Caller must ensure `T` matches the original allocation - pub unsafe fn to_typed_arena_pointer(self) -> ArenaPointer<'arena, T> { - ArenaPointer(self.0.cast::>(), PhantomData) + /// caller must ensure `T` matches the original allocation + pub unsafe fn to_typed_pool_pointer(self) -> PoolPointer<'pool, T> { + PoolPointer(self.0.cast::>(), PhantomData) } pub fn as_non_null(&self) -> NonNull { self.0 } - /// Extend the lifetime of this erased arena pointer to 'static - /// - /// # Safety - /// - /// Safe because the gc collector owns the arena and keeps it alive - pub(crate) unsafe fn extend_lifetime(self) -> ErasedArenaPointer<'static> { - ErasedArenaPointer(self.0, PhantomData) + /// extend the lifetime of this erased pool pointer to 'static + // + // SAFETY: same as PoolPointer::extend_lifetime + pub(crate) unsafe fn extend_lifetime(self) -> ErasedPoolPointer<'static> { + ErasedPoolPointer(self.0, PhantomData) } } -// typed pointer into a pool slot +/// typed pointer into a pool slot #[derive(Debug, Clone, Copy)] #[repr(transparent)] -pub struct ArenaPointer<'arena, T>(NonNull>, PhantomData<&'arena T>); +pub struct PoolPointer<'pool, T>(NonNull>, PhantomData<&'pool T>); -impl<'arena, T> ArenaPointer<'arena, T> { - pub(crate) unsafe fn from_raw(raw: NonNull>) -> Self { +impl<'pool, T> PoolPointer<'pool, T> { + pub(crate) unsafe fn from_raw(raw: NonNull>) -> Self { Self(raw, PhantomData) } - pub fn as_inner_ref(&self) -> &'arena T { + pub fn as_inner_ref(&self) -> &'pool T { // SAFETY: pointer is valid and properly aligned unsafe { &(*self.0.as_ptr()).0 } } - pub fn as_ptr(&self) -> NonNull> { + pub fn as_ptr(&self) -> NonNull> { self.0 } - pub fn to_erased(self) -> ErasedArenaPointer<'arena> { - ErasedArenaPointer(self.0.cast::(), PhantomData) + pub fn to_erased(self) -> ErasedPoolPointer<'pool> { + ErasedPoolPointer(self.0.cast::(), PhantomData) } - // SAFETY: safe because the gc collector owns the arena and keeps it alive - pub(crate) unsafe fn extend_lifetime(self) -> ArenaPointer<'static, T> { - ArenaPointer(self.0, PhantomData) + // SAFETY: safe because the gc collector owns the pool and keeps it alive + pub(crate) unsafe fn extend_lifetime(self) -> PoolPointer<'static, T> { + PoolPointer(self.0, PhantomData) } } // ==== SlotPool ==== // - impl core::fmt::Debug for SlotPool { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.debug_struct("SlotPool") .field("slot_size", &self.slot_size) .field("slot_count", &self.slot_count) .field("layout", &self.layout) - .field("bitmap_words", &self.bitmap_words) + .field("bitmap_bytes", &self.bitmap_bytes) .field("bump", &self.bump.get()) .field("live", &self.live.get()) .finish() } } -// fixed size slot pool with the layout: `[ bitmap ][ slots ]` -// bitmap tracks live slots, freed slots form a linked list to be reused -// +/// fixed size slot pool with the layout: `[ bitmap ][ slots ]` +/// bitmap tracks live slots, freed slots form a linked list to be reused pub(crate) struct SlotPool { pub(crate) slot_size: usize, pub(crate) slot_count: usize, pub(crate) layout: Layout, pub(crate) buffer: NonNull, - pub(crate) bitmap_words: usize, + pub(crate) bitmap_bytes: usize, pub(crate) bump: Cell, // head of the free list, None when empty pub(crate) free_list: Cell>>, @@ -126,24 +121,23 @@ impl SlotPool { slot_size: usize, total_capacity: usize, max_align: usize, - ) -> Result { + ) -> Result { assert!( slot_size >= core::mem::size_of::(), "slot_size must fit a FreeSlot (needed for the intrusive free list)" ); // guess the slot count (ignoring bitmap size), size the bitmap based on that guess - // (rounded up to 64 bit words), then subtract the bitmap size from the total capacity to get the real slot count + // (rounded up to 64 bits), then subtract the bitmap size from the total capacity to get the real slot count // example (512 capacity, 16 slot size): guess 32 slots -> 8 byte bitmap, real 504 bytes left -> 31 slots // layout: [ 8-byte bitmap ][ 31 x 16-byte slots ] = 504 bytes used let estimated = total_capacity / slot_size; - let bitmap_words = estimated.div_ceil(64); - let bitmap_bytes = bitmap_words * 8; + let bitmap_bytes = estimated.div_ceil(64) * 8; let slot_area = total_capacity.saturating_sub(bitmap_bytes); let slot_count = slot_area / slot_size; let layout = Layout::from_size_align(total_capacity, max_align) - .map_err(ArenaAllocError::LayoutError)?; + .map_err(PoolAllocError::LayoutError)?; // SAFETY: allocating with a valid Layout let buffer = unsafe { @@ -165,22 +159,17 @@ impl SlotPool { slot_count, layout, buffer, - bitmap_words, + bitmap_bytes, bump: Cell::new(0), free_list: Cell::new(None), live: Cell::new(0), }) } - #[inline] - pub(crate) fn bitmap_bytes(&self) -> usize { - self.bitmap_words * 8 - } - #[inline] fn slot_base(&self) -> *mut u8 { // SAFETY: adding bitmap_bytes is within the buffer bounds - unsafe { self.buffer.as_ptr().add(self.bitmap_bytes()) } + unsafe { self.buffer.as_ptr().add(self.bitmap_bytes) } } #[inline] @@ -208,35 +197,35 @@ impl SlotPool { #[inline] fn bitmap_set(&self, i: usize) { // SAFETY: pointer addition and cast are within the bitmap bounds - let word = unsafe { &*(self.buffer.as_ptr().add((i / 64) * 8) as *const Cell) }; - word.set(word.get() | (1u64 << (i % 64))); + let chunk = unsafe { &*(self.buffer.as_ptr().add((i / 64) * 8) as *const Cell) }; + chunk.set(chunk.get() | (1u64 << (i % 64))); } #[inline] fn bitmap_clear(&self, i: usize) { // SAFETY: pointer addition and cast are within the bitmap bounds - let word = unsafe { &*(self.buffer.as_ptr().add((i / 64) * 8) as *const Cell) }; - word.set(word.get() & !(1u64 << (i % 64))); + let chunk = unsafe { &*(self.buffer.as_ptr().add((i / 64) * 8) as *const Cell) }; + chunk.set(chunk.get() & !(1u64 << (i % 64))); } - // mark the slot as occupied outside of alloc_slot + /// mark the slot as occupied outside of alloc_slot pub fn mark_slot(&self, ptr: NonNull) { let idx = self.slot_index(ptr); self.bitmap_set(idx); } - // returns true if the slot at `ptr` is marked as occupied in the bitmap + /// returns true if the slot at `ptr` is marked as occupied in the bitmap // // TODO: for the planned bitmap based sweep, unused until then #[allow(dead_code)] pub fn is_marked(&self, ptr: NonNull) -> bool { let i = self.slot_index(ptr); // SAFETY: pointer addition and cast are within the bitmap bounds - let word = unsafe { &*(self.buffer.as_ptr().add((i / 64) * 8) as *const Cell) }; - (word.get() & (1u64 << (i % 64))) != 0 + let chunk = unsafe { &*(self.buffer.as_ptr().add((i / 64) * 8) as *const Cell) }; + (chunk.get() & (1u64 << (i % 64))) != 0 } - // allocate a slot, returns None if full. + /// allocate a slot, returns None if full. pub fn alloc_slot(&self) -> Option> { // pop from free list if available if let Some(head) = self.free_list.get() { @@ -263,7 +252,7 @@ impl SlotPool { Some(ptr) } - // return a slot to the free list + /// return a slot to the free list pub fn free_slot(&self, ptr: NonNull) { let idx = self.slot_index(ptr); self.bitmap_clear(idx); @@ -283,8 +272,8 @@ impl SlotPool { self.live.set(self.live.get().saturating_sub(1)); } - // returns true when the pool is empty and safe to drop - // `live` tracks the count, so no bitmap scan is needed + /// returns true when the pool is empty and safe to drop + /// `live` tracks the count, so no bitmap scan is needed pub fn run_drop_check(&self) -> bool { self.live.get() == 0 } @@ -297,24 +286,24 @@ impl Drop for SlotPool { } } -/// BumpPage /// +// ==== BumpPage ==== // -// pure bump allocator for raw bytes with a linear pointer over a buffer -// no per allocation tracking, the whole page is dropped when empty +/// pure bump allocator for raw bytes with a linear pointer over a buffer +/// no per allocation tracking, the whole page is dropped when empty #[derive(Debug)] pub(crate) struct BumpPage { pub(crate) layout: Layout, pub(crate) buffer: NonNull, pub(crate) bump: Cell, // number of live allocations on this page, when hits 0 the page - // is eligible for reclamation by drop_dead_arenas + // is eligible for reclamation by drop_empty_pools pub(crate) active_allocs: Cell, } impl BumpPage { - pub fn try_init(total_capacity: usize, max_align: usize) -> Result { + pub fn try_init(total_capacity: usize, max_align: usize) -> Result { let layout = Layout::from_size_align(total_capacity, max_align) - .map_err(ArenaAllocError::LayoutError)?; + .map_err(PoolAllocError::LayoutError)?; // SAFETY: allocating with a valid Layout let buffer = unsafe { @@ -333,23 +322,23 @@ impl BumpPage { }) } - pub fn try_alloc(&self, layout: Layout) -> Result, ArenaAllocError> { + pub fn try_alloc(&self, layout: Layout) -> Result, PoolAllocError> { let size = layout.size(); let align = layout.align(); if align > self.layout.align() { - return Err(ArenaAllocError::AlignmentNotPossible); + return Err(PoolAllocError::AlignmentNotPossible); } // SAFETY: bump is within buffer bounds let current_ptr = unsafe { self.buffer.as_ptr().add(self.bump.get()) }; let padding = current_ptr.align_offset(align); if padding == usize::MAX { - return Err(ArenaAllocError::AlignmentNotPossible); + return Err(PoolAllocError::AlignmentNotPossible); } let offset = self.bump.get() + padding; if offset + size > self.layout.size() { - return Err(ArenaAllocError::OutOfMemory); + return Err(PoolAllocError::OutOfMemory); } self.bump.set(offset + size); @@ -360,8 +349,8 @@ impl BumpPage { Ok(NonNull::slice_from_raw_parts(ptr, size)) } - // decrements the live allocation count - // the page is freed by `drop_dead_arenas` when active_allocs hits zero + /// decrements the live allocation count + /// the page is freed by `drop_empty_pools` when active_allocs hits zero pub fn dealloc(&self) { self.active_allocs .set(self.active_allocs.get().saturating_sub(1)); @@ -374,7 +363,7 @@ impl BumpPage { addr >= start && addr < end } - // try to shrink the most recent allocation in place by rewinding the bump + /// try to shrink the most recent allocation in place by rewinding the bump pub fn shrink_in_place( &self, ptr: NonNull, @@ -390,7 +379,7 @@ impl BumpPage { } } - // try to grow the most recent allocation in place by extending the bump + /// try to grow the most recent allocation in place by extending the bump pub fn grow_in_place(&self, ptr: NonNull, old_layout: Layout, new_layout: Layout) -> bool { let offset = ptr.as_ptr() as usize - self.buffer.as_ptr() as usize; if offset + old_layout.size() == self.bump.get() { @@ -403,7 +392,7 @@ impl BumpPage { false } - // returns true when all allocations on this page have been released. + /// returns true when all allocations on this page have been released. pub fn run_drop_check(&self) -> bool { self.active_allocs.get() == 0 } diff --git a/oscars/src/alloc/arena3/mod.rs b/oscars/src/alloc/mempool3/mod.rs similarity index 65% rename from oscars/src/alloc/arena3/mod.rs rename to oscars/src/alloc/mempool3/mod.rs index f8f45c2..dec54aa 100644 --- a/oscars/src/alloc/arena3/mod.rs +++ b/oscars/src/alloc/mempool3/mod.rs @@ -1,8 +1,6 @@ //! size-class memory pool. typed GC objects go into per size class slot pools //! where freed slots are recycled via a free list, raw byte allocations use //! separate bump pages -//! -//! TODO: move from `arena` to `mempool` use core::{cell::Cell, ptr::NonNull}; use rust_alloc::alloc::{Layout, LayoutError}; @@ -10,20 +8,20 @@ use rust_alloc::vec::Vec; mod alloc; -pub use alloc::{ArenaHeapItem, ArenaPointer, ErasedArenaPointer}; use alloc::{BumpPage, SlotPool}; +pub use alloc::{ErasedPoolPointer, PoolItem, PoolPointer}; #[cfg(test)] mod tests; #[derive(Debug, Clone)] -pub enum ArenaAllocError { +pub enum PoolAllocError { LayoutError(LayoutError), OutOfMemory, AlignmentNotPossible, } -impl From for ArenaAllocError { +impl From for PoolAllocError { fn from(value: LayoutError) -> Self { Self::LayoutError(value) } @@ -42,18 +40,18 @@ fn size_class_index_for(size: usize) -> usize { idx.unwrap_or(SIZE_CLASSES.len() - 1) } -const DEFAULT_ARENA_SIZE: usize = 4096; +const DEFAULT_PAGE_SIZE: usize = 4096; const DEFAULT_HEAP_THRESHOLD: usize = 2_097_152; #[derive(Debug)] -pub struct ArenaAllocator<'alloc> { +pub struct PoolAllocator<'alloc> { pub(crate) heap_threshold: usize, - pub(crate) arena_size: usize, + pub(crate) page_size: usize, pub(crate) current_heap_size: usize, // per size-class slot pools - pub(crate) typed_arenas: Vec, + pub(crate) slot_pools: Vec, // bump pages for raw byte allocs - pub(crate) raw_arenas: Vec, + pub(crate) bump_pages: Vec, // cached index of the last pool used by free_slot pub(crate) free_cache: Cell, // per size class cached index of the last pool used by alloc_slot @@ -61,14 +59,14 @@ pub struct ArenaAllocator<'alloc> { _marker: core::marker::PhantomData<&'alloc ()>, } -impl<'alloc> Default for ArenaAllocator<'alloc> { +impl<'alloc> Default for PoolAllocator<'alloc> { fn default() -> Self { Self { heap_threshold: DEFAULT_HEAP_THRESHOLD, - arena_size: DEFAULT_ARENA_SIZE, + page_size: DEFAULT_PAGE_SIZE, current_heap_size: 0, - typed_arenas: Vec::new(), - raw_arenas: Vec::new(), + slot_pools: Vec::new(), + bump_pages: Vec::new(), free_cache: Cell::new(usize::MAX), alloc_cache: [ Cell::new(usize::MAX), @@ -89,9 +87,9 @@ impl<'alloc> Default for ArenaAllocator<'alloc> { } } -impl<'alloc> ArenaAllocator<'alloc> { - pub fn with_arena_size(mut self, arena_size: usize) -> Self { - self.arena_size = arena_size; +impl<'alloc> PoolAllocator<'alloc> { + pub fn with_page_size(mut self, page_size: usize) -> Self { + self.page_size = page_size; self } pub fn with_heap_threshold(mut self, heap_threshold: usize) -> Self { @@ -99,12 +97,12 @@ impl<'alloc> ArenaAllocator<'alloc> { self } - // total live pool + page count - pub fn arenas_len(&self) -> usize { - self.typed_arenas.len() + self.raw_arenas.len() + /// total live slot pool + bump page count + pub fn pools_len(&self) -> usize { + self.slot_pools.len() + self.bump_pages.len() } - // exact heap size in bytes + /// exact heap size in bytes fn heap_size(&self) -> usize { self.current_heap_size } @@ -116,69 +114,69 @@ impl<'alloc> ArenaAllocator<'alloc> { } pub fn increase_threshold(&mut self) { - self.heap_threshold += self.arena_size * 4; + self.heap_threshold += self.page_size * 4; } } -impl<'alloc> ArenaAllocator<'alloc> { - pub fn try_alloc(&mut self, value: T) -> Result, ArenaAllocError> { - let needed = core::mem::size_of::>().max(8); +impl<'alloc> PoolAllocator<'alloc> { + pub fn try_alloc(&mut self, value: T) -> Result, PoolAllocError> { + let needed = core::mem::size_of::>().max(8); let sc_idx = size_class_index_for(needed); let slot_size = SIZE_CLASSES.get(sc_idx).copied().unwrap_or(needed); let cached_idx = self.alloc_cache[sc_idx].get(); - if cached_idx < self.typed_arenas.len() { - let pool = &self.typed_arenas[cached_idx]; + if cached_idx < self.slot_pools.len() { + let pool = &self.slot_pools[cached_idx]; if pool.slot_size == slot_size && let Some(slot_ptr) = pool.alloc_slot() { // SAFETY: slot_ptr was successfully allocated for this size class return unsafe { - let dst = slot_ptr.as_ptr() as *mut ArenaHeapItem; - dst.write(ArenaHeapItem(value)); - Ok(ArenaPointer::from_raw(NonNull::new_unchecked(dst))) + let dst = slot_ptr.as_ptr() as *mut PoolItem; + dst.write(PoolItem(value)); + Ok(PoolPointer::from_raw(NonNull::new_unchecked(dst))) }; } } // try existing pools with matching slot_size first - for (i, pool) in self.typed_arenas.iter().enumerate().rev() { + for (i, pool) in self.slot_pools.iter().enumerate().rev() { if pool.slot_size == slot_size && let Some(slot_ptr) = pool.alloc_slot() { self.alloc_cache[sc_idx].set(i); // SAFETY: slot_ptr was successfully allocated for this size class return unsafe { - let dst = slot_ptr.as_ptr() as *mut ArenaHeapItem; - dst.write(ArenaHeapItem(value)); - Ok(ArenaPointer::from_raw(NonNull::new_unchecked(dst))) + let dst = slot_ptr.as_ptr() as *mut PoolItem; + dst.write(PoolItem(value)); + Ok(PoolPointer::from_raw(NonNull::new_unchecked(dst))) }; } } // need a new pool for this size class - let total = self.arena_size.max(slot_size * 4); + let total = self.page_size.max(slot_size * 4); let new_pool = SlotPool::try_init(slot_size, total, 16)?; self.current_heap_size += new_pool.layout.size(); - let slot_ptr = new_pool.alloc_slot().ok_or(ArenaAllocError::OutOfMemory)?; - let insert_idx = self.typed_arenas.len(); - self.typed_arenas.push(new_pool); + let slot_ptr = new_pool.alloc_slot().ok_or(PoolAllocError::OutOfMemory)?; + let insert_idx = self.slot_pools.len(); + self.slot_pools.push(new_pool); self.alloc_cache[sc_idx].set(insert_idx); // SAFETY: slot_ptr was successfully allocated for this size class unsafe { - let dst = slot_ptr.as_ptr() as *mut ArenaHeapItem; - dst.write(ArenaHeapItem(value)); - Ok(ArenaPointer::from_raw(NonNull::new_unchecked(dst))) + let dst = slot_ptr.as_ptr() as *mut PoolItem; + dst.write(PoolItem(value)); + Ok(PoolPointer::from_raw(NonNull::new_unchecked(dst))) } } - /// Drops the value at `ptr` and returns the slot to the allocator + /// drops the value at `ptr` and returns the slot to the allocator /// /// # Safety - /// `ptr` must be a live `ArenaHeapItem` allocated by this allocator, + /// `ptr` must be a live `PoolItem` allocated by this allocator, /// must not be used after this call - pub unsafe fn free_slot_typed(&mut self, ptr: NonNull>) { + pub unsafe fn free_slot_typed(&mut self, ptr: NonNull>) { // SAFETY: guaranteed by caller unsafe { core::ptr::drop_in_place(ptr.as_ptr()) }; self.free_slot(ptr.cast::()); @@ -186,15 +184,15 @@ impl<'alloc> ArenaAllocator<'alloc> { pub fn free_slot(&mut self, ptr: NonNull) { let cached = self.free_cache.get(); - if cached < self.typed_arenas.len() { - let pool = &self.typed_arenas[cached]; + if cached < self.slot_pools.len() { + let pool = &self.slot_pools[cached]; if pool.owns(ptr) { pool.free_slot(ptr); return; } } - for (i, pool) in self.typed_arenas.iter().enumerate().rev() { + for (i, pool) in self.slot_pools.iter().enumerate().rev() { if pool.owns(ptr) { pool.free_slot(ptr); self.free_cache.set(i); @@ -208,30 +206,30 @@ impl<'alloc> ArenaAllocator<'alloc> { ); } - // bump allocate raw bytes onto a BumpPage - pub fn try_alloc_bytes(&mut self, layout: Layout) -> Result, ArenaAllocError> { - // try the most recent raw page first - if let Some(page) = self.raw_arenas.last() + /// bump allocate raw bytes onto a BumpPage + pub fn try_alloc_bytes(&mut self, layout: Layout) -> Result, PoolAllocError> { + // try the most recent bump page first + if let Some(page) = self.bump_pages.last() && let Ok(ptr) = page.try_alloc(layout) { return Ok(ptr); } - // allocate a new raw page with margin for padding + // allocate a new bump page with margin for padding let margin = 64; - let total = self.arena_size.max(layout.size() + layout.align() + margin); + let total = self.page_size.max(layout.size() + layout.align() + margin); let max_align = layout.align().max(16); let page = BumpPage::try_init(total, max_align)?; self.current_heap_size += page.layout.size(); let ptr = page .try_alloc(layout) - .map_err(|_| ArenaAllocError::OutOfMemory)?; - self.raw_arenas.push(page); + .map_err(|_| PoolAllocError::OutOfMemory)?; + self.bump_pages.push(page); Ok(ptr) } - // decrement live allocation count for the page owning ptr + /// decrement live allocation count for the page owning ptr pub fn dealloc_bytes(&mut self, ptr: NonNull) { - for page in self.raw_arenas.iter().rev() { + for page in self.bump_pages.iter().rev() { if page.owns(ptr) { page.dealloc(); return; @@ -239,14 +237,14 @@ impl<'alloc> ArenaAllocator<'alloc> { } } - // try to shrink a raw allocation in place + /// try to shrink a raw allocation in place pub fn shrink_bytes_in_place( &mut self, ptr: NonNull, old_layout: Layout, new_layout: Layout, ) -> bool { - for page in self.raw_arenas.iter().rev() { + for page in self.bump_pages.iter().rev() { if page.owns(ptr) { return page.shrink_in_place(ptr, old_layout, new_layout); } @@ -254,14 +252,14 @@ impl<'alloc> ArenaAllocator<'alloc> { false } - // try to grow a raw allocation in place + /// try to grow a raw allocation in place pub fn grow_bytes_in_place( &mut self, ptr: NonNull, old_layout: Layout, new_layout: Layout, ) -> bool { - for page in self.raw_arenas.iter().rev() { + for page in self.bump_pages.iter().rev() { if page.owns(ptr) { return page.grow_in_place(ptr, old_layout, new_layout); } @@ -269,9 +267,9 @@ impl<'alloc> ArenaAllocator<'alloc> { false } - // drop empty slot pools and raw pages - pub fn drop_dead_arenas(&mut self) { - self.typed_arenas.retain(|p| { + /// drop empty slot pools and bump pages + pub fn drop_empty_pools(&mut self) { + self.slot_pools.retain(|p| { if p.run_drop_check() { self.current_heap_size = self.current_heap_size.saturating_sub(p.layout.size()); false @@ -279,7 +277,7 @@ impl<'alloc> ArenaAllocator<'alloc> { true } }); - self.raw_arenas.retain(|p| { + self.bump_pages.retain(|p| { if p.run_drop_check() { self.current_heap_size = self.current_heap_size.saturating_sub(p.layout.size()); false @@ -293,9 +291,9 @@ impl<'alloc> ArenaAllocator<'alloc> { } } - // mark the slot at `ptr` as occupied (only typed slot pools have a bitmap) + /// mark the slot at `ptr` as occupied (only slot pools have a bitmap) pub fn mark_slot(&self, ptr: NonNull) { - for pool in self.typed_arenas.iter() { + for pool in self.slot_pools.iter() { if pool.owns(ptr) { pool.mark_slot(ptr); return; diff --git a/oscars/src/alloc/arena3/tests.rs b/oscars/src/alloc/mempool3/tests.rs similarity index 51% rename from oscars/src/alloc/arena3/tests.rs rename to oscars/src/alloc/mempool3/tests.rs index 1ae85db..b6b43b5 100644 --- a/oscars/src/alloc/arena3/tests.rs +++ b/oscars/src/alloc/mempool3/tests.rs @@ -1,57 +1,57 @@ use core::ptr::NonNull; use rust_alloc::vec::Vec; -use crate::alloc::arena3::ArenaHeapItem; +use crate::alloc::mempool3::PoolItem; -use super::ArenaAllocator; +use super::PoolAllocator; #[test] fn alloc_dealloc() { - // Let's just allocate with half a Kb per arena - let mut allocator = ArenaAllocator::default().with_arena_size(512); + // Let's just allocate with half a Kb per page + let mut allocator = PoolAllocator::default().with_page_size(512); - let mut first_region: Vec>> = Vec::default(); + let mut first_region: Vec>> = Vec::default(); for i in 0..32 { let ap = allocator.try_alloc(i).unwrap(); first_region.push(ap.as_ptr()); } assert!( - allocator.arenas_len() >= 1, - "at least one arena must exist after allocations" + allocator.pools_len() >= 1, + "at least one pool must exist after allocations" ); - let mut second_region: Vec>> = Vec::default(); + let mut second_region: Vec>> = Vec::default(); for i in 0..32 { let ap = allocator.try_alloc(i).unwrap(); second_region.push(ap.as_ptr()); } assert!( - allocator.arenas_len() >= 1, - "arenas must still exist after further allocations" + allocator.pools_len() >= 1, + "pools must still exist after further allocations" ); // release first region via free_slot for ptr in first_region { allocator.free_slot(ptr.cast::()); } - allocator.drop_dead_arenas(); + allocator.drop_empty_pools(); - // there may or may not be one fewer arena depending on slot packing, but + // there may or may not be one fewer pool depending on slot packing, but // the allocator must still contain the second region - assert!(allocator.arenas_len() <= 2); + assert!(allocator.pools_len() <= 2); drop(second_region); } #[test] fn free_list_reclaims_slots() { - let mut allocator = ArenaAllocator::default().with_arena_size(4096); + let mut allocator = PoolAllocator::default().with_page_size(4096); - let mut ptrs: Vec>> = (0u64..32) + let mut ptrs: Vec>> = (0u64..32) .map(|i| allocator.try_alloc(i).unwrap().as_ptr()) .collect(); - let arenas_after_alloc = allocator.arenas_len(); - assert!(arenas_after_alloc >= 1); + let pools_after_alloc = allocator.pools_len(); + assert!(pools_after_alloc >= 1); // free the first 16 slots via free_slot let to_free = ptrs.drain(..16).collect::>(); @@ -60,39 +60,39 @@ fn free_list_reclaims_slots() { } // reallocate 16 more items, they should reuse freed slots not create - // a new arena + // a new pool for i in 32u64..48 { let _ = allocator.try_alloc(i).unwrap(); } assert_eq!( - allocator.arenas_len(), - arenas_after_alloc, - "free list must allow slot reuse without new arenas" + allocator.pools_len(), + pools_after_alloc, + "free list must allow slot reuse without new pools" ); } -// bitmap drop check, if all slots freed then arena is reclaimed +// bitmap drop check, if all slots freed then pool is reclaimed #[test] fn bitmap_drop_check() { - let mut allocator = ArenaAllocator::default().with_arena_size(4096); + let mut allocator = PoolAllocator::default().with_page_size(4096); - let ptrs: Vec>> = (0u64..16) + let ptrs: Vec>> = (0u64..16) .map(|i| allocator.try_alloc(i).unwrap().as_ptr()) .collect(); - assert_eq!(allocator.arenas_len(), 1); + assert_eq!(allocator.pools_len(), 1); for ptr in ptrs { allocator.free_slot(ptr.cast::()); } - allocator.drop_dead_arenas(); + allocator.drop_empty_pools(); assert_eq!( - allocator.arenas_len(), + allocator.pools_len(), 0, - "all-empty arena must be dropped by drop_dead_arenas" + "all-empty pool must be dropped by drop_empty_pools" ); } @@ -113,26 +113,26 @@ fn arc_drop() { let dropped = Rc::new(AtomicBool::new(false)); - let mut allocator = ArenaAllocator::default(); + let mut allocator = PoolAllocator::default(); let a = allocator .try_alloc(MyS { dropped: dropped.clone(), }) .unwrap(); - assert_eq!(allocator.arenas_len(), 1); + assert_eq!(allocator.pools_len(), 1); // drop the inner value and return the slot to the allocator - let heap_item_ptr = a.as_ptr(); + let pool_item_ptr = a.as_ptr(); unsafe { - allocator.free_slot_typed(heap_item_ptr); + allocator.free_slot_typed(pool_item_ptr); } assert!(dropped.load(Ordering::SeqCst), "destructor must have run"); - assert_eq!(allocator.arenas_len(), 1); + assert_eq!(allocator.pools_len(), 1); - allocator.drop_dead_arenas(); - assert_eq!(allocator.arenas_len(), 0, "empty arena must be reclaimed"); + allocator.drop_empty_pools(); + assert_eq!(allocator.pools_len(), 0, "empty pool must be reclaimed"); } // SlotPool slot count arithmetic tests @@ -141,35 +141,35 @@ fn arc_drop() { // slot count and bitmap size for different inputs fn slot_pool_layout(slot_size: usize, total_capacity: usize) -> (usize, usize) { - use crate::alloc::arena3::alloc::SlotPool; + use crate::alloc::mempool3::alloc::SlotPool; let pool = SlotPool::try_init(slot_size, total_capacity, 8).unwrap(); - (pool.slot_count, pool.bitmap_words) + (pool.slot_count, pool.bitmap_bytes) } #[test] fn slot_count_example_from_doc() { - let (slot_count, bitmap_words) = slot_pool_layout(16, 512); - assert_eq!(bitmap_words, 1, "one 64-bit word covers 32 estimated slots"); + let (slot_count, bitmap_bytes) = slot_pool_layout(16, 512); + assert_eq!(bitmap_bytes, 8, "8 bytes covers 32 estimated slots"); assert_eq!(slot_count, 31); } #[test] -fn slot_count_needs_two_bitmap_words() { - let (slot_count, bitmap_words) = slot_pool_layout(8, 4096); - assert_eq!(bitmap_words, 8); +fn slot_count_needs_two_bitmap_chunks() { + let (slot_count, bitmap_bytes) = slot_pool_layout(8, 4096); + assert_eq!(bitmap_bytes, 64); assert_eq!(slot_count, 504); } #[test] fn slot_count_large_slot_size() { - let (slot_count, bitmap_words) = slot_pool_layout(256, 4096); - assert_eq!(bitmap_words, 1); + let (slot_count, bitmap_bytes) = slot_pool_layout(256, 4096); + assert_eq!(bitmap_bytes, 8); assert_eq!(slot_count, 15); } #[test] fn slot_count_tight_capacity() { - let (slot_count, bitmap_words) = slot_pool_layout(64, 512); - assert_eq!(bitmap_words, 1); + let (slot_count, bitmap_bytes) = slot_pool_layout(64, 512); + assert_eq!(bitmap_bytes, 8); assert_eq!(slot_count, 7); } diff --git a/oscars/src/alloc/mod.rs b/oscars/src/alloc/mod.rs index ee55fdb..1f4a7d4 100644 --- a/oscars/src/alloc/mod.rs +++ b/oscars/src/alloc/mod.rs @@ -2,6 +2,6 @@ pub mod arena; pub mod arena2; -pub mod arena3; pub mod mempool; pub mod mempool2; +pub mod mempool3; diff --git a/oscars/src/collectors/collector.rs b/oscars/src/collectors/collector.rs index bbb7cec..b10aa5a 100644 --- a/oscars/src/collectors/collector.rs +++ b/oscars/src/collectors/collector.rs @@ -5,7 +5,7 @@ // - alloc methods accept raw values so the `GcBox` header gets its color // after any GC collections happen, preventing tracing bugs -use crate::alloc::arena3::ArenaPointer; +use crate::alloc::mempool3::PoolPointer; use crate::collectors::mark_sweep::{ TraceColor, internals::{Ephemeron, GcBox}, @@ -27,7 +27,7 @@ pub trait Collector: allocator_api2::alloc::Allocator { fn alloc_gc_node<'gc, T: Trace + 'static>( &'gc self, value: T, - ) -> Result>, allocator_api2::alloc::AllocError>; + ) -> Result>, allocator_api2::alloc::AllocError>; // Allocates an ephemeron node pointing to an existing GC key, and a new value // @@ -36,7 +36,7 @@ pub trait Collector: allocator_api2::alloc::Allocator { &'gc self, key: &crate::collectors::mark_sweep::Gc, value: V, - ) -> Result>, allocator_api2::alloc::AllocError>; + ) -> Result>, allocator_api2::alloc::AllocError>; // register a weak map with the GC so it can prune dead entries #[doc(hidden)] @@ -61,7 +61,7 @@ pub trait Collector { fn alloc_gc_node<'gc, T: Trace + 'static>( &'gc self, value: T, - ) -> Result>, crate::alloc::arena3::ArenaAllocError>; + ) -> Result>, crate::alloc::mempool3::PoolAllocError>; // Allocates an ephemeron node pointing to an existing GC key, and a new value // @@ -70,7 +70,7 @@ pub trait Collector { &'gc self, key: &crate::collectors::mark_sweep::Gc, value: V, - ) -> Result>, crate::alloc::arena3::ArenaAllocError>; + ) -> Result>, crate::alloc::mempool3::PoolAllocError>; // Register a weak map with the GC so it can prune dead entries #[doc(hidden)] diff --git a/oscars/src/collectors/mark_sweep/gc_collections.rs b/oscars/src/collectors/mark_sweep/gc_collections.rs index 6075741..87c6e9f 100644 --- a/oscars/src/collectors/mark_sweep/gc_collections.rs +++ b/oscars/src/collectors/mark_sweep/gc_collections.rs @@ -1,5 +1,3 @@ -#![cfg(feature = "gc_allocator")] - use crate::collectors::mark_sweep::{MarkSweepGarbageCollector, TraceColor, trace::Trace}; use core::ops::{Deref, DerefMut}; @@ -200,7 +198,7 @@ unsafe impl Trace for GcAllocBox { mod tests { use super::*; use crate::collectors::mark_sweep::{MarkSweepGarbageCollector, cell::GcRefCell, pointers::Gc}; - use rust_alloc::vec::Vec; + use rust_alloc::vec; #[test] fn gc_alloc_vec_basic() { @@ -221,7 +219,7 @@ mod tests { #[test] fn gc_alloc_vec_survives_collection() { let collector = &mut MarkSweepGarbageCollector::default() - .with_arena_size(256) + .with_page_size(256) .with_heap_threshold(512); let vec = GcAllocVec::with_capacity(100, collector); @@ -249,15 +247,7 @@ mod tests { #[test] fn gc_alloc_box_survives_collection() { let collector = &mut MarkSweepGarbageCollector::default(); - let data = { - let mut v = Vec::new(); - v.push(1); - v.push(2); - v.push(3); - v.push(4); - v.push(5); - v - }; + let data = vec![1, 2, 3, 4, 5]; let boxed = GcAllocBox::new_in(data, collector); let gc_box = Gc::new_in(GcRefCell::new(boxed), collector); diff --git a/oscars/src/collectors/mark_sweep/internals/ephemeron.rs b/oscars/src/collectors/mark_sweep/internals/ephemeron.rs index 53d0c92..85b22cd 100644 --- a/oscars/src/collectors/mark_sweep/internals/ephemeron.rs +++ b/oscars/src/collectors/mark_sweep/internals/ephemeron.rs @@ -4,7 +4,7 @@ use core::any::TypeId; use core::marker::PhantomData; use crate::{ - alloc::arena3::ArenaHeapItem, + alloc::mempool3::PoolItem, collectors::mark_sweep::{ ErasedEphemeron, TraceColor, internals::{GcBox, WeakGcBox}, @@ -112,11 +112,7 @@ pub(crate) const fn vtable_of() -> &'sta color: TraceColor, ) { // SAFETY: The caller must ensure that the passed erased pointer is `GcBox`. - let ephemeron = unsafe { - this.cast::>>() - .as_ref() - .value() - }; + let ephemeron = unsafe { this.cast::>>().as_ref().value() }; // SAFETY: The implementor must ensure that `trace` is correctly implemented. unsafe { @@ -127,8 +123,8 @@ pub(crate) const fn vtable_of() -> &'sta // SAFETY: The caller must ensure that the passed erased pointer is `GcBox`. unsafe fn drop_fn(this: ErasedEphemeron) { - // SAFETY: The caller must ensure that the passed erased pointer is `ArenaHeapItem>`. - let mut this = this.cast::>>(); + // SAFETY: The caller must ensure that the passed erased pointer is `PoolItem>`. + let mut this = this.cast::>>(); // drop the Ephemeron value in place, the arena bitmap is cleared // by the sweep loop after this function returns @@ -141,17 +137,11 @@ pub(crate) const fn vtable_of() -> &'sta trace_fn: EphemeronMarker::::trace_fn::, drop_fn: EphemeronMarker::::drop_fn::, is_reachable_fn: |this, color| unsafe { - let ephemeron = this - .cast::>>() - .as_ref() - .value(); + let ephemeron = this.cast::>>().as_ref().value(); ephemeron.active.get() && ephemeron.key.is_reachable(color) }, finalize_fn: |this| unsafe { - let ephemeron = this - .cast::>>() - .as_ref() - .value(); + let ephemeron = this.cast::>>().as_ref().value(); Finalize::finalize(ephemeron); }, _key_type_id: TypeId::of::(), diff --git a/oscars/src/collectors/mark_sweep/internals/gc_box.rs b/oscars/src/collectors/mark_sweep/internals/gc_box.rs index 5ce8a31..df18fd4 100644 --- a/oscars/src/collectors/mark_sweep/internals/gc_box.rs +++ b/oscars/src/collectors/mark_sweep/internals/gc_box.rs @@ -26,17 +26,17 @@ unsafe impl Trace for NonTraceable { // NOTE: This may not be the best idea, but let's find out. // -use crate::alloc::arena3::{ArenaHeapItem, ErasedArenaPointer}; +use crate::alloc::mempool3::{ErasedPoolPointer, PoolItem}; use core::marker::PhantomData; use core::ptr::NonNull; pub struct WeakGcBox { - pub(crate) inner_ptr: ErasedArenaPointer<'static>, + pub(crate) inner_ptr: ErasedPoolPointer<'static>, pub(crate) marker: PhantomData, } impl WeakGcBox { - pub fn new(inner_ptr: ErasedArenaPointer<'static>) -> Self { + pub fn new(inner_ptr: ErasedPoolPointer<'static>) -> Self { Self { inner_ptr, marker: PhantomData, @@ -45,16 +45,16 @@ impl WeakGcBox { pub(crate) fn erased_inner_ptr(&self) -> NonNull> { // SAFETY: `as_heap_ptr` returns a valid pointer to - // `ArenaHeapItem` whose lifetime is tied to the arena + // `PoolItem` whose lifetime is tied to the pool let heap_item = unsafe { self.as_heap_ptr().as_mut() }; // SAFETY: We just removed this value from a NonNull unsafe { NonNull::new_unchecked(heap_item.as_ptr()) } } - pub(crate) fn as_heap_ptr(&self) -> NonNull>> { + pub(crate) fn as_heap_ptr(&self) -> NonNull>> { self.inner_ptr .as_non_null() - .cast::>>() + .cast::>>() } pub(crate) fn inner_ref(&self) -> &GcBox { @@ -69,10 +69,10 @@ impl WeakGcBox { } impl WeakGcBox { - pub(crate) fn inner_ptr(&self) -> crate::alloc::arena3::ArenaPointer<'static, GcBox> { + pub(crate) fn inner_ptr(&self) -> crate::alloc::mempool3::PoolPointer<'static, GcBox> { // SAFETY: This pointer started out as a `GcBox`, so it's safe to cast // it back, the `PhantomData` guarantees that the type `T` is still correct - unsafe { self.inner_ptr.to_typed_arena_pointer::>() } + unsafe { self.inner_ptr.to_typed_pool_pointer::>() } } pub fn value(&self) -> &T { diff --git a/oscars/src/collectors/mark_sweep/internals/vtable.rs b/oscars/src/collectors/mark_sweep/internals/vtable.rs index 6583734..b8eb643 100644 --- a/oscars/src/collectors/mark_sweep/internals/vtable.rs +++ b/oscars/src/collectors/mark_sweep/internals/vtable.rs @@ -1,6 +1,6 @@ use core::any::TypeId; -use crate::alloc::arena3::ArenaHeapItem; +use crate::alloc::mempool3::PoolItem; use crate::collectors::mark_sweep::{GcBox, GcErasedPointer, Trace, TraceColor}; @@ -11,7 +11,7 @@ pub(crate) const fn vtable_of() -> &'static VTable { unsafe fn trace_fn(this: GcErasedPointer, color: TraceColor) { // SAFETY: The caller must ensure that the passed erased pointer is `GcBox`. - let value = unsafe { this.cast::>>().as_ref().value() }; + let value = unsafe { this.cast::>>().as_ref().value() }; // SAFETY: The implementor must ensure that `trace` is correctly implemented. unsafe { @@ -22,7 +22,7 @@ pub(crate) const fn vtable_of() -> &'static VTable { // SAFETY: The caller must ensure that the passed erased pointer is `GcBox`. unsafe fn drop_fn(this: GcErasedPointer) { // SAFETY: The caller must ensure that the passed erased pointer is `GcBox`. - let mut this = this.cast::>>(); + let mut this = this.cast::>>(); // SAFETY: The caller must ensure the erased pointer is not dropped or deallocated. unsafe { core::ptr::drop_in_place(this.as_mut()) }; diff --git a/oscars/src/collectors/mark_sweep/mod.rs b/oscars/src/collectors/mark_sweep/mod.rs index 408a0d1..22eed9f 100644 --- a/oscars/src/collectors/mark_sweep/mod.rs +++ b/oscars/src/collectors/mark_sweep/mod.rs @@ -7,7 +7,7 @@ use core::cell::{Cell, RefCell}; use core::ptr::NonNull; use crate::{ - alloc::arena3::{ArenaAllocator, ArenaHeapItem, ArenaPointer}, + alloc::mempool3::{PoolAllocator, PoolItem, PoolPointer}, collectors::mark_sweep::internals::{Ephemeron, GcBox, NonTraceable}, }; use rust_alloc::vec::Vec; @@ -33,8 +33,8 @@ pub use trace::{Finalize, Trace, TraceColor}; #[cfg(feature = "gc_allocator")] pub use gc_collections::{GcAllocBox, GcAllocVec}; -type GcErasedPointer = NonNull>>; -pub(crate) type ErasedEphemeron = NonNull>>; +type GcErasedPointer = NonNull>>; +pub(crate) type ErasedEphemeron = NonNull>>; /* TODO: Figure out the best way to adapt the thread local concept in no_std * @@ -56,7 +56,7 @@ pub(crate) type ErasedEphemeron = NonNull>, + pub(crate) allocator: RefCell>, root_queue: RefCell>, ephemeron_queue: RefCell>, // current trace color epoch, flips each cycle @@ -79,17 +79,17 @@ impl MarkSweepGarbageCollector { self } - pub fn with_arena_size(mut self, arena_size: usize) -> Self { - self.allocator.get_mut().arena_size = arena_size; + pub fn with_page_size(mut self, page_size: usize) -> Self { + self.allocator.get_mut().page_size = page_size; self } - //returns the number of live arenas held by this collector + // returns the number of live slot pools + bump pages held by this collector // - //prefer this over accessing `self.allocator` directly in tests so that - //the arena representation can change without touching every call site - pub fn arenas_len(&self) -> usize { - self.allocator.borrow().arenas_len() + // prefer this over accessing `self.allocator` directly in tests so that + // the pool representation can change without touching every call site + pub fn pools_len(&self) -> usize { + self.allocator.borrow().pools_len() } } @@ -125,7 +125,7 @@ impl Drop for MarkSweepGarbageCollector { // SAFETY: // `Gc` pointers act as if they live forever (`'static`). // if the GC drops while they exist, we leak the memory to prevent a UAF - if self.arenas_len() > 0 + if self.pools_len() > 0 && (!self.root_queue.borrow().is_empty() || !self.pending_root_queue.borrow().is_empty()) { @@ -143,26 +143,16 @@ impl Drop for MarkSweepGarbageCollector { // ==== Collection methods ==== -// RAII guard that clears `is_collecting` even if a Trace or Finalize impl panics -// without this, a panic inside run_mark_phase / run_sweep_phase would leave -// is_collecting == true forever, silently disabling the deferred collect -struct CollectingGuard<'a>(&'a Cell); - -impl Drop for CollectingGuard<'_> { - fn drop(&mut self) { - self.0.set(false); - } -} - impl MarkSweepGarbageCollector { - // trigger a full collection cycle - // - // exposes `&self` to run without borrow conflicts when live collections exist pub fn collect(&self) { - // lock the main queues so allocations buffer into pending queues - // the guard resets is_collecting even if a Trace/Finalize impl panics self.is_collecting.set(true); - let _guard = CollectingGuard(&self.is_collecting); + struct CollectionGuard<'a>(&'a Cell); + impl<'a> Drop for CollectionGuard<'a> { + fn drop(&mut self) { + self.0.set(false); + } + } + let _guard = CollectionGuard(&self.is_collecting); self.run_mark_phase(); @@ -176,7 +166,7 @@ impl MarkSweepGarbageCollector { // finally tell the allocator to reclaim raw OS memory // from arenas that are completely empty now - self.allocator.borrow_mut().drop_dead_arenas(); + self.allocator.borrow_mut().drop_empty_pools(); } // Force drops all elements in the internal tracking queues and clears @@ -241,9 +231,9 @@ impl MarkSweepGarbageCollector { let new_color = sweep_color.flip(); self.trace_color.set(new_color); - // NOTE: It would actually be interesting to reuse the arenas that are dead rather + // NOTE: It would actually be interesting to reuse the pools that are empty rather // than drop the page and reallocate when a new page is needed ... TBD - self.allocator.borrow_mut().drop_dead_arenas(); + self.allocator.borrow_mut().drop_empty_pools(); // Drain pending queues while `is_collecting` is still true so that any // allocation triggered by `drop(_guard)` flushes to pending (not main) @@ -449,7 +439,7 @@ unsafe impl allocator_api2::alloc::Allocator for MarkSweepGarbageCollector { unsafe fn deallocate(&self, ptr: NonNull, _layout: allocator_api2::alloc::Layout) { // decrements active_raw_allocs for the arena containing ptr - // allowing drop_dead_arenas to reclaim the page when it reaches zero + // allowing drop_empty_pools to reclaim the page when it reaches zero self.allocator.borrow_mut().dealloc_bytes(ptr); // debug only: remove from tracking @@ -572,7 +562,7 @@ impl crate::collectors::collector::Collector for MarkSweepGarbageCollector { fn alloc_gc_node<'gc, T: Trace + 'static>( &'gc self, value: T, - ) -> Result>, allocator_api2::alloc::AllocError> { + ) -> Result>, allocator_api2::alloc::AllocError> { if self.collect_needed.get() && !self.is_collecting.get() { self.collect_needed.set(false); self.collect(); @@ -593,7 +583,7 @@ impl crate::collectors::collector::Collector for MarkSweepGarbageCollector { self.collect_needed.set(true); } - let erased: NonNull>> = arena_ptr.as_ptr().cast(); + let erased: NonNull>> = arena_ptr.as_ptr().cast(); if self.is_collecting.get() { self.pending_root_queue.borrow_mut().push(erased); } else { @@ -611,7 +601,7 @@ impl crate::collectors::collector::Collector for MarkSweepGarbageCollector { &'gc self, key: &crate::collectors::mark_sweep::pointers::Gc, value: V, - ) -> Result>, allocator_api2::alloc::AllocError> { + ) -> Result>, allocator_api2::alloc::AllocError> { if self.collect_needed.get() && !self.is_collecting.get() { self.collect_needed.set(false); self.collect(); @@ -632,7 +622,7 @@ impl crate::collectors::collector::Collector for MarkSweepGarbageCollector { let eph_ptr = inner_ptr .as_ptr() - .cast::>>(); + .cast::>>(); if self.is_collecting.get() { self.pending_ephemeron_queue.borrow_mut().push(eph_ptr); @@ -670,7 +660,7 @@ impl crate::collectors::collector::Collector for MarkSweepGarbageCollector { fn alloc_gc_node<'gc, T: Trace + 'static>( &'gc self, value: T, - ) -> Result>, crate::alloc::arena3::ArenaAllocError> { + ) -> Result>, crate::alloc::mempool3::PoolAllocError> { if self.collect_needed.get() && !self.is_collecting.get() { self.collect_needed.set(false); self.collect(); @@ -689,7 +679,7 @@ impl crate::collectors::collector::Collector for MarkSweepGarbageCollector { self.collect_needed.set(true); } - let erased: NonNull>> = arena_ptr.as_ptr().cast(); + let erased: NonNull>> = arena_ptr.as_ptr().cast(); if self.is_collecting.get() { self.pending_root_queue.borrow_mut().push(erased); } else { @@ -707,7 +697,7 @@ impl crate::collectors::collector::Collector for MarkSweepGarbageCollector { &'gc self, key: &crate::collectors::mark_sweep::pointers::Gc, value: V, - ) -> Result>, crate::alloc::arena3::ArenaAllocError> { + ) -> Result>, crate::alloc::mempool3::PoolAllocError> { if self.collect_needed.get() && !self.is_collecting.get() { self.collect_needed.set(false); self.collect(); @@ -727,7 +717,7 @@ impl crate::collectors::collector::Collector for MarkSweepGarbageCollector { let eph_ptr = inner_ptr .as_ptr() - .cast::>>(); + .cast::>>(); if self.is_collecting.get() { self.pending_ephemeron_queue.borrow_mut().push(eph_ptr); diff --git a/oscars/src/collectors/mark_sweep/pointers/gc.rs b/oscars/src/collectors/mark_sweep/pointers/gc.rs index 13f3383..1c10bb7 100644 --- a/oscars/src/collectors/mark_sweep/pointers/gc.rs +++ b/oscars/src/collectors/mark_sweep/pointers/gc.rs @@ -1,4 +1,4 @@ -use crate::alloc::arena3::{ArenaHeapItem, ArenaPointer, ErasedArenaPointer}; +use crate::alloc::mempool3::{ErasedPoolPointer, PoolItem, PoolPointer}; use crate::collectors::collector::Collector; use crate::collectors::mark_sweep::Finalize; use crate::collectors::mark_sweep::internals::NonTraceable; @@ -11,7 +11,7 @@ use core::{marker::PhantomData, ptr::NonNull}; /// A garbage-collected pointer type over an immutable value. pub struct Gc { - pub(crate) inner_ptr: ErasedArenaPointer<'static>, + pub(crate) inner_ptr: ErasedPoolPointer<'static>, pub(crate) marker: PhantomData, } @@ -37,8 +37,8 @@ impl Gc { } impl Gc { - pub(crate) fn inner_ptr(&self) -> ArenaPointer<'static, GcBox> { - unsafe { self.inner_ptr.to_typed_arena_pointer::>() } + pub(crate) fn inner_ptr(&self) -> PoolPointer<'static, GcBox> { + unsafe { self.inner_ptr.to_typed_pool_pointer::>() } } } @@ -48,10 +48,10 @@ impl Gc { unsafe { NonNull::new_unchecked(heap_item.as_ptr()) } } - pub(crate) fn as_heap_ptr(&self) -> NonNull>> { + pub(crate) fn as_heap_ptr(&self) -> NonNull>> { self.inner_ptr .as_non_null() - .cast::>>() + .cast::>>() } pub(crate) fn inner_ref(&self) -> &GcBox { diff --git a/oscars/src/collectors/mark_sweep/pointers/weak.rs b/oscars/src/collectors/mark_sweep/pointers/weak.rs index 2217022..e338426 100644 --- a/oscars/src/collectors/mark_sweep/pointers/weak.rs +++ b/oscars/src/collectors/mark_sweep/pointers/weak.rs @@ -2,14 +2,14 @@ // per weak pointer. This overhead is acceptable for now but could be // optimized in the future use crate::{ - alloc::arena3::ArenaPointer, + alloc::mempool3::PoolPointer, collectors::collector::Collector, collectors::mark_sweep::{Trace, internals::Ephemeron}, }; #[repr(transparent)] pub struct WeakGc { - inner_ptr: ArenaPointer<'static, Ephemeron>, + inner_ptr: PoolPointer<'static, Ephemeron>, } impl WeakGc { diff --git a/oscars/src/collectors/mark_sweep/pointers/weak_map.rs b/oscars/src/collectors/mark_sweep/pointers/weak_map.rs index 6504c92..e575725 100644 --- a/oscars/src/collectors/mark_sweep/pointers/weak_map.rs +++ b/oscars/src/collectors/mark_sweep/pointers/weak_map.rs @@ -1,7 +1,7 @@ use rustc_hash::FxHashMap; use crate::{ - alloc::arena3::ArenaPointer, + alloc::mempool3::PoolPointer, collectors::collector::Collector, collectors::mark_sweep::{Finalize, TraceColor, internals::Ephemeron, trace::Trace}, }; @@ -20,7 +20,7 @@ pub trait ErasedWeakMap { // // TODO: a HashTable might be a better approach here struct WeakMapInner { - entries: FxHashMap>>, + entries: FxHashMap>>, is_alive: core::cell::Cell, } @@ -41,7 +41,7 @@ impl WeakMapInner { fn insert_ptr( &mut self, key_addr: usize, - ephemeron_ptr: ArenaPointer<'static, Ephemeron>, + ephemeron_ptr: PoolPointer<'static, Ephemeron>, ) { self.entries.insert(key_addr, ephemeron_ptr); } diff --git a/oscars/src/collectors/mark_sweep/tests.rs b/oscars/src/collectors/mark_sweep/tests.rs index 691a255..1fa7c45 100644 --- a/oscars/src/collectors/mark_sweep/tests.rs +++ b/oscars/src/collectors/mark_sweep/tests.rs @@ -8,16 +8,16 @@ use super::cell::GcRefCell; #[test] fn basic_gc() { let collector = &mut MarkSweepGarbageCollector::default() - .with_arena_size(64) + .with_page_size(64) .with_heap_threshold(128); let gc = Gc::new_in(GcRefCell::new(10), collector); - assert_eq!(collector.allocator.borrow().arenas_len(), 1); + assert_eq!(collector.allocator.borrow().pools_len(), 1); collector.collect(); - assert_eq!(collector.allocator.borrow().arenas_len(), 1); + assert_eq!(collector.allocator.borrow().pools_len(), 1); assert_eq!(*gc.borrow(), 10); } @@ -25,7 +25,7 @@ fn basic_gc() { #[test] fn nested_gc() { let collector = &mut MarkSweepGarbageCollector::default() - .with_arena_size(80) + .with_page_size(80) .with_heap_threshold(128); // We are allocating 32 bytes, per GC, which with the linked list pointer should be @@ -39,24 +39,24 @@ fn nested_gc() { collector.collect(); - assert_eq!(collector.allocator.borrow().arenas_len(), 2); + assert_eq!(collector.allocator.borrow().pools_len(), 2); assert_eq!(*nested_gc.borrow(), 10); let new_gc = Gc::new_in(GcRefCell::new(8), collector); - assert_eq!(collector.allocator.borrow().arenas_len(), 2); + assert_eq!(collector.allocator.borrow().pools_len(), 2); drop(new_gc); collector.collect(); - assert_eq!(collector.allocator.borrow().arenas_len(), 2); + assert_eq!(collector.allocator.borrow().pools_len(), 2); assert_eq!(*nested_gc.borrow(), 10); } #[test] fn gc_recursion() { let collector = &mut MarkSweepGarbageCollector::default() - .with_arena_size(4096) + .with_page_size(4096) .with_heap_threshold(8_192); #[derive(Debug, Finalize, Trace)] @@ -89,30 +89,30 @@ fn gc_recursion() { #[test] fn drop_gc() { let collector = &mut MarkSweepGarbageCollector::default() - .with_arena_size(256) + .with_page_size(256) .with_heap_threshold(512); let gc = Gc::new_in(GcRefCell::new(7u64), collector); - assert_eq!(collector.allocator.borrow().arenas_len(), 1); + assert_eq!(collector.allocator.borrow().pools_len(), 1); collector.collect(); - assert_eq!(collector.allocator.borrow().arenas_len(), 1); + assert_eq!(collector.allocator.borrow().pools_len(), 1); drop(gc); collector.collect(); - // after collecting a dead Gc, its slot is freed and the arena page dropped + // after collecting a dead Gc, its slot is freed and the pool page dropped assert_eq!( - collector.allocator.borrow().arenas_len(), + collector.allocator.borrow().pools_len(), 0, - "arena not freed" + "pool not freed" ); } #[test] fn clone_gc() { let collector = &mut MarkSweepGarbageCollector::default() - .with_arena_size(256) + .with_page_size(256) .with_heap_threshold(512); let gc = Gc::new_in(GcRefCell::new(42u32), collector); @@ -127,7 +127,7 @@ fn clone_gc() { #[test] fn multi_gc() { let collector = &mut MarkSweepGarbageCollector::default() - .with_arena_size(128) + .with_page_size(128) .with_heap_threshold(512); for _ in 0..3 { @@ -135,15 +135,15 @@ fn multi_gc() { .map(|i| Gc::new_in(GcRefCell::new(i as u64), collector)) .collect(); - assert!(collector.allocator.borrow().arenas_len() >= 1); + assert!(collector.allocator.borrow().pools_len() >= 1); drop(objects); collector.collect(); assert_eq!( - collector.allocator.borrow().arenas_len(), + collector.allocator.borrow().pools_len(), 0, - "arenas not reclaimed" + "pools not reclaimed" ); } } @@ -151,7 +151,7 @@ fn multi_gc() { #[test] fn pressure_gc() { let collector = &mut MarkSweepGarbageCollector::default() - .with_arena_size(128) + .with_page_size(128) .with_heap_threshold(256); let root = Gc::new_in(GcRefCell::new(99u64), collector); @@ -168,7 +168,7 @@ fn pressure_gc() { #[test] fn borrow_mut_gc() { let collector = &mut MarkSweepGarbageCollector::default() - .with_arena_size(256) + .with_page_size(256) .with_heap_threshold(512); let gc = Gc::new_in(GcRefCell::new(0u64), collector); @@ -182,7 +182,7 @@ fn borrow_mut_gc() { #[test] fn long_lived_gc() { let collector = &mut MarkSweepGarbageCollector::default() - .with_arena_size(256) + .with_page_size(256) .with_heap_threshold(512); let gc = Gc::new_in(GcRefCell::new(77u64), collector); @@ -193,16 +193,16 @@ fn long_lived_gc() { assert_eq!(*gc.borrow(), 77u64, "swept during color-flip"); assert_eq!( - collector.allocator.borrow().arenas_len(), + collector.allocator.borrow().pools_len(), 1, - "arena freed while live" + "pool freed while live" ); } #[test] fn basic_wm() { let collector = &mut MarkSweepGarbageCollector::default() - .with_arena_size(256) + .with_page_size(256) .with_heap_threshold(512); let mut map = WeakMap::new(collector); @@ -217,7 +217,7 @@ fn basic_wm() { #[test] fn dead_wm() { let collector = &mut MarkSweepGarbageCollector::default() - .with_arena_size(256) + .with_page_size(256) .with_heap_threshold(512); let mut map = WeakMap::new(collector); @@ -230,7 +230,7 @@ fn dead_wm() { collector.collect(); assert_eq!( - collector.allocator.borrow().arenas_len(), + collector.allocator.borrow().pools_len(), 0, "ephemeron not swept" ); @@ -239,7 +239,7 @@ fn dead_wm() { #[test] fn update_wm() { let collector = &mut MarkSweepGarbageCollector::default() - .with_arena_size(256) + .with_page_size(256) .with_heap_threshold(512); let mut map = WeakMap::new(collector); @@ -256,16 +256,23 @@ fn update_wm() { // both ephemerons (old invalidated, new key dead) should be freed assert_eq!( - collector.allocator.borrow().arenas_len(), + collector.allocator.borrow().pools_len(), 0, "arena leaked after update" ); // both ephemerons (old invalidated, new key-dead) should be freed assert_eq!( - collector.allocator.borrow().arenas_len(), + collector.allocator.borrow().pools_len(), 0, - "arena leaked after update" + "pool leaked after update" + ); + + // both ephemerons (old invalidated, new key-dead) should be freed + assert_eq!( + collector.allocator.borrow().pools_len(), + 0, + "pool leaked after update" ); } @@ -278,7 +285,7 @@ fn trace_wm() { } let collector = &mut MarkSweepGarbageCollector::default() - .with_arena_size(256) + .with_page_size(256) .with_heap_threshold(512); let container = Gc::new_in( @@ -296,7 +303,7 @@ fn trace_wm() { #[test] fn remove_wm() { let collector = &mut MarkSweepGarbageCollector::default() - .with_arena_size(256) + .with_page_size(256) .with_heap_threshold(512); let mut map = WeakMap::new(collector); @@ -323,40 +330,40 @@ fn prune_wm() { // dangling pointer fix // ensure insert doesn't read freed memory on dead entries let collector = &mut MarkSweepGarbageCollector::default() - .with_arena_size(256) + .with_page_size(256) .with_heap_threshold(512); let mut map = WeakMap::new(collector); let key1 = Gc::new_in(1u64, collector); assert_eq!( - collector.allocator.borrow().arenas_len(), + collector.allocator.borrow().pools_len(), 1, "after key1 alloc" ); map.insert(&key1.clone(), 10u64, collector); assert_eq!( - collector.allocator.borrow().arenas_len(), + collector.allocator.borrow().pools_len(), 2, "after insert key1" ); drop(key1); collector.collect(); assert_eq!( - collector.allocator.borrow().arenas_len(), + collector.allocator.borrow().pools_len(), 0, "after first collect" ); let key2 = Gc::new_in(2u64, collector); assert_eq!( - collector.allocator.borrow().arenas_len(), + collector.allocator.borrow().pools_len(), 1, "after key2 alloc" ); map.insert(&key2.clone(), 20u64, collector); assert_eq!( - collector.allocator.borrow().arenas_len(), + collector.allocator.borrow().pools_len(), 2, "after insert key2" ); @@ -365,14 +372,14 @@ fn prune_wm() { drop(key2); collector.collect(); - assert_eq!(collector.allocator.borrow().arenas_len(), 0); + assert_eq!(collector.allocator.borrow().pools_len(), 0); } #[test] fn remove_then_collect() { // ensure remove() doesn't leak the backing ephemeron after key is gone let collector = &mut MarkSweepGarbageCollector::default() - .with_arena_size(256) + .with_page_size(256) .with_heap_threshold(512); let mut map = WeakMap::new(collector); @@ -387,7 +394,7 @@ fn remove_then_collect() { collector.collect(); assert_eq!( - collector.allocator.borrow().arenas_len(), + collector.allocator.borrow().pools_len(), 0, "ephemeron leaked after remove" ); @@ -396,7 +403,7 @@ fn remove_then_collect() { #[test] fn alive_wm() { let collector = &mut MarkSweepGarbageCollector::default() - .with_arena_size(256) + .with_page_size(256) .with_heap_threshold(512); let mut map = WeakMap::new(collector); @@ -436,7 +443,7 @@ mod gc_edge_cases { #[test] fn deep_object_graph() { let collector = &mut MarkSweepGarbageCollector::default() - .with_arena_size(4096) + .with_page_size(4096) .with_heap_threshold(8_192); #[derive(Debug, Finalize, Trace)] @@ -472,7 +479,7 @@ mod gc_edge_cases { #[test] fn cyclic_references() { let collector = &mut MarkSweepGarbageCollector::default() - .with_arena_size(4096) + .with_page_size(4096) .with_heap_threshold(8_192); #[derive(Debug, Finalize, Trace)] @@ -514,7 +521,7 @@ mod gc_edge_cases { #[test] fn weak_map_cleanup() { let collector = &mut MarkSweepGarbageCollector::default() - .with_arena_size(1024) + .with_page_size(1024) .with_heap_threshold(2048); let mut map = WeakMap::new(collector); @@ -548,7 +555,7 @@ mod gc_edge_cases { #[test] fn finalizer_safety() { let collector = &mut MarkSweepGarbageCollector::default() - .with_arena_size(4096) + .with_page_size(4096) .with_heap_threshold(8_192); #[derive(Trace)] @@ -582,7 +589,7 @@ mod gc_edge_cases { #[test] fn repeated_collections_stable() { let collector = &mut MarkSweepGarbageCollector::default() - .with_arena_size(256) + .with_page_size(256) .with_heap_threshold(512); let root = Gc::new_in(GcRefCell::new(99u64), collector); @@ -605,7 +612,7 @@ mod gc_edge_cases { #[test] fn deep_dead_graph_sweep() { let collector = &mut MarkSweepGarbageCollector::default() - .with_arena_size(4096) + .with_page_size(4096) .with_heap_threshold(8_192); #[derive(Debug, Finalize, Trace)]