use crate::{
store::{AutoAssertNoGc, StoreOpaque},
AsContextMut, FuncType, GcHeapOutOfMemory, GcRefImpl, GcRootIndex, HeapType, ManuallyRooted,
RefType, Result, RootSet, Rooted, StoreContext, StoreContextMut, ValRaw, ValType, WasmTy,
};
use anyhow::Context;
use std::any::Any;
use std::num::NonZeroU64;
use wasmtime_runtime::VMGcRef;
#[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 { std::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)
.context("unrecoverable error when allocating new `externref`")?
.map_err(|x| GcHeapOutOfMemory::<T>::new(*x.downcast().unwrap()))
.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)
.context("unrecoverable error when allocating new `externref`")?
.map_err(|x| GcHeapOutOfMemory::<T>::new(*x.downcast().unwrap()))
.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> {
type Abi = NonZeroU64;
#[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, _: &FuncType) -> Result<()> {
unreachable!()
}
#[inline]
fn is_non_i31_gc_ref(&self) -> bool {
true
}
#[inline]
unsafe fn abi_from_raw(raw: *mut ValRaw) -> Self::Abi {
let raw = (*raw).get_externref();
debug_assert_ne!(raw, 0);
NonZeroU64::new_unchecked(u64::from(raw))
}
#[inline]
unsafe fn abi_into_raw(abi: Self::Abi, raw: *mut ValRaw) {
let externref = u32::try_from(abi.get()).unwrap();
*raw = ValRaw::externref(externref);
}
#[inline]
fn into_abi(self, store: &mut AutoAssertNoGc<'_>) -> Result<Self::Abi> {
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);
Ok(unsafe { NonZeroU64::new_unchecked(r64) })
}
#[inline]
unsafe fn from_abi(abi: Self::Abi, store: &mut AutoAssertNoGc<'_>) -> Self {
let gc_ref = VMGcRef::from_r64(abi.get())
.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>> {
type Abi = u64;
#[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, _: &FuncType) -> Result<()> {
unreachable!()
}
#[inline]
fn is_non_i31_gc_ref(&self) -> bool {
true
}
#[inline]
unsafe fn abi_from_raw(raw: *mut ValRaw) -> Self::Abi {
let externref = (*raw).get_externref();
u64::from(externref)
}
#[inline]
unsafe fn abi_into_raw(abi: Self::Abi, raw: *mut ValRaw) {
let externref = u32::try_from(abi).unwrap();
*raw = ValRaw::externref(externref);
}
#[inline]
fn into_abi(self, store: &mut AutoAssertNoGc<'_>) -> Result<Self::Abi> {
Ok(if let Some(x) = self {
<Rooted<ExternRef> as WasmTy>::into_abi(x, store)?.get()
} else {
0
})
}
#[inline]
unsafe fn from_abi(abi: Self::Abi, store: &mut AutoAssertNoGc<'_>) -> Self {
let gc_ref = VMGcRef::from_r64(abi).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> {
type Abi = NonZeroU64;
#[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, _: &FuncType) -> Result<()> {
unreachable!()
}
#[inline]
fn is_non_i31_gc_ref(&self) -> bool {
true
}
#[inline]
unsafe fn abi_from_raw(raw: *mut ValRaw) -> Self::Abi {
let externref = (*raw).get_externref();
debug_assert_ne!(externref, 0);
NonZeroU64::new_unchecked(u64::from(externref))
}
#[inline]
unsafe fn abi_into_raw(abi: Self::Abi, raw: *mut ValRaw) {
let externref = u32::try_from(abi.get()).unwrap();
*raw = ValRaw::externref(externref);
}
#[inline]
fn into_abi(self, store: &mut AutoAssertNoGc<'_>) -> Result<Self::Abi> {
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);
Ok(unsafe { NonZeroU64::new_unchecked(r64) })
}
#[inline]
unsafe fn from_abi(abi: Self::Abi, store: &mut AutoAssertNoGc<'_>) -> Self {
let gc_ref = VMGcRef::from_r64(abi.get())
.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>> {
type Abi = u64;
#[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, _: &FuncType) -> Result<()> {
unreachable!()
}
#[inline]
fn is_non_i31_gc_ref(&self) -> bool {
true
}
#[inline]
unsafe fn abi_from_raw(raw: *mut ValRaw) -> Self::Abi {
let externref = (*raw).get_externref();
u64::from(externref)
}
#[inline]
unsafe fn abi_into_raw(abi: Self::Abi, raw: *mut ValRaw) {
let externref = u32::try_from(abi).unwrap();
*raw = ValRaw::externref(externref);
}
#[inline]
fn into_abi(self, store: &mut AutoAssertNoGc<'_>) -> Result<Self::Abi> {
Ok(if let Some(x) = self {
<ManuallyRooted<ExternRef> as WasmTy>::into_abi(x, store)?.get()
} else {
0
})
}
#[inline]
unsafe fn from_abi(abi: Self::Abi, store: &mut AutoAssertNoGc<'_>) -> Self {
let gc_ref = VMGcRef::from_r64(abi).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"),
)
})
}
}