use std::{
fmt,
marker::PhantomData,
mem::{size_of, MaybeUninit},
ptr::NonNull,
};
use super::{Mutator, Pointer, Root, Slot, Trace, TARGET};
pub struct Bucket<'a, T: fmt::Debug, const N: usize> {
entries: [Slot<T>; N],
len: usize,
next: usize,
marker: PhantomData<Mutator<'a>>,
}
impl<'a, T: fmt::Debug, const N: usize> Bucket<'a, T, N> {
#[must_use]
pub fn new() -> Box<Self> {
log::debug!(target: TARGET,
"Bucket: new bucket of {} with capacity of {} values",
std::any::type_name::<T>(), N);
let mut bucket = Self {
entries: unsafe { MaybeUninit::zeroed().assume_init() },
len: 0,
next: 0,
marker: PhantomData,
};
for i in 0..N {
bucket.entries[i] = Slot::Vacant(i + 1);
}
bucket.into()
}
pub const fn len(&self) -> usize {
self.len
}
pub const fn available(&self) -> usize {
N - self.len
}
pub const fn is_full(&self) -> bool {
self.len == N
}
pub const fn is_empty(&self) -> bool {
self.len == 0
}
pub fn is_valid_pointer(&self, ptr: NonNull<Slot<T>>) -> bool {
let addr = ptr.as_ptr() as usize;
let start = std::ptr::addr_of!(self.entries[0]) as usize;
let end = std::ptr::addr_of!(self.entries[N - 1]) as usize;
if addr >= start && addr <= end && (addr - start) % size_of::<Slot<T>>() == 0 {
unsafe { ptr.as_ref() }.is_occupied()
} else {
false
}
}
#[must_use]
pub fn alloc(&mut self, value: T) -> Option<Pointer<'a, T>> {
if self.len == N {
log::debug!(target: TARGET, "Bucket: full, cannot allocate");
return None;
}
let index = self.next;
self.next = match self.entries[index] {
Slot::Vacant(next) => next,
_ => unreachable!(),
};
self.entries[index] = Slot::Occupied(1, value);
self.len += 1;
log::debug!(target: TARGET,
"Bucket: allocated value at index 0x{:x}, {:p}",
index, std::ptr::addr_of_mut!(self.entries[0]));
let ptr = NonNull::new(&mut self.entries[index]).expect("null-pointer");
let ptr = unsafe { Pointer::new_unchecked(ptr) };
Some(ptr)
}
pub(super) fn drop_all(&mut self) {
for i in 0..N {
self.entries[i] = Slot::Dropped;
}
self.next = N;
self.len = N;
}
}
impl<'a, T: Trace<'a> + fmt::Debug, const N: usize> Bucket<'a, T, N> {
pub fn mark(&mut self, pending: &mut Vec<Root<'a>>) {
let mut marked = 0;
let pending_len = pending.len();
for entry in &mut self.entries {
if entry.is_marked() {
continue;
}
if entry.is_locked() {
entry.mark();
entry.trace(pending);
marked += 1;
}
}
log::debug!(target: TARGET,
"Bucket: marked {} values with pending {} values",
marked, pending.len() - pending_len);
}
#[inline]
pub fn sweep(&mut self) -> usize {
self.sweep_and(|_| {})
}
pub fn sweep_and(&mut self, mut f: impl FnMut(&mut Slot<T>)) -> usize {
let allocated = self.len;
for (index, entry) in self.entries.iter_mut().enumerate() {
if entry.is_marked() {
entry.unmark();
} else if entry.is_collectable() {
f(entry);
*entry = Slot::Vacant(self.next);
self.next = index;
self.len -= 1;
}
}
log::debug!(target: TARGET,
"Bucket: sweeped {} values and collected {} values",
allocated, allocated - self.len);
allocated - self.len
}
}
impl<'a, T: fmt::Debug, const N: usize> fmt::Debug for Bucket<'a, T, N> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Bucket")
.field("len", &self.len)
.field("next", &self.next)
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn create() {
let bucket = Bucket::<String, 512>::new();
assert!(bucket.is_empty());
assert!(!bucket.is_full());
assert_eq!(bucket.len(), 0);
assert_eq!(bucket.available(), 512);
assert_eq!(bucket.next, 0);
for (idx, entry) in bucket.entries.iter().enumerate() {
assert_eq!(entry, &Slot::Vacant(idx + 1));
}
}
#[test]
fn allocate() {
let mut bucket = Bucket::<String, 512>::new();
for i in 0..512 {
let value = bucket.alloc(format!("String {i}"));
assert_eq!(
value,
Some(Pointer::new(NonNull::new(&mut bucket.entries[i]).unwrap()))
);
assert_eq!(bucket.len(), i + 1);
assert_eq!(bucket.available(), 511 - i);
assert_eq!(bucket.next, i + 1);
assert_eq!(bucket.entries[i], Slot::Occupied(1, format!("String {i}")));
}
assert!(bucket.is_full());
assert!(!bucket.is_empty());
}
}