use core::marker::PhantomData;
use crate::{
ecmascript::Agent,
engine::{Bindable, HeapRootCollection, HeapRootRef, NoGcScope, Rootable, ScopeToken},
};
use super::{HeapRootData, RootableCollection};
#[derive(Hash, Clone)]
#[repr(transparent)]
#[allow(private_bounds)]
pub struct Scoped<'a, T: 'static + Rootable> {
pub(crate) inner: T::RootRepr,
_marker: PhantomData<T>,
_scope: PhantomData<&'a ScopeToken>,
}
impl<T: 'static + Rootable> core::fmt::Debug for Scoped<'_, T> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "Scoped<{}>", core::any::type_name::<T>())
}
}
#[allow(private_bounds)]
impl<T: 'static + Rootable> Scoped<'static, T> {
#[inline(always)]
pub(crate) const fn from_root_repr(value: T::RootRepr) -> Scoped<'static, T> {
Self {
inner: value,
_marker: PhantomData,
_scope: PhantomData,
}
}
}
#[allow(private_bounds)]
pub trait Scopable: Rootable + Bindable
where
for<'a> Self::Of<'a>: Rootable + Bindable,
{
fn scope<'scope>(
self,
agent: &mut Agent,
gc: NoGcScope<'_, 'scope>,
) -> Scoped<'scope, Self::Of<'static>> {
Scoped::new(agent, self.unbind(), gc)
}
}
impl<T: Rootable + Bindable> Scopable for T where for<'a> Self::Of<'a>: Rootable + Bindable {}
#[allow(private_bounds)]
impl<'scope, T: Rootable> Scoped<'scope, T> {
pub(crate) unsafe fn into_root_repr(self) -> T::RootRepr {
self.inner
}
pub fn new(agent: &Agent, value: T, _gc: NoGcScope<'_, 'scope>) -> Self {
let value = match T::to_root_repr(value) {
Ok(stack_repr) => {
return Self {
inner: stack_repr,
_marker: PhantomData,
_scope: PhantomData,
};
}
Err(heap_data) => heap_data,
};
let mut stack_refs = agent.stack_refs.borrow_mut();
let next_index = stack_refs.len();
stack_refs.push(value);
Self {
inner: T::from_heap_ref(HeapRootRef::from_index(next_index)),
_marker: PhantomData,
_scope: PhantomData,
}
}
#[must_use]
pub unsafe fn take(self, agent: &Agent) -> T {
match T::from_root_repr(&self.inner) {
Ok(value) => value,
Err(heap_root_ref) => {
let index = heap_root_ref.to_index();
let mut stack_refs = agent.stack_refs.borrow_mut();
let Some(heap_data) = stack_refs.get_mut(index) else {
handle_bound_check_failure()
};
let heap_data = core::mem::replace(heap_data, HeapRootData::Empty);
if index == stack_refs.len() - 1 {
Self::drop_empty_slots(&mut stack_refs);
}
let Some(value) = T::from_heap_data(heap_data) else {
handle_invalid_scoped_conversion()
};
value
}
}
}
fn drop_empty_slots(stack_refs: &mut Vec<HeapRootData>) {
let last_non_empty_index = stack_refs
.iter()
.enumerate()
.rfind(|(_, v)| !matches!(v, HeapRootData::Empty))
.map_or(0, |(index, _)| index + 1);
debug_assert!(last_non_empty_index < stack_refs.len());
unsafe { stack_refs.set_len(last_non_empty_index) };
}
pub fn get(&self, agent: &Agent) -> T {
match T::from_root_repr(&self.inner) {
Ok(value) => value,
Err(heap_root_ref) => {
let Some(&heap_data) = agent.stack_refs.borrow().get(heap_root_ref.to_index())
else {
handle_bound_check_failure()
};
let Some(value) = T::from_heap_data(heap_data) else {
handle_invalid_scoped_conversion()
};
value
}
}
}
#[inline(always)]
pub fn unwrap(&self) -> T {
let Ok(value) = T::from_root_repr(&self.inner) else {
unreachable!("Scoped value was a heap reference")
};
value
}
pub unsafe fn replace(&mut self, agent: &Agent, value: T) {
let heap_data = match T::to_root_repr(value) {
Ok(stack_repr) => {
let previous = core::mem::replace(
self,
Self {
inner: stack_repr,
_marker: PhantomData,
_scope: PhantomData,
},
);
let _ = unsafe { previous.take(agent) };
return;
}
Err(heap_data) => heap_data,
};
match T::from_root_repr(&self.inner) {
Ok(_) => {
let mut stack_refs = agent.stack_refs.borrow_mut();
let next_index = stack_refs.len();
stack_refs.push(heap_data);
*self = Self {
inner: T::from_heap_ref(HeapRootRef::from_index(next_index)),
_marker: PhantomData,
_scope: PhantomData,
}
}
Err(heap_root_ref) => {
let mut stack_refs_borrow = agent.stack_refs.borrow_mut();
let Some(heap_slot) = stack_refs_borrow.get_mut(heap_root_ref.to_index()) else {
handle_bound_check_failure()
};
*heap_slot = heap_data;
}
}
}
pub unsafe fn replace_self<U: 'static + Rootable>(
self,
agent: &mut Agent,
value: U,
) -> Scoped<'scope, U> {
let heap_data = match U::to_root_repr(value) {
Ok(stack_repr) => {
let _ = unsafe { self.take(agent) };
return Scoped {
inner: stack_repr,
_marker: PhantomData,
_scope: PhantomData,
};
}
Err(heap_data) => heap_data,
};
match T::from_root_repr(&self.inner) {
Ok(_) => {
let mut stack_refs = agent.stack_refs.borrow_mut();
let next_index = stack_refs.len();
stack_refs.push(heap_data);
Scoped {
inner: U::from_heap_ref(HeapRootRef::from_index(next_index)),
_marker: PhantomData,
_scope: PhantomData,
}
}
Err(heap_root_ref) => {
let mut stack_refs_borrow = agent.stack_refs.borrow_mut();
let Some(heap_slot) = stack_refs_borrow.get_mut(heap_root_ref.to_index()) else {
handle_bound_check_failure()
};
*heap_slot = heap_data;
Scoped {
inner: U::from_heap_ref(heap_root_ref),
_marker: PhantomData,
_scope: PhantomData,
}
}
}
}
}
#[allow(private_bounds)]
pub trait ScopableCollection: Bindable
where
Self::Of<'static>: RootableCollection,
{
fn scope<'scope>(
self,
agent: &Agent,
gc: NoGcScope<'_, 'scope>,
) -> ScopedCollection<'scope, Self::Of<'static>>;
}
#[derive(Debug, Hash, Clone)]
#[repr(transparent)]
#[allow(private_bounds)]
pub struct ScopedCollection<'a, T: 'static + RootableCollection> {
pub(crate) inner: u32,
_marker: PhantomData<T>,
_scope: PhantomData<&'a ScopeToken>,
}
#[allow(private_bounds)]
impl<'a, T: 'static + RootableCollection> ScopedCollection<'a, T> {
pub(crate) fn new(agent: &Agent, rootable: T, _gc: NoGcScope<'_, 'a>) -> Self {
let heap_data = rootable.to_heap_data();
let inner = u32::try_from(agent.stack_ref_collections.borrow().len())
.expect("ScopedCollections stack overflowed");
agent.stack_ref_collections.borrow_mut().push(heap_data);
Self {
inner,
_marker: PhantomData,
_scope: PhantomData,
}
}
#[must_use]
pub(crate) fn take(self, agent: &Agent) -> T {
let index = self.inner;
let mut stack_ref_collections = agent.stack_ref_collections.borrow_mut();
let heap_slot = stack_ref_collections.get_mut(index as usize).unwrap();
let heap_data = core::mem::replace(heap_slot, HeapRootCollection::Empty);
if index as usize == stack_ref_collections.len() - 1 {
Self::drop_empty_slots(&mut stack_ref_collections);
}
T::from_heap_data(heap_data)
}
fn drop_empty_slots(stack_ref_collections: &mut Vec<HeapRootCollection>) {
let last_non_empty_index = stack_ref_collections
.iter()
.enumerate()
.rfind(|(_, v)| !v.is_empty())
.map_or(0, |(index, _)| index + 1);
debug_assert!(last_non_empty_index < stack_ref_collections.len());
unsafe { stack_ref_collections.set_len(last_non_empty_index) };
}
}
#[cold]
#[inline(never)]
fn handle_invalid_scoped_conversion() -> ! {
panic!("Attempted to convert mismatched Scoped");
}
#[cold]
#[inline(never)]
fn handle_bound_check_failure() -> ! {
panic!("Attempted to access dropped Scoped")
}