use super::{AnyRef, RootedGcRefImpl};
use crate::prelude::*;
use crate::runtime::vm::{self, VMGcRef};
use crate::store::Asyncness;
#[cfg(feature = "async")]
use crate::vm::VMStore;
use crate::{
AsContextMut, GcHeapOutOfMemory, GcRefImpl, GcRootIndex, HeapType, OwnedRooted, RefType,
Result, Rooted, StoreContext, StoreContextMut, ValRaw, ValType, WasmTy,
store::{AutoAssertNoGc, StoreOpaque, StoreResourceLimiter},
};
use core::any::Any;
use core::mem;
use core::mem::MaybeUninit;
#[derive(Debug, Clone)]
#[repr(transparent)]
pub struct ExternRef {
pub(crate) inner: GcRootIndex,
}
unsafe impl GcRefImpl for ExternRef {
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 store: impl AsContextMut, value: T) -> Result<Rooted<ExternRef>>
where
T: 'static + Any + Send + Sync,
{
let (mut limiter, store) = store
.as_context_mut()
.0
.validate_sync_resource_limiter_and_store_opaque()?;
vm::assert_ready(Self::_new_async(
store,
limiter.as_mut(),
value,
Asyncness::No,
))
}
#[cfg(feature = "async")]
pub async fn new_async<T>(mut store: impl AsContextMut, value: T) -> Result<Rooted<ExternRef>>
where
T: 'static + Any + Send + Sync,
{
let (mut limiter, store) = store.as_context_mut().0.resource_limiter_and_store_opaque();
Self::_new_async(store, limiter.as_mut(), value, Asyncness::Yes).await
}
pub(crate) async fn _new_async<T>(
store: &mut StoreOpaque,
limiter: Option<&mut StoreResourceLimiter<'_>>,
value: T,
asyncness: Asyncness,
) -> Result<Rooted<ExternRef>>
where
T: 'static + Any + Send + Sync,
{
let value: Box<dyn Any + Send + Sync> = Box::new(value);
let gc_ref = store
.retry_after_gc_async(limiter, value, asyncness, |store, value| {
store
.require_gc_store_mut()?
.alloc_externref(value)
.context("unrecoverable error when allocating new `externref`")?
.map_err(|(x, n)| GcHeapOutOfMemory::new(x, n).into())
})
.await
.map_err(
|e| match e.downcast::<GcHeapOutOfMemory<Box<dyn Any + Send + Sync>>>() {
Ok(oom) => oom.map_inner(|x| *x.downcast::<T>().unwrap()).into(),
Err(e) => e,
},
)?;
let mut ctx = AutoAssertNoGc::new(store);
Ok(Self::from_cloned_gc_ref(&mut ctx, gc_ref.into()))
}
pub fn convert_any(
mut context: impl AsContextMut,
anyref: Rooted<AnyRef>,
) -> Result<Rooted<ExternRef>> {
let mut store = AutoAssertNoGc::new(context.as_context_mut().0);
Self::_convert_any(&mut store, anyref)
}
pub(crate) fn _convert_any(
store: &mut AutoAssertNoGc<'_>,
anyref: Rooted<AnyRef>,
) -> Result<Rooted<ExternRef>> {
let gc_ref = anyref.try_clone_gc_ref(store)?;
Ok(Self::from_cloned_gc_ref(store, gc_ref))
}
pub(crate) fn from_cloned_gc_ref(
store: &mut AutoAssertNoGc<'_>,
gc_ref: VMGcRef,
) -> Rooted<Self> {
if !gc_ref.is_i31() {
assert!(
gc_ref.is_extern_ref(&*store.unwrap_gc_store().gc_heap)
|| gc_ref.is_any_ref(&*store.unwrap_gc_store().gc_heap),
"GC reference {gc_ref:#p} should be an externref or anyref"
);
}
Rooted::new(store, gc_ref)
}
pub fn data<'a, T>(
&self,
store: impl Into<StoreContext<'a, T>>,
) -> Result<Option<&'a (dyn Any + Send + Sync)>>
where
T: 'static,
{
let store = store.into().0;
let gc_ref = self.inner.try_gc_ref(&store)?;
if gc_ref.is_i31() {
return Ok(None);
}
let gc_store = store.require_gc_store()?;
if let Some(externref) = gc_ref.as_externref(&*gc_store.gc_heap) {
Ok(Some(gc_store.externref_host_data(externref)))
} else {
Ok(None)
}
}
pub fn data_mut<'a, T>(
&self,
store: impl Into<StoreContextMut<'a, T>>,
) -> Result<Option<&'a mut (dyn Any + Send + Sync)>>
where
T: 'static,
{
let store = store.into().0;
let gc_ref = self.inner.try_gc_ref(store)?.unchecked_copy();
if gc_ref.is_i31() {
return Ok(None);
}
let gc_store = store.require_gc_store_mut()?;
if let Some(externref) = gc_ref.as_externref(&*gc_store.gc_heap) {
Ok(Some(gc_store.externref_host_data_mut(externref)))
} else {
Ok(None)
}
}
pub fn from_raw(mut store: impl AsContextMut, raw: u32) -> Option<Rooted<ExternRef>> {
let mut store = AutoAssertNoGc::new(store.as_context_mut().0);
Self::_from_raw(&mut store, raw)
}
pub(crate) fn _from_raw(store: &mut AutoAssertNoGc, raw: u32) -> Option<Rooted<ExternRef>> {
let gc_ref = VMGcRef::from_raw_u32(raw)?;
let gc_ref = store.clone_gc_ref(&gc_ref);
Some(Self::from_cloned_gc_ref(store, gc_ref))
}
pub fn to_raw(&self, mut store: impl AsContextMut) -> Result<u32> {
let mut store = AutoAssertNoGc::new(store.as_context_mut().0);
self._to_raw(&mut store)
}
pub(crate) fn _to_raw(&self, store: &mut AutoAssertNoGc) -> Result<u32> {
let gc_ref = self.inner.try_clone_gc_ref(store)?;
let raw = store.unwrap_gc_store_mut().expose_gc_ref_to_wasm(gc_ref);
Ok(raw.get())
}
}
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<()> {
self.wasm_ty_store(store, ptr, ValRaw::externref)
}
unsafe fn load(store: &mut AutoAssertNoGc<'_>, ptr: &ValRaw) -> Self {
Self::wasm_ty_load(store, ptr.get_externref(), ExternRef::from_cloned_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<()> {
<Rooted<ExternRef>>::wasm_ty_option_store(self, store, ptr, ValRaw::externref)
}
unsafe fn load(store: &mut AutoAssertNoGc<'_>, ptr: &ValRaw) -> Self {
<Rooted<ExternRef>>::wasm_ty_option_load(
store,
ptr.get_externref(),
ExternRef::from_cloned_gc_ref,
)
}
}
unsafe impl WasmTy for OwnedRooted<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<()> {
self.wasm_ty_store(store, ptr, ValRaw::externref)
}
unsafe fn load(store: &mut AutoAssertNoGc<'_>, ptr: &ValRaw) -> Self {
Self::wasm_ty_load(store, ptr.get_externref(), ExternRef::from_cloned_gc_ref)
}
}
unsafe impl WasmTy for Option<OwnedRooted<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<()> {
<OwnedRooted<ExternRef>>::wasm_ty_option_store(self, store, ptr, ValRaw::externref)
}
unsafe fn load(store: &mut AutoAssertNoGc<'_>, ptr: &ValRaw) -> Self {
<OwnedRooted<ExternRef>>::wasm_ty_option_load(
store,
ptr.get_externref(),
ExternRef::from_cloned_gc_ref,
)
}
}