use core::{cell::RefCell, fmt, mem};
use alloc::{
rc::{Rc, Weak},
vec::Vec,
};
use crate::{
Gc, Mutation, Rootable,
arena::Root,
collect::{Collect, Trace},
};
#[derive(Copy, Clone)]
pub struct DynamicRootSet<'gc>(Gc<'gc, Inner<'gc>>);
unsafe impl<'gc> Collect<'gc> for DynamicRootSet<'gc> {
fn trace<T: Trace<'gc>>(&self, cc: &mut T) {
cc.trace(&self.0);
}
}
impl<'gc> DynamicRootSet<'gc> {
pub fn new(mc: &Mutation<'gc>) -> Self {
DynamicRootSet(Gc::new(
mc,
Inner {
slots: Rc::new(RefCell::new(Slots::new())),
},
))
}
pub fn stash<R: for<'a> Rootable<'a>>(
&self,
mc: &Mutation<'gc>,
root: Gc<'gc, Root<'gc, R>>,
) -> DynamicRoot<R> {
mc.backward_barrier(Gc::erase(self.0), Some(Gc::erase(root)));
let mut slots = self.0.slots.borrow_mut();
let index = slots.add(unsafe { Gc::cast(root) });
let ptr =
unsafe { mem::transmute::<Gc<'gc, Root<'gc, R>>, Gc<'static, Root<'static, R>>>(root) };
let slots = unsafe {
mem::transmute::<Weak<RefCell<Slots<'gc>>>, Weak<RefCell<Slots<'static>>>>(
Rc::downgrade(&self.0.slots),
)
};
DynamicRoot { ptr, slots, index }
}
#[inline]
pub fn fetch<R: for<'r> Rootable<'r>>(&self, root: &DynamicRoot<R>) -> Gc<'gc, Root<'gc, R>> {
if self.contains(root) {
unsafe {
mem::transmute::<Gc<'static, Root<'static, R>>, Gc<'gc, Root<'gc, R>>>(root.ptr)
}
} else {
panic!("mismatched root set")
}
}
#[inline]
pub fn try_fetch<R: for<'r> Rootable<'r>>(
&self,
root: &DynamicRoot<R>,
) -> Result<Gc<'gc, Root<'gc, R>>, MismatchedRootSet> {
if self.contains(root) {
Ok(unsafe {
mem::transmute::<Gc<'static, Root<'static, R>>, Gc<'gc, Root<'gc, R>>>(root.ptr)
})
} else {
Err(MismatchedRootSet(()))
}
}
#[inline]
pub fn contains<R: for<'r> Rootable<'r>>(&self, root: &DynamicRoot<R>) -> bool {
let ours = unsafe {
mem::transmute::<*const RefCell<Slots<'gc>>, *const RefCell<Slots<'static>>>(
Rc::as_ptr(&self.0.slots),
)
};
let theirs = Weak::as_ptr(&root.slots);
ours == theirs
}
}
pub struct DynamicRoot<R: for<'gc> Rootable<'gc>> {
ptr: Gc<'static, Root<'static, R>>,
slots: Weak<RefCell<Slots<'static>>>,
index: Index,
}
impl<R: for<'gc> Rootable<'gc>> Drop for DynamicRoot<R> {
fn drop(&mut self) {
if let Some(slots) = self.slots.upgrade() {
slots.borrow_mut().dec(self.index);
}
}
}
impl<R: for<'gc> Rootable<'gc>> Clone for DynamicRoot<R> {
fn clone(&self) -> Self {
if let Some(slots) = self.slots.upgrade() {
slots.borrow_mut().inc(self.index);
}
Self {
ptr: self.ptr,
slots: self.slots.clone(),
index: self.index,
}
}
}
impl<R: for<'gc> Rootable<'gc>> DynamicRoot<R> {
#[inline]
pub fn as_ptr<'gc>(&self) -> *const Root<'gc, R> {
unsafe { mem::transmute::<&Root<'static, R>, &Root<'gc, R>>(&self.ptr) as *const _ }
}
}
#[derive(Debug)]
pub struct MismatchedRootSet(());
impl fmt::Display for MismatchedRootSet {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("mismatched root set")
}
}
#[cfg(feature = "std")]
impl std::error::Error for MismatchedRootSet {}
struct Inner<'gc> {
slots: Rc<RefCell<Slots<'gc>>>,
}
unsafe impl<'gc> Collect<'gc> for Inner<'gc> {
fn trace<T: Trace<'gc>>(&self, cc: &mut T) {
cc.trace(&*self.slots.borrow());
}
}
type Index = usize;
const NULL_INDEX: Index = usize::MAX;
enum Slot<'gc> {
Vacant { next_free: Index },
Occupied { root: Gc<'gc, ()>, ref_count: usize },
}
unsafe impl<'gc> Collect<'gc> for Slot<'gc> {
fn trace<T: Trace<'gc>>(&self, cc: &mut T) {
match self {
Slot::Vacant { .. } => {}
Slot::Occupied { root, ref_count: _ } => cc.trace_gc(*root),
}
}
}
struct Slots<'gc> {
slots: Vec<Slot<'gc>>,
next_free: Index,
}
unsafe impl<'gc> Collect<'gc> for Slots<'gc> {
fn trace<T: Trace<'gc>>(&self, cc: &mut T) {
cc.trace(&self.slots);
}
}
impl<'gc> Slots<'gc> {
fn new() -> Self {
Self {
slots: Vec::new(),
next_free: NULL_INDEX,
}
}
fn add(&mut self, p: Gc<'gc, ()>) -> Index {
if self.next_free != NULL_INDEX {
let idx = self.next_free;
let slot = &mut self.slots[idx];
match *slot {
Slot::Vacant { next_free } => {
self.next_free = next_free;
}
Slot::Occupied { .. } => panic!("free slot linked list corrupted"),
}
*slot = Slot::Occupied {
root: p,
ref_count: 0,
};
idx
} else {
let idx = self.slots.len();
self.slots.push(Slot::Occupied {
root: p,
ref_count: 0,
});
idx
}
}
fn inc(&mut self, idx: Index) {
match &mut self.slots[idx] {
Slot::Occupied { ref_count, .. } => {
*ref_count = ref_count
.checked_add(1)
.expect("DynamicRoot refcount overflow!");
}
Slot::Vacant { .. } => panic!("taken slot has been improperly freed"),
}
}
fn dec(&mut self, idx: Index) {
let slot = &mut self.slots[idx];
match slot {
Slot::Occupied { ref_count, .. } => {
if *ref_count == 0 {
*slot = Slot::Vacant {
next_free: self.next_free,
};
self.next_free = idx;
} else {
*ref_count -= 1;
}
}
Slot::Vacant { .. } => panic!("taken slot has been improperly freed"),
}
}
}