diff --git a/crates/environ/src/compile/module_environ.rs b/crates/environ/src/compile/module_environ.rs index 59ddb758cf83..12dd08415af2 100644 --- a/crates/environ/src/compile/module_environ.rs +++ b/crates/environ/src/compile/module_environ.rs @@ -3,7 +3,6 @@ use crate::module::{ FuncRefIndex, Initializer, MemoryInitialization, MemoryInitializer, Module, TableSegment, TableSegmentElements, }; -use crate::prelude::*; use crate::{ ConstExpr, ConstOp, DataIndex, DefinedFuncIndex, ElemIndex, EngineOrModuleTypeIndex, EntityIndex, EntityType, FuncIndex, FuncKey, GlobalIndex, IndexType, InitMemory, MemoryIndex, @@ -12,6 +11,7 @@ use crate::{ Tunables, TypeConvert, TypeIndex, WasmError, WasmHeapTopType, WasmHeapType, WasmResult, WasmValType, WasmparserTypeConverter, }; +use crate::{NeedsGcRooting, prelude::*}; use cranelift_entity::SecondaryMap; use cranelift_entity::packed_option::ReservedValue; use std::borrow::Cow; @@ -531,7 +531,8 @@ impl<'a, 'data> ModuleEnvironment<'a, 'data> { } TableSegmentElements::Functions(elems.into()) } - ElementItems::Expressions(_ty, items) => { + ElementItems::Expressions(ty, items) => { + let ty = self.convert_ref_type(ty)?; let mut exprs = Vec::with_capacity(usize::try_from(items.count()).unwrap()); for expr in items { @@ -541,7 +542,14 @@ impl<'a, 'data> ModuleEnvironment<'a, 'data> { self.flag_func_escaped(func); } } - TableSegmentElements::Expressions(exprs.into()) + TableSegmentElements::Expressions { + needs_gc_rooting: if ty.is_vmgcref_type_and_not_i31() { + NeedsGcRooting::Yes + } else { + NeedsGcRooting::No + }, + exprs: exprs.into(), + } } }; @@ -565,12 +573,12 @@ impl<'a, 'data> ModuleEnvironment<'a, 'data> { ElementKind::Passive => { let elem_index = ElemIndex::from_u32(index as u32); - let index = self.result.module.passive_elements.len(); - self.result.module.passive_elements.push(elements)?; + let passive_index = + self.result.module.passive_elements.push(elements)?; self.result .module .passive_elements_map - .insert(elem_index, index); + .insert(elem_index, passive_index); } ElementKind::Declared => {} @@ -1309,7 +1317,7 @@ impl ModuleTranslation<'_> { // expressions are deferred to get evaluated at runtime. let function_elements = match &segment.elements { TableSegmentElements::Functions(indices) => indices, - TableSegmentElements::Expressions(_) => break, + TableSegmentElements::Expressions { .. } => break, }; let precomputed = diff --git a/crates/environ/src/module.rs b/crates/environ/src/module.rs index c2f52c2f60a5..1f2b7dde8b4c 100644 --- a/crates/environ/src/module.rs +++ b/crates/environ/src/module.rs @@ -266,6 +266,15 @@ pub struct TableSegment { pub elements: TableSegmentElements, } +/// Does something need GC rooting? +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub enum NeedsGcRooting { + /// GC rooting is needed. + Yes, + /// GC rooting is not needed. + No, +} + /// Elements of a table segment, either a list of functions or list of arbitrary /// expressions. #[derive(Clone, Debug, Serialize, Deserialize)] @@ -274,7 +283,13 @@ pub enum TableSegmentElements { /// indicates a null function. Functions(Box<[FuncIndex]>), /// Arbitrary expressions, aka either functions, null or a load of a global. - Expressions(Box<[ConstExpr]>), + Expressions { + /// Is this expression's result of a type that needs GC rooting and + /// tracing? + needs_gc_rooting: NeedsGcRooting, + /// The const expressions for this segment's elements. + exprs: Box<[ConstExpr]>, + }, } impl TableSegmentElements { @@ -282,7 +297,7 @@ impl TableSegmentElements { pub fn len(&self) -> u64 { match self { Self::Functions(s) => u64::try_from(s.len()).unwrap(), - Self::Expressions(s) => u64::try_from(s.len()).unwrap(), + Self::Expressions { exprs, .. } => u64::try_from(exprs.len()).unwrap(), } } } @@ -316,10 +331,11 @@ pub struct Module { pub memory_initialization: MemoryInitialization, /// WebAssembly passive elements. - pub passive_elements: TryVec, + pub passive_elements: TryPrimaryMap, - /// The map from passive element index (element segment index space) to index in `passive_elements`. - pub passive_elements_map: BTreeMap, + /// The map from passive element index (element segment index space) to + /// index in `passive_elements`. + pub passive_elements_map: BTreeMap, /// The map from passive data index (data segment index space) to index in `passive_data`. pub passive_data_map: BTreeMap>, diff --git a/crates/environ/src/types.rs b/crates/environ/src/types.rs index 21b552636915..e582bc7c5df0 100644 --- a/crates/environ/src/types.rs +++ b/crates/environ/src/types.rs @@ -1692,11 +1692,19 @@ impl Default for VMSharedTypeIndex { pub struct DataIndex(u32); entity_impl_with_try_clone!(DataIndex); -/// Index type of a passive element segment inside the WebAssembly module. +/// Index type of an element segment inside the WebAssembly module. #[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug, Serialize, Deserialize)] pub struct ElemIndex(u32); entity_impl_with_try_clone!(ElemIndex); +/// Dense index space of the subset of element segments that are passive. +/// +/// Not a spec-level concept, just used to get dense index spaces for passive +/// element segments inside of Wasmtime. +#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug, Serialize, Deserialize)] +pub struct PassiveElemIndex(u32); +entity_impl_with_try_clone!(PassiveElemIndex); + /// Index type of a defined tag inside the WebAssembly module. #[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug, Serialize, Deserialize)] pub struct DefinedTagIndex(u32); diff --git a/crates/wasmtime/src/runtime/externals/table.rs b/crates/wasmtime/src/runtime/externals/table.rs index 39b13e7bd5f4..98202ba71b9f 100644 --- a/crates/wasmtime/src/runtime/externals/table.rs +++ b/crates/wasmtime/src/runtime/externals/table.rs @@ -131,10 +131,10 @@ impl Table { /// /// Panics if `store` does not own this table. pub fn ty(&self, store: impl AsContext) -> TableType { - self._ty(store.as_context().0) + self.ty_(store.as_context().0) } - fn _ty(&self, store: &StoreOpaque) -> TableType { + pub(crate) fn ty_(&self, store: &StoreOpaque) -> TableType { TableType::from_wasmtime_table(store.engine(), self.wasmtime_ty(store)) } @@ -185,7 +185,7 @@ impl Table { .ok()? .map(|r| r.unchecked_copy()) .map(|r| store.clone_gc_ref(&r)); - Some(match self._ty(&store).element().heap_type().top() { + Some(match self.ty_(&store).element().heap_type().top() { HeapType::Extern => { Ref::Extern(gc_ref.map(|r| ExternRef::from_cloned_gc_ref(&mut store, r))) } @@ -220,7 +220,7 @@ impl Table { } pub(crate) fn set_(&self, store: &mut StoreOpaque, index: u64, val: Ref) -> Result<()> { - let ty = self._ty(store); + let ty = self.ty_(store); match element_type(&ty) { TableElementType::Func => { let element = val.into_table_func(store, ty.element())?; @@ -250,10 +250,10 @@ impl Table { /// /// Panics if `store` does not own this table. pub fn size(&self, store: impl AsContext) -> u64 { - self._size(store.as_context().0) + self.size_(store.as_context().0) } - pub(crate) fn _size(&self, store: &StoreOpaque) -> u64 { + pub(crate) fn size_(&self, store: &StoreOpaque) -> u64 { // unwrap here should be ok because the runtime should always guarantee // that we can fit the number of elements in a 64-bit integer. u64::try_from(store[self.instance].table(self.index).current_elements).unwrap() @@ -503,7 +503,7 @@ impl Table { val: Ref, len: u64, ) -> Result<()> { - let ty = self._ty(&store); + let ty = self.ty_(&store); match element_type(&ty) { TableElementType::Func => { let val = val.into_table_func(store, ty.element())?; @@ -531,7 +531,7 @@ impl Table { #[cfg(feature = "gc")] pub(crate) fn trace_roots(&self, store: &mut StoreOpaque, gc_roots_list: &mut vm::GcRootsList) { if !self - ._ty(store) + .ty_(store) .element() .is_vmgcref_type_and_points_to_object() { diff --git a/crates/wasmtime/src/runtime/func.rs b/crates/wasmtime/src/runtime/func.rs index d9d1079d03f9..f7c65e6c1e7c 100644 --- a/crates/wasmtime/src/runtime/func.rs +++ b/crates/wasmtime/src/runtime/func.rs @@ -1057,7 +1057,15 @@ impl Func { /// This value is safe to pass to [`Func::from_raw`] so long as the same /// `store` is provided. pub fn to_raw(&self, mut store: impl AsContextMut) -> *mut c_void { - self.vm_func_ref(store.as_context_mut().0).as_ptr().cast() + self.to_raw_(store.as_context_mut().0) + } + + pub(crate) fn to_raw_(&self, store: &mut StoreOpaque) -> *mut c_void { + self.vm_func_ref(store).as_ptr().cast() + } + + pub(crate) fn to_val_raw(&self, store: &mut StoreOpaque) -> ValRaw { + ValRaw::funcref(self.to_raw_(store)) } /// Invokes this function with the `params` given, returning the results diff --git a/crates/wasmtime/src/runtime/gc/disabled/anyref.rs b/crates/wasmtime/src/runtime/gc/disabled/anyref.rs index 94915fd2248c..1a7cfdea646d 100644 --- a/crates/wasmtime/src/runtime/gc/disabled/anyref.rs +++ b/crates/wasmtime/src/runtime/gc/disabled/anyref.rs @@ -75,6 +75,10 @@ impl AnyRef { match *self {} } + pub(crate) fn _to_raw(&self, _store: &mut AutoAssertNoGc<'_>) -> Result { + match *self {} + } + pub fn ty(&self, _store: impl AsContext) -> Result { match *self {} } diff --git a/crates/wasmtime/src/runtime/gc/disabled/externref.rs b/crates/wasmtime/src/runtime/gc/disabled/externref.rs index 8a4e7a76ac17..c81e013aa52c 100644 --- a/crates/wasmtime/src/runtime/gc/disabled/externref.rs +++ b/crates/wasmtime/src/runtime/gc/disabled/externref.rs @@ -43,7 +43,7 @@ impl ExternRef { None } - pub fn _from_raw(_store: &mut AutoAssertNoGc<'_>, raw: u32) -> Option> { + pub(crate) fn _from_raw(_store: &mut AutoAssertNoGc<'_>, raw: u32) -> Option> { assert_eq!(raw, 0); None } @@ -51,4 +51,8 @@ impl ExternRef { pub fn to_raw(&self, _store: impl AsContextMut) -> Result { match *self {} } + + pub(crate) fn _to_raw(&self, _store: &mut AutoAssertNoGc<'_>) -> Result { + match *self {} + } } diff --git a/crates/wasmtime/src/runtime/linker.rs b/crates/wasmtime/src/runtime/linker.rs index 35e5cb16c01c..184442c8756e 100644 --- a/crates/wasmtime/src/runtime/linker.rs +++ b/crates/wasmtime/src/runtime/linker.rs @@ -1404,7 +1404,7 @@ impl Definition { *size = m.size(); } Definition::Extern(Extern::Table(m), DefinitionType::Table(_, size)) => { - *size = m._size(store); + *size = m.size_(store); } _ => {} } @@ -1415,7 +1415,7 @@ impl DefinitionType { pub(crate) fn from(store: &StoreOpaque, item: &Extern) -> DefinitionType { match item { Extern::Func(f) => DefinitionType::Func(f.type_index(store)), - Extern::Table(t) => DefinitionType::Table(*t.wasmtime_ty(store), t._size(store)), + Extern::Table(t) => DefinitionType::Table(*t.wasmtime_ty(store), t.size_(store)), Extern::Global(t) => DefinitionType::Global(*t.wasmtime_ty(store)), Extern::Memory(t) => { DefinitionType::Memory(*t.wasmtime_ty(store), t.internal_size(store)) diff --git a/crates/wasmtime/src/runtime/store.rs b/crates/wasmtime/src/runtime/store.rs index 557428e1e76d..67365fc3c53d 100644 --- a/crates/wasmtime/src/runtime/store.rs +++ b/crates/wasmtime/src/runtime/store.rs @@ -2109,6 +2109,7 @@ impl StoreOpaque { if asyncness != Asyncness::No { vm::Yield::new().await; } + #[cfg(feature = "stack-switching")] { self.trace_wasm_continuation_roots(gc_roots_list); @@ -2116,11 +2117,22 @@ impl StoreOpaque { vm::Yield::new().await; } } + self.trace_vmctx_roots(gc_roots_list); if asyncness != Asyncness::No { vm::Yield::new().await; } + + self.trace_instance_roots(gc_roots_list); + if asyncness != Asyncness::No { + vm::Yield::new().await; + } + self.trace_user_roots(gc_roots_list); + if asyncness != Asyncness::No { + vm::Yield::new().await; + } + self.trace_pending_exception_roots(gc_roots_list); log::trace!("End trace GC roots") @@ -2250,6 +2262,19 @@ impl StoreOpaque { log::trace!("End trace GC roots :: vmctx"); } + #[cfg(feature = "gc")] + fn trace_instance_roots(&mut self, gc_roots_list: &mut GcRootsList) { + log::trace!("Begin trace GC roots :: instance"); + for (_id, instance) in &mut self.instances { + // SAFETY: the instance's GC roots will remain valid for the + // duration of this GC cycle. + unsafe { + instance.handle.get_mut().trace_roots(gc_roots_list); + } + } + log::trace!("End trace GC roots :: instance"); + } + #[cfg(feature = "gc")] fn trace_user_roots(&mut self, gc_roots_list: &mut GcRootsList) { log::trace!("Begin trace GC roots :: user"); diff --git a/crates/wasmtime/src/runtime/values.rs b/crates/wasmtime/src/runtime/values.rs index af0b86d32afd..832652a6949a 100644 --- a/crates/wasmtime/src/runtime/values.rs +++ b/crates/wasmtime/src/runtime/values.rs @@ -265,7 +265,12 @@ impl Val { /// The returned [`ValRaw`] does not carry type information and is only safe /// to use within the context of this store itself. For more information see /// [`ExternRef::to_raw`] and [`Func::to_raw`]. - pub fn to_raw(&self, store: impl AsContextMut) -> Result { + pub fn to_raw(&self, mut store: impl AsContextMut) -> Result { + 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 { match self { Val::I32(i) => Ok(ValRaw::i32(*i)), Val::I64(i) => Ok(ValRaw::i64(*i)), @@ -274,18 +279,18 @@ impl Val { Val::V128(b) => Ok(ValRaw::v128(b.as_u128())), Val::ExternRef(e) => Ok(ValRaw::externref(match e { None => 0, - Some(e) => e.to_raw(store)?, + Some(e) => e._to_raw(store)?, })), Val::AnyRef(e) => Ok(ValRaw::anyref(match e { None => 0, - Some(e) => e.to_raw(store)?, + Some(e) => e._to_raw(store)?, })), Val::ExnRef(e) => Ok(ValRaw::exnref(match e { None => 0, - Some(e) => e.to_raw(store)?, + Some(e) => e._to_raw(store)?, })), Val::FuncRef(f) => Ok(ValRaw::funcref(match f { - Some(f) => f.to_raw(store), + Some(f) => f.to_raw_(store), None => ptr::null_mut(), })), Val::ContRef(_) => { diff --git a/crates/wasmtime/src/runtime/vm/const_expr.rs b/crates/wasmtime/src/runtime/vm/const_expr.rs index bcecb5b1863d..b865867d4176 100644 --- a/crates/wasmtime/src/runtime/vm/const_expr.rs +++ b/crates/wasmtime/src/runtime/vm/const_expr.rs @@ -33,6 +33,10 @@ impl Default for ConstExprEvaluator { /// The context within which a particular const expression is evaluated. pub struct ConstEvalContext { pub(crate) instance: InstanceId, + #[cfg_attr( + not(feature = "gc"), + expect(dead_code, reason = "easier than conditionally compiling this field") + )] pub(crate) asyncness: Asyncness, } diff --git a/crates/wasmtime/src/runtime/vm/instance.rs b/crates/wasmtime/src/runtime/vm/instance.rs index eb459f0f1798..2125cf8b6bf6 100644 --- a/crates/wasmtime/src/runtime/vm/instance.rs +++ b/crates/wasmtime/src/runtime/vm/instance.rs @@ -2,11 +2,8 @@ //! wasm module (except its callstack and register state). An //! `InstanceHandle` is a reference-counting handle for an `Instance`. -use crate::OpaqueRootScope; use crate::code::ModuleWithCode; use crate::module::ModuleRegistry; -use crate::prelude::*; -use crate::runtime::vm::const_expr::{ConstEvalContext, ConstExprEvaluator}; use crate::runtime::vm::export::{Export, ExportMemory}; use crate::runtime::vm::memory::{Memory, RuntimeMemoryCreator}; use crate::runtime::vm::table::{Table, TableElementType}; @@ -20,9 +17,11 @@ use crate::runtime::vm::{ VMStoreRawPtr, VmPtr, VmSafe, WasmFault, catch_unwind_and_record_trap, }; use crate::store::{ - Asyncness, InstanceId, StoreId, StoreInstanceId, StoreOpaque, StoreResourceLimiter, + AutoAssertNoGc, InstanceId, StoreId, StoreInstanceId, StoreOpaque, StoreResourceLimiter, }; -use crate::vm::VMWasmCallFunction; +use crate::vm::{VMWasmCallFunction, ValRaw}; +use crate::{OpaqueRootScope, Val}; +use crate::{ValType, prelude::*}; use alloc::sync::Arc; use core::alloc::Layout; use core::marker; @@ -37,8 +36,8 @@ use wasmtime_environ::ModuleInternedTypeIndex; use wasmtime_environ::error::OutOfMemory; use wasmtime_environ::{ DataIndex, DefinedGlobalIndex, DefinedMemoryIndex, DefinedTableIndex, DefinedTagIndex, - ElemIndex, EntityIndex, EntityRef, FuncIndex, GlobalIndex, HostPtr, MemoryIndex, PtrSize, - TableIndex, TableInitialValue, TableSegmentElements, TagIndex, Trap, VMCONTEXT_MAGIC, + ElemIndex, EntityIndex, EntityRef, FuncIndex, GlobalIndex, HostPtr, MemoryIndex, + NeedsGcRooting, PtrSize, TableIndex, TableInitialValue, TagIndex, Trap, VMCONTEXT_MAGIC, VMOffsets, VMSharedTypeIndex, packed_option::ReservedValue, }; #[cfg(feature = "wmemcheck")] @@ -128,9 +127,13 @@ pub struct Instance { /// table. tables: TryPrimaryMap, - /// Stores the dropped passive element segments in this instantiation by index. - /// If the index is present in the set, the segment has been dropped. - dropped_elements: TryEntitySet, + /// Evaluated passive element segments. + /// + /// If an entry is none, then it has been dropped. + // + // TODO(#12621): This should be a `TrySecondaryMap` + // but that type is currently footgun-y / isn't actually OOM-safe yet. + passive_elements: TryVec)>>, /// Stores the dropped passive data segments in this instantiation by index. /// If the index is present in the set, the segment has been dropped. @@ -171,7 +174,7 @@ impl Instance { ) -> Result { let module = req.runtime_info.env_module(); let memory_tys = &module.memories; - let dropped_elements = TryEntitySet::with_capacity(module.passive_elements.len())?; + let passive_elements = TryVec::with_capacity(module.passive_elements.len())?; let dropped_data = TryEntitySet::with_capacity(module.passive_data_map.len())?; #[cfg(feature = "wmemcheck")] @@ -195,7 +198,7 @@ impl Instance { runtime_info: req.runtime_info.clone(), memories, tables, - dropped_elements, + passive_elements, dropped_data, #[cfg(feature = "wmemcheck")] wmemcheck_state, @@ -211,6 +214,35 @@ impl Instance { Ok(ret) } + /// Trace GC roots inside this `Instance`. + /// + /// NB: This instance's `vmctx` roots are traced separately in + /// `Store::trace_vmctx_roots`. + /// + /// # Safety + /// + /// This instance must live for the duration of the associated GC cycle. + #[cfg(feature = "gc")] + pub(crate) unsafe fn trace_roots(self: Pin<&mut Self>, gc_roots: &mut crate::vm::GcRootsList) { + // SAFETY: not moving data out of `self`. + let passive_elements = &mut unsafe { self.get_unchecked_mut() }.passive_elements; + + for segment in passive_elements { + if let Some((wasmtime_environ::NeedsGcRooting::Yes, elems)) = segment { + for e in elems { + let root: SendSyncPtr = SendSyncPtr::from(e); + let root: SendSyncPtr = root.cast(); + + // Safety: We know this is a type that needs GC rooting and + // the lifetime is implied by our safety contract. + unsafe { + gc_roots.add_root(root, "passive element segment"); + } + } + } + } + } + /// Converts a raw `VMContext` pointer into a raw `Instance` pointer. /// /// # Safety @@ -902,38 +934,28 @@ impl Instance { } /// Get the passive elements segment at the given index. - /// - /// Returns an empty segment if the index is out of bounds or if the segment - /// has been dropped. - /// - /// The `storage` parameter should always be `None`; it is a bit of a hack - /// to work around lifetime issues. - pub(crate) fn passive_element_segment<'a>( - &self, - storage: &'a mut Option<(Arc, TableSegmentElements)>, - elem_index: ElemIndex, - ) -> &'a TableSegmentElements { - debug_assert!(storage.is_none()); - *storage = Some(( - // TODO: this `clone()` shouldn't be necessary but is used for now to - // inform `rustc` that the lifetime of the elements here are - // disconnected from the lifetime of `self`. - self.env_module().clone(), - // NB: fall back to an expressions-based list of elements which - // doesn't have static type information (as opposed to - // `TableSegmentElements::Functions`) since we don't know what type - // is needed in the caller's context. Let the type be inferred by - // how they use the segment. - TableSegmentElements::Expressions(Box::new([])), - )); - let (module, empty) = storage.as_ref().unwrap(); - - match module.passive_elements_map.get(&elem_index) { - Some(index) if !self.dropped_elements.contains(elem_index) => { - &module.passive_elements[*index] - } - _ => empty, - } + pub(crate) fn passive_element_segment(&self, elem_index: ElemIndex) -> &[ValRaw] { + let Some(passive) = self + .env_module() + .passive_elements_map + .get(&elem_index) + .copied() + else { + return &[]; + }; + + let Some((_, seg)) = &self.passive_elements[passive.index()] else { + return &[]; + }; + + &**seg + } + + pub(crate) fn passive_elements_mut( + self: Pin<&mut Self>, + ) -> Pin<&mut TryVec)>>> { + // SAFETY: Not moving data out of `self`. + Pin::new(&mut unsafe { self.get_unchecked_mut() }.passive_elements) } /// The `table.init` operation: initializes a portion of a table with a @@ -943,99 +965,61 @@ impl Instance { /// /// Returns a `Trap` error when the range within the table is out of bounds /// or the range within the passive element is out of bounds. - pub(crate) async fn table_init( + pub(crate) fn table_init( store: &mut StoreOpaque, - limiter: Option<&mut StoreResourceLimiter<'_>>, - asyncness: Asyncness, - instance: InstanceId, + instance_id: InstanceId, table_index: TableIndex, elem_index: ElemIndex, dst: u64, src: u64, len: u64, ) -> Result<()> { - let mut storage = None; - let elements = store - .instance(instance) - .passive_element_segment(&mut storage, elem_index); - let mut const_evaluator = ConstExprEvaluator::default(); - Self::table_init_segment( - store, - limiter, - asyncness, - instance, - &mut const_evaluator, - table_index, - elements, - dst, - src, - len, - ) - .await - } - - pub(crate) async fn table_init_segment( - store: &mut StoreOpaque, - mut limiter: Option<&mut StoreResourceLimiter<'_>>, - asyncness: Asyncness, - elements_instance_id: InstanceId, - const_evaluator: &mut ConstExprEvaluator, - table_index: TableIndex, - elements: &TableSegmentElements, - dst: u64, - src: u64, - len: u64, - ) -> Result<()> { - // https://webassembly.github.io/bulk-memory-operations/core/exec/instructions.html#exec-table-init - + let mut store = OpaqueRootScope::new(store); let store_id = store.id(); - let elements_instance = store.instance_mut(elements_instance_id); - let table = elements_instance.get_exported_table(store_id, table_index); - let table_size = table._size(store); - - // Perform a bounds check on the table being written to. This is done by - // ensuring that `dst + len <= table.size()` via checked arithmetic. - // - // Note that the bounds check for the element segment happens below when - // the original segment is sliced via `src` and `len`. - table_size - .checked_sub(dst) - .and_then(|i| i.checked_sub(len)) - .ok_or(Trap::TableOutOfBounds)?; + let instance = store.instance(instance_id); + let elements = instance.passive_element_segment(elem_index); + let end = dst.checked_add(len).ok_or_else(|| Trap::TableOutOfBounds)?; let src = usize::try_from(src).map_err(|_| Trap::TableOutOfBounds)?; let len = usize::try_from(len).map_err(|_| Trap::TableOutOfBounds)?; - let positions = dst..dst + u64::try_from(len).unwrap(); - match elements { - TableSegmentElements::Functions(funcs) => { - let elements = funcs - .get(src..) - .and_then(|s| s.get(..len)) - .ok_or(Trap::TableOutOfBounds)?; - for (i, func_idx) in positions.zip(elements) { - let (instance, registry) = - store.instance_and_module_registry_mut(elements_instance_id); - // SAFETY: the `store_id` passed to `get_exported_func` is - // indeed the store that owns the function. - let func = unsafe { instance.get_exported_func(registry, store_id, *func_idx) }; - table.set_(store, i, func.into()).unwrap(); - } - } - TableSegmentElements::Expressions(exprs) => { - let mut store = OpaqueRootScope::new(store); - let exprs = exprs - .get(src..) - .and_then(|s| s.get(..len)) - .ok_or(Trap::TableOutOfBounds)?; - let mut context = ConstEvalContext::new(elements_instance_id, asyncness); - for (i, expr) in positions.zip(exprs) { - let element = const_evaluator - .eval(&mut store, limiter.as_deref_mut(), &mut context, expr) - .await?; - table.set_(&mut store, i, element.ref_().unwrap()).unwrap(); - } - } + let table = instance.get_exported_table(store_id, table_index); + if end > table.size_(&store) { + return Err(Trap::TableOutOfBounds.into()); + } + + // Subslice into just the target elements. + let elements = elements + .get(src..) + .and_then(|elements| elements.get(..len)) + .ok_or_else(|| Trap::TableOutOfBounds)? + .iter() + .copied() + .try_collect::, OutOfMemory>()?; + + let elem_ty = ValType::from(table.ty_(&store).element().clone()); + + let refs = { + let mut store = AutoAssertNoGc::new(&mut store); + elements + .into_iter() + // SAFETY: the raw elements are valid because we got them from + // this instance. + .map(|raw| unsafe { Val::_from_raw(&mut store, raw, &elem_ty) }) + .map(|v| v.ref_().expect("due to validation")) + .try_collect::, OutOfMemory>()? + }; + + let instance = store.instance(instance_id); + let table = instance.get_exported_table(store_id, table_index); + + for (i, r) in refs.into_iter().enumerate() { + let i = u64::try_from(i) + .expect("okay because of `src` and `len` conversions to `usize` up above"); + let j = i + .checked_add(dst) + .expect("okay because of `checked_add` up above"); + table.set_(&mut store, j, r)?; } Ok(()) @@ -1048,11 +1032,17 @@ impl Instance { ) -> Result<(), OutOfMemory> { // https://webassembly.github.io/reference-types/core/exec/instructions.html#exec-elem-drop - self.dropped_elements_mut().insert(elem_index)?; - - // Note that we don't check that we actually removed a segment because - // dropping a non-passive segment is a no-op (not a trap). + let Some(passive_index) = self + .env_module() + .passive_elements_map + .get(&elem_index) + .copied() + else { + // Note: dropping a non-passive segment is a no-op (not a trap). + return Ok(()); + }; + self.passive_elements_mut()[passive_index.index()] = None; Ok(()) } @@ -1626,11 +1616,6 @@ impl Instance { unsafe { &mut self.get_unchecked_mut().store } } - fn dropped_elements_mut(self: Pin<&mut Self>) -> &mut TryEntitySet { - // SAFETY: see `store_mut` above. - unsafe { &mut self.get_unchecked_mut().dropped_elements } - } - fn dropped_data_mut(self: Pin<&mut Self>) -> &mut TryEntitySet { // SAFETY: see `store_mut` above. unsafe { &mut self.get_unchecked_mut().dropped_data } diff --git a/crates/wasmtime/src/runtime/vm/instance/allocator.rs b/crates/wasmtime/src/runtime/vm/instance/allocator.rs index 9b70b73e8b63..ba607f6f8da4 100644 --- a/crates/wasmtime/src/runtime/vm/instance/allocator.rs +++ b/crates/wasmtime/src/runtime/vm/instance/allocator.rs @@ -6,14 +6,15 @@ use crate::runtime::vm::memory::Memory; use crate::runtime::vm::mpk::ProtectionKey; use crate::runtime::vm::table::Table; use crate::runtime::vm::{CompiledModuleId, ModuleRuntimeInfo}; -use crate::store::{Asyncness, InstanceId, StoreOpaque, StoreResourceLimiter}; +use crate::store::{Asyncness, AutoAssertNoGc, InstanceId, StoreOpaque, StoreResourceLimiter}; use crate::{OpaqueRootScope, Val}; use core::future::Future; use core::pin::Pin; use core::{mem, ptr}; use wasmtime_environ::{ - DefinedMemoryIndex, DefinedTableIndex, HostPtr, InitMemory, MemoryInitialization, - MemoryInitializer, Module, SizeOverflow, TableInitialValue, Trap, VMOffsets, + DefinedMemoryIndex, DefinedTableIndex, EntityRef, HostPtr, InitMemory, MemoryInitialization, + MemoryInitializer, Module, NeedsGcRooting, SizeOverflow, TableInitialValue, + TableSegmentElements, Trap, VMOffsets, }; #[cfg(feature = "gc")] @@ -547,7 +548,7 @@ async fn initialize_tables( let table = store .instance_mut(context.instance) .get_exported_table(id, idx); - let size = table._size(&store); + let size = table.size_(&store); table._fill(&mut store, 0, init.ref_().unwrap(), size)?; } } @@ -565,19 +566,48 @@ async fn initialize_tables( .eval_int(&mut store, context, &segment.offset) .expect("const expression should be valid"); let start = get_index(start, module.tables[segment.table_index].idx_type); - Instance::table_init_segment( - &mut store, - limiter.as_deref_mut(), - context.asyncness, - context.instance, - const_evaluator, - segment.table_index, - &segment.elements, - start, - 0, - segment.elements.len(), - ) - .await?; + + let end = start + .checked_add(segment.elements.len()) + .ok_or_else(|| Trap::TableOutOfBounds)?; + + let store_id = store.id(); + let table = { + let instance = store.instance(context.instance); + instance.get_exported_table(store_id, segment.table_index) + }; + + if end > table.size_(&store) { + return Err(Trap::TableOutOfBounds.into()); + } + + let positions = start..end; + + match &segment.elements { + TableSegmentElements::Functions(funcs) => { + for (i, func_idx) in positions.zip(funcs) { + let func = { + let (instance, registry) = + store.instance_and_module_registry_mut(context.instance); + // SAFETY: the `store_id` passed to `get_exported_func` is + // indeed the store that owns the function. + unsafe { instance.get_exported_func(registry, store_id, *func_idx) } + }; + table.set_(&mut store, i, func.into())?; + } + } + TableSegmentElements::Expressions { + exprs, + needs_gc_rooting: _, + } => { + for (i, expr) in positions.zip(exprs) { + let val = const_evaluator + .eval(&mut store, limiter.as_deref_mut(), context, expr) + .await?; + table.set_(&mut store, i, val.ref_().unwrap())?; + } + } + } } Ok(()) @@ -811,6 +841,69 @@ async fn initialize_globals( Ok(()) } +async fn initialize_passive_elements( + store: &mut StoreOpaque, + mut limiter: Option<&mut StoreResourceLimiter<'_>>, + context: &mut ConstEvalContext, + const_evaluator: &mut ConstExprEvaluator, + module: &Module, +) -> Result<()> { + let store_id = store.id(); + + let instance = store.instance_mut(context.instance); + debug_assert!(instance.passive_elements.is_empty()); + instance + .passive_elements_mut() + .reserve(module.passive_elements.len())?; + + for (idx, segment) in &module.passive_elements { + match segment { + TableSegmentElements::Functions(func_indices) => { + let mut vals = TryVec::with_capacity(func_indices.len())?; + for func_idx in func_indices { + let (instance, registry) = + store.instance_and_module_registry_mut(context.instance); + // SAFETY: `store_id` is for the store that owns this instance. + let func = unsafe { instance.get_exported_func(registry, store_id, *func_idx) }; + vals.push(func.to_val_raw(store))?; + } + let instance = store.instance_mut(context.instance); + debug_assert_eq!(instance.passive_elements.len(), idx.index()); + instance + .passive_elements_mut() + .push(Some((NeedsGcRooting::No, vals)))?; + } + TableSegmentElements::Expressions { + needs_gc_rooting, + exprs, + } => { + let mut vals = TryVec::with_capacity(exprs.len())?; + for expr in exprs { + let mut store = OpaqueRootScope::new(&mut *store); + + let val = const_evaluator + .eval(&mut store, limiter.as_deref_mut(), context, expr) + .await?; + + let mut store = AutoAssertNoGc::new(&mut store); + vals.push(val.to_raw_(&mut store)?)?; + } + let instance = store.instance_mut(context.instance); + debug_assert_eq!(instance.passive_elements.len(), idx.index()); + instance + .passive_elements_mut() + .push(Some((*needs_gc_rooting, vals)))?; + } + } + } + + debug_assert_eq!( + module.passive_elements.len(), + store.instance(context.instance).passive_elements.len() + ); + Ok(()) +} + pub async fn initialize_instance( store: &mut StoreOpaque, mut limiter: Option<&mut StoreResourceLimiter<'_>>, @@ -838,6 +931,7 @@ pub async fn initialize_instance( module, ) .await?; + initialize_tables( store, limiter.as_deref_mut(), @@ -846,8 +940,14 @@ pub async fn initialize_instance( module, ) .await?; + initialize_memories(store, &mut context, &mut const_evaluator, &module)?; + if is_bulk_memory { + initialize_passive_elements(store, limiter, &mut context, &mut const_evaluator, &module) + .await?; + } + Ok(()) } diff --git a/crates/wasmtime/src/runtime/vm/libcalls.rs b/crates/wasmtime/src/runtime/vm/libcalls.rs index 186d1c02acd8..03217fedc0d6 100644 --- a/crates/wasmtime/src/runtime/vm/libcalls.rs +++ b/crates/wasmtime/src/runtime/vm/libcalls.rs @@ -521,22 +521,7 @@ fn table_init( ) -> Result<()> { let table_index = TableIndex::from_u32(table_index); let elem_index = ElemIndex::from_u32(elem_index); - - let (mut limiter, store) = store.resource_limiter_and_store_opaque(); - block_on!(store, async |store, asyncness| { - vm::Instance::table_init( - store, - limiter.as_mut(), - asyncness, - instance, - table_index, - elem_index, - dst, - src, - len, - ) - .await - })??; + vm::Instance::table_init(store, instance, table_index, elem_index, dst, src, len)?; Ok(()) } @@ -972,67 +957,53 @@ fn array_new_elem( len: u32, ) -> Result { use crate::{ - ArrayRef, ArrayRefPre, ArrayType, Func, OpaqueRootScope, RootedGcRefImpl, Val, + ArrayRef, ArrayRefPre, ArrayType, OpaqueRootScope, RootedGcRefImpl, Val, store::AutoAssertNoGc, - vm::const_expr::{ConstEvalContext, ConstExprEvaluator}, }; - use wasmtime_environ::{ModuleInternedTypeIndex, TableSegmentElements}; + use wasmtime_environ::ModuleInternedTypeIndex; // Convert indices to their typed forms. let array_type_index = ModuleInternedTypeIndex::from_u32(array_type_index); let elem_index = ElemIndex::from_u32(elem_index); let instance = store.instance(instance_id); - let mut storage = None; - let elements = instance.passive_element_segment(&mut storage, elem_index); + let elements = instance.passive_element_segment(elem_index); let src = usize::try_from(src).map_err(|_| Trap::TableOutOfBounds)?; let len = usize::try_from(len).map_err(|_| Trap::TableOutOfBounds)?; + let elements = elements + .get(src..) + .and_then(|elements| elements.get(..len)) + .ok_or_else(|| Trap::TableOutOfBounds)? + .iter() + .copied() + .try_collect::, OutOfMemory>()?; + let shared_ty = instance.engine_type_index(array_type_index); let array_ty = ArrayType::from_shared_type_index(store.engine(), shared_ty); + let elem_ty = match array_ty.element_type() { + crate::StorageType::ValType(ty) => ty, + _ => unreachable!("due to validation"), + }; + let pre = ArrayRefPre::_new(store, array_ty); let (mut limiter, store) = store.resource_limiter_and_store_opaque(); block_on!(store, async |store, asyncness| { let mut store = OpaqueRootScope::new(store); + // Turn the elements into `Val`s. - let mut vals = Vec::with_capacity(usize::try_from(elements.len()).unwrap()); - match elements { - TableSegmentElements::Functions(fs) => { - let store_id = store.id(); - let (mut instance, registry) = store.instance_and_module_registry_mut(instance_id); - vals.extend( - fs.get(src..) - .and_then(|s| s.get(..len)) - .ok_or_else(|| Trap::TableOutOfBounds)? - .iter() - .map(|f| { - let raw_func_ref = instance.as_mut().get_func_ref(registry, *f); - let func = unsafe { - raw_func_ref.map(|p| Func::from_vm_func_ref(store_id, p)) - }; - Val::FuncRef(func) - }), - ); - } - TableSegmentElements::Expressions(xs) => { - let xs = xs - .get(src..) - .and_then(|s| s.get(..len)) - .ok_or_else(|| Trap::TableOutOfBounds)?; - - let mut const_context = ConstEvalContext::new(instance_id, asyncness); - let mut const_evaluator = ConstExprEvaluator::default(); - - for x in xs.iter() { - let val = *const_evaluator - .eval(&mut store, limiter.as_mut(), &mut const_context, x) - .await?; - vals.push(val); - } - } - } + // + // Safety: Validation ensures that the type is correct; `raw` is valid + // because we got it from this instance. + let vals = { + let mut store = AutoAssertNoGc::new(&mut store); + elements + .into_iter() + .map(|raw| unsafe { Val::_from_raw(&mut store, raw, &elem_ty) }) + .try_collect::, OutOfMemory>()? + }; let array = ArrayRef::_new_fixed_async(&mut store, limiter.as_mut(), &pre, &vals, asyncness) @@ -1056,91 +1027,77 @@ fn array_init_elem( src: u32, len: u32, ) -> Result<()> { - use crate::{ - ArrayRef, Func, OpaqueRootScope, Val, - store::AutoAssertNoGc, - vm::const_expr::{ConstEvalContext, ConstExprEvaluator}, - }; - use wasmtime_environ::{ModuleInternedTypeIndex, TableSegmentElements}; + use crate::{ArrayRef, OpaqueRootScope, Val, store::AutoAssertNoGc}; + use wasmtime_environ::ModuleInternedTypeIndex; - let (mut limiter, store) = store.resource_limiter_and_store_opaque(); - block_on!(store, async |store, asyncness| { - let mut store = OpaqueRootScope::new(store); + let mut store = OpaqueRootScope::new(store); - // Convert the indices into their typed forms. - let _array_type_index = ModuleInternedTypeIndex::from_u32(array_type_index); - let elem_index = ElemIndex::from_u32(elem_index); + // Convert the indices into their typed forms. + let array_type_index = ModuleInternedTypeIndex::from_u32(array_type_index); + let elem_index = ElemIndex::from_u32(elem_index); - log::trace!( - "array.init_elem(array={array:#x}, dst={dst}, elem_index={elem_index:?}, src={src}, len={len})", - ); + log::trace!( + "array.init_elem(array={array:#x}, dst={dst}, elem_index={elem_index:?}, src={src}, len={len})", + ); - // Convert the raw GC ref into a `Rooted`. - let array = VMGcRef::from_raw_u32(array).ok_or_else(|| Trap::NullReference)?; - let array = store.unwrap_gc_store_mut().clone_gc_ref(&array); - let array = { - let mut no_gc = AutoAssertNoGc::new(&mut store); - ArrayRef::from_cloned_gc_ref(&mut no_gc, array) - }; + // Convert the raw GC ref into a `Rooted`. + let array = VMGcRef::from_raw_u32(array).ok_or_else(|| Trap::NullReference)?; + let array = store.unwrap_gc_store_mut().clone_gc_ref(&array); + let array = { + let mut no_gc = AutoAssertNoGc::new(&mut store); + ArrayRef::from_cloned_gc_ref(&mut no_gc, array) + }; - // Bounds check the destination within the array. - let array_len = array._len(&store)?; - log::trace!("array_len = {array_len}"); - if dst.checked_add(len).ok_or_else(|| Trap::ArrayOutOfBounds)? > array_len { - return Err(Trap::ArrayOutOfBounds.into()); - } + // Bounds check the destination within the array. + let array_len = array._len(&store)?; + log::trace!("array_len = {array_len}"); + if dst.checked_add(len).ok_or_else(|| Trap::ArrayOutOfBounds)? > array_len { + return Err(Trap::ArrayOutOfBounds.into()); + } - // Get the passive element segment. - let mut storage = None; - let store_id = store.id(); - let (mut instance, registry) = store.instance_and_module_registry_mut(instance); - let elements = instance.passive_element_segment(&mut storage, elem_index); + // Get the passive element segment. + let instance = store.instance(instance); + let elements = instance.passive_element_segment(elem_index); - // Convert array offsets into `usize`s. - let src = usize::try_from(src).map_err(|_| Trap::TableOutOfBounds)?; - let len = usize::try_from(len).map_err(|_| Trap::TableOutOfBounds)?; + // Convert array offsets into `usize`s. + let src = usize::try_from(src).map_err(|_| Trap::TableOutOfBounds)?; + let len = usize::try_from(len).map_err(|_| Trap::TableOutOfBounds)?; - // Turn the elements into `Val`s. - let vals = match elements { - TableSegmentElements::Functions(fs) => fs - .get(src..) - .and_then(|s| s.get(..len)) - .ok_or_else(|| Trap::TableOutOfBounds)? - .iter() - .map(|f| { - let raw_func_ref = instance.as_mut().get_func_ref(registry, *f); - let func = unsafe { raw_func_ref.map(|p| Func::from_vm_func_ref(store_id, p)) }; - Val::FuncRef(func) - }) - .collect::>(), - TableSegmentElements::Expressions(xs) => { - let mut const_context = ConstEvalContext::new(instance.id(), asyncness); - let mut const_evaluator = ConstExprEvaluator::default(); - - let mut vals = Vec::new(); - for x in xs - .get(src..) - .and_then(|s| s.get(..len)) - .ok_or_else(|| Trap::TableOutOfBounds)? - { - let val = *const_evaluator - .eval(&mut store, limiter.as_mut(), &mut const_context, x) - .await?; - vals.push(val); - } - vals - } - }; + // Subslice into just the target elements. + let elements = elements + .get(src..) + .and_then(|elements| elements.get(..len)) + .ok_or_else(|| Trap::TableOutOfBounds)? + .iter() + .copied() + .try_collect::, OutOfMemory>()?; - // Copy the values into the array. - for (i, val) in vals.into_iter().enumerate() { - let i = u32::try_from(i).unwrap(); - let j = dst.checked_add(i).unwrap(); - array._set(&mut store, j, val)?; - } + let shared_ty = instance.engine_type_index(array_type_index); + let array_ty = crate::ArrayType::from_shared_type_index(store.engine(), shared_ty); + let elem_ty = match array_ty.element_type() { + crate::StorageType::ValType(ty) => ty, + _ => unreachable!("due to validation"), + }; - Ok(()) - })? + // Turn the elements into `Val`s. + let vals = { + let mut store = AutoAssertNoGc::new(&mut store); + elements + .into_iter() + // Safety: Validation ensures that the type is correct; `raw` is + // valid because we got it from this instance. + .map(|raw| unsafe { Val::_from_raw(&mut store, raw, &elem_ty) }) + .try_collect::, OutOfMemory>()? + }; + + // Copy the values into the array. + for (i, val) in vals.into_iter().enumerate() { + let i = u32::try_from(i).unwrap(); + let j = dst.checked_add(i).unwrap(); + array._set(&mut store, j, val)?; + } + + Ok(()) } // TODO: Specialize this libcall for only non-GC array elements, so we never diff --git a/tests/all/gc.rs b/tests/all/gc.rs index a49ada513380..b4decf9d2393 100644 --- a/tests/all/gc.rs +++ b/tests/all/gc.rs @@ -1569,7 +1569,7 @@ fn owned_rooted_lots_of_root_creation() -> Result<()> { #[test] #[cfg_attr(miri, ignore)] -fn runtime_table_init_oom() -> Result<()> { +fn instantiate_global_init_oom() -> Result<()> { let mut config = Config::new(); config.wasm_gc(true); config.wasm_function_references(true); @@ -1584,28 +1584,13 @@ fn runtime_table_init_oom() -> Result<()> { r#" (module (table 100 arrayref) - (type $a (array i31ref)) - - (func (export "run") - i32.const 0 - i32.const 0 - i32.const 5 - table.init $e) - (elem $e arrayref - (array.new_default $a (i32.const 100)) - (array.new_default $a (i32.const 10000)) - (array.new_default $a (i32.const 10000)) - (array.new_default $a (i32.const 10000)) - (array.new_default $a (i32.const 1000000)) - ) + (global (ref $a) (array.new_default $a (i32.const 10000000))) ) "#, )?; - let instance = Instance::new(&mut store, &module, &[])?; - let func = instance.get_typed_func::<(), ()>(&mut store, "run")?; - func.call(&mut store, ()) + Instance::new(&mut store, &module, &[]) .unwrap_err() .downcast::>()?; @@ -1614,7 +1599,7 @@ fn runtime_table_init_oom() -> Result<()> { #[test] #[cfg_attr(miri, ignore)] -fn instantiate_table_init_oom() -> Result<()> { +fn elem_const_eval_oom() -> Result<()> { let mut config = Config::new(); config.wasm_gc(true); config.wasm_function_references(true); @@ -1628,11 +1613,8 @@ fn instantiate_table_init_oom() -> Result<()> { store.engine(), r#" (module - (table 100 arrayref) - (type $a (array i31ref)) - - (elem (i32.const 0) arrayref + (elem arrayref (array.new_default $a (i32.const 100)) (array.new_default $a (i32.const 10000)) (array.new_default $a (i32.const 10000)) @@ -1650,157 +1632,6 @@ fn instantiate_table_init_oom() -> Result<()> { Ok(()) } -#[test] -#[cfg_attr(miri, ignore)] -fn instantiate_table_init_expr_oom() -> Result<()> { - let mut config = Config::new(); - config.wasm_gc(true); - config.wasm_function_references(true); - config.memory_may_move(false); - config.memory_reservation(64 << 10); - config.memory_reservation_for_growth(0); - let engine = Engine::new(&config)?; - let mut store = Store::new(&engine, ()); - - let module = Module::new( - store.engine(), - r#" - (module - (type $a (array i31ref)) - (table 100 (ref $a) (array.new_default $a (i32.const 100000))) - ) - "#, - )?; - - Instance::new(&mut store, &module, &[]) - .unwrap_err() - .downcast::>()?; - - Ok(()) -} - -#[test] -#[cfg_attr(miri, ignore)] -fn instantiate_global_init_oom() -> Result<()> { - let mut config = Config::new(); - config.wasm_gc(true); - config.wasm_function_references(true); - config.memory_may_move(false); - config.memory_reservation(64 << 10); - config.memory_reservation_for_growth(0); - let engine = Engine::new(&config)?; - let mut store = Store::new(&engine, ()); - - let module = Module::new( - store.engine(), - r#" - (module - (table 100 arrayref) - (type $a (array i31ref)) - (global (ref $a) (array.new_default $a (i32.const 10000000))) - ) - "#, - )?; - - Instance::new(&mut store, &module, &[]) - .unwrap_err() - .downcast::>()?; - - Ok(()) -} - -#[test] -#[cfg_attr(miri, ignore)] -fn array_new_elem_oom() -> Result<()> { - let mut config = Config::new(); - config.wasm_gc(true); - config.wasm_function_references(true); - config.memory_may_move(false); - config.memory_reservation(64 << 10); - config.memory_reservation_for_growth(0); - let engine = Engine::new(&config)?; - let mut store = Store::new(&engine, ()); - - let module = Module::new( - store.engine(), - r#" - (module - (type $a (array (mut arrayref))) - (type $i (array i31ref)) - - (func (export "run") - i32.const 0 - i32.const 5 - array.new_elem $a $e - drop) - - (elem $e arrayref - (array.new_default $i (i32.const 100)) - (array.new_default $i (i32.const 10000)) - (array.new_default $i (i32.const 10000)) - (array.new_default $i (i32.const 10000)) - (array.new_default $i (i32.const 1000000)) - ) - ) - "#, - )?; - - let instance = Instance::new(&mut store, &module, &[])?; - let func = instance.get_typed_func::<(), ()>(&mut store, "run")?; - func.call(&mut store, ()) - .unwrap_err() - .downcast::>()?; - - Ok(()) -} - -#[test] -#[cfg_attr(miri, ignore)] -fn array_init_elem_oom() -> Result<()> { - let mut config = Config::new(); - config.wasm_gc(true); - config.wasm_function_references(true); - config.memory_may_move(false); - config.memory_reservation(64 << 10); - config.memory_reservation_for_growth(0); - let engine = Engine::new(&config)?; - let mut store = Store::new(&engine, ()); - - let module = Module::new( - store.engine(), - r#" - (module - (type $a (array (mut arrayref))) - (type $i (array i31ref)) - - (func (export "run") - i32.const 5 - array.new_default $a - i32.const 0 - i32.const 0 - i32.const 5 - array.init_elem $a $e) - - (elem $e arrayref - (array.new_default $i (i32.const 100)) - (array.new_default $i (i32.const 10000)) - (array.new_default $i (i32.const 10000)) - (array.new_default $i (i32.const 10000)) - (array.new_default $i (i32.const 1000000)) - ) - ) - "#, - )?; - - let instance = Instance::new(&mut store, &module, &[])?; - let func = instance.get_typed_func::<(), ()>(&mut store, "run")?; - func.call(&mut store, ()) - .unwrap_err() - .downcast::>()?; - - Ok(()) -} - // The result of a `select` instruction with a GC reference type should be // declared as needing a stack map. #[test] diff --git a/tests/misc_testsuite/gc/eval-const-expr-once.wast b/tests/misc_testsuite/gc/eval-const-expr-once.wast new file mode 100644 index 000000000000..a06926b8fb3b --- /dev/null +++ b/tests/misc_testsuite/gc/eval-const-expr-once.wast @@ -0,0 +1,24 @@ +;;! gc = true +;;! bulk_memory = true + +;; Test that we only evaluate a const expression once, even if it is referenced +;; by e.g. `array.new_elem` many times. +(module + (type $arr (array (mut arrayref))) + + (elem $elem arrayref + (item (array.new_default $arr (i32.const 0)))) + + (func (export "run") (result i32) + (local $a (ref null $arr)) + (local $b (ref null $arr)) + + (local.set $a (array.new_elem $arr $elem (i32.const 0) (i32.const 1))) + (local.set $b (array.new_elem $arr $elem (i32.const 0) (i32.const 1))) + + (ref.eq (array.get $arr (local.get $a) (i32.const 0)) + (array.get $arr (local.get $b) (i32.const 0))) + ) +) + +(assert_return (invoke "run") (i32.const 1))