use core::alloc::Layout;
use core::cell::Cell;
use core::marker::PhantomData;
use core::ptr::NonNull;
use core::{mem, ptr};
use crate::collect::Collect;
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub(crate) struct GcBox(NonNull<GcBoxInner<()>>);
impl GcBox {
#[inline(always)]
pub(crate) unsafe fn erase<T: ?Sized>(ptr: NonNull<GcBoxInner<T>>) -> Self {
let erased = ptr.as_ptr() as *mut GcBoxInner<()>;
Self(NonNull::new_unchecked(erased))
}
#[inline(always)]
fn unerased_value<T>(&self) -> *mut T {
unsafe {
let ptr = self.0.as_ptr() as *mut GcBoxInner<T>;
ptr::addr_of_mut!((*ptr).value) as *mut T
}
}
#[inline(always)]
pub(crate) fn header(&self) -> &GcBoxHeader {
unsafe { &self.0.as_ref().header }
}
#[inline(always)]
pub(crate) unsafe fn trace_value(&self, cc: &crate::Collection) {
(self.header().vtable().trace_value)(*self, cc)
}
#[inline(always)]
pub(crate) unsafe fn drop_in_place(&mut self) {
(self.header().vtable().drop_value)(*self)
}
#[inline(always)]
pub(crate) unsafe fn dealloc(self) {
let layout = self.header().vtable().box_layout;
let ptr = self.0.as_ptr() as *mut u8;
alloc::alloc::dealloc(ptr, layout);
}
}
pub(crate) struct GcBoxHeader {
next: Cell<Option<GcBox>>,
tagged_vtable: Cell<*const CollectVtable>,
}
impl GcBoxHeader {
#[inline(always)]
pub fn new<T: Collect>() -> Self {
trait HasCollectVtable {
const VTABLE: CollectVtable;
}
impl<T: Collect> HasCollectVtable for T {
const VTABLE: CollectVtable = CollectVtable::vtable_for::<T>();
}
let vtable: &'static _ = &<T as HasCollectVtable>::VTABLE;
Self {
next: Cell::new(None),
tagged_vtable: Cell::new(vtable as *const _),
}
}
#[inline(always)]
fn vtable(&self) -> &'static CollectVtable {
let ptr = tagged_ptr::untag(self.tagged_vtable.get());
unsafe { &*ptr }
}
#[inline(always)]
pub(crate) fn next(&self) -> Option<GcBox> {
self.next.get()
}
#[inline(always)]
pub(crate) fn set_next(&self, next: Option<GcBox>) {
self.next.set(next)
}
#[inline(always)]
pub(crate) fn size_of_box(&self) -> usize {
self.vtable().box_layout.size()
}
#[inline]
pub(crate) fn color(&self) -> GcColor {
match tagged_ptr::get::<0x3, _>(self.tagged_vtable.get()) {
0x0 => GcColor::White,
0x1 => GcColor::WhiteWeak,
0x2 => GcColor::Gray,
_ => GcColor::Black,
}
}
#[inline]
pub(crate) fn set_color(&self, color: GcColor) {
tagged_ptr::set::<0x3, _>(
&self.tagged_vtable,
match color {
GcColor::White => 0x0,
GcColor::WhiteWeak => 0x1,
GcColor::Gray => 0x2,
GcColor::Black => 0x3,
},
);
}
#[inline]
pub(crate) fn needs_trace(&self) -> bool {
tagged_ptr::get::<0x4, _>(self.tagged_vtable.get()) != 0x0
}
#[inline]
pub(crate) fn is_live(&self) -> bool {
tagged_ptr::get::<0x8, _>(self.tagged_vtable.get()) != 0x0
}
#[inline]
pub(crate) fn set_needs_trace(&self, needs_trace: bool) {
tagged_ptr::set_bool::<0x4, _>(&self.tagged_vtable, needs_trace);
}
#[inline]
pub(crate) fn set_live(&self, alive: bool) {
tagged_ptr::set_bool::<0x8, _>(&self.tagged_vtable, alive);
}
}
#[repr(align(16))]
struct CollectVtable {
box_layout: Layout,
drop_value: unsafe fn(GcBox),
trace_value: unsafe fn(GcBox, &crate::Collection),
}
impl CollectVtable {
#[inline(always)]
const fn vtable_for<T: Collect>() -> Self {
Self {
box_layout: Layout::new::<GcBoxInner<T>>(),
drop_value: |erased| unsafe {
ptr::drop_in_place(erased.unerased_value::<T>());
},
trace_value: |erased, cc| unsafe {
let val = &*(erased.unerased_value::<T>());
val.trace(cc)
},
}
}
}
#[repr(C)]
pub(crate) struct GcBoxInner<T: ?Sized> {
header: GcBoxHeader,
pub(crate) value: mem::ManuallyDrop<T>,
}
impl<T: ?Sized> GcBoxInner<T> {
#[inline(always)]
pub(crate) fn new(header: GcBoxHeader, t: T) -> Self
where
T: Collect + Sized,
{
Self {
header,
value: mem::ManuallyDrop::new(t),
}
}
}
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub(crate) enum GcColor {
White,
WhiteWeak,
Gray,
Black,
}
pub(crate) type Invariant<'a> = PhantomData<Cell<&'a ()>>;
mod tagged_ptr {
#![cfg_attr(not(miri), allow(unstable_name_collisions))]
#[cfg(not(miri))]
use sptr::Strict as _;
use core::cell::Cell;
trait ValidMask<const MASK: usize> {
const CHECK: ();
}
impl<T, const MASK: usize> ValidMask<MASK> for T {
const CHECK: () = assert!(MASK < core::mem::align_of::<T>());
}
macro_rules! check_mask {
($type:ty, $mask:expr) => {
let _ = <$type as ValidMask<$mask>>::CHECK;
};
}
#[inline(always)]
pub(super) fn untag<T>(tagged_ptr: *const T) -> *const T {
let mask = core::mem::align_of::<T>() - 1;
tagged_ptr.map_addr(|addr| addr & !mask)
}
#[inline(always)]
pub(super) fn get<const MASK: usize, T>(tagged_ptr: *const T) -> usize {
check_mask!(T, MASK);
tagged_ptr.addr() & MASK
}
#[inline(always)]
pub(super) fn set<const MASK: usize, T>(pcell: &Cell<*const T>, tag: usize) {
check_mask!(T, MASK);
let ptr = pcell.get();
let ptr = ptr.map_addr(|addr| (addr & !MASK) | (tag & MASK));
pcell.set(ptr)
}
#[inline(always)]
pub(super) fn set_bool<const MASK: usize, T>(pcell: &Cell<*const T>, value: bool) {
check_mask!(T, MASK);
let ptr = pcell.get();
let ptr = ptr.map_addr(|addr| (addr & !MASK) | if value { MASK } else { 0 });
pcell.set(ptr)
}
}