diff --git a/crates/c-api/src/ref.rs b/crates/c-api/src/ref.rs index 44f14c3f9945..e3d8d6f160ff 100644 --- a/crates/c-api/src/ref.rs +++ b/crates/c-api/src/ref.rs @@ -346,7 +346,7 @@ pub unsafe extern "C" fn wasmtime_externref_data( externref .and_then(|e| e.as_wasmtime()) .and_then(|e| { - let data = e.data(cx).ok()?; + let data = e.data(cx).ok()??; Some(data.downcast_ref::().unwrap().data) }) .unwrap_or(ptr::null_mut()) diff --git a/crates/cranelift/src/translate/code_translator.rs b/crates/cranelift/src/translate/code_translator.rs index e69b30e15fe3..210f7ce2079b 100644 --- a/crates/cranelift/src/translate/code_translator.rs +++ b/crates/cranelift/src/translate/code_translator.rs @@ -2755,13 +2755,19 @@ pub fn translate_operator( )?; state.push1(result); } + Operator::AnyConvertExtern => { + // Pop an `externref`, push an `anyref`. But they have the same + // representation, so we don't actually need to do anything. + } + Operator::ExternConvertAny => { + // Pop an `anyref`, push an `externref`. But they have the same + // representation, so we don't actually need to do anything. + } Operator::RefCastNonNull { .. } | Operator::RefCastNullable { .. } | Operator::BrOnCast { .. } - | Operator::BrOnCastFail { .. } - | Operator::AnyConvertExtern - | Operator::ExternConvertAny => { + | Operator::BrOnCastFail { .. } => { return Err(wasm_unsupported!("GC operator not yet implemented: {op:?}")); } diff --git a/crates/environ/src/gc.rs b/crates/environ/src/gc.rs index fad280759f60..167b3517b928 100644 --- a/crates/environ/src/gc.rs +++ b/crates/environ/src/gc.rs @@ -225,25 +225,23 @@ impl GcStructLayout { /// VMGcKind::EqRef`. /// /// Furthermore, this type only uses the highest 6 bits of its `u32` -/// representation, allowing the lower 26 bytes to be bitpacked with other stuff +/// representation, allowing the lower 27 bytes to be bitpacked with other stuff /// as users see fit. #[repr(u32)] #[derive(Clone, Copy, Debug, PartialEq, Eq)] #[rustfmt::skip] #[allow(missing_docs)] pub enum VMGcKind { - ExternRef = 0b010000 << 26, - ExternOfAnyRef = 0b011000 << 26, - AnyRef = 0b100000 << 26, - AnyOfExternRef = 0b100100 << 26, - EqRef = 0b101000 << 26, - ArrayRef = 0b101001 << 26, - StructRef = 0b101010 << 26, + ExternRef = 0b01000 << 27, + AnyRef = 0b10000 << 27, + EqRef = 0b10100 << 27, + ArrayRef = 0b10101 << 27, + StructRef = 0b10110 << 27, } impl VMGcKind { /// Mask this value with a `u32` to get just the bits that `VMGcKind` uses. - pub const MASK: u32 = 0b111111 << 26; + pub const MASK: u32 = 0b11111 << 27; /// Mask this value with a `u32` that potentially contains a `VMGcKind` to /// get the bits that `VMGcKind` doesn't use. @@ -262,9 +260,7 @@ impl VMGcKind { let masked = val & Self::MASK; match masked { x if x == Self::ExternRef.as_u32() => Self::ExternRef, - x if x == Self::ExternOfAnyRef.as_u32() => Self::ExternOfAnyRef, x if x == Self::AnyRef.as_u32() => Self::AnyRef, - x if x == Self::AnyOfExternRef.as_u32() => Self::AnyOfExternRef, x if x == Self::EqRef.as_u32() => Self::EqRef, x if x == Self::ArrayRef.as_u32() => Self::ArrayRef, x if x == Self::StructRef.as_u32() => Self::StructRef, @@ -294,21 +290,11 @@ mod tests { #[test] fn kind_matches() { - let all = [ - ExternRef, - ExternOfAnyRef, - AnyRef, - AnyOfExternRef, - EqRef, - ArrayRef, - StructRef, - ]; + let all = [ExternRef, AnyRef, EqRef, ArrayRef, StructRef]; for (sup, subs) in [ - (ExternRef, vec![ExternOfAnyRef]), - (ExternOfAnyRef, vec![]), - (AnyRef, vec![AnyOfExternRef, EqRef, ArrayRef, StructRef]), - (AnyOfExternRef, vec![]), + (ExternRef, vec![]), + (AnyRef, vec![EqRef, ArrayRef, StructRef]), (EqRef, vec![ArrayRef, StructRef]), (ArrayRef, vec![]), (StructRef, vec![]), diff --git a/crates/fuzzing/src/oracles.rs b/crates/fuzzing/src/oracles.rs index b22695aaf9f6..aa2f46e23be1 100644 --- a/crates/fuzzing/src/oracles.rs +++ b/crates/fuzzing/src/oracles.rs @@ -707,15 +707,27 @@ pub fn table_ops( // run into a use-after-free bug with one of these refs we // are more likely to trigger a segfault. if let Some(a) = a { - let a = a.data(&caller)?.downcast_ref::().unwrap(); + let a = a + .data(&caller)? + .unwrap() + .downcast_ref::() + .unwrap(); assert!(a.0.load(SeqCst) <= expected_drops.load(SeqCst)); } if let Some(b) = b { - let b = b.data(&caller)?.downcast_ref::().unwrap(); + let b = b + .data(&caller)? + .unwrap() + .downcast_ref::() + .unwrap(); assert!(b.0.load(SeqCst) <= expected_drops.load(SeqCst)); } if let Some(c) = c { - let c = c.data(&caller)?.downcast_ref::().unwrap(); + let c = c + .data(&caller)? + .unwrap() + .downcast_ref::() + .unwrap(); assert!(c.0.load(SeqCst) <= expected_drops.load(SeqCst)); } Ok(()) diff --git a/crates/wasmtime/src/runtime/gc/enabled/anyref.rs b/crates/wasmtime/src/runtime/gc/enabled/anyref.rs index cfa75af9f4ea..82efee808936 100644 --- a/crates/wasmtime/src/runtime/gc/enabled/anyref.rs +++ b/crates/wasmtime/src/runtime/gc/enabled/anyref.rs @@ -1,5 +1,6 @@ //! Implementation of `anyref` in Wasmtime. +use super::{ExternRef, RootedGcRefImpl}; use crate::prelude::*; use crate::runtime::vm::VMGcRef; use crate::{ @@ -183,6 +184,64 @@ impl AnyRef { Rooted::new(store, gc_ref) } + /// Convert an `externref` into an `anyref`. + /// + /// This is equivalent to the `any.convert_extern` instruction in Wasm. + /// + /// You can recover the underlying `externref` again via the + /// [`ExternRef::convert_any`] method or the `extern.convert_any` Wasm + /// instruction. + /// + /// Returns an error if the `externref` GC reference has been unrooted (eg + /// if you attempt to use a `Rooted` after exiting the scope it + /// was rooted within). See the documentation for + /// [`Rooted`][crate::Rooted] for more details. + /// + /// # Example + /// + /// ``` + /// use wasmtime::*; + /// # fn foo() -> Result<()> { + /// let engine = Engine::default(); + /// let mut store = Store::new(&engine, ()); + /// + /// // Create an `externref`. + /// let externref = ExternRef::new(&mut store, "hello")?; + /// + /// // Convert that `externref` into an `anyref`. + /// let anyref = AnyRef::convert_extern(&mut store, externref)?; + /// + /// // The converted value is an `anyref` but is not an `eqref`. + /// assert_eq!(anyref.matches_ty(&store, &HeapType::Any)?, true); + /// assert_eq!(anyref.matches_ty(&store, &HeapType::Eq)?, false); + /// + /// // We can convert it back to the original `externref` and get its + /// // associated host data again. + /// let externref = ExternRef::convert_any(&mut store, anyref)?; + /// let data = externref + /// .data(&store)? + /// .expect("externref should have host data") + /// .downcast_ref::<&str>() + /// .expect("host data should be a str"); + /// assert_eq!(*data, "hello"); + /// # Ok(()) } + /// # foo().unwrap(); + pub fn convert_extern( + mut store: impl AsContextMut, + externref: Rooted, + ) -> Result> { + let mut store = AutoAssertNoGc::new(store.as_context_mut().0); + Self::_convert_extern(&mut store, externref) + } + + pub(crate) fn _convert_extern( + store: &mut AutoAssertNoGc<'_>, + externref: Rooted, + ) -> Result> { + let gc_ref = externref.try_clone_gc_ref(store)?; + Ok(Self::from_cloned_gc_ref(store, gc_ref)) + } + /// Creates a new strongly-owned [`AnyRef`] from the raw value provided. /// /// This is intended to be used in conjunction with [`Func::new_unchecked`], @@ -238,6 +297,11 @@ impl AnyRef { .header(&gc_ref) .kind() .matches(VMGcKind::AnyRef) + || store + .unwrap_gc_store() + .header(&gc_ref) + .kind() + .matches(VMGcKind::ExternRef) ); Rooted::new(store, gc_ref) } @@ -292,6 +356,13 @@ impl AnyRef { let header = store.gc_store()?.header(gc_ref); + if header.kind().matches(VMGcKind::ExternRef) { + return Ok(HeapType::Any); + } + + debug_assert!(header.kind().matches(VMGcKind::AnyRef)); + debug_assert!(header.kind().matches(VMGcKind::EqRef)); + if header.kind().matches(VMGcKind::StructRef) { return Ok(HeapType::ConcreteStruct( StructType::from_shared_type_index(store.engine(), header.ty().unwrap()), diff --git a/crates/wasmtime/src/runtime/gc/enabled/externref.rs b/crates/wasmtime/src/runtime/gc/enabled/externref.rs index 1cbdeac0a9c2..3db2fc5dbdd4 100644 --- a/crates/wasmtime/src/runtime/gc/enabled/externref.rs +++ b/crates/wasmtime/src/runtime/gc/enabled/externref.rs @@ -1,5 +1,6 @@ //! Implementation of `externref` in Wasmtime. +use super::{AnyRef, RootedGcRefImpl}; use crate::prelude::*; use crate::runtime::vm::VMGcRef; use crate::{ @@ -58,12 +59,14 @@ use core::mem::MaybeUninit; /// |mut caller: Caller<'_, ()>, a: Rooted, b: Rooted| -> Result> { /// let mut s = a /// .data(&caller)? +/// .ok_or_else(|| Error::msg("externref has no host data"))? /// .downcast_ref::>() /// .ok_or_else(|| Error::msg("externref was not a string"))? /// .clone() /// .into_owned(); /// let b = b /// .data(&caller)? +/// .ok_or_else(|| Error::msg("externref has no host data"))? /// .downcast_ref::>() /// .ok_or_else(|| Error::msg("externref was not a string"))?; /// s.push_str(&b); @@ -99,7 +102,11 @@ use core::mem::MaybeUninit; /// /// // The module should have concatenated the strings together! /// assert_eq!( -/// result.data(&store)?.downcast_ref::>().unwrap(), +/// result +/// .data(&store)? +/// .expect("externref should have host data") +/// .downcast_ref::>() +/// .expect("host data should be a `Cow`"), /// "Hello, World!" /// ); /// # Ok(()) @@ -212,6 +219,63 @@ impl ExternRef { Ok(Self::from_cloned_gc_ref(&mut ctx, gc_ref.into())) } + /// Convert an `anyref` into an `externref`. + /// + /// This is equivalent to the `extern.convert_any` instruction in Wasm. + /// + /// You can get the underlying `anyref` again via the + /// [`AnyRef::convert_extern`] method or the `any.convert_extern` Wasm + /// instruction. + /// + /// The resulting `ExternRef` will not have any host data associated with + /// it, so [`ExternRef::data`] and [`ExternRef::data_mut`] will both return + /// `None`. + /// + /// Returns an error if the `anyref` GC reference has been unrooted (eg if + /// you attempt to use a `Rooted` after exiting the scope it was + /// rooted within). See the documentation for [`Rooted`][crate::Rooted] + /// for more details. + /// + /// # Example + /// + /// ``` + /// use wasmtime::*; + /// # fn foo() -> Result<()> { + /// let engine = Engine::default(); + /// let mut store = Store::new(&engine, ()); + /// + /// // Create an `anyref`. + /// let i31 = I31::wrapping_u32(0x1234); + /// let anyref = AnyRef::from_i31(&mut store, i31); + /// + /// // Convert that `anyref` into an `externref`. + /// let externref = ExternRef::convert_any(&mut store, anyref)?; + /// + /// // This `externref` doesn't have any associated host data. + /// assert!(externref.data(&store)?.is_none()); + /// + /// // We can convert it back to an `anyref` and get its underlying `i31` + /// // data. + /// let anyref = AnyRef::convert_extern(&mut store, externref)?; + /// assert_eq!(anyref.unwrap_i31(&store)?.get_u32(), 0x1234); + /// # Ok(()) } + /// # foo().unwrap(); + pub fn convert_any( + mut context: impl AsContextMut, + anyref: Rooted, + ) -> Result> { + 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, + ) -> Result> { + let gc_ref = anyref.try_clone_gc_ref(store)?; + Ok(Self::from_cloned_gc_ref(store, gc_ref)) + } + /// Creates a new, manually-rooted instance of `ExternRef` wrapping the /// given value. /// @@ -280,14 +344,19 @@ impl ExternRef { gc_ref: VMGcRef, ) -> Rooted { assert!( - gc_ref.is_extern_ref(&*store.unwrap_gc_store().gc_heap), - "GC reference {gc_ref:#p} is not an externref" + 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) } /// Get a shared borrow of the underlying data for this `ExternRef`. /// + /// Returns `None` if this is an `externref` wrapper of an `anyref` created + /// by the `extern.convert_any` instruction or the + /// [`ExternRef::convert_any`] method. + /// /// Returns an error if this `externref` GC reference has been unrooted (eg /// if you attempt to use a `Rooted` after exiting the scope it /// was rooted within). See the documentation for @@ -303,7 +372,7 @@ impl ExternRef { /// let externref = ExternRef::new(&mut store, "hello")?; /// /// // Access the `externref`'s host data. - /// let data = externref.data(&store)?; + /// let data = externref.data(&store)?.ok_or_else(|| Error::msg("no host data"))?; /// // Dowcast it to a `&str`. /// let data = data.downcast_ref::<&str>().ok_or_else(|| Error::msg("not a str"))?; /// // We should have got the data we created the `externref` with! @@ -314,18 +383,26 @@ impl ExternRef { pub fn data<'a, T>( &self, store: impl Into>, - ) -> Result<&'a (dyn Any + Send + Sync)> + ) -> Result> where T: 'a, { let store = store.into().0; let gc_ref = self.inner.try_gc_ref(&store)?; - let externref = gc_ref.as_externref_unchecked(); - Ok(store.gc_store()?.externref_host_data(externref)) + let gc_store = store.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) + } } /// Get an exclusive borrow of the underlying data for this `ExternRef`. /// + /// Returns `None` if this is an `externref` wrapper of an `anyref` created + /// by the `extern.convert_any` instruction or the + /// [`ExternRef::convert_any`] constructor. + /// /// Returns an error if this `externref` GC reference has been unrooted (eg /// if you attempt to use a `Rooted` after exiting the scope it /// was rooted within). See the documentation for @@ -341,7 +418,7 @@ impl ExternRef { /// let externref = ExternRef::new::(&mut store, 0)?; /// /// // Access the `externref`'s host data. - /// let data = externref.data_mut(&mut store)?; + /// let data = externref.data_mut(&mut store)?.ok_or_else(|| Error::msg("no host data"))?; /// // Dowcast it to a `usize`. /// let data = data.downcast_mut::().ok_or_else(|| Error::msg("not a usize"))?; /// // We initialized to zero. @@ -354,7 +431,7 @@ impl ExternRef { pub fn data_mut<'a, T>( &self, store: impl Into>, - ) -> Result<&'a mut (dyn Any + Send + Sync)> + ) -> Result> where T: 'a, { @@ -363,8 +440,12 @@ impl ExternRef { // so that we can get the store's GC store. But importantly we cannot // trigger a GC while we are working with `gc_ref` here. let gc_ref = self.inner.try_gc_ref(store)?.unchecked_copy(); - let externref = gc_ref.as_externref_unchecked(); - Ok(store.gc_store_mut()?.externref_host_data_mut(externref)) + let gc_store = store.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) + } } /// Creates a new strongly-owned [`ExternRef`] from the raw value provided. diff --git a/crates/wasmtime/src/runtime/gc/enabled/rooting.rs b/crates/wasmtime/src/runtime/gc/enabled/rooting.rs index 436ea220dbb3..2f03866922da 100644 --- a/crates/wasmtime/src/runtime/gc/enabled/rooting.rs +++ b/crates/wasmtime/src/runtime/gc/enabled/rooting.rs @@ -496,6 +496,7 @@ impl RootSet { /// // have a `Rooted`. /// let data = hello /// .data(&store)? +/// .ok_or_else(|| Error::msg("externref has no host data"))? /// .downcast_ref::<&str>() /// .ok_or_else(|| Error::msg("not a str"))?; /// assert_eq!(*data, "hello"); @@ -785,8 +786,8 @@ impl Rooted { /// /// // Now we can still access the reference outside the scope it was /// // originally defined within. - /// let data = y.data(&store)?; - /// let data = data.downcast_ref::<&str>().unwrap(); + /// let data = y.data(&store)?.expect("should have host data"); + /// let data = data.downcast_ref::<&str>().expect("host data should be str"); /// assert_eq!(*data, "hello!"); /// /// // But we have to manually unroot `y`. diff --git a/crates/wasmtime/src/runtime/vm/gc/enabled/drc.rs b/crates/wasmtime/src/runtime/vm/gc/enabled/drc.rs index 02c65b68b778..91d0dbdee67a 100644 --- a/crates/wasmtime/src/runtime/vm/gc/enabled/drc.rs +++ b/crates/wasmtime/src/runtime/vm/gc/enabled/drc.rs @@ -480,7 +480,7 @@ impl VMDrcHeader { /// /// This is stored in the inner `VMGcHeader`'s reserved bits. fn object_size(&self) -> usize { - let size = self.header.reserved_u26(); + let size = self.header.reserved_u27(); usize::try_from(size).unwrap() } } @@ -596,8 +596,8 @@ unsafe impl GcHeap for DrcHeap { Some(index) => VMGcRef::from_heap_index(index).unwrap(), }; - debug_assert_eq!(header.reserved_u26(), 0); - header.set_reserved_u26(size); + debug_assert_eq!(header.reserved_u27(), 0); + header.set_reserved_u27(size); *self.index_mut(drc_ref(&gc_ref)) = VMDrcHeader { header, diff --git a/crates/wasmtime/src/runtime/vm/gc/enabled/externref.rs b/crates/wasmtime/src/runtime/vm/gc/enabled/externref.rs index 1c4622077830..01dcaadab54c 100644 --- a/crates/wasmtime/src/runtime/vm/gc/enabled/externref.rs +++ b/crates/wasmtime/src/runtime/vm/gc/enabled/externref.rs @@ -58,7 +58,7 @@ impl VMGcRef { /// Get this GC reference as an `externref` reference, if it actually is an /// `externref` reference. - pub fn as_externref(&self, gc_heap: &impl GcHeap) -> Option<&VMExternRef> { + pub fn as_externref(&self, gc_heap: &(impl GcHeap + ?Sized)) -> Option<&VMExternRef> { if self.is_i31() { return None; } diff --git a/crates/wasmtime/src/runtime/vm/gc/gc_ref.rs b/crates/wasmtime/src/runtime/vm/gc/gc_ref.rs index aaf89035c3c8..c1f3bf899854 100644 --- a/crates/wasmtime/src/runtime/vm/gc/gc_ref.rs +++ b/crates/wasmtime/src/runtime/vm/gc/gc_ref.rs @@ -16,11 +16,11 @@ use wasmtime_environ::{VMGcKind, VMSharedTypeIndex}; /// /// ```ignore /// struct VMGcHeader { -/// // Highest 6 bits. +/// // Highest 5 bits. /// kind: VMGcKind, /// -/// // 26 bits available for the `GcRuntime` to make use of however it sees fit. -/// reserved: u26, +/// // 27 bits available for the `GcRuntime` to make use of however it sees fit. +/// reserved: u27, /// /// // The `VMSharedTypeIndex` for this GC object, if it isn't an /// // `externref` (or an `externref` re-wrapped as an `anyref`). `None` is @@ -31,7 +31,7 @@ use wasmtime_environ::{VMGcKind, VMSharedTypeIndex}; #[repr(C, align(8))] #[derive(Debug, Clone, Copy)] pub struct VMGcHeader { - /// The object's `VMGcKind` and 26 bits of space reserved for however the GC + /// The object's `VMGcKind` and 27 bits of space reserved for however the GC /// sees fit to use it. kind: u32, @@ -72,20 +72,20 @@ impl VMGcHeader { VMGcKind::from_high_bits_of_u32(self.kind) } - /// Get the reserved 26 bits in this header. + /// Get the reserved 27 bits in this header. /// /// These are bits are reserved for `GcRuntime` implementations to make use /// of however they see fit. - pub fn reserved_u26(&self) -> u32 { + pub fn reserved_u27(&self) -> u32 { self.kind & VMGcKind::UNUSED_MASK } - /// Set the 26-bit reserved value. + /// Set the 27-bit reserved value. /// /// # Panics /// /// Panics if the given `value` has any of the upper 6 bits set. - pub fn set_reserved_u26(&mut self, value: u32) { + pub fn set_reserved_u27(&mut self, value: u32) { assert!( VMGcKind::value_fits_in_unused_bits(value), "VMGcHeader::set_reserved_u26 with value using more than 26 bits" @@ -93,11 +93,11 @@ impl VMGcHeader { self.kind |= value; } - /// Set the 26-bit reserved value. + /// Set the 27-bit reserved value. /// /// # Safety /// - /// The given `value` must only use the lower 26 bits; its upper 6 bits must + /// The given `value` must only use the lower 27 bits; its upper 5 bits must /// be unset. pub unsafe fn unchecked_set_reserved_u26(&mut self, value: u32) { debug_assert_eq!(value & VMGcKind::MASK, 0); @@ -389,6 +389,15 @@ impl VMGcRef { self.gc_header(gc_heap) .map_or(false, |h| h.kind().matches(VMGcKind::ExternRef)) } + + /// Is this `VMGcRef` an `anyref`? + #[inline] + pub fn is_any_ref(&self, gc_heap: &(impl GcHeap + ?Sized)) -> bool { + self.is_i31() + || self + .gc_header(gc_heap) + .map_or(false, |h| h.kind().matches(VMGcKind::AnyRef)) + } } /// A trait implemented by all objects allocated inside a GC heap. diff --git a/crates/wast/src/core.rs b/crates/wast/src/core.rs index ed98c1b926b8..99101f780e29 100644 --- a/crates/wast/src/core.rs +++ b/crates/wast/src/core.rs @@ -1,6 +1,6 @@ -use anyhow::{bail, Context, Result}; +use anyhow::{anyhow, bail, Context, Result}; use std::fmt::{Display, LowerHex}; -use wasmtime::{ExternRef, Store, Val}; +use wasmtime::{AnyRef, ExternRef, Store, Val}; use wast::core::{AbstractHeapType, HeapType, NanPattern, V128Pattern, WastArgCore, WastRetCore}; use wast::token::{F32, F64}; @@ -31,6 +31,11 @@ pub fn val(store: &mut Store, v: &WastArgCore<'_>) -> Result { ty: AbstractHeapType::None, }) => Val::AnyRef(None), RefExtern(x) => Val::ExternRef(Some(ExternRef::new(store, *x)?)), + RefHost(x) => { + let x = ExternRef::new(&mut *store, *x)?; + let x = AnyRef::convert_extern(&mut *store, x)?; + Val::AnyRef(Some(x)) + } other => bail!("couldn't convert {:?} to a runtime value", other), }) } @@ -51,7 +56,7 @@ fn extract_lane_as_i64(bytes: u128, lane: usize) -> i64 { (bytes >> (lane * 64)) as i64 } -pub fn match_val(store: &Store, actual: &Val, expected: &WastRetCore) -> Result<()> { +pub fn match_val(store: &mut Store, actual: &Val, expected: &WastRetCore) -> Result<()> { match (actual, expected) { (_, WastRetCore::Either(expected)) => { for expected in expected { @@ -89,11 +94,15 @@ pub fn match_val(store: &Store, actual: &Val, expected: &WastRetCore) -> R shared: false, })), ) => { - let x = x - .data(store)? - .downcast_ref::() - .expect("only u32 externrefs created in wast test suites"); - bail!("expected null externref, found non-null externref of {x}"); + match x.data(store)?.map(|x| { + x.downcast_ref::() + .expect("only u32 externrefs created in wast test suites") + }) { + None => { + bail!("expected null externref, found non-null externref without host data") + } + Some(x) => bail!("expected null externref, found non-null externref of {x}"), + } } (Val::ExternRef(Some(_)) | Val::FuncRef(Some(_)), WastRetCore::RefNull(_)) => { bail!("expected null, found non-null reference: {actual:?}") @@ -101,9 +110,13 @@ pub fn match_val(store: &Store, actual: &Val, expected: &WastRetCore) -> R // Non-null references. (Val::FuncRef(Some(_)), WastRetCore::RefFunc(_)) => Ok(()), + (Val::ExternRef(Some(_)), WastRetCore::RefExtern(None)) => Ok(()), (Val::ExternRef(Some(x)), WastRetCore::RefExtern(Some(y))) => { let x = x .data(store)? + .ok_or_else(|| { + anyhow!("expected an externref of a u32, found externref without host data") + })? .downcast_ref::() .expect("only u32 externrefs created in wast test suites"); if x == y { @@ -142,6 +155,24 @@ pub fn match_val(store: &Store, actual: &Val, expected: &WastRetCore) -> R bail!("expected a array reference, found {x:?}") } } + (Val::AnyRef(Some(x)), WastRetCore::RefHost(y)) => { + let x = ExternRef::convert_any(&mut *store, *x)?; + let x = x + .data(&mut *store)? + .ok_or_else(|| { + anyhow!( + "expected anyref of externref of u32, found anyref that is \ + not a converted externref" + ) + })? + .downcast_ref::() + .expect("only u32 externrefs created in wast test suites"); + if x == y { + Ok(()) + } else { + bail!("expected anyref of externref of {y}, found anyref of externref of {x}") + } + } _ => bail!( "don't know how to compare {:?} and {:?} yet", diff --git a/crates/wast/src/wast.rs b/crates/wast/src/wast.rs index 96fde0fae425..09f9e0115c24 100644 --- a/crates/wast/src/wast.rs +++ b/crates/wast/src/wast.rs @@ -338,7 +338,7 @@ where ))) } - fn assert_return(&self, result: Outcome, results: &[WastRet<'_>]) -> Result<()> { + fn assert_return(&mut self, result: Outcome, results: &[WastRet<'_>]) -> Result<()> { match result.into_result()? { Results::Core(values) => { if values.len() != results.len() { @@ -351,7 +351,7 @@ where bail!("expected component value found core value") } }; - core::match_val(&self.store, v, e) + core::match_val(&mut self.store, v, e) .with_context(|| format!("result {i} didn't match"))?; } } diff --git a/examples/externref.rs b/examples/externref.rs index 7739fd8a1268..2b98b2fb9ecc 100644 --- a/examples/externref.rs +++ b/examples/externref.rs @@ -19,10 +19,14 @@ fn main() -> Result<()> { println!("Creating new `externref`..."); let externref = ExternRef::new(&mut store, "Hello, World!")?; - assert!(externref.data(&store)?.is::<&'static str>()); + assert!(externref + .data(&store)? + .expect("should have host data") + .is::<&'static str>()); assert_eq!( *externref .data(&store)? + .expect("should have host data") .downcast_ref::<&'static str>() .unwrap(), "Hello, World!" diff --git a/tests/all/async_functions.rs b/tests/all/async_functions.rs index c94dc4e3b650..790d32b699b9 100644 --- a/tests/all/async_functions.rs +++ b/tests/all/async_functions.rs @@ -970,7 +970,10 @@ async fn gc_preserves_externref_on_historical_async_stacks() -> Result<()> { "", "test", |cx: Caller<'_, _>, val: i32, handle: Option>| -> Result<()> { - assert_eq!(handle.unwrap().data(&cx)?.downcast_ref(), Some(&val)); + assert_eq!( + handle.unwrap().data(&cx)?.unwrap().downcast_ref(), + Some(&val) + ); Ok(()) }, )?; diff --git a/tests/all/externals.rs b/tests/all/externals.rs index 76890d0aa386..09b860b4568a 100644 --- a/tests/all/externals.rs +++ b/tests/all/externals.rs @@ -165,8 +165,17 @@ fn get_set_externref_globals_via_api() -> anyhow::Result<()> { let hello = ExternRef::new(&mut store, "hello".to_string())?; global.set(&mut store, hello.into())?; let r = global.get(&mut store).unwrap_externref().cloned().unwrap(); - assert!(r.data(&store)?.is::()); - assert_eq!(r.data(&store)?.downcast_ref::().unwrap(), "hello"); + assert!(r + .data(&store)? + .expect("should have host data") + .is::()); + assert_eq!( + r.data(&store)? + .expect("should have host data") + .downcast_ref::() + .unwrap(), + "hello" + ); // Initialize with a non-null externref. @@ -177,8 +186,15 @@ fn get_set_externref_globals_via_api() -> anyhow::Result<()> { externref.into(), )?; let r = global.get(&mut store).unwrap_externref().cloned().unwrap(); - assert!(r.data(&store)?.is::()); - assert_eq!(r.data(&store)?.downcast_ref::().copied().unwrap(), 42); + assert!(r.data(&store)?.expect("should have host data").is::()); + assert_eq!( + r.data(&store)? + .expect("should have host data") + .downcast_ref::() + .copied() + .unwrap(), + 42 + ); Ok(()) } @@ -298,6 +314,7 @@ fn create_get_set_externref_tables_via_api() -> anyhow::Result<()> { .unwrap_extern() .unwrap() .data(&store)? + .expect("should have host data") .downcast_ref::() .unwrap(), 42 @@ -336,6 +353,7 @@ fn fill_externref_tables_via_api() -> anyhow::Result<()> { .unwrap_extern() .unwrap() .data(&store)? + .expect("should have host data") .downcast_ref::() .unwrap(), 42 diff --git a/tests/all/func.rs b/tests/all/func.rs index c408952b4eec..ce7a16103564 100644 --- a/tests/all/func.rs +++ b/tests/all/func.rs @@ -644,6 +644,7 @@ fn import_works() -> Result<()> { f.as_ref() .unwrap() .data(&caller)? + .unwrap() .downcast_ref::() .unwrap(), "hello" @@ -652,6 +653,7 @@ fn import_works() -> Result<()> { g.as_ref() .unwrap() .data(&caller)? + .unwrap() .downcast_ref::() .unwrap(), "goodbye" @@ -1510,7 +1512,12 @@ fn calls_with_funcref_and_externref(config: &mut Config) -> anyhow::Result<()> { } fn assert_my_externref(store: impl AsContext, externref: Option>) { assert_eq!( - externref.unwrap().data(&store).unwrap().downcast_ref(), + externref + .unwrap() + .data(&store) + .unwrap() + .unwrap() + .downcast_ref(), Some(&99u32) ); } diff --git a/tests/all/gc.rs b/tests/all/gc.rs index 8dbfbb540d6e..73affd6897f5 100644 --- a/tests/all/gc.rs +++ b/tests/all/gc.rs @@ -185,6 +185,7 @@ fn many_live_refs() -> Result<()> { .unwrap() .data(&caller) .unwrap() + .unwrap() .downcast_ref::() .unwrap(); assert!(r.live_refs.load(SeqCst) > 0); @@ -449,6 +450,7 @@ fn no_gc_middle_of_args() -> Result<()> { assert_eq!( a.data(&caller) .expect("rooted") + .expect("host data") .downcast_ref::() .expect("is string"), "a" @@ -456,6 +458,7 @@ fn no_gc_middle_of_args() -> Result<()> { assert_eq!( b.data(&caller) .expect("rooted") + .expect("host data") .downcast_ref::() .expect("is string"), "b" @@ -463,6 +466,7 @@ fn no_gc_middle_of_args() -> Result<()> { assert_eq!( c.data(&caller) .expect("rooted") + .expect("host data") .downcast_ref::() .expect("is string"), "c" @@ -616,15 +620,27 @@ fn gc_and_tail_calls_and_stack_arguments() -> Result<()> { let b = b.unwrap(); let c = c.unwrap(); assert_eq!( - a.data(&caller).unwrap().downcast_ref::().unwrap(), + a.data(&caller) + .unwrap() + .unwrap() + .downcast_ref::() + .unwrap(), "a" ); assert_eq!( - b.data(&caller).unwrap().downcast_ref::().unwrap(), + b.data(&caller) + .unwrap() + .unwrap() + .downcast_ref::() + .unwrap(), "b" ); assert_eq!( - c.data(&caller).unwrap().downcast_ref::().unwrap(), + c.data(&caller) + .unwrap() + .unwrap() + .downcast_ref::() + .unwrap(), "c" ); }, diff --git a/tests/all/host_funcs.rs b/tests/all/host_funcs.rs index 8a61c74a0398..d887c510dc82 100644 --- a/tests/all/host_funcs.rs +++ b/tests/all/host_funcs.rs @@ -292,6 +292,7 @@ fn import_works() -> Result<()> { .unwrap() .data(&caller) .unwrap() + .unwrap() .downcast_ref::() .unwrap(), "hello" diff --git a/tests/disas/gc/array-new-fixed.wat b/tests/disas/gc/array-new-fixed.wat index de705b81044c..5012f5a8421e 100644 --- a/tests/disas/gc/array-new-fixed.wat +++ b/tests/disas/gc/array-new-fixed.wat @@ -23,10 +23,10 @@ ;; @0025 trapnz v46, user18 ; v46 = 0 ;; @0025 v6 = iconst.i32 24 ;; @0025 v12 = uadd_overflow_trap v6, v6, user18 ; v6 = 24, v6 = 24 -;; @0025 v14 = iconst.i32 -1543503872 +;; @0025 v14 = iconst.i32 -1476395008 ;; @0025 v15 = iconst.i32 0 ;; @0025 v16 = iconst.i32 8 -;; @0025 v17 = call fn0(v0, v14, v15, v12, v16) ; v14 = -1543503872, v15 = 0, v16 = 8 +;; @0025 v17 = call fn0(v0, v14, v15, v12, v16) ; v14 = -1476395008, v15 = 0, v16 = 8 ;; @0025 v21 = uextend.i64 v17 ;; @0025 v22 = iconst.i64 16 ;; @0025 v23 = uadd_overflow_trap v21, v22, user1 ; v22 = 16 diff --git a/tests/disas/gc/array-new.wat b/tests/disas/gc/array-new.wat index 83301af10989..72680a8fe865 100644 --- a/tests/disas/gc/array-new.wat +++ b/tests/disas/gc/array-new.wat @@ -29,10 +29,10 @@ ;; v45 = iconst.i32 3 ;; v46 = ishl v3, v45 ; v45 = 3 ;; @0022 v10 = uadd_overflow_trap v5, v46, user18 ; v5 = 24 -;; @0022 v12 = iconst.i32 -1543503872 +;; @0022 v12 = iconst.i32 -1476395008 ;; @0022 v13 = iconst.i32 0 ;; v43 = iconst.i32 8 -;; @0022 v15 = call fn0(v0, v12, v13, v10, v43) ; v12 = -1543503872, v13 = 0, v43 = 8 +;; @0022 v15 = call fn0(v0, v12, v13, v10, v43) ; v12 = -1476395008, v13 = 0, v43 = 8 ;; @0022 v19 = uextend.i64 v15 ;; @0022 v20 = iconst.i64 16 ;; @0022 v21 = uadd_overflow_trap v19, v20, user1 ; v20 = 16 diff --git a/tests/disas/gc/funcref-in-gc-heap-new.wat b/tests/disas/gc/funcref-in-gc-heap-new.wat index c1daf8fd6c95..6b2fc16b4e89 100644 --- a/tests/disas/gc/funcref-in-gc-heap-new.wat +++ b/tests/disas/gc/funcref-in-gc-heap-new.wat @@ -22,11 +22,11 @@ ;; stack_limit = gv2 ;; ;; block0(v0: i64, v1: i64, v2: i64): -;; @0020 v6 = iconst.i32 -1476395008 +;; @0020 v6 = iconst.i32 -1342177280 ;; @0020 v7 = iconst.i32 0 ;; @0020 v4 = iconst.i32 24 ;; @0020 v8 = iconst.i32 8 -;; @0020 v9 = call fn0(v0, v6, v7, v4, v8) ; v6 = -1476395008, v7 = 0, v4 = 24, v8 = 8 +;; @0020 v9 = call fn0(v0, v6, v7, v4, v8) ; v6 = -1342177280, v7 = 0, v4 = 24, v8 = 8 ;; v24 = stack_addr.i64 ss0 ;; store notrap v9, v24 ;; @0020 v13 = uextend.i64 v9 diff --git a/tests/disas/gc/ref-test-array.wat b/tests/disas/gc/ref-test-array.wat index 627990cefa10..f4a3742793ed 100644 --- a/tests/disas/gc/ref-test-array.wat +++ b/tests/disas/gc/ref-test-array.wat @@ -41,9 +41,9 @@ ;; @001b v16 = load.i64 notrap aligned readonly v0+40 ;; @001b v24 = iadd v16, v20 ;; @001b v25 = load.i32 notrap aligned readonly v24 -;; @001b v26 = iconst.i32 -1543503872 -;; @001b v27 = band v25, v26 ; v26 = -1543503872 -;; @001b v28 = icmp eq v27, v26 ; v26 = -1543503872 +;; @001b v26 = iconst.i32 -1476395008 +;; @001b v27 = band v25, v26 ; v26 = -1476395008 +;; @001b v28 = icmp eq v27, v26 ; v26 = -1476395008 ;; @001b v29 = uextend.i32 v28 ;; @001b jump block4(v29) ;; diff --git a/tests/disas/gc/ref-test-struct.wat b/tests/disas/gc/ref-test-struct.wat index b7f154091b99..1d61a39bcf93 100644 --- a/tests/disas/gc/ref-test-struct.wat +++ b/tests/disas/gc/ref-test-struct.wat @@ -41,9 +41,9 @@ ;; @001b v16 = load.i64 notrap aligned readonly v0+40 ;; @001b v24 = iadd v16, v20 ;; @001b v25 = load.i32 notrap aligned readonly v24 -;; @001b v26 = iconst.i32 -1476395008 -;; @001b v27 = band v25, v26 ; v26 = -1476395008 -;; @001b v28 = icmp eq v27, v26 ; v26 = -1476395008 +;; @001b v26 = iconst.i32 -1342177280 +;; @001b v27 = band v25, v26 ; v26 = -1342177280 +;; @001b v28 = icmp eq v27, v26 ; v26 = -1342177280 ;; @001b v29 = uextend.i32 v28 ;; @001b jump block4(v29) ;; diff --git a/tests/disas/gc/struct-new-default.wat b/tests/disas/gc/struct-new-default.wat index 5bb9136d3f81..06ef16fd6238 100644 --- a/tests/disas/gc/struct-new-default.wat +++ b/tests/disas/gc/struct-new-default.wat @@ -21,11 +21,11 @@ ;; stack_limit = gv2 ;; ;; block0(v0: i64, v1: i64): -;; @0021 v8 = iconst.i32 -1476395008 +;; @0021 v8 = iconst.i32 -1342177280 ;; @0021 v4 = iconst.i32 0 ;; @0021 v6 = iconst.i32 32 ;; @0021 v10 = iconst.i32 8 -;; @0021 v11 = call fn0(v0, v8, v4, v6, v10) ; v8 = -1476395008, v4 = 0, v6 = 32, v10 = 8 +;; @0021 v11 = call fn0(v0, v8, v4, v6, v10) ; v8 = -1342177280, v4 = 0, v6 = 32, v10 = 8 ;; @0021 v15 = uextend.i64 v11 ;; @0021 v16 = iconst.i64 16 ;; @0021 v17 = uadd_overflow_trap v15, v16, user1 ; v16 = 16 diff --git a/tests/disas/gc/struct-new.wat b/tests/disas/gc/struct-new.wat index fd1d6b820476..20588342aea9 100644 --- a/tests/disas/gc/struct-new.wat +++ b/tests/disas/gc/struct-new.wat @@ -24,11 +24,11 @@ ;; block0(v0: i64, v1: i64, v2: f32, v3: i32, v4: i32): ;; v71 = stack_addr.i64 ss0 ;; store notrap v4, v71 -;; @002a v8 = iconst.i32 -1476395008 +;; @002a v8 = iconst.i32 -1342177280 ;; @002a v9 = iconst.i32 0 ;; @002a v6 = iconst.i32 32 ;; @002a v10 = iconst.i32 8 -;; @002a v11 = call fn0(v0, v8, v9, v6, v10), stack_map=[i32 @ ss0+0] ; v8 = -1476395008, v9 = 0, v6 = 32, v10 = 8 +;; @002a v11 = call fn0(v0, v8, v9, v6, v10), stack_map=[i32 @ ss0+0] ; v8 = -1342177280, v9 = 0, v6 = 32, v10 = 8 ;; @002a v15 = uextend.i64 v11 ;; @002a v16 = iconst.i64 16 ;; @002a v17 = uadd_overflow_trap v15, v16, user1 ; v16 = 16 diff --git a/tests/misc_testsuite/gc/array-alloc-too-large.wast b/tests/misc_testsuite/gc/array-alloc-too-large.wast index 34fe32843bf4..eaf61cb98d5f 100644 --- a/tests/misc_testsuite/gc/array-alloc-too-large.wast +++ b/tests/misc_testsuite/gc/array-alloc-too-large.wast @@ -12,9 +12,9 @@ (array.new_default $arr_i8 (i32.const -1)) ) - ;; Larger than can fit in `VMGcHeader`'s reserved 26 bits. + ;; Larger than can fit in `VMGcHeader`'s reserved 27 bits. (func (export "bigger-than-reserved-bits") (result (ref $arr_i8)) - (array.new_default $arr_i8 (i32.shl (i32.const 1) (i32.const 26))) + (array.new_default $arr_i8 (i32.shl (i32.const 1) (i32.const 27))) ) ) diff --git a/tests/wast.rs b/tests/wast.rs index 691e9c086b91..ec95ffb3ba16 100644 --- a/tests/wast.rs +++ b/tests/wast.rs @@ -207,9 +207,7 @@ fn should_fail(test: &Path, strategy: Strategy) -> bool { "binary_gc.wast", "br_on_cast_fail.wast", "br_on_cast.wast", - "extern.wast", "ref_cast.wast", - "ref_test.wast", "table_sub.wast", "type_canon.wast", "type_equivalence.wast",