use crate::prelude::*;
use crate::runtime::vm::VMGcRef;
use crate::{
store::{AutoAssertNoGc, StoreOpaque},
AsContextMut, GcHeapOutOfMemory, GcRefImpl, GcRootIndex, HeapType, ManuallyRooted, RefType,
Result, RootSet, Rooted, StoreContext, StoreContextMut, ValRaw, ValType, WasmTy,
};
use anyhow::Context;
use core::any::Any;
use core::mem;
use core::mem::MaybeUninit;
#[derive(Debug, Clone)]
#[repr(transparent)]
pub struct ExternRef {
inner: GcRootIndex,
}
unsafe impl GcRefImpl for ExternRef {
#[allow(private_interfaces)]
fn transmute_ref(index: &GcRootIndex) -> &Self {
let me: &Self = unsafe { mem::transmute(index) };
assert!(matches!(
me,
Self {
inner: GcRootIndex { .. },
}
));
me
}
}
impl ExternRef {
pub fn new<T>(mut context: impl AsContextMut, value: T) -> Result<Rooted<ExternRef>>
where
T: 'static + Any + Send + Sync,
{
let ctx = context.as_context_mut().0;
let value: Box<dyn Any + Send + Sync> = Box::new(value);
let gc_ref = ctx
.gc_store_mut()?
.alloc_externref(value)
.err2anyhow()
.context("unrecoverable error when allocating new `externref`")?
.map_err(|x| GcHeapOutOfMemory::<T>::new(*x.downcast().unwrap()))
.err2anyhow()
.context("failed to allocate `externref`")?;
let mut ctx = AutoAssertNoGc::new(ctx);
Ok(Self::from_cloned_gc_ref(&mut ctx, gc_ref.into()))
}
pub fn new_manually_rooted<T>(
mut store: impl AsContextMut,
value: T,
) -> Result<ManuallyRooted<ExternRef>>
where
T: 'static + Any + Send + Sync,
{
let ctx = store.as_context_mut().0;
let value: Box<dyn Any + Send + Sync> = Box::new(value);
let gc_ref = ctx
.gc_store_mut()?
.alloc_externref(value)
.err2anyhow()
.context("unrecoverable error when allocating new `externref`")?
.map_err(|x| GcHeapOutOfMemory::<T>::new(*x.downcast().unwrap()))
.err2anyhow()
.context("failed to allocate `externref`")?;
let mut ctx = AutoAssertNoGc::new(ctx);
Ok(ManuallyRooted::new(&mut ctx, gc_ref.into()))
}
pub(crate) fn from_cloned_gc_ref(
store: &mut AutoAssertNoGc<'_>,
gc_ref: VMGcRef,
) -> Rooted<Self> {
assert!(
gc_ref.is_extern_ref(),
"GC reference {gc_ref:#p} is not an externref"
);
Rooted::new(store, gc_ref)
}
pub fn data<'a, T>(
&self,
store: impl Into<StoreContext<'a, T>>,
) -> Result<&'a (dyn Any + Send + Sync)>
where
T: 'a,
{
let store = store.into().0;
let gc_ref = self.inner.unchecked_try_gc_ref(&store)?;
let externref = gc_ref.as_externref_unchecked();
Ok(store.gc_store()?.externref_host_data(externref))
}
pub fn data_mut<'a, T>(
&self,
store: impl Into<StoreContextMut<'a, T>>,
) -> Result<&'a mut (dyn Any + Send + Sync)>
where
T: 'a,
{
let store = store.into().0;
let gc_ref = self.inner.unchecked_try_gc_ref(store)?.unchecked_copy();
let externref = gc_ref.as_externref_unchecked();
Ok(store.gc_store_mut()?.externref_host_data_mut(externref))
}
pub unsafe fn from_raw(mut store: impl AsContextMut, raw: u32) -> Option<Rooted<ExternRef>> {
let mut store = AutoAssertNoGc::new(store.as_context_mut().0);
let gc_ref = VMGcRef::from_raw_u32(raw)?;
let gc_ref = store.unwrap_gc_store_mut().clone_gc_ref(&gc_ref);
Some(Self::from_cloned_gc_ref(&mut store, gc_ref))
}
pub unsafe fn to_raw(&self, mut store: impl AsContextMut) -> Result<u32> {
let mut store = AutoAssertNoGc::new(store.as_context_mut().0);
let gc_ref = self.inner.try_clone_gc_ref(&mut store)?;
let raw = gc_ref.as_raw_u32();
store.unwrap_gc_store_mut().expose_gc_ref_to_wasm(gc_ref);
Ok(raw)
}
}
unsafe impl WasmTy for Rooted<ExternRef> {
#[inline]
fn valtype() -> ValType {
ValType::Ref(RefType::new(false, HeapType::Extern))
}
#[inline]
fn compatible_with_store(&self, store: &StoreOpaque) -> bool {
self.comes_from_same_store(store)
}
#[inline]
fn dynamic_concrete_type_check(&self, _: &StoreOpaque, _: bool, _: &HeapType) -> Result<()> {
unreachable!()
}
fn store(self, store: &mut AutoAssertNoGc<'_>, ptr: &mut MaybeUninit<ValRaw>) -> Result<()> {
let gc_ref = self.inner.try_clone_gc_ref(store)?;
let r64 = gc_ref.as_r64();
store.gc_store_mut()?.expose_gc_ref_to_wasm(gc_ref);
debug_assert_ne!(r64, 0);
let externref = u32::try_from(r64).unwrap();
ptr.write(ValRaw::externref(externref));
Ok(())
}
unsafe fn load(store: &mut AutoAssertNoGc<'_>, ptr: &ValRaw) -> Self {
let raw = ptr.get_externref();
debug_assert_ne!(raw, 0);
let gc_ref = VMGcRef::from_r64(raw.into())
.expect("valid r64")
.expect("non-null");
let gc_ref = store.unwrap_gc_store_mut().clone_gc_ref(&gc_ref);
ExternRef::from_cloned_gc_ref(store, gc_ref)
}
}
unsafe impl WasmTy for Option<Rooted<ExternRef>> {
#[inline]
fn valtype() -> ValType {
ValType::EXTERNREF
}
#[inline]
fn compatible_with_store(&self, store: &StoreOpaque) -> bool {
self.map_or(true, |x| x.comes_from_same_store(store))
}
#[inline]
fn dynamic_concrete_type_check(&self, _: &StoreOpaque, _: bool, _: &HeapType) -> Result<()> {
unreachable!()
}
#[inline]
fn is_vmgcref_and_points_to_object(&self) -> bool {
self.is_some()
}
fn store(self, store: &mut AutoAssertNoGc<'_>, ptr: &mut MaybeUninit<ValRaw>) -> Result<()> {
match self {
Some(r) => r.store(store, ptr),
None => {
ptr.write(ValRaw::externref(0));
Ok(())
}
}
}
unsafe fn load(store: &mut AutoAssertNoGc<'_>, ptr: &ValRaw) -> Self {
let gc_ref = VMGcRef::from_r64(ptr.get_externref().into()).expect("valid r64")?;
let gc_ref = store.unwrap_gc_store_mut().clone_gc_ref(&gc_ref);
Some(ExternRef::from_cloned_gc_ref(store, gc_ref))
}
}
unsafe impl WasmTy for ManuallyRooted<ExternRef> {
#[inline]
fn valtype() -> ValType {
ValType::Ref(RefType::new(false, HeapType::Extern))
}
#[inline]
fn compatible_with_store(&self, store: &StoreOpaque) -> bool {
self.comes_from_same_store(store)
}
#[inline]
fn dynamic_concrete_type_check(&self, _: &StoreOpaque, _: bool, _: &HeapType) -> Result<()> {
unreachable!()
}
#[inline]
fn is_vmgcref_and_points_to_object(&self) -> bool {
true
}
fn store(self, store: &mut AutoAssertNoGc<'_>, ptr: &mut MaybeUninit<ValRaw>) -> Result<()> {
let gc_ref = self.inner.try_clone_gc_ref(store)?;
let r64 = gc_ref.as_r64();
store.gc_store_mut()?.expose_gc_ref_to_wasm(gc_ref);
debug_assert_ne!(r64, 0);
let externref = u32::try_from(r64).unwrap();
ptr.write(ValRaw::externref(externref));
Ok(())
}
unsafe fn load(store: &mut AutoAssertNoGc<'_>, ptr: &ValRaw) -> Self {
let raw = ptr.get_externref();
debug_assert_ne!(raw, 0);
let gc_ref = VMGcRef::from_r64(raw.into())
.expect("valid r64")
.expect("non-null");
let gc_ref = store.unwrap_gc_store_mut().clone_gc_ref(&gc_ref);
RootSet::with_lifo_scope(store, |store| {
let rooted = ExternRef::from_cloned_gc_ref(store, gc_ref);
rooted
._to_manually_rooted(store)
.expect("rooted is in scope")
})
}
}
unsafe impl WasmTy for Option<ManuallyRooted<ExternRef>> {
#[inline]
fn valtype() -> ValType {
ValType::EXTERNREF
}
#[inline]
fn compatible_with_store(&self, store: &StoreOpaque) -> bool {
self.as_ref()
.map_or(true, |x| x.comes_from_same_store(store))
}
#[inline]
fn dynamic_concrete_type_check(&self, _: &StoreOpaque, _: bool, _: &HeapType) -> Result<()> {
unreachable!()
}
#[inline]
fn is_vmgcref_and_points_to_object(&self) -> bool {
self.is_some()
}
fn store(self, store: &mut AutoAssertNoGc<'_>, ptr: &mut MaybeUninit<ValRaw>) -> Result<()> {
match self {
Some(r) => r.store(store, ptr),
None => {
ptr.write(ValRaw::externref(0));
Ok(())
}
}
}
unsafe fn load(store: &mut AutoAssertNoGc<'_>, ptr: &ValRaw) -> Self {
let raw = ptr.get_externref();
debug_assert_ne!(raw, 0);
let gc_ref = VMGcRef::from_r64(raw.into()).expect("valid r64")?;
let gc_ref = store.unwrap_gc_store_mut().clone_gc_ref(&gc_ref);
RootSet::with_lifo_scope(store, |store| {
let rooted = ExternRef::from_cloned_gc_ref(store, gc_ref);
Some(
rooted
._to_manually_rooted(store)
.expect("rooted is in scope"),
)
})
}
}