diff --git a/crates/wasmtime/src/runtime/func.rs b/crates/wasmtime/src/runtime/func.rs index e5c0092a19e..fa7a0ecd5b4 100644 --- a/crates/wasmtime/src/runtime/func.rs +++ b/crates/wasmtime/src/runtime/func.rs @@ -601,18 +601,23 @@ impl Func { /// | `Func` | `(ref func)` | /// | `Option` | `nullfuncref` aka `(ref null nofunc)` | /// | `NoFunc` | `(ref nofunc)` | - /// | `Option` | `externref` aka `(ref null extern)` | - /// | `ExternRef` | `(ref extern)` | + /// | `Option>` | `externref` aka `(ref null extern)` | + /// | `Rooted` | `(ref extern)` | /// | `Option` | `nullexternref` aka `(ref null noextern)` | /// | `NoExtern` | `(ref noextern)` | - /// | `Option` | `anyref` aka `(ref null any)` | - /// | `AnyRef` | `(ref any)` | + /// | `Option>` | `anyref` aka `(ref null any)` | + /// | `Rooted` | `(ref any)` | + /// | `Option>` | `eqref` aka `(ref null eq)` | + /// | `Rooted` | `(ref eq)` | /// | `Option` | `i31ref` aka `(ref null i31)` | /// | `I31` | `(ref i31)` | - /// | `Option` | `(ref null struct)` | - /// | `StructRef` | `(ref struct)` | - /// | `Option` | `(ref null array)` | - /// | `ArrayRef` | `(ref array)` | + /// | `Option>` | `(ref null struct)` | + /// | `Rooted` | `(ref struct)` | + /// | `Option>` | `(ref null array)` | + /// | `Rooted` | `(ref array)` | + /// + /// Note that anywhere a `Rooted` appears, a `ManuallyRooted` may also + /// be used. /// /// Any of the Rust types can be returned from the closure as well, in /// addition to some extra types @@ -1425,27 +1430,30 @@ impl Func { /// | `i64` | `i64` or `u64` | /// | `f32` | `f32` | /// | `f64` | `f64` | - /// | `externref` aka `(ref null extern)` | `Option` | - /// | `(ref extern)` | `ExternRef` | - /// | `(ref noextern)` | `NoExtern` | + /// | `externref` aka `(ref null extern)` | `Option>` | + /// | `(ref extern)` | `Rooted` | /// | `nullexternref` aka `(ref null noextern)` | `Option` | - /// | `anyref` aka `(ref null any)` | `Option` | - /// | `(ref any)` | `AnyRef` | + /// | `(ref noextern)` | `NoExtern` | + /// | `anyref` aka `(ref null any)` | `Option>` | + /// | `(ref any)` | `Rooted` | + /// | `eqref` aka `(ref null eq)` | `Option>` | + /// | `(ref eq)` | `Rooted` | /// | `i31ref` aka `(ref null i31)` | `Option` | /// | `(ref i31)` | `I31` | - /// | `structref` aka `(ref null struct)` | `Option` | - /// | `(ref struct)` | `Struct` | - /// | `arrayref` aka `(ref null array)` | `Option` | - /// | `(ref array)` | `Array` | + /// | `structref` aka `(ref null struct)` | `Option>` | + /// | `(ref struct)` | `Rooted` | + /// | `arrayref` aka `(ref null array)` | `Option>` | + /// | `(ref array)` | `Rooted` | /// | `funcref` aka `(ref null func)` | `Option` | /// | `(ref func)` | `Func` | /// | `(ref null )` | `Option` | /// | `(ref )` | `Func` | /// | `nullfuncref` aka `(ref null nofunc)` | `Option` | /// | `(ref nofunc)` | `NoFunc` | - /// | `v128` | `V128` on `x86-64` and `aarch64` only | + /// | `v128` | `V128` | /// - /// (Note that this mapping is the same as that of [`Func::wrap`]). + /// (Note that this mapping is the same as that of [`Func::wrap`], and that + /// anywhere a `Rooted` appears, a `ManuallyRooted` may also appear). /// /// Note that once the [`TypedFunc`] return value is acquired you'll use either /// [`TypedFunc::call`] or [`TypedFunc::call_async`] as necessary to actually invoke diff --git a/crates/wasmtime/src/runtime/gc/disabled.rs b/crates/wasmtime/src/runtime/gc/disabled.rs index 9b785043e0b..86c2c0ead12 100644 --- a/crates/wasmtime/src/runtime/gc/disabled.rs +++ b/crates/wasmtime/src/runtime/gc/disabled.rs @@ -10,6 +10,7 @@ mod anyref; mod arrayref; +mod eqref; mod externref; mod i31; mod rooting; @@ -17,6 +18,7 @@ mod structref; pub use anyref::*; pub use arrayref::*; +pub use eqref::*; pub use externref::*; pub use i31::*; pub use rooting::*; diff --git a/crates/wasmtime/src/runtime/gc/disabled/anyref.rs b/crates/wasmtime/src/runtime/gc/disabled/anyref.rs index 8357516a0e8..097fc96399d 100644 --- a/crates/wasmtime/src/runtime/gc/disabled/anyref.rs +++ b/crates/wasmtime/src/runtime/gc/disabled/anyref.rs @@ -1,7 +1,7 @@ use crate::runtime::vm::VMGcRef; use crate::{ store::{AutoAssertNoGc, StoreOpaque}, - ArrayRef, AsContext, AsContextMut, GcRefImpl, HeapType, ManuallyRooted, Result, Rooted, + ArrayRef, AsContext, AsContextMut, EqRef, GcRefImpl, HeapType, ManuallyRooted, Result, Rooted, StructRef, I31, }; @@ -9,6 +9,20 @@ use crate::{ /// was not enabled. pub enum AnyRef {} +impl From> for Rooted { + #[inline] + fn from(s: Rooted) -> Self { + match s.inner {} + } +} + +impl From> for ManuallyRooted { + #[inline] + fn from(s: ManuallyRooted) -> Self { + match s.inner {} + } +} + impl From> for Rooted { #[inline] fn from(s: Rooted) -> Self { @@ -73,6 +87,22 @@ impl AnyRef { match *self {} } + pub fn is_eqref(&self, _store: impl AsContext) -> Result { + match *self {} + } + + pub(crate) fn _is_eqref(&self, _store: &StoreOpaque) -> Result { + match *self {} + } + + pub fn as_eqref(&self, _store: impl AsContext) -> Result> { + match *self {} + } + + pub fn unwrap_eqref(&self, _store: impl AsContext) -> Result { + match *self {} + } + pub fn is_i31(&self, _store: impl AsContext) -> Result { match *self {} } diff --git a/crates/wasmtime/src/runtime/gc/disabled/eqref.rs b/crates/wasmtime/src/runtime/gc/disabled/eqref.rs new file mode 100644 index 00000000000..460653402bb --- /dev/null +++ b/crates/wasmtime/src/runtime/gc/disabled/eqref.rs @@ -0,0 +1,117 @@ +use crate::runtime::vm::VMGcRef; +use crate::{ + store::{AutoAssertNoGc, StoreOpaque}, + ArrayRef, AsContext, AsContextMut, GcRefImpl, HeapType, ManuallyRooted, Result, Rooted, + StructRef, I31, +}; + +/// Support for `eqref` disabled at compile time because the `gc` cargo feature +/// was not enabled. +pub enum EqRef {} + +impl From> for Rooted { + #[inline] + fn from(s: Rooted) -> Self { + match s.inner {} + } +} + +impl From> for ManuallyRooted { + #[inline] + fn from(s: ManuallyRooted) -> Self { + match s.inner {} + } +} + +impl From> for Rooted { + #[inline] + fn from(s: Rooted) -> Self { + match s.inner {} + } +} + +impl From> for ManuallyRooted { + #[inline] + fn from(s: ManuallyRooted) -> Self { + match s.inner {} + } +} + +impl GcRefImpl for EqRef {} + +impl EqRef { + pub(crate) fn from_cloned_gc_ref( + _store: &mut AutoAssertNoGc<'_>, + _gc_ref: VMGcRef, + ) -> Rooted { + unreachable!() + } + + pub fn ty(&self, _store: impl AsContext) -> Result { + match *self {} + } + + pub(crate) fn _ty(&self, _store: &StoreOpaque) -> Result { + match *self {} + } + + pub fn matches_ty(&self, _store: impl AsContext, _ty: &HeapType) -> Result { + match *self {} + } + + pub fn is_i31(&self, _store: impl AsContext) -> Result { + match *self {} + } + + pub(crate) fn _is_i31(&self, _store: &StoreOpaque) -> Result { + match *self {} + } + + pub fn as_i31(&self, _store: impl AsContext) -> Result> { + match *self {} + } + + pub fn unwrap_i31(&self, _store: impl AsContext) -> Result { + match *self {} + } + + pub fn is_struct(&self, _store: impl AsContext) -> Result { + match *self {} + } + + pub(crate) fn _is_struct(&self, _store: &StoreOpaque) -> Result { + match *self {} + } + + pub fn as_struct(&self, _store: impl AsContext) -> Result> { + match *self {} + } + + pub(crate) fn _as_struct(&self, _store: &StoreOpaque) -> Result> { + match *self {} + } + + pub fn unwrap_struct(&self, _store: impl AsContext) -> Result { + match *self {} + } + + pub fn is_array(&self, _store: impl AsContext) -> Result { + match *self {} + } + + pub(crate) fn _is_array(&self, _store: &StoreOpaque) -> Result { + match *self {} + } + + pub fn as_array(&self, _store: impl AsContext) -> Result> { + match *self {} + } + + pub(crate) fn _as_array(&self, _store: &StoreOpaque) -> Result> { + match *self {} + } + + pub fn unwrap_array(&self, _store: impl AsContext) -> Result { + match *self {} + } +} diff --git a/crates/wasmtime/src/runtime/gc/enabled.rs b/crates/wasmtime/src/runtime/gc/enabled.rs index 7e1f70ddb48..8694a0387db 100644 --- a/crates/wasmtime/src/runtime/gc/enabled.rs +++ b/crates/wasmtime/src/runtime/gc/enabled.rs @@ -3,6 +3,7 @@ mod anyref; mod arrayref; +mod eqref; mod externref; mod i31; mod rooting; @@ -10,6 +11,7 @@ mod structref; pub use anyref::*; pub use arrayref::*; +pub use eqref::*; pub use externref::*; pub use i31::*; pub use rooting::*; diff --git a/crates/wasmtime/src/runtime/gc/enabled/anyref.rs b/crates/wasmtime/src/runtime/gc/enabled/anyref.rs index 520aff82c01..cfa75af9f4e 100644 --- a/crates/wasmtime/src/runtime/gc/enabled/anyref.rs +++ b/crates/wasmtime/src/runtime/gc/enabled/anyref.rs @@ -4,8 +4,8 @@ use crate::prelude::*; use crate::runtime::vm::VMGcRef; use crate::{ store::{AutoAssertNoGc, StoreOpaque}, - ArrayRef, ArrayType, AsContext, AsContextMut, GcRefImpl, GcRootIndex, HeapType, ManuallyRooted, - RefType, Result, Rooted, StructRef, StructType, ValRaw, ValType, WasmTy, I31, + ArrayRef, ArrayType, AsContext, AsContextMut, EqRef, GcRefImpl, GcRootIndex, HeapType, + ManuallyRooted, RefType, Result, Rooted, StructRef, StructType, ValRaw, ValType, WasmTy, I31, }; use core::mem; use core::mem::MaybeUninit; @@ -95,6 +95,20 @@ pub struct AnyRef { pub(super) inner: GcRootIndex, } +impl From> for Rooted { + #[inline] + fn from(e: Rooted) -> Self { + e.to_anyref() + } +} + +impl From> for ManuallyRooted { + #[inline] + fn from(e: ManuallyRooted) -> Self { + e.to_anyref() + } +} + impl From> for Rooted { #[inline] fn from(s: Rooted) -> Self { @@ -326,6 +340,71 @@ impl AnyRef { } } + /// Is this `anyref` an `eqref`? + /// + /// # Errors + /// + /// Return an error if this reference has been unrooted. + /// + /// # Panics + /// + /// Panics if this reference is associated with a different store. + pub fn is_eqref(&self, store: impl AsContext) -> Result { + self._is_eqref(store.as_context().0) + } + + pub(crate) fn _is_eqref(&self, store: &StoreOpaque) -> Result { + assert!(self.comes_from_same_store(store)); + let gc_ref = self.inner.try_gc_ref(store)?; + Ok(gc_ref.is_i31() || store.gc_store()?.kind(gc_ref).matches(VMGcKind::EqRef)) + } + + /// Downcast this `anyref` to an `eqref`. + /// + /// If this `anyref` is an `eqref`, then `Some(_)` is returned. + /// + /// If this `anyref` is not an `eqref`, then `None` is returned. + /// + /// # Errors + /// + /// Return an error if this reference has been unrooted. + /// + /// # Panics + /// + /// Panics if this reference is associated with a different store. + pub fn as_eqref(&self, store: impl AsContext) -> Result>> { + self._as_eqref(store.as_context().0) + } + + pub(crate) fn _as_eqref(&self, store: &StoreOpaque) -> Result>> { + if self._is_eqref(store)? { + Ok(Some(Rooted::from_gc_root_index(self.inner))) + } else { + Ok(None) + } + } + + /// Downcast this `anyref` to an `eqref`, panicking if this `anyref` is not + /// an `eqref`. + /// + /// # Errors + /// + /// Return an error if this reference has been unrooted. + /// + /// # Panics + /// + /// Panics if this reference is associated with a different store, or if + /// this `anyref` is not an `eqref`. + pub fn unwrap_eqref(&self, store: impl AsContext) -> Result> { + self._unwrap_eqref(store.as_context().0) + } + + pub(crate) fn _unwrap_eqref(&self, store: &StoreOpaque) -> Result> { + Ok(self + ._as_eqref(store)? + .expect("AnyRef::unwrap_eqref on non-eqref")) + } + /// Is this `anyref` an `i31`? /// /// # Errors diff --git a/crates/wasmtime/src/runtime/gc/enabled/arrayref.rs b/crates/wasmtime/src/runtime/gc/enabled/arrayref.rs index fb23cfaef38..bde9094273f 100644 --- a/crates/wasmtime/src/runtime/gc/enabled/arrayref.rs +++ b/crates/wasmtime/src/runtime/gc/enabled/arrayref.rs @@ -6,7 +6,7 @@ use crate::vm::{VMArrayRef, VMGcHeader}; use crate::{ prelude::*, store::{AutoAssertNoGc, StoreContextMut, StoreOpaque}, - ArrayType, AsContext, AsContextMut, GcHeapOutOfMemory, GcRefImpl, GcRootIndex, HeapType, + ArrayType, AsContext, AsContextMut, EqRef, GcHeapOutOfMemory, GcRefImpl, GcRootIndex, HeapType, ManuallyRooted, RefType, Rooted, Val, ValRaw, ValType, WasmTy, }; use crate::{AnyRef, FieldType}; @@ -206,6 +206,12 @@ impl Rooted { pub fn to_anyref(self) -> Rooted { self.unchecked_cast() } + + /// Upcast this `arrayref` into an `eqref`. + #[inline] + pub fn to_eqref(self) -> Rooted { + self.unchecked_cast() + } } impl ManuallyRooted { @@ -214,6 +220,12 @@ impl ManuallyRooted { pub fn to_anyref(self) -> ManuallyRooted { self.unchecked_cast() } + + /// Upcast this `arrayref` into an `eqref`. + #[inline] + pub fn to_eqref(self) -> ManuallyRooted { + self.unchecked_cast() + } } impl ArrayRef { diff --git a/crates/wasmtime/src/runtime/gc/enabled/eqref.rs b/crates/wasmtime/src/runtime/gc/enabled/eqref.rs new file mode 100644 index 00000000000..f24e74aa4a0 --- /dev/null +++ b/crates/wasmtime/src/runtime/gc/enabled/eqref.rs @@ -0,0 +1,589 @@ +//! Working with GC `eqref`s. + +use crate::{ + prelude::*, + runtime::vm::VMGcRef, + store::{AutoAssertNoGc, StoreOpaque}, + AnyRef, ArrayRef, ArrayType, AsContext, GcRefImpl, GcRootIndex, HeapType, ManuallyRooted, + RefType, Rooted, StructRef, StructType, ValRaw, ValType, WasmTy, I31, +}; +use core::mem::{self, MaybeUninit}; +use wasmtime_environ::VMGcKind; + +/// A reference to a GC-managed object that can be tested for equality. +/// +/// The WebAssembly reference types that can be tested for equality, and +/// therefore are `eqref`s, include `structref`s, `arrayref`s, and +/// `i31ref`s. `funcref`s, `exnref`s, and `externref`s cannot be tested for +/// equality by Wasm, and are not `eqref`s. +/// +/// Use the [`Rooted::ref_eq`][Rooted::ref_eq] method to actually test two +/// references for equality. +/// +/// Like all WebAssembly references, these are opaque to and unforgeable by +/// Wasm: they cannot be faked and Wasm cannot, for example, cast the integer +/// `0x12345678` into a reference, pretend it is a valid `eqref`, and trick the +/// host into dereferencing it and segfaulting or worse. +/// +/// Note that you can also use `Rooted` and `ManuallyRooted` as a +/// type parameter with [`Func::typed`][crate::Func::typed]- and +/// [`Func::wrap`][crate::Func::wrap]-style APIs. +/// +/// # Example +/// +/// ``` +/// use wasmtime::*; +/// +/// # fn foo() -> Result<()> { +/// let mut config = Config::new(); +/// config.wasm_function_references(true); +/// config.wasm_gc(true); +/// +/// let engine = Engine::new(&config)?; +/// let mut store = Store::new(&engine, ()); +/// +/// // Define a module that exports a function that returns a new `eqref` each +/// // time it is invoked. +/// let module = Module::new(&engine, r#" +/// (module +/// (global $g (mut i32) (i32.const 0)) +/// (func (export "new-eqref") (result (ref eq)) +/// ;; Increment $g. +/// global.get $g +/// i32.const 1 +/// i32.add +/// global.set $g +/// +/// ;; Create an `i31ref`, which is a kind of `eqref`, from $g. +/// global.get $g +/// ref.i31 +/// ) +/// ) +/// "#)?; +/// +/// // Instantiate the module. +/// let instance = Instance::new(&mut store, &module, &[])?; +/// +/// // Get the exported function. +/// let new_eqref = instance.get_typed_func::<(), Rooted>(&mut store, "new-eqref")?; +/// +/// { +/// let mut scope = RootScope::new(&mut store); +/// +/// // Call the function to get an `eqref`. +/// let x = new_eqref.call(&mut scope, ())?; +/// +/// // `x` is equal to itself! +/// assert!(Rooted::ref_eq(&scope, &x, &x)?); +/// +/// // Call the function again to get a new, different `eqref`. +/// let y = new_eqref.call(&mut scope, ())?; +/// +/// // `x` is not equal to `y`! +/// assert!(!Rooted::ref_eq(&scope, &x, &y)?); +/// } +/// # Ok(()) +/// # } +/// # foo().unwrap(); +/// ``` +#[derive(Debug)] +#[repr(transparent)] +pub struct EqRef { + pub(super) inner: GcRootIndex, +} + +impl From> for Rooted { + #[inline] + fn from(s: Rooted) -> Self { + s.to_eqref() + } +} + +impl From> for ManuallyRooted { + #[inline] + fn from(s: ManuallyRooted) -> Self { + s.to_eqref() + } +} + +impl From> for Rooted { + #[inline] + fn from(s: Rooted) -> Self { + s.to_eqref() + } +} + +impl From> for ManuallyRooted { + #[inline] + fn from(s: ManuallyRooted) -> Self { + s.to_eqref() + } +} + +unsafe impl GcRefImpl for EqRef { + #[allow(private_interfaces)] + fn transmute_ref(index: &GcRootIndex) -> &Self { + // Safety: `EqRef` is a newtype of a `GcRootIndex`. + let me: &Self = unsafe { mem::transmute(index) }; + + // Assert we really are just a newtype of a `GcRootIndex`. + assert!(matches!( + me, + Self { + inner: GcRootIndex { .. }, + } + )); + + me + } +} + +impl Rooted { + /// Upcast this `eqref` into an `anyref`. + #[inline] + pub fn to_anyref(self) -> Rooted { + self.unchecked_cast() + } +} + +impl ManuallyRooted { + /// Upcast this `eqref` into an `anyref`. + #[inline] + pub fn to_anyref(self) -> ManuallyRooted { + self.unchecked_cast() + } +} + +impl EqRef { + /// Create a new `Rooted` from the given GC reference. + /// + /// `gc_ref` should point to a valid `anyref` and should belong to the + /// store's GC heap. Failure to uphold these invariants is memory safe but + /// will lead to general incorrectness such as panics or wrong results. + pub(crate) fn from_cloned_gc_ref( + store: &mut AutoAssertNoGc<'_>, + gc_ref: VMGcRef, + ) -> Rooted { + debug_assert!( + gc_ref.is_i31() + || store + .unwrap_gc_store() + .header(&gc_ref) + .kind() + .matches(VMGcKind::EqRef) + ); + Rooted::new(store, gc_ref) + } + + #[inline] + pub(crate) fn comes_from_same_store(&self, store: &StoreOpaque) -> bool { + self.inner.comes_from_same_store(store) + } + + /// Get the type of this reference. + /// + /// # Errors + /// + /// Return an error if this reference has been unrooted. + /// + /// # Panics + /// + /// Panics if this reference is associated with a different store. + pub fn ty(&self, store: impl AsContext) -> Result { + self._ty(store.as_context().0) + } + + pub(crate) fn _ty(&self, store: &StoreOpaque) -> Result { + let gc_ref = self.inner.try_gc_ref(store)?; + if gc_ref.is_i31() { + return Ok(HeapType::I31); + } + + let header = store.gc_store()?.header(gc_ref); + + if header.kind().matches(VMGcKind::StructRef) { + return Ok(HeapType::ConcreteStruct( + StructType::from_shared_type_index(store.engine(), header.ty().unwrap()), + )); + } + + if header.kind().matches(VMGcKind::ArrayRef) { + return Ok(HeapType::ConcreteArray(ArrayType::from_shared_type_index( + store.engine(), + header.ty().unwrap(), + ))); + } + + unreachable!("no other kinds of `eqref`s") + } + + /// Does this `eqref` match the given type? + /// + /// That is, is this object's type a subtype of the given type? + /// + /// # Errors + /// + /// Return an error if this reference has been unrooted. + /// + /// # Panics + /// + /// Panics if this reference is associated with a different store. + pub fn matches_ty(&self, store: impl AsContext, ty: &HeapType) -> Result { + self._matches_ty(store.as_context().0, ty) + } + + pub(crate) fn _matches_ty(&self, store: &StoreOpaque, ty: &HeapType) -> Result { + assert!(self.comes_from_same_store(store)); + Ok(self._ty(store)?.matches(ty)) + } + + pub(crate) fn ensure_matches_ty(&self, store: &StoreOpaque, ty: &HeapType) -> Result<()> { + if !self.comes_from_same_store(store) { + bail!("function used with wrong store"); + } + if self._matches_ty(store, ty)? { + Ok(()) + } else { + let actual_ty = self._ty(store)?; + bail!("type mismatch: expected `(ref {ty})`, found `(ref {actual_ty})`") + } + } + + /// Is this `eqref` an `i31`? + /// + /// # Errors + /// + /// Return an error if this reference has been unrooted. + /// + /// # Panics + /// + /// Panics if this reference is associated with a different store. + pub fn is_i31(&self, store: impl AsContext) -> Result { + self._is_i31(store.as_context().0) + } + + pub(crate) fn _is_i31(&self, store: &StoreOpaque) -> Result { + assert!(self.comes_from_same_store(store)); + let gc_ref = self.inner.try_gc_ref(store)?; + Ok(gc_ref.is_i31()) + } + + /// Downcast this `eqref` to an `i31`. + /// + /// If this `eqref` is an `i31`, then `Some(_)` is returned. + /// + /// If this `eqref` is not an `i31`, then `None` is returned. + /// + /// # Errors + /// + /// Return an error if this reference has been unrooted. + /// + /// # Panics + /// + /// Panics if this reference is associated with a different store. + pub fn as_i31(&self, store: impl AsContext) -> Result> { + self._as_i31(store.as_context().0) + } + + pub(crate) fn _as_i31(&self, store: &StoreOpaque) -> Result> { + assert!(self.comes_from_same_store(store)); + let gc_ref = self.inner.try_gc_ref(store)?; + Ok(gc_ref.as_i31().map(Into::into)) + } + + /// Downcast this `eqref` to an `i31`, panicking if this `eqref` is not an + /// `i31`. + /// + /// # Errors + /// + /// Return an error if this reference has been unrooted. + /// + /// # Panics + /// + /// Panics if this reference is associated with a different store, or if + /// this `eqref` is not an `i31`. + pub fn unwrap_i31(&self, store: impl AsContext) -> Result { + Ok(self.as_i31(store)?.expect("EqRef::unwrap_i31 on non-i31")) + } + + /// Is this `eqref` a `structref`? + /// + /// # Errors + /// + /// Return an error if this reference has been unrooted. + /// + /// # Panics + /// + /// Panics if this reference is associated with a different store. + pub fn is_struct(&self, store: impl AsContext) -> Result { + self._is_struct(store.as_context().0) + } + + pub(crate) fn _is_struct(&self, store: &StoreOpaque) -> Result { + let gc_ref = self.inner.try_gc_ref(store)?; + Ok(!gc_ref.is_i31() && store.gc_store()?.kind(gc_ref).matches(VMGcKind::StructRef)) + } + + /// Downcast this `eqref` to a `structref`. + /// + /// If this `eqref` is a `structref`, then `Some(_)` is returned. + /// + /// If this `eqref` is not a `structref`, then `None` is returned. + /// + /// # Errors + /// + /// Return an error if this reference has been unrooted. + /// + /// # Panics + /// + /// Panics if this reference is associated with a different store. + pub fn as_struct(&self, store: impl AsContext) -> Result>> { + self._as_struct(store.as_context().0) + } + + pub(crate) fn _as_struct(&self, store: &StoreOpaque) -> Result>> { + if self._is_struct(store)? { + Ok(Some(Rooted::from_gc_root_index(self.inner))) + } else { + Ok(None) + } + } + + /// Downcast this `eqref` to a `structref`, panicking if this `eqref` is + /// not a `structref`. + /// + /// # Errors + /// + /// Return an error if this reference has been unrooted. + /// + /// # Panics + /// + /// Panics if this reference is associated with a different store, or if + /// this `eqref` is not a `struct`. + pub fn unwrap_struct(&self, store: impl AsContext) -> Result> { + self._unwrap_struct(store.as_context().0) + } + + pub(crate) fn _unwrap_struct(&self, store: &StoreOpaque) -> Result> { + Ok(self + ._as_struct(store)? + .expect("EqRef::unwrap_struct on non-structref")) + } + + /// Is this `eqref` an `arrayref`? + /// + /// # Errors + /// + /// Return an error if this reference has been unrooted. + /// + /// # Panics + /// + /// Panics if this reference is associated with a different store. + pub fn is_array(&self, store: impl AsContext) -> Result { + self._is_array(store.as_context().0) + } + + pub(crate) fn _is_array(&self, store: &StoreOpaque) -> Result { + let gc_ref = self.inner.try_gc_ref(store)?; + Ok(!gc_ref.is_i31() && store.gc_store()?.kind(gc_ref).matches(VMGcKind::ArrayRef)) + } + + /// Downcast this `eqref` to an `arrayref`. + /// + /// If this `eqref` is an `arrayref`, then `Some(_)` is returned. + /// + /// If this `eqref` is not an `arrayref`, then `None` is returned. + /// + /// # Errors + /// + /// Return an error if this reference has been unrooted. + /// + /// # Panics + /// + /// Panics if this reference is associated with a different store. + pub fn as_array(&self, store: impl AsContext) -> Result>> { + self._as_array(store.as_context().0) + } + + pub(crate) fn _as_array(&self, store: &StoreOpaque) -> Result>> { + if self._is_array(store)? { + Ok(Some(Rooted::from_gc_root_index(self.inner))) + } else { + Ok(None) + } + } + + /// Downcast this `eqref` to an `arrayref`, panicking if this `eqref` is + /// not an `arrayref`. + /// + /// # Errors + /// + /// Return an error if this reference has been unrooted. + /// + /// # Panics + /// + /// Panics if this reference is associated with a different store, or if + /// this `eqref` is not an `array`. + pub fn unwrap_array(&self, store: impl AsContext) -> Result> { + self._unwrap_array(store.as_context().0) + } + + pub(crate) fn _unwrap_array(&self, store: &StoreOpaque) -> Result> { + Ok(self + ._as_array(store)? + .expect("EqRef::unwrap_array on non-arrayref")) + } +} + +unsafe impl WasmTy for Rooted { + #[inline] + fn valtype() -> ValType { + ValType::Ref(RefType::new(false, HeapType::Eq)) + } + + #[inline] + fn compatible_with_store(&self, store: &StoreOpaque) -> bool { + self.comes_from_same_store(store) + } + + #[inline] + fn dynamic_concrete_type_check( + &self, + store: &StoreOpaque, + _nullable: bool, + ty: &HeapType, + ) -> Result<()> { + self.ensure_matches_ty(store, ty) + } + + fn store(self, store: &mut AutoAssertNoGc<'_>, ptr: &mut MaybeUninit) -> Result<()> { + self.wasm_ty_store(store, ptr, ValRaw::anyref) + } + + unsafe fn load(store: &mut AutoAssertNoGc<'_>, ptr: &ValRaw) -> Self { + Self::wasm_ty_load(store, ptr.get_anyref(), EqRef::from_cloned_gc_ref) + } +} + +unsafe impl WasmTy for Option> { + #[inline] + fn valtype() -> ValType { + ValType::EQREF + } + + #[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, + store: &StoreOpaque, + nullable: bool, + ty: &HeapType, + ) -> Result<()> { + match self { + Some(s) => Rooted::::dynamic_concrete_type_check(s, store, nullable, ty), + None => { + ensure!( + nullable, + "expected a non-null reference, but found a null reference" + ); + Ok(()) + } + } + } + + #[inline] + fn is_vmgcref_and_points_to_object(&self) -> bool { + self.is_some() + } + + fn store(self, store: &mut AutoAssertNoGc<'_>, ptr: &mut MaybeUninit) -> Result<()> { + >::wasm_ty_option_store(self, store, ptr, ValRaw::anyref) + } + + unsafe fn load(store: &mut AutoAssertNoGc<'_>, ptr: &ValRaw) -> Self { + >::wasm_ty_option_load(store, ptr.get_anyref(), EqRef::from_cloned_gc_ref) + } +} + +unsafe impl WasmTy for ManuallyRooted { + #[inline] + fn valtype() -> ValType { + ValType::Ref(RefType::new(false, HeapType::Eq)) + } + + #[inline] + fn compatible_with_store(&self, store: &StoreOpaque) -> bool { + self.comes_from_same_store(store) + } + + #[inline] + fn dynamic_concrete_type_check( + &self, + store: &StoreOpaque, + _: bool, + ty: &HeapType, + ) -> Result<()> { + self.ensure_matches_ty(store, ty) + } + + fn store(self, store: &mut AutoAssertNoGc<'_>, ptr: &mut MaybeUninit) -> Result<()> { + self.wasm_ty_store(store, ptr, ValRaw::anyref) + } + + unsafe fn load(store: &mut AutoAssertNoGc<'_>, ptr: &ValRaw) -> Self { + Self::wasm_ty_load(store, ptr.get_anyref(), EqRef::from_cloned_gc_ref) + } +} + +unsafe impl WasmTy for Option> { + #[inline] + fn valtype() -> ValType { + ValType::EQREF + } + + #[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, + store: &StoreOpaque, + nullable: bool, + ty: &HeapType, + ) -> Result<()> { + match self { + Some(s) => ManuallyRooted::::dynamic_concrete_type_check(s, store, nullable, ty), + None => { + ensure!( + nullable, + "expected a non-null reference, but found a null reference" + ); + Ok(()) + } + } + } + + #[inline] + fn is_vmgcref_and_points_to_object(&self) -> bool { + self.is_some() + } + + fn store(self, store: &mut AutoAssertNoGc<'_>, ptr: &mut MaybeUninit) -> Result<()> { + >::wasm_ty_option_store(self, store, ptr, ValRaw::anyref) + } + + unsafe fn load(store: &mut AutoAssertNoGc<'_>, ptr: &ValRaw) -> Self { + >::wasm_ty_option_load( + store, + ptr.get_anyref(), + EqRef::from_cloned_gc_ref, + ) + } +} diff --git a/crates/wasmtime/src/runtime/gc/enabled/structref.rs b/crates/wasmtime/src/runtime/gc/enabled/structref.rs index 1438a0d56f3..a1d82e74ff8 100644 --- a/crates/wasmtime/src/runtime/gc/enabled/structref.rs +++ b/crates/wasmtime/src/runtime/gc/enabled/structref.rs @@ -6,8 +6,8 @@ use crate::vm::{VMGcHeader, VMStructRef}; use crate::{ prelude::*, store::{AutoAssertNoGc, StoreContextMut, StoreOpaque}, - AsContext, AsContextMut, GcHeapOutOfMemory, GcRefImpl, GcRootIndex, HeapType, ManuallyRooted, - RefType, Rooted, StructType, Val, ValRaw, ValType, WasmTy, + AsContext, AsContextMut, EqRef, GcHeapOutOfMemory, GcRefImpl, GcRootIndex, HeapType, + ManuallyRooted, RefType, Rooted, StructType, Val, ValRaw, ValType, WasmTy, }; use crate::{AnyRef, FieldType}; use core::mem::{self, MaybeUninit}; @@ -189,6 +189,12 @@ impl Rooted { pub fn to_anyref(self) -> Rooted { self.unchecked_cast() } + + /// Upcast this `structref` into an `eqref`. + #[inline] + pub fn to_eqref(self) -> Rooted { + self.unchecked_cast() + } } impl ManuallyRooted { @@ -197,6 +203,12 @@ impl ManuallyRooted { pub fn to_anyref(self) -> ManuallyRooted { self.unchecked_cast() } + + /// Upcast this `structref` into an `eqref`. + #[inline] + pub fn to_eqref(self) -> ManuallyRooted { + self.unchecked_cast() + } } impl StructRef { diff --git a/crates/wasmtime/src/runtime/types.rs b/crates/wasmtime/src/runtime/types.rs index 08e7b1938f9..1221296a7b0 100644 --- a/crates/wasmtime/src/runtime/types.rs +++ b/crates/wasmtime/src/runtime/types.rs @@ -139,6 +139,9 @@ impl ValType { /// The `anyref` type, aka `(ref null any)`. pub const ANYREF: Self = ValType::Ref(RefType::ANYREF); + /// The `eqref` type, aka `(ref null eq)`. + pub const EQREF: Self = ValType::Ref(RefType::EQREF); + /// The `i31ref` type, aka `(ref null i31)`. pub const I31REF: Self = ValType::Ref(RefType::I31REF); @@ -417,6 +420,12 @@ impl RefType { heap_type: HeapType::Any, }; + /// The `eqref` type, aka `(ref null eq)`. + pub const EQREF: Self = RefType { + is_nullable: true, + heap_type: HeapType::Eq, + }; + /// The `i31ref` type, aka `(ref null i31)`. pub const I31REF: Self = RefType { is_nullable: true, diff --git a/crates/wasmtime/src/runtime/vm/gc/disabled.rs b/crates/wasmtime/src/runtime/vm/gc/disabled.rs index 4b459e715e8..245631605f0 100644 --- a/crates/wasmtime/src/runtime/vm/gc/disabled.rs +++ b/crates/wasmtime/src/runtime/vm/gc/disabled.rs @@ -29,6 +29,8 @@ unsafe impl GcRuntime for DisabledCollector { pub enum VMExternRef {} +pub enum VMEqRef {} + pub enum VMStructRef {} pub enum VMArrayRef {}