use core::any::Any;
use core::marker::PhantomData;
use crate::canon::RefKind;
use crate::store::{
decode_anyref_handle, AnyRefHandle, AsContext, AsContextMut, GcObject, ObjKind, StoreContext,
StoreContextMut,
};
use crate::{Error, Result};
use super::gc_aggregate::{ArrayRef, StructRef};
const UNCHECKED_GENERATION: u32 = u32::MAX;
const UNCHECKED_STORE: u64 = 0;
pub struct Rooted<T> {
index: u32,
arena: RefKind,
store: u64,
generation: u32,
_marker: PhantomData<T>,
}
impl<T> Clone for Rooted<T> {
fn clone(&self) -> Self {
*self
}
}
impl<T> Copy for Rooted<T> {}
impl<T> Rooted<T> {
pub(crate) fn from_raw(index: u32, arena: RefKind) -> Self {
Rooted {
index,
arena,
store: UNCHECKED_STORE,
generation: UNCHECKED_GENERATION,
_marker: PhantomData,
}
}
pub(crate) fn from_raw_gen(index: u32, generation: u32, store: u64, arena: RefKind) -> Self {
Rooted {
index,
arena,
store,
generation,
_marker: PhantomData,
}
}
pub(crate) fn from_raw_store(index: u32, store: u64, arena: RefKind) -> Self {
Rooted {
index,
arena,
store,
generation: UNCHECKED_GENERATION,
_marker: PhantomData,
}
}
pub(crate) fn raw(self) -> u32 {
self.index
}
pub(crate) fn check_store(self, inner: &crate::store::StoreInner) {
inner.check_handle(self.store);
}
pub(crate) fn belongs_to_store(self, inner: &crate::store::StoreInner) -> bool {
self.store == UNCHECKED_STORE || self.store == inner.store_id()
}
pub(crate) fn cast<U>(self) -> Rooted<U> {
Rooted {
index: self.index,
arena: self.arena,
store: self.store,
generation: self.generation,
_marker: PhantomData,
}
}
pub(crate) fn gc_slot_checked(self, inner: &crate::store::StoreInner) -> Result<u32> {
inner.check_handle(self.store);
let slot = gc_slot(inner, self.index)?;
if self.generation != UNCHECKED_GENERATION
&& inner.gc.generation(slot) != Some(self.generation)
{
return Err(Error::msg("stale gc reference (object was collected)"));
}
Ok(slot)
}
pub(crate) fn checked(
self,
current_generation: Option<u32>,
inner: &crate::store::StoreInner,
) -> Result<u32> {
inner.check_handle(self.store);
let Some(current_generation) = current_generation else {
return Err(Error::msg("stale reference (object was collected)"));
};
if self.generation != UNCHECKED_GENERATION && current_generation != self.generation {
return Err(Error::msg("stale reference (object was collected)"));
}
Ok(self.index)
}
pub fn ref_eq<U>(store: impl AsContext, a: &Rooted<T>, b: &Rooted<U>) -> Result<bool> {
let ctx = store.as_context();
ctx.inner().check_handle(a.store);
ctx.inner().check_handle(b.store);
a.validate_liveness(ctx.inner())?;
b.validate_liveness(ctx.inner())?;
Ok(a.arena == b.arena
&& a.index == b.index
&& (a.generation == UNCHECKED_GENERATION
|| b.generation == UNCHECKED_GENERATION
|| a.generation == b.generation))
}
fn validate_liveness(self, inner: &crate::store::StoreInner) -> Result<()> {
match self.arena {
RefKind::Any => {
if matches!(decode_anyref_handle(self.index), AnyRefHandle::Slot(_)) {
self.gc_slot_checked(inner)?;
}
}
RefKind::Extern => {
let _ = inner.externref_checked(self.cast::<ExternRef>())?;
}
RefKind::Exn => {
let _ = inner.exn_checked(self.cast::<ExnRef>())?;
}
RefKind::Func => {
return Err(Error::msg("invalid rooted reference arena"));
}
}
Ok(())
}
}
impl<T> core::fmt::Debug for Rooted<T> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Rooted").finish_non_exhaustive()
}
}
pub struct RootScope<S: AsContextMut> {
store: S,
mark: usize,
}
impl<S: AsContextMut> RootScope<S> {
pub fn new(mut store: S) -> Self {
let mark = store.as_context_mut().inner_mut().gc_roots_mark();
RootScope { store, mark }
}
}
impl<S: AsContextMut> Drop for RootScope<S> {
fn drop(&mut self) {
self.store
.as_context_mut()
.inner_mut()
.gc_roots_truncate(self.mark);
}
}
impl<S: AsContextMut> AsContext for RootScope<S> {
type Data = S::Data;
fn as_context(&self) -> StoreContext<'_, S::Data> {
self.store.as_context()
}
}
impl<S: AsContextMut> AsContextMut for RootScope<S> {
fn as_context_mut(&mut self) -> StoreContextMut<'_, S::Data> {
self.store.as_context_mut()
}
}
impl<S: AsContextMut> core::fmt::Debug for RootScope<S> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("RootScope").finish_non_exhaustive()
}
}
#[derive(Debug)]
pub struct ExternRef {
_private: (),
}
impl ExternRef {
pub fn new<T>(mut store: impl AsContextMut, value: T) -> Result<Rooted<ExternRef>>
where
T: Any + Send + Sync + 'static,
{
let mut ctx = store.as_context_mut();
let index = ctx.store_mut().gc_alloc_externref(Box::new(value))?;
let inner = ctx.inner_mut();
inner.push_gc_root(index, crate::canon::RefKind::Extern);
Ok(Rooted::from_raw_gen(
index,
inner.externref_generation(index),
inner.store_id(),
RefKind::Extern,
))
}
}
impl Rooted<ExternRef> {
pub fn data<'a, T>(
&self,
store: impl Into<StoreContext<'a, T>>,
) -> Result<Option<&'a (dyn Any + Send + Sync)>>
where
T: 'static,
{
store.into().inner().externref_checked(*self)
}
pub fn data_mut<'a, T>(
&self,
store: impl Into<StoreContextMut<'a, T>>,
) -> Result<Option<&'a mut (dyn Any + Send + Sync)>>
where
T: 'static,
{
store.into().into_inner_mut().externref_checked_mut(*self)
}
}
#[derive(Debug)]
pub struct AnyRef {
_private: (),
}
impl Rooted<AnyRef> {
pub fn unwrap_struct(&self, store: impl AsContext) -> Result<Rooted<StructRef>> {
self.as_struct(store)?
.ok_or_else(|| Error::msg("anyref is not a struct"))
}
pub fn as_struct(&self, store: impl AsContext) -> Result<Option<Rooted<StructRef>>> {
let ctx = store.as_context();
Ok(if obj_kind(ctx.inner(), *self)? == Some(ObjKind::Struct) {
Some(self.cast())
} else {
None
})
}
pub fn unwrap_array(&self, store: impl AsContext) -> Result<Rooted<ArrayRef>> {
self.as_array(store)?
.ok_or_else(|| Error::msg("anyref is not an array"))
}
pub fn as_array(&self, store: impl AsContext) -> Result<Option<Rooted<ArrayRef>>> {
let ctx = store.as_context();
Ok(if obj_kind(ctx.inner(), *self)? == Some(ObjKind::Array) {
Some(self.cast())
} else {
None
})
}
}
#[derive(Debug)]
pub struct ExnRef {
_private: (),
}
pub(super) fn gc_slot(inner: &crate::store::StoreInner, handle: u32) -> Result<u32> {
match decode_anyref_handle(handle) {
AnyRefHandle::Slot(i) => Ok(i),
AnyRefHandle::I31(_) => Err(Error::msg("reference is an i31, not a heap object")),
}
.and_then(|i| {
gc_object(inner, i)?;
Ok(i)
})
}
pub(super) fn gc_object(inner: &crate::store::StoreInner, slot: u32) -> Result<&GcObject> {
inner
.gc_object(slot)
.ok_or_else(|| Error::msg("dangling gc reference"))
}
fn obj_kind(inner: &crate::store::StoreInner, handle: Rooted<AnyRef>) -> Result<Option<ObjKind>> {
match decode_anyref_handle(handle.raw()) {
AnyRefHandle::I31(_) => Ok(None),
AnyRefHandle::Slot(_) => {
let slot = handle.gc_slot_checked(inner)?;
Ok(Some(gc_object(inner, slot)?.header.kind))
}
}
}