diff --git a/crates/arena/src/lib.rs b/crates/arena/src/lib.rs index 785dba07d1..df4d7f49a7 100644 --- a/crates/arena/src/lib.rs +++ b/crates/arena/src/lib.rs @@ -32,6 +32,7 @@ mod tests; pub use self::{component_vec::ComponentVec, dedup::DedupArena, guarded::GuardedEntity}; use alloc::vec::Vec; use core::{ + cmp::max, iter::{DoubleEndedIterator, Enumerate, ExactSizeIterator}, marker::PhantomData, ops::{Index, IndexMut}, @@ -149,6 +150,21 @@ where pub fn get_mut(&mut self, index: Idx) -> Option<&mut T> { self.entities.get_mut(index.into_usize()) } + + /// Returns an exclusive reference to the pair of entities at the given indices if any. + /// + /// Returns `None` if `fst` and `snd` refer to the same entity. + /// Returns `None` if either `fst` or `snd` is invalid for this [`Arena`]. + #[inline] + pub fn get_pair_mut(&mut self, fst: Idx, snd: Idx) -> Option<(&mut T, &mut T)> { + let fst_index = fst.into_usize(); + let snd_index = snd.into_usize(); + let max_index = max(fst_index, snd_index); + let (fst_set, snd_set) = self.entities.split_at_mut(max_index); + let fst = fst_set.get_mut(fst_index)?; + let snd = snd_set.get_mut(0)?; + Some((fst, snd)) + } } impl FromIterator for Arena { diff --git a/crates/wasmi/src/element.rs b/crates/wasmi/src/element.rs new file mode 100644 index 0000000000..c4251bc00d --- /dev/null +++ b/crates/wasmi/src/element.rs @@ -0,0 +1,100 @@ +use crate::{module, module::FuncIdx, store::Stored, AsContextMut}; +use alloc::sync::Arc; +use wasmi_arena::ArenaIndex; + +/// A raw index to a element segment entity. +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct ElementSegmentIdx(u32); + +impl ArenaIndex for ElementSegmentIdx { + fn into_usize(self) -> usize { + self.0 as usize + } + + fn from_usize(value: usize) -> Self { + let value = value.try_into().unwrap_or_else(|error| { + panic!("index {value} is out of bounds as element segment index: {error}") + }); + Self(value) + } +} + +/// A Wasm data segment reference. +#[derive(Debug, Copy, Clone)] +#[repr(transparent)] +pub struct ElementSegment(Stored); + +impl ElementSegment { + /// Creates a new linear memory reference. + pub fn from_inner(stored: Stored) -> Self { + Self(stored) + } + + /// Returns the underlying stored representation. + pub fn as_inner(&self) -> &Stored { + &self.0 + } + + /// Allocates a new [`ElementSegment`] on the store. + /// + /// # Errors + /// + /// If more than [`u32::MAX`] much linear memory is allocated. + pub fn new(mut ctx: impl AsContextMut, segment: &module::ElementSegment) -> Self { + let entity = ElementSegmentEntity::from(segment); + ctx.as_context_mut() + .store + .inner + .alloc_element_segment(entity) + } +} + +/// An instantiated [`ElementSegmentEntity`]. +/// +/// # Note +/// +/// With the `bulk-memory` Wasm proposal it is possible to interact +/// with element segments at runtime. Therefore Wasm instances now have +/// a need to have an instantiated representation of data segments. +#[derive(Debug)] +pub struct ElementSegmentEntity { + /// The underlying items of the instance element segment. + /// + /// # Note + /// + /// These items are just readable after instantiation. + /// Using Wasm `elem.drop` simply replaces the instance + /// with an empty one. + items: Option]>>, +} + +impl From<&'_ module::ElementSegment> for ElementSegmentEntity { + fn from(segment: &'_ module::ElementSegment) -> Self { + match segment.kind() { + module::ElementSegmentKind::Passive => Self { + items: Some(segment.clone_items()), + }, + module::ElementSegmentKind::Active(_) => Self::empty(), + } + } +} + +impl ElementSegmentEntity { + /// Create an empty [`ElementSegmentEntity`] representing dropped element segments. + fn empty() -> Self { + Self { items: None } + } + + /// Returns the items of the [`ElementSegmentEntity`]. + pub fn items(&self) -> &[Option] { + self.items + .as_ref() + .map(|items| &items[..]) + .unwrap_or_else(|| &[]) + } + + /// Drops the items of the [`ElementSegmentEntity`]. + pub fn drop_items(&mut self) { + self.items = None; + } +} diff --git a/crates/wasmi/src/engine/bytecode/mod.rs b/crates/wasmi/src/engine/bytecode/mod.rs index dbdd7ffb88..c010d688dd 100644 --- a/crates/wasmi/src/engine/bytecode/mod.rs +++ b/crates/wasmi/src/engine/bytecode/mod.rs @@ -8,8 +8,10 @@ mod tests; pub use self::utils::{ BranchOffset, BranchParams, + DataSegmentIdx, DropKeep, DropKeepError, + ElementSegmentIdx, FuncIdx, GlobalIdx, LocalDepth, @@ -70,6 +72,13 @@ pub enum Instruction { I64Store32(Offset), MemorySize, MemoryGrow, + MemoryFill, + MemoryCopy, + MemoryInit(DataSegmentIdx), + DataDrop(DataSegmentIdx), + TableCopy, + TableInit(ElementSegmentIdx), + ElemDrop(ElementSegmentIdx), Const(UntypedValue), I32Eqz, I32Eq, diff --git a/crates/wasmi/src/engine/bytecode/utils.rs b/crates/wasmi/src/engine/bytecode/utils.rs index 4a6257a6a1..478dd76e27 100644 --- a/crates/wasmi/src/engine/bytecode/utils.rs +++ b/crates/wasmi/src/engine/bytecode/utils.rs @@ -130,7 +130,7 @@ impl LocalDepth { /// /// Refers to a global variable of a [`Store`]. /// -/// [`Store`]: [`crate::v2::Store`] +/// [`Store`]: [`crate::Store`] #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[repr(transparent)] pub struct GlobalIdx(u32); @@ -148,6 +148,54 @@ impl GlobalIdx { } } +/// A data segment index. +/// +/// # Note +/// +/// Refers to a data segment of a [`Store`]. +/// +/// [`Store`]: [`crate::Store`] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[repr(transparent)] +pub struct DataSegmentIdx(u32); + +impl From for DataSegmentIdx { + fn from(index: u32) -> Self { + Self(index) + } +} + +impl DataSegmentIdx { + /// Returns the inner `u32` index. + pub fn into_inner(self) -> u32 { + self.0 + } +} + +/// An element segment index. +/// +/// # Note +/// +/// Refers to a data segment of a [`Store`]. +/// +/// [`Store`]: [`crate::Store`] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[repr(transparent)] +pub struct ElementSegmentIdx(u32); + +impl From for ElementSegmentIdx { + fn from(index: u32) -> Self { + Self(index) + } +} + +impl ElementSegmentIdx { + /// Returns the inner `u32` index. + pub fn into_inner(self) -> u32 { + self.0 + } +} + /// A linear memory access offset. /// /// # Note diff --git a/crates/wasmi/src/engine/cache.rs b/crates/wasmi/src/engine/cache.rs index 5ac09c30a9..b315ca7387 100644 --- a/crates/wasmi/src/engine/cache.rs +++ b/crates/wasmi/src/engine/cache.rs @@ -1,5 +1,9 @@ use crate::{ + element::{ElementSegment, ElementSegmentEntity}, + instance::InstanceEntity, + memory::DataSegment, module::{DEFAULT_MEMORY_INDEX, DEFAULT_TABLE_INDEX}, + table::TableEntity, Func, Instance, Memory, @@ -9,6 +13,8 @@ use crate::{ use core::ptr::NonNull; use wasmi_core::UntypedValue; +use super::bytecode::{DataSegmentIdx, ElementSegmentIdx}; + /// A cache for frequently used entities of an [`Instance`]. #[derive(Debug)] pub struct InstanceCache { @@ -63,21 +69,89 @@ impl InstanceCache { self.set_instance(instance); } + /// Loads the [`DataSegment`] at `index` of the currently used [`Instance`]. + /// + /// # Panics + /// + /// If there is no [`DataSegment`] for the [`Instance`] at the `index`. + #[inline] + pub fn get_data_segment(&mut self, ctx: &mut StoreInner, index: u32) -> DataSegment { + let instance = self.instance(); + ctx.resolve_instance(self.instance()) + .get_data_segment(index) + .unwrap_or_else(|| { + panic!("missing data segment ({index:?}) for instance: {instance:?}",) + }) + } + + /// Loads the [`ElementSegment`] at `index` of the currently used [`Instance`]. + /// + /// # Panics + /// + /// If there is no [`ElementSegment`] for the [`Instance`] at the `index`. + #[inline] + pub fn get_element_segment( + &mut self, + ctx: &mut StoreInner, + index: ElementSegmentIdx, + ) -> ElementSegment { + let instance = self.instance(); + ctx.resolve_instance(self.instance()) + .get_element_segment(index.into_inner()) + .unwrap_or_else(|| { + panic!("missing element segment ({index:?}) for instance: {instance:?}",) + }) + } + + /// Loads the [`DataSegment`] at `index` of the currently used [`Instance`]. + /// + /// # Panics + /// + /// If there is no [`DataSegment`] for the [`Instance`] at the `index`. + #[inline] + pub fn get_default_memory_and_data_segment<'a>( + &mut self, + ctx: &'a mut StoreInner, + segment: DataSegmentIdx, + ) -> (&'a mut [u8], &'a [u8]) { + let mem = self.default_memory(ctx); + let seg = self.get_data_segment(ctx, segment.into_inner()); + let (memory, segment) = ctx.resolve_memory_mut_and_data_segment(&mem, &seg); + (memory.data_mut(), segment.bytes()) + } + + /// Loads the [`ElementSegment`] at `index` of the currently used [`Instance`]. + /// + /// # Panics + /// + /// If there is no [`ElementSegment`] for the [`Instance`] at the `index`. + #[inline] + pub fn get_default_table_and_element_segment<'a>( + &mut self, + ctx: &'a mut StoreInner, + segment: ElementSegmentIdx, + ) -> ( + &'a InstanceEntity, + &'a mut TableEntity, + &'a ElementSegmentEntity, + ) { + let tab = self.default_table(ctx); + let seg = self.get_element_segment(ctx, segment); + let inst = self.instance(); + ctx.resolve_instance_table_element(inst, &tab, &seg) + } + /// Loads the default [`Memory`] of the currently used [`Instance`]. /// /// # Panics /// /// If the currently used [`Instance`] does not have a default linear memory. fn load_default_memory(&mut self, ctx: &StoreInner) -> Memory { + let instance = self.instance(); let default_memory = ctx - .resolve_instance(self.instance()) + .resolve_instance(instance) .get_memory(DEFAULT_MEMORY_INDEX) - .unwrap_or_else(|| { - panic!( - "missing default linear memory for instance: {:?}", - self.instance - ) - }); + .unwrap_or_else(|| panic!("missing default linear memory for instance: {instance:?}",)); self.default_memory = Some(default_memory); default_memory } @@ -88,10 +162,11 @@ impl InstanceCache { /// /// If the currently used [`Instance`] does not have a default table. fn load_default_table(&mut self, ctx: &StoreInner) -> Table { + let instance = self.instance(); let default_table = ctx - .resolve_instance(self.instance()) + .resolve_instance(instance) .get_table(DEFAULT_TABLE_INDEX) - .unwrap_or_else(|| panic!("missing default table for instance: {:?}", self.instance)); + .unwrap_or_else(|| panic!("missing default table for instance: {instance:?}")); self.default_table = Some(default_table); default_table } diff --git a/crates/wasmi/src/engine/config.rs b/crates/wasmi/src/engine/config.rs index 710484b3c4..c75a9ffa1b 100644 --- a/crates/wasmi/src/engine/config.rs +++ b/crates/wasmi/src/engine/config.rs @@ -21,6 +21,8 @@ pub struct Config { saturating_float_to_int: bool, /// Is `true` if the [`multi-value`] Wasm proposal is enabled. multi_value: bool, + /// Is `true` if the [`bulk-memory`] Wasm proposal is enabled. + bulk_memory: bool, } impl Default for Config { @@ -32,6 +34,7 @@ impl Default for Config { sign_extension: true, saturating_float_to_int: true, multi_value: true, + bulk_memory: true, } } } @@ -112,6 +115,18 @@ impl Config { self } + /// Enable or disable the [`bulk-memory`] Wasm proposal for the [`Config`]. + /// + /// # Note + /// + /// Enabled by default. + /// + /// [`multi-value`]: https://github.com/WebAssembly/bulk-memory-operations + pub fn wasm_bulk_memory(&mut self, enable: bool) -> &mut Self { + self.bulk_memory = enable; + self + } + /// Returns the [`WasmFeatures`] represented by the [`Config`]. pub fn wasm_features(&self) -> WasmFeatures { WasmFeatures { @@ -119,8 +134,8 @@ impl Config { mutable_global: self.mutable_global, saturating_float_to_int: self.saturating_float_to_int, sign_extension: self.sign_extension, + bulk_memory: self.bulk_memory, reference_types: false, - bulk_memory: false, component_model: false, simd: false, relaxed_simd: false, diff --git a/crates/wasmi/src/engine/executor.rs b/crates/wasmi/src/engine/executor.rs index df23d9d7d0..e3b73992e0 100644 --- a/crates/wasmi/src/engine/executor.rs +++ b/crates/wasmi/src/engine/executor.rs @@ -1,6 +1,16 @@ use super::{ super::{Memory, Table}, - bytecode::{BranchParams, FuncIdx, GlobalIdx, Instruction, LocalDepth, Offset, SignatureIdx}, + bytecode::{ + BranchParams, + DataSegmentIdx, + ElementSegmentIdx, + FuncIdx, + GlobalIdx, + Instruction, + LocalDepth, + Offset, + SignatureIdx, + }, cache::InstanceCache, code_map::InstructionPtr, stack::ValueStackRef, @@ -10,7 +20,7 @@ use super::{ ValueStack, }; use crate::{core::TrapCode, Func, StoreInner}; -use core::cmp; +use core::cmp::{self}; use wasmi_core::{Pages, UntypedValue}; /// Executes the given function `frame`. @@ -132,8 +142,15 @@ impl<'ctx, 'engine, 'func> Executor<'ctx, 'engine, 'func> { Instr::I64Store8(offset) => self.visit_i64_store_8(offset)?, Instr::I64Store16(offset) => self.visit_i64_store_16(offset)?, Instr::I64Store32(offset) => self.visit_i64_store_32(offset)?, - Instr::MemorySize => self.visit_current_memory(), - Instr::MemoryGrow => self.visit_grow_memory(), + Instr::MemorySize => self.visit_memory_size(), + Instr::MemoryGrow => self.visit_memory_grow(), + Instr::MemoryFill => self.visit_memory_fill()?, + Instr::MemoryCopy => self.visit_memory_copy()?, + Instr::MemoryInit(segment) => self.visit_memory_init(segment)?, + Instr::DataDrop(segment) => self.visit_data_drop(segment), + Instr::TableCopy => self.visit_table_copy()?, + Instr::TableInit(segment) => self.visit_table_init(segment)?, + Instr::ElemDrop(segment) => self.visit_element_drop(segment), Instr::Const(bytes) => self.visit_const(bytes), Instr::I32Eqz => self.visit_i32_eqz(), Instr::I32Eq => self.visit_i32_eq(), @@ -568,14 +585,14 @@ impl<'ctx, 'engine, 'func> Executor<'ctx, 'engine, 'func> { self.next_instr() } - fn visit_current_memory(&mut self) { + fn visit_memory_size(&mut self) { let memory = self.default_memory(); let result: u32 = self.ctx.resolve_memory(&memory).current_pages().into(); self.value_stack.push(result); self.next_instr() } - fn visit_grow_memory(&mut self) { + fn visit_memory_grow(&mut self) { /// The WebAssembly spec demands to return `0xFFFF_FFFF` /// in case of failure for the `memory.grow` instruction. const ERR_VALUE: u32 = u32::MAX; @@ -595,6 +612,107 @@ impl<'ctx, 'engine, 'func> Executor<'ctx, 'engine, 'func> { self.next_instr() } + fn visit_memory_fill(&mut self) -> Result<(), TrapCode> { + let bytes = self.cache.default_memory_bytes(self.ctx); + // The `n`, `val` and `d` variable bindings are extracted from the Wasm specification. + let (d, val, n) = self.value_stack.pop3(); + let n = i32::from(n) as usize; + let offset = i32::from(d) as usize; + let byte = u8::from(val); + let memory = bytes + .data_mut() + .get_mut(offset..) + .and_then(|memory| memory.get_mut(..n)) + .ok_or(TrapCode::MemoryOutOfBounds)?; + memory.fill(byte); + self.next_instr(); + Ok(()) + } + + fn visit_memory_copy(&mut self) -> Result<(), TrapCode> { + // The `n`, `s` and `d` variable bindings are extracted from the Wasm specification. + let (d, s, n) = self.value_stack.pop3(); + let n = i32::from(n) as usize; + let src_offset = i32::from(s) as usize; + let dst_offset = i32::from(d) as usize; + let data = self.cache.default_memory_bytes(self.ctx).data_mut(); + // These accesses just perform the bounds checks required by the Wasm spec. + data.get(src_offset..) + .and_then(|memory| memory.get(..n)) + .ok_or(TrapCode::MemoryOutOfBounds)?; + data.get(dst_offset..) + .and_then(|memory| memory.get(..n)) + .ok_or(TrapCode::MemoryOutOfBounds)?; + data.copy_within(src_offset..src_offset.wrapping_add(n), dst_offset); + self.next_instr(); + Ok(()) + } + + fn visit_memory_init(&mut self, segment: DataSegmentIdx) -> Result<(), TrapCode> { + // The `n`, `s` and `d` variable bindings are extracted from the Wasm specification. + let (d, s, n) = self.value_stack.pop3(); + let n = i32::from(n) as usize; + let src_offset = i32::from(s) as usize; + let dst_offset = i32::from(d) as usize; + let (memory, data) = self + .cache + .get_default_memory_and_data_segment(self.ctx, segment); + let memory = memory + .get_mut(dst_offset..) + .and_then(|memory| memory.get_mut(..n)) + .ok_or(TrapCode::MemoryOutOfBounds)?; + let data = data + .get(src_offset..) + .and_then(|data| data.get(..n)) + .ok_or(TrapCode::MemoryOutOfBounds)?; + memory.copy_from_slice(data); + self.next_instr(); + Ok(()) + } + + fn visit_data_drop(&mut self, segment_index: DataSegmentIdx) { + let segment = self + .cache + .get_data_segment(self.ctx, segment_index.into_inner()); + self.ctx.resolve_data_segment_mut(&segment).drop_bytes(); + self.next_instr(); + } + + fn visit_table_copy(&mut self) -> Result<(), TrapCode> { + // The `n`, `s` and `d` variable bindings are extracted from the Wasm specification. + let (d, s, n) = self.value_stack.pop3(); + let len = u32::from(n); + let src_index = u32::from(s); + let dst_index = u32::from(d); + let table = self + .ctx + .resolve_table_mut(&self.cache.default_table(self.ctx)); + // Now copy table elements within. + table.copy_within(dst_index, src_index, len)?; + self.next_instr(); + Ok(()) + } + + fn visit_table_init(&mut self, segment: ElementSegmentIdx) -> Result<(), TrapCode> { + // The `n`, `s` and `d` variable bindings are extracted from the Wasm specification. + let (d, s, n) = self.value_stack.pop3(); + let len = u32::from(n); + let src_index = u32::from(s); + let dst_index = u32::from(d); + let (instance, table, element) = self + .cache + .get_default_table_and_element_segment(self.ctx, segment); + table.init(instance, dst_index, element, src_index, len)?; + self.next_instr(); + Ok(()) + } + + fn visit_element_drop(&mut self, segment_index: ElementSegmentIdx) { + let segment = self.cache.get_element_segment(self.ctx, segment_index); + self.ctx.resolve_element_segment_mut(&segment).drop_items(); + self.next_instr(); + } + fn visit_i32_load(&mut self, offset: Offset) -> Result<(), TrapCode> { self.execute_load_extend(offset, UntypedValue::i32_load) } diff --git a/crates/wasmi/src/engine/func_builder/mod.rs b/crates/wasmi/src/engine/func_builder/mod.rs index eb03def2dd..c832b729d1 100644 --- a/crates/wasmi/src/engine/func_builder/mod.rs +++ b/crates/wasmi/src/engine/func_builder/mod.rs @@ -27,7 +27,7 @@ pub use self::{ }; use super::{DropKeep, FuncBody, Instruction}; use crate::{ - engine::bytecode::{BranchParams, Offset}, + engine::bytecode::{BranchParams, DataSegmentIdx, ElementSegmentIdx, Offset}, module::{ BlockType, FuncIdx, @@ -801,7 +801,7 @@ impl<'parser> FuncBuilder<'parser> { let global_idx = GlobalIdx(global_idx); builder.stack_height.push(); let (global_type, init_value) = builder.res.get_global(global_idx); - let instr = match init_value.and_then(InitExpr::eval_const) { + let instr = match init_value.and_then(InitExpr::to_const) { Some(value) if global_type.mutability().is_const() => Instruction::constant(value), _ => Instruction::GlobalGet(global_idx.into_u32().into()), }; @@ -1106,12 +1106,11 @@ impl<'parser> FuncBuilder<'parser> { /// Translate a Wasm `memory.grow` instruction. pub fn translate_memory_grow( &mut self, - memory_idx: u32, + memory_index: u32, _mem_byte: u8, ) -> Result<(), TranslationError> { self.translate_if_reachable(|builder| { - let memory_idx = MemoryIdx(memory_idx); - debug_assert_eq!(memory_idx.into_u32(), DEFAULT_MEMORY_INDEX); + debug_assert_eq!(memory_index, DEFAULT_MEMORY_INDEX); builder .alloc .inst_builder @@ -1120,6 +1119,181 @@ impl<'parser> FuncBuilder<'parser> { }) } + /// Translate a Wasm `memory.init` instruction. + pub fn translate_memory_init( + &mut self, + segment_index: u32, + memory_index: u32, + ) -> Result<(), TranslationError> { + self.translate_if_reachable(|builder| { + debug_assert_eq!(memory_index, DEFAULT_MEMORY_INDEX); + builder.stack_height.pop3(); + builder + .alloc + .inst_builder + .push_inst(Instruction::MemoryInit(DataSegmentIdx::from(segment_index))); + Ok(()) + }) + } + + /// Translate a Wasm `memory.fill` instruction. + pub fn translate_memory_fill(&mut self, memory_index: u32) -> Result<(), TranslationError> { + self.translate_if_reachable(|builder| { + debug_assert_eq!(memory_index, DEFAULT_MEMORY_INDEX); + builder.stack_height.pop3(); + builder + .alloc + .inst_builder + .push_inst(Instruction::MemoryFill); + Ok(()) + }) + } + + /// Translate a Wasm `memory.copy` instruction. + pub fn translate_memory_copy( + &mut self, + dst_mem: u32, + src_mem: u32, + ) -> Result<(), TranslationError> { + self.translate_if_reachable(|builder| { + debug_assert_eq!(dst_mem, DEFAULT_MEMORY_INDEX); + debug_assert_eq!(src_mem, DEFAULT_MEMORY_INDEX); + builder.stack_height.pop3(); + builder + .alloc + .inst_builder + .push_inst(Instruction::MemoryCopy); + Ok(()) + }) + } + + /// Translate a Wasm `data.drop` instruction. + pub fn translate_data_drop(&mut self, segment_index: u32) -> Result<(), TranslationError> { + self.translate_if_reachable(|builder| { + let segment_index = DataSegmentIdx::from(segment_index); + builder + .alloc + .inst_builder + .push_inst(Instruction::DataDrop(segment_index)); + Ok(()) + }) + } + + /// Translate a Wasm `table.size` instruction. + pub fn translate_table_size(&mut self, _table_index: u32) -> Result<(), TranslationError> { + self.translate_if_reachable(|_builder| { + // debug_assert_eq!(table_index, DEFAULT_TABLE_INDEX); + // let table_index = MemoryIdx(table_index); + // builder.stack_height.push(); + // builder + // .alloc + // .inst_builder + // .push_inst(Instruction::TableSize); + // Ok(()) + unimplemented!("wasmi does not yet support the `reference-types` Wasm proposal") + }) + } + + /// Translate a Wasm `table.grow` instruction. + pub fn translate_table_grow(&mut self, _table_index: u32) -> Result<(), TranslationError> { + self.translate_if_reachable(|_builder| { + // debug_assert_eq!(table_index, DEFAULT_TABLE_INDEX); + // let table_index = MemoryIdx(table_index); + // builder + // .alloc + // .inst_builder + // .push_inst(Instruction::TableGrow); + // Ok(()) + unimplemented!("wasmi does not yet support the `reference-types` Wasm proposal") + }) + } + + pub fn translate_table_copy( + &mut self, + _dst_table: u32, + _src_table: u32, + ) -> Result<(), TranslationError> { + self.translate_if_reachable(|builder| { + // debug_assert_eq!(dst_table, DEFAULT_TABLE_INDEX); + // debug_assert_eq!(src_table, DEFAULT_TABLE_INDEX); + // let dst_table = TableIdx(dst_table); + // let src_table = TableIdx(src_table); + builder.stack_height.pop3(); + builder.alloc.inst_builder.push_inst(Instruction::TableCopy); + Ok(()) + }) + } + + pub fn translate_table_fill(&mut self, _table_index: u32) -> Result<(), TranslationError> { + self.translate_if_reachable(|_builder| { + // debug_assert_eq!(table_index, DEFAULT_TABLE_INDEX); + // let memory_index = TableIdx(table_index); + // builder.stack_height.pop3(); + // builder + // .alloc + // .inst_builder + // .push_inst(Instruction::MemoryFill { table_index }); + unimplemented!("wasmi does not yet support the `reference-types` Wasm proposal") + }) + } + + pub fn translate_table_get(&mut self, _table_index: u32) -> Result<(), TranslationError> { + self.translate_if_reachable(|_builder| { + // debug_assert_eq!(table_index, DEFAULT_TABLE_INDEX); + // let memory_index = TableIdx(table_index); + // builder + // .alloc + // .inst_builder + // .push_inst(Instruction::TableGet { table_index }); + unimplemented!("wasmi does not yet support the `reference-types` Wasm proposal") + }) + } + + pub fn translate_table_set(&mut self, _table_index: u32) -> Result<(), TranslationError> { + self.translate_if_reachable(|_builder| { + // debug_assert_eq!(table_index, DEFAULT_TABLE_INDEX); + // let memory_index = TableIdx(table_index); + // builder.stack_height.pop1(); + // builder + // .alloc + // .inst_builder + // .push_inst(Instruction::TableSet { table_index }); + unimplemented!("wasmi does not yet support the `reference-types` Wasm proposal") + }) + } + + /// Translate a Wasm `table.init` instruction. + pub fn translate_table_init( + &mut self, + segment_index: u32, + table_index: u32, + ) -> Result<(), TranslationError> { + self.translate_if_reachable(|builder| { + debug_assert_eq!(table_index, DEFAULT_MEMORY_INDEX); + builder.stack_height.pop3(); + builder + .alloc + .inst_builder + .push_inst(Instruction::TableInit(ElementSegmentIdx::from( + segment_index, + ))); + Ok(()) + }) + } + + /// Translate a Wasm `elem.drop` instruction. + pub fn translate_elem_drop(&mut self, segment_index: u32) -> Result<(), TranslationError> { + self.translate_if_reachable(|builder| { + builder + .alloc + .inst_builder + .push_inst(Instruction::ElemDrop(ElementSegmentIdx::from( + segment_index, + ))); + Ok(()) + }) + } + /// Translate a Wasm `.const` instruction. /// /// # Note diff --git a/crates/wasmi/src/engine/func_builder/visit.rs b/crates/wasmi/src/engine/func_builder/visit.rs index 71274fc0e9..d595e11114 100644 --- a/crates/wasmi/src/engine/func_builder/visit.rs +++ b/crates/wasmi/src/engine/func_builder/visit.rs @@ -67,6 +67,18 @@ macro_rules! for_each_supported_operator { fn visit_i64_store32(memarg: wasmparser::MemArg) => fn translate_i64_store32 fn visit_memory_size(mem: u32, mem_byte: u8) => fn translate_memory_size fn visit_memory_grow(mem: u32, mem_byte: u8) => fn translate_memory_grow + fn visit_memory_copy(dst_mem: u32, src_mem: u32) => fn translate_memory_copy + fn visit_memory_fill(mem: u32) => fn translate_memory_fill + fn visit_memory_init(seg: u32, mem: u32) => fn translate_memory_init + fn visit_data_drop(seg: u32) => fn translate_data_drop + fn visit_table_size(table: u32) => fn translate_table_size + fn visit_table_grow(table: u32) => fn translate_table_grow + fn visit_table_copy(dst_table: u32, src_table: u32) => fn translate_table_copy + fn visit_table_fill(table: u32) => fn translate_table_fill + fn visit_table_get(table: u32) => fn translate_table_get + fn visit_table_set(table: u32) => fn translate_table_set + fn visit_table_init(seg: u32, table: u32) => fn translate_table_init + fn visit_elem_drop(seg: u32) => fn translate_elem_drop fn visit_i32_const(value: i32) => fn translate_i32_const fn visit_i64_const(value: i64) => fn translate_i64_const fn visit_f32_const(value: wasmparser::Ieee32) => fn translate_f32_const @@ -256,6 +268,10 @@ macro_rules! define_unsupported_visit_operator { // Supported operators are handled by `define_supported_visit_operator`. define_unsupported_visit_operator!($($rest)*); }; + ( @bulk_memory $op:ident $({ $($arg:ident: $argty:ty),* })? => $visit:ident $($rest:tt)* ) => { + // Supported operators are handled by `define_supported_visit_operator`. + define_unsupported_visit_operator!($($rest)*); + }; ( @$proposal:ident $op:ident $({ $($arg:ident: $argty:ty),* })? => $visit:ident $($rest:tt)* ) => { fn $visit(&mut self, offset: usize $($(,$arg: $argty)*)?) -> Self::Output { self.validator.$visit(offset $($(,$arg)*)?).map_err(::core::convert::Into::into) diff --git a/crates/wasmi/src/engine/stack/values/vref.rs b/crates/wasmi/src/engine/stack/values/vref.rs index 740b75395b..335e9bf130 100644 --- a/crates/wasmi/src/engine/stack/values/vref.rs +++ b/crates/wasmi/src/engine/stack/values/vref.rs @@ -217,6 +217,24 @@ impl<'a> ValueStackRef<'a> { ) } + /// Pops the last triple of [`UntypedValue`] from the [`ValueStack`]. + /// + /// # Note + /// + /// - This operation is slightly more efficient than using + /// [`ValueStackRef::pop`] trice. + /// - This operation heavily relies on the prior validation of + /// the executed WebAssembly bytecode for correctness. + #[inline] + pub fn pop3(&mut self) -> (UntypedValue, UntypedValue, UntypedValue) { + self.stack_ptr -= 3; + ( + self.get_release_unchecked(self.stack_ptr), + self.get_release_unchecked(self.stack_ptr + 1), + self.get_release_unchecked(self.stack_ptr + 2), + ) + } + /// Evaluates the given closure `f` for the 3 top most stack values. #[inline] pub fn eval_top3(&mut self, f: F) diff --git a/crates/wasmi/src/instance.rs b/crates/wasmi/src/instance.rs index 2749c10eac..240f0060d3 100644 --- a/crates/wasmi/src/instance.rs +++ b/crates/wasmi/src/instance.rs @@ -11,7 +11,9 @@ use super::{ Table, }; use crate::{ + element::ElementSegment, func::FuncError, + memory::DataSegment, module::FuncIdx, Error, ExternType, @@ -55,6 +57,8 @@ pub struct InstanceEntity { memories: Box<[Memory]>, globals: Box<[Global]>, exports: BTreeMap, Extern>, + data_segments: Box<[DataSegment]>, + elem_segments: Box<[ElementSegment]>, } impl InstanceEntity { @@ -68,6 +72,8 @@ impl InstanceEntity { memories: [].into(), globals: [].into(), exports: BTreeMap::new(), + data_segments: [].into(), + elem_segments: [].into(), } } @@ -106,6 +112,16 @@ impl InstanceEntity { self.func_types.get(index as usize).copied() } + /// Returns the [`DataSegment`] at the `index` if any. + pub(crate) fn get_data_segment(&self, index: u32) -> Option { + self.data_segments.get(index as usize).copied() + } + + /// Returns the [`ElementSegment`] at the `index` if any. + pub(crate) fn get_element_segment(&self, index: u32) -> Option { + self.elem_segments.get(index as usize).copied() + } + /// Returns the value exported to the given `name` if any. pub(crate) fn get_export(&self, name: &str) -> Option { self.exports.get(name).copied() @@ -232,6 +248,8 @@ pub struct InstanceEntityBuilder { globals: Vec, start_fn: Option, exports: BTreeMap, Extern>, + data_segments: Vec, + elem_segments: Vec, } impl InstanceEntityBuilder { @@ -270,6 +288,8 @@ impl InstanceEntityBuilder { globals: vec_with_capacity_exact(len_globals), start_fn: None, exports: BTreeMap::default(), + data_segments: Vec::new(), + elem_segments: Vec::new(), } } @@ -383,6 +403,16 @@ impl InstanceEntityBuilder { self.exports.insert(name.into(), new_value); } + /// Pushes the [`DataSegment`] to the [`InstanceEntity`] under construction. + pub fn push_data_segment(&mut self, segment: DataSegment) { + self.data_segments.push(segment); + } + + /// Pushes the [`ElementSegment`] to the [`InstanceEntity`] under construction. + pub fn push_element_segment(&mut self, segment: ElementSegment) { + self.elem_segments.push(segment); + } + /// Finishes constructing the [`InstanceEntity`]. pub fn finish(self) -> InstanceEntity { InstanceEntity { @@ -393,6 +423,8 @@ impl InstanceEntityBuilder { memories: self.memories.into(), globals: self.globals.into(), exports: self.exports, + data_segments: self.data_segments.into(), + elem_segments: self.elem_segments.into(), } } } diff --git a/crates/wasmi/src/lib.rs b/crates/wasmi/src/lib.rs index 6d83d42b13..fc75bba46e 100644 --- a/crates/wasmi/src/lib.rs +++ b/crates/wasmi/src/lib.rs @@ -87,6 +87,7 @@ extern crate std as alloc; #[macro_use] mod foreach_tuple; +mod element; mod engine; mod error; mod external; @@ -116,6 +117,15 @@ pub mod errors { }; } +use self::{ + element::{ElementSegment, ElementSegmentEntity, ElementSegmentIdx}, + func::{FuncEntity, FuncIdx}, + global::{GlobalEntity, GlobalIdx}, + instance::{InstanceEntity, InstanceEntityBuilder, InstanceIdx}, + memory::{DataSegmentEntity, DataSegmentIdx, MemoryEntity, MemoryIdx}, + store::{StoreInner, Stored}, + table::{TableEntity, TableIdx}, +}; pub use self::{ engine::{ Config, @@ -156,11 +166,3 @@ pub use self::{ store::{AsContext, AsContextMut, Store, StoreContext, StoreContextMut}, table::{Table, TableType}, }; -use self::{ - func::{FuncEntity, FuncIdx}, - global::{GlobalEntity, GlobalIdx}, - instance::{InstanceEntity, InstanceEntityBuilder, InstanceIdx}, - memory::{MemoryEntity, MemoryIdx}, - store::{StoreInner, Stored}, - table::{TableEntity, TableIdx}, -}; diff --git a/crates/wasmi/src/memory/data.rs b/crates/wasmi/src/memory/data.rs new file mode 100644 index 0000000000..2cdcfc7d1d --- /dev/null +++ b/crates/wasmi/src/memory/data.rs @@ -0,0 +1,97 @@ +use crate::{module, store::Stored, AsContextMut}; +use alloc::sync::Arc; +use wasmi_arena::ArenaIndex; + +/// A raw index to a data segment entity. +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct DataSegmentIdx(u32); + +impl ArenaIndex for DataSegmentIdx { + fn into_usize(self) -> usize { + self.0 as usize + } + + fn from_usize(value: usize) -> Self { + let value = value.try_into().unwrap_or_else(|error| { + panic!("index {value} is out of bounds as data segment index: {error}") + }); + Self(value) + } +} + +/// A Wasm data segment reference. +#[derive(Debug, Copy, Clone)] +#[repr(transparent)] +pub struct DataSegment(Stored); + +impl DataSegment { + /// Creates a new linear memory reference. + pub fn from_inner(stored: Stored) -> Self { + Self(stored) + } + + /// Returns the underlying stored representation. + pub fn as_inner(&self) -> &Stored { + &self.0 + } + + /// Allocates a new [`DataSegment`] on the store. + /// + /// # Errors + /// + /// If more than [`u32::MAX`] much linear memory is allocated. + pub fn new(mut ctx: impl AsContextMut, segment: &module::DataSegment) -> Self { + let entity = DataSegmentEntity::from(segment); + ctx.as_context_mut().store.inner.alloc_data_segment(entity) + } +} + +/// An instantiated [`DataSegmentEntity`]. +/// +/// # Note +/// +/// With the `bulk-memory` Wasm proposal it is possible to interact +/// with data segments at runtime. Therefore Wasm instances now have +/// a need to have an instantiated representation of data segments. +#[derive(Debug)] +pub struct DataSegmentEntity { + /// The underlying bytes of the instance data segment. + /// + /// # Note + /// + /// These bytes are just readable after instantiation. + /// Using Wasm `data.drop` simply replaces the instance + /// with an empty one. + bytes: Option>, +} + +impl From<&'_ module::DataSegment> for DataSegmentEntity { + fn from(segment: &'_ module::DataSegment) -> Self { + match segment.kind() { + module::DataSegmentKind::Passive => Self { + bytes: Some(segment.clone_bytes()), + }, + module::DataSegmentKind::Active(_) => Self::empty(), + } + } +} + +impl DataSegmentEntity { + /// Create an empty [`DataSegmentEntity`] representing dropped data segments. + fn empty() -> Self { + Self { bytes: None } + } + + /// Returns the bytes of the [`DataSegmentEntity`]. + pub fn bytes(&self) -> &[u8] { + self.bytes + .as_ref() + .map(|bytes| &bytes[..]) + .unwrap_or_else(|| &[]) + } + + /// Drops the bytes of the [`DataSegmentEntity`]. + pub fn drop_bytes(&mut self) { + self.bytes = None; + } +} diff --git a/crates/wasmi/src/memory/mod.rs b/crates/wasmi/src/memory/mod.rs index b30cd61996..c8e716513f 100644 --- a/crates/wasmi/src/memory/mod.rs +++ b/crates/wasmi/src/memory/mod.rs @@ -1,6 +1,8 @@ mod byte_buffer; +mod data; use self::byte_buffer::ByteBuffer; +pub use self::data::{DataSegment, DataSegmentEntity, DataSegmentIdx}; use super::{AsContext, AsContextMut, StoreContext, StoreContextMut, Stored}; use core::{fmt, fmt::Display}; use wasmi_arena::ArenaIndex; @@ -48,13 +50,13 @@ impl Display for MemoryError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Self::OutOfBoundsAllocation => { - write!(f, "out of bounds linear memory allocation") + write!(f, "out of bounds memory allocation") } Self::OutOfBoundsGrowth => { - write!(f, "out fo bounds linear memory growth") + write!(f, "out fo bounds memory growth") } Self::OutOfBoundsAccess => { - write!(f, "out of bounds linear memory access") + write!(f, "out of bounds memory access") } Self::InvalidMemoryType => { write!(f, "tried to create an invalid virtual memory type") diff --git a/crates/wasmi/src/module/data.rs b/crates/wasmi/src/module/data.rs index cabfc91443..51b7d09679 100644 --- a/crates/wasmi/src/module/data.rs +++ b/crates/wasmi/src/module/data.rs @@ -1,51 +1,92 @@ use super::{InitExpr, MemoryIdx}; use crate::errors::ModuleError; -use alloc::boxed::Box; +use alloc::sync::Arc; -/// A linear memory data segment within a [`Module`]. +/// A Wasm [`Module`] data segment. /// /// [`Module`]: [`super::Module`] #[derive(Debug)] pub struct DataSegment { - _memory_index: MemoryIdx, + /// The kind of the data segment. + kind: DataSegmentKind, + /// The bytes of the data segment. + bytes: Arc<[u8]>, +} + +/// The kind of a Wasm module [`DataSegment`]. +#[derive(Debug)] +pub enum DataSegmentKind { + /// A passive data segment from the `bulk-memory` Wasm proposal. + Passive, + /// An active data segment that is initialized upon module instantiation. + Active(ActiveDataSegment), +} + +/// An active data segment. +#[derive(Debug)] +pub struct ActiveDataSegment { + /// The linear memory that is to be initialized with this active segment. + memory_index: MemoryIdx, + /// The offset at which the data segment is initialized. offset: InitExpr, - data: Box<[u8]>, } -impl TryFrom> for DataSegment { +impl ActiveDataSegment { + /// Returns the Wasm module memory index that is to be initialized. + pub fn memory_index(&self) -> MemoryIdx { + self.memory_index + } + + /// Returns the offset expression of the [`ActiveDataSegment`]. + pub fn offset(&self) -> &InitExpr { + &self.offset + } +} + +impl TryFrom> for DataSegmentKind { type Error = ModuleError; - fn try_from(data: wasmparser::Data<'_>) -> Result { - let (memory_index, offset) = match data.kind { + fn try_from(data_kind: wasmparser::DataKind<'_>) -> Result { + match data_kind { wasmparser::DataKind::Active { memory_index, offset_expr, } => { let memory_index = MemoryIdx(memory_index); - let offset = InitExpr::try_from(offset_expr)?; - (memory_index, offset) - } - wasmparser::DataKind::Passive => { - panic!("wasmi does not support the `bulk-memory` Wasm proposal") + let offset = InitExpr::new(offset_expr); + Ok(Self::Active(ActiveDataSegment { + memory_index, + offset, + })) } - }; - let data = data.data.into(); - Ok(DataSegment { - _memory_index: memory_index, - offset, - data, - }) + wasmparser::DataKind::Passive => Ok(Self::Passive), + } + } +} + +impl TryFrom> for DataSegment { + type Error = ModuleError; + + fn try_from(data: wasmparser::Data<'_>) -> Result { + let kind = DataSegmentKind::try_from(data.kind)?; + let bytes = data.data.into(); + Ok(DataSegment { kind, bytes }) } } impl DataSegment { - /// Returns the offset expression of the [`DataSegment`]. - pub fn offset(&self) -> &InitExpr { - &self.offset + /// Returns the [`DataSegmentKind`] of the [`DataSegment`]. + pub fn kind(&self) -> &DataSegmentKind { + &self.kind + } + + /// Returns the bytes of the [`DataSegment`]. + pub fn bytes(&self) -> &[u8] { + &self.bytes[..] } - /// Returns the element items of the [`DataSegment`]. - pub fn data(&self) -> &[u8] { - &self.data[..] + /// Clone the underlying bytes of the [`DataSegment`]. + pub fn clone_bytes(&self) -> Arc<[u8]> { + self.bytes.clone() } } diff --git a/crates/wasmi/src/module/element.rs b/crates/wasmi/src/module/element.rs index 57b6a293a3..385c461505 100644 --- a/crates/wasmi/src/module/element.rs +++ b/crates/wasmi/src/module/element.rs @@ -1,70 +1,111 @@ use super::{FuncIdx, InitExpr, TableIdx}; use crate::errors::ModuleError; -use alloc::{boxed::Box, vec::Vec}; +use alloc::sync::Arc; /// A table element segment within a [`Module`]. /// /// [`Module`]: [`super::Module`] #[derive(Debug)] pub struct ElementSegment { - _table_index: TableIdx, + /// The kind of the [`ElementSegment`]. + kind: ElementSegmentKind, + /// The items of the [`ElementSegment`]. + items: Arc<[Option]>, +} + +/// The kind of a Wasm [`ElementSegment`]. +#[derive(Debug)] +pub enum ElementSegmentKind { + /// A passive [`ElementSegment`] from the `bulk-memory` Wasm proposal. + Passive, + /// An active [`ElementSegment`]. + Active(ActiveElementSegment), +} + +/// An active Wasm element segment. +#[derive(Debug)] +pub struct ActiveElementSegment { + /// The index of the Wasm table that is to be initialized. + table_index: TableIdx, + /// The offset where the Wasm table is to be initialized. offset: InitExpr, - items: Box<[FuncIdx]>, } -impl TryFrom> for ElementSegment { +impl ActiveElementSegment { + /// Returns the Wasm module table index that is to be initialized. + pub fn table_index(&self) -> TableIdx { + self.table_index + } + + /// Returns the offset expression of the [`ActiveElementSegment`]. + pub fn offset(&self) -> &InitExpr { + &self.offset + } +} + +impl TryFrom> for ElementSegmentKind { type Error = ModuleError; - fn try_from(element: wasmparser::Element<'_>) -> Result { - assert_eq!( - element.ty, - wasmparser::ValType::FuncRef, - "wasmi does not support the `reference-types` Wasm proposal" - ); - let (table_index, offset) = match element.kind { + fn try_from(element_kind: wasmparser::ElementKind<'_>) -> Result { + match element_kind { wasmparser::ElementKind::Active { table_index, offset_expr, } => { let table_index = TableIdx(table_index); - let offset = InitExpr::try_from(offset_expr)?; - (table_index, offset) - } - wasmparser::ElementKind::Passive => { - panic!("wasmi does not support the `bulk-memory` Wasm proposal but found passive element segment") + let offset = InitExpr::new(offset_expr); + Ok(Self::Active(ActiveElementSegment { + table_index, + offset, + })) } + wasmparser::ElementKind::Passive => Ok(Self::Passive), wasmparser::ElementKind::Declared => { panic!("wasmi does not support the `reference-types` Wasm proposal but found declared element segment") } - }; + } + } +} + +impl TryFrom> for ElementSegment { + type Error = ModuleError; + + fn try_from(element: wasmparser::Element<'_>) -> Result { + assert_eq!( + element.ty, + wasmparser::ValType::FuncRef, + "wasmi does not support the `reference-types` Wasm proposal" + ); + let kind = ElementSegmentKind::try_from(element.kind)?; let items = element .items .get_items_reader()? .into_iter() - .map(|item| match item? { - wasmparser::ElementItem::Func(func_idx) => Ok(FuncIdx(func_idx)), - wasmparser::ElementItem::Expr(expr) => { - panic!("wasmi does not support the `bulk-memory` Wasm proposal but found an expression item: {expr:?}") - } + .map(|item| { + let func_ref = match item? { + wasmparser::ElementItem::Func(func_idx) => Some(FuncIdx(func_idx)), + wasmparser::ElementItem::Expr(expr) => InitExpr::new(expr).to_elemexpr(), + }; + >::Ok(func_ref) }) - .collect::, ModuleError>>()? - .into_boxed_slice(); - Ok(ElementSegment { - _table_index: table_index, - offset, - items, - }) + .collect::, _>>()?; + Ok(ElementSegment { kind, items }) } } impl ElementSegment { /// Returns the offset expression of the [`ElementSegment`]. - pub fn offset(&self) -> &InitExpr { - &self.offset + pub fn kind(&self) -> &ElementSegmentKind { + &self.kind } /// Returns the element items of the [`ElementSegment`]. - pub fn items(&self) -> &[FuncIdx] { + pub fn items(&self) -> &[Option] { &self.items[..] } + + /// Clone the underlying items of the [`ElementSegment`]. + pub fn clone_items(&self) -> Arc<[Option]> { + self.items.clone() + } } diff --git a/crates/wasmi/src/module/global.rs b/crates/wasmi/src/module/global.rs index dc7be620fd..f64bdf125c 100644 --- a/crates/wasmi/src/module/global.rs +++ b/crates/wasmi/src/module/global.rs @@ -40,7 +40,7 @@ impl TryFrom> for Global { fn try_from(global: wasmparser::Global<'_>) -> Result { let global_type = global.ty.try_into()?; - let init_expr = global.init_expr.try_into()?; + let init_expr = InitExpr::new(global.init_expr); Ok(Global { global_type, init_expr, diff --git a/crates/wasmi/src/module/init_expr.rs b/crates/wasmi/src/module/init_expr.rs index 545058b6c7..3efbb64696 100644 --- a/crates/wasmi/src/module/init_expr.rs +++ b/crates/wasmi/src/module/init_expr.rs @@ -1,4 +1,4 @@ -use super::GlobalIdx; +use super::{FuncIdx, GlobalIdx}; use crate::errors::ModuleError; use wasmi_core::{Value, F32, F64}; @@ -20,43 +20,74 @@ pub struct InitExpr { op: InitExprOperand, } -impl TryFrom> for InitExpr { - type Error = ModuleError; - - fn try_from(init_expr: wasmparser::ConstExpr<'_>) -> Result { - let mut reader = init_expr.get_operators_reader(); - let op = reader.read()?.try_into()?; - let end_op = reader.read()?; +impl InitExpr { + /// Creates a new [`InitExpr`] from the given Wasm constant expression. + pub fn new(expr: wasmparser::ConstExpr<'_>) -> Self { + let mut reader = expr.get_operators_reader(); + let wasm_op = reader.read().unwrap_or_else(|error| { + panic!("expected valid Wasm const expression operand: {error}") + }); + let op = InitExprOperand::new(wasm_op); + let end_op = reader.read(); assert!( - matches!(end_op, wasmparser::Operator::End), + matches!(end_op, Ok(wasmparser::Operator::End)), "expected the Wasm end operator but found {end_op:?}", ); assert!( reader.ensure_end().is_ok(), "expected no more Wasm operands" ); - Ok(InitExpr { op }) + Self { op } } -} -impl InitExpr { - /// Returns a slice over the operators of the [`InitExpr`]. - pub fn operators(&self) -> &[InitExprOperand] { - core::slice::from_ref(&self.op) + /// Convert the [`InitExpr`] into the underlying Wasm `elemexpr` if possible. + /// + /// Returns `None` if the function reference is `null`. + /// + /// # Panics + /// + /// If a non Wasm `elemexpr` operand is encountered. + pub fn to_elemexpr(&self) -> Option { + match self.op { + InitExprOperand::RefNull => None, + InitExprOperand::FuncRef(func_index) => Some(FuncIdx(func_index)), + InitExprOperand::Const(_) | InitExprOperand::GlobalGet(_) => { + panic!("encountered an unexpected Wasm elemexpr {:?}", self.op) + } + } } - /// Evaluates the given constant [`InitExpr`] if possible. + /// Return the `Const` [`InitExpr`] if any. + /// + /// Returns `None` if the underlying operand is not `Const`. /// - /// Returns `None` if it is not possible to constant evaluate `expr`. - pub fn eval_const(&self) -> Option { + /// # Panics + /// + /// If a non-const expression operand is encountered. + pub fn to_const(&self) -> Option { match self.op { InitExprOperand::Const(value) => Some(value), - InitExprOperand::GlobalGet(_) => { - // Note: We do not need to handle `global.get` since - // that is only allowed for imported non-mutable - // global variables which have a value that is only - // known post-instantiation time. - None + // Note: We do not need to handle `global.get` since + // that is only allowed for imported non-mutable + // global variables which have a value that is only + // known post-instantiation time. + InitExprOperand::GlobalGet(_) + | InitExprOperand::RefNull + | InitExprOperand::FuncRef(_) => None, + } + } + + /// Evaluates the [`InitExpr`] given the context for global variables. + /// + /// # Panics + /// + /// If a non-const expression operand is encountered. + pub fn to_const_with_context(&self, global_get: impl Fn(u32) -> Value) -> Value { + match self.op { + InitExprOperand::Const(value) => value, + InitExprOperand::GlobalGet(index) => global_get(index.into_u32()), + ref error @ (InitExprOperand::FuncRef(_) | InitExprOperand::RefNull) => { + panic!("encountered non-const expression operand: {error:?}") } } } @@ -79,9 +110,35 @@ pub enum InitExprOperand { /// /// In the Wasm MVP only immutable globals are allowed to be evaluated. GlobalGet(GlobalIdx), + /// A Wasm `ref.null` value. + RefNull, + /// A Wasm `ref.func index` value. + FuncRef(u32), } impl InitExprOperand { + /// Creates a new [`InitExprOperand`] from the given Wasm operator. + /// + /// # Panics + /// + /// If the Wasm operator is not a valid [`InitExprOperand`]. + fn new(operator: wasmparser::Operator<'_>) -> Self { + match operator { + wasmparser::Operator::I32Const { value } => Self::constant(value), + wasmparser::Operator::I64Const { value } => Self::constant(value), + wasmparser::Operator::F32Const { value } => Self::constant(F32::from(value.bits())), + wasmparser::Operator::F64Const { value } => Self::constant(F64::from(value.bits())), + wasmparser::Operator::GlobalGet { global_index } => { + Self::GlobalGet(GlobalIdx(global_index)) + } + wasmparser::Operator::RefNull { .. } => Self::RefNull, + wasmparser::Operator::RefFunc { function_index } => Self::FuncRef(function_index), + operator => { + panic!("encountered unsupported const expression operator: {operator:?}") + } + } + } + /// Creates a new constant [`InitExprOperand`]. fn constant(value: T) -> Self where @@ -107,8 +164,12 @@ impl TryFrom> for InitExprOperand { wasmparser::Operator::GlobalGet { global_index } => { Ok(InitExprOperand::GlobalGet(GlobalIdx(global_index))) } + wasmparser::Operator::RefNull { .. } => Ok(InitExprOperand::RefNull), + wasmparser::Operator::RefFunc { function_index } => { + Ok(InitExprOperand::FuncRef(function_index)) + } operator => { - panic!("encountered unsupported operator: {operator:?}") + panic!("encountered unsupported const expression operator: {operator:?}") } } } diff --git a/crates/wasmi/src/module/instantiate/mod.rs b/crates/wasmi/src/module/instantiate/mod.rs index b0ae7461ca..6e28a03b7e 100644 --- a/crates/wasmi/src/module/instantiate/mod.rs +++ b/crates/wasmi/src/module/instantiate/mod.rs @@ -5,9 +5,10 @@ mod pre; mod tests; pub use self::{error::InstantiationError, pre::InstancePre}; -use super::{export, InitExpr, Module}; +use super::{element::ElementSegmentKind, export, DataSegmentKind, InitExpr, Module}; use crate::{ - module::{init_expr::InitExprOperand, DEFAULT_MEMORY_INDEX, DEFAULT_TABLE_INDEX}, + element::ElementSegment, + memory::DataSegment, AsContext, AsContextMut, Error, @@ -245,20 +246,8 @@ impl Module { builder: &InstanceEntityBuilder, init_expr: &InitExpr, ) -> Value { - let operands = init_expr.operators(); - debug_assert_eq!( - operands.len(), - 1, - "in Wasm MVP code length of initializer expressions must be 1 but found {} operands", - operands.len(), - ); - match operands[0] { - InitExprOperand::Const(value) => value, - InitExprOperand::GlobalGet(global_index) => { - let global = builder.get_global(global_index.into_u32()); - global.get(context) - } - } + init_expr + .to_const_with_context(|global_index| builder.get_global(global_index).get(&context)) } /// Extracts the Wasm exports from the module and registers them into the [`Instance`]. @@ -303,36 +292,39 @@ impl Module { context: &mut impl AsContextMut, builder: &mut InstanceEntityBuilder, ) -> Result<(), Error> { - for element_segment in &self.element_segments[..] { - let offset_expr = element_segment.offset(); - let offset = Self::eval_init_expr(context.as_context_mut(), builder, offset_expr) - .try_into::() - .unwrap_or_else(|| { - panic!( - "expected offset value of type `i32` due to \ - Wasm validation but found: {offset_expr:?}", - ) - }); - let table = builder.get_table(DEFAULT_TABLE_INDEX); - // Note: This checks not only that the elements in the element segments properly - // fit into the table at the given offset but also that the element segment - // consists of at least 1 element member. - let len_table = table.size(&context); - let len_items = element_segment.items().len() as u32; - len_items - .checked_add(offset) - .filter(|&req| req <= len_table) - .ok_or(InstantiationError::ElementSegmentDoesNotFit { - table, - offset, - amount: len_items, - })?; - // Finally do the actual initialization of the table elements. - for (i, func_index) in element_segment.items().iter().enumerate() { - let func_index = func_index.into_u32(); - let func = builder.get_func(func_index); - table.set(context.as_context_mut(), offset + i as u32, Some(func))?; + for segment in &self.element_segments[..] { + let items = segment.items(); + if let ElementSegmentKind::Active(segment) = segment.kind() { + let offset_expr = segment.offset(); + let offset = Self::eval_init_expr(&mut *context, builder, offset_expr) + .try_into::() + .unwrap_or_else(|| { + panic!( + "expected offset value of type `i32` due to \ + Wasm validation but found: {offset_expr:?}", + ) + }); + let table = builder.get_table(segment.table_index().into_u32()); + // Note: This checks not only that the elements in the element segments properly + // fit into the table at the given offset but also that the element segment + // consists of at least 1 element member. + let len_table = table.size(&context); + let len_items = items.len() as u32; + len_items + .checked_add(offset) + .filter(|&req| req <= len_table) + .ok_or(InstantiationError::ElementSegmentDoesNotFit { + table, + offset, + amount: len_items, + })?; + // Finally do the actual initialization of the table elements. + for (i, func_index) in items.iter().enumerate() { + let func = func_index.map(|index| builder.get_func(index.into_u32())); + table.set(&mut *context, offset + i as u32, func)?; + } } + builder.push_element_segment(ElementSegment::new(context.as_context_mut(), segment)); } Ok(()) } @@ -343,18 +335,22 @@ impl Module { context: &mut impl AsContextMut, builder: &mut InstanceEntityBuilder, ) -> Result<(), Error> { - for data_segment in &self.data_segments[..] { - let offset_expr = data_segment.offset(); - let offset = Self::eval_init_expr(context.as_context_mut(), builder, offset_expr) - .try_into::() - .unwrap_or_else(|| { - panic!( - "expected offset value of type `i32` due to \ - Wasm validation but found: {offset_expr:?}", - ) - }) as usize; - let memory = builder.get_memory(DEFAULT_MEMORY_INDEX); - memory.write(context.as_context_mut(), offset, data_segment.data())?; + for segment in &self.data_segments[..] { + let bytes = segment.bytes(); + if let DataSegmentKind::Active(segment) = segment.kind() { + let offset_expr = segment.offset(); + let offset = Self::eval_init_expr(&mut *context, builder, offset_expr) + .try_into::() + .unwrap_or_else(|| { + panic!( + "expected offset value of type `i32` due to \ + Wasm validation but found: {offset_expr:?}", + ) + }) as usize; + let memory = builder.get_memory(segment.memory_index().into_u32()); + memory.write(&mut *context, offset, bytes)?; + } + builder.push_data_segment(DataSegment::new(context.as_context_mut(), segment)); } Ok(()) } diff --git a/crates/wasmi/src/module/mod.rs b/crates/wasmi/src/module/mod.rs index d74687342a..c7fb521584 100644 --- a/crates/wasmi/src/module/mod.rs +++ b/crates/wasmi/src/module/mod.rs @@ -12,11 +12,8 @@ mod parser; mod read; mod utils; -pub(crate) use self::init_expr::InitExpr; use self::{ builder::ModuleBuilder, - data::DataSegment, - element::ElementSegment, export::ExternIdx, global::Global, import::{ExternTypeIdx, Import}, @@ -34,6 +31,11 @@ pub use self::{ parser::ReusableAllocations, read::Read, }; +pub(crate) use self::{ + data::{DataSegment, DataSegmentKind}, + element::{ElementSegment, ElementSegmentKind}, + init_expr::InitExpr, +}; use crate::{ engine::{DedupFuncType, FuncBody}, Engine, diff --git a/crates/wasmi/src/store.rs b/crates/wasmi/src/store.rs index 1869dd2465..24cb6c1b88 100644 --- a/crates/wasmi/src/store.rs +++ b/crates/wasmi/src/store.rs @@ -1,5 +1,10 @@ use super::{ engine::DedupFuncType, + DataSegmentEntity, + DataSegmentIdx, + ElementSegment, + ElementSegmentEntity, + ElementSegmentIdx, Engine, Func, FuncEntity, @@ -18,6 +23,7 @@ use super::{ TableEntity, TableIdx, }; +use crate::memory::DataSegment; use core::{ fmt::Debug, sync::atomic::{AtomicU32, Ordering}, @@ -96,6 +102,10 @@ pub struct StoreInner { globals: Arena, /// Stored module instances. instances: Arena, + /// Stored data segments. + datas: Arena, + /// Stored data segments. + elems: Arena, /// The [`Engine`] in use by the [`Store`]. /// /// Amongst others the [`Engine`] stores the Wasm function definitions. @@ -123,6 +133,8 @@ impl StoreInner { tables: Arena::new(), globals: Arena::new(), instances: Arena::new(), + datas: Arena::new(), + elems: Arena::new(), } } @@ -210,6 +222,21 @@ impl StoreInner { Memory::from_inner(self.wrap_stored(memory)) } + /// Allocates a new [`DataSegmentEntity`] and returns a [`DataSegment`] reference to it. + pub fn alloc_data_segment(&mut self, segment: DataSegmentEntity) -> DataSegment { + let segment = self.datas.alloc(segment); + DataSegment::from_inner(self.wrap_stored(segment)) + } + + /// Allocates a new [`ElementSegmentEntity`] and returns a [`ElementSegment`] reference to it. + pub(super) fn alloc_element_segment( + &mut self, + segment: ElementSegmentEntity, + ) -> ElementSegment { + let segment = self.elems.alloc(segment); + ElementSegment::from_inner(self.wrap_stored(segment)) + } + /// Allocates a new uninitialized [`InstanceEntity`] and returns an [`Instance`] reference to it. /// /// # Note @@ -358,6 +385,83 @@ impl StoreInner { Self::resolve_mut(idx, &mut self.tables) } + /// Returns an exclusive reference to the [`TableEntity`] associated to the given [`Table`]. + /// + /// # Panics + /// + /// - If the [`Table`] does not originate from this [`Store`]. + /// - If the [`Table`] cannot be resolved to its entity. + pub fn resolve_table_pair_mut( + &mut self, + fst: &Table, + snd: &Table, + ) -> (&mut TableEntity, &mut TableEntity) { + let fst = self.unwrap_stored(fst.as_inner()); + let snd = self.unwrap_stored(snd.as_inner()); + self.tables.get_pair_mut(fst, snd).unwrap_or_else(|| { + panic!("failed to resolve stored pair of entities: {fst:?} and {snd:?}") + }) + } + + /// Returns a triple of: + /// + /// - A shared reference to the [`InstanceEntity`] associated to the given [`Instance`]. + /// - An exclusive reference to the [`TableEntity`] associated to the given [`Table`]. + /// - A shared reference to the [`ElementSegmentEntity`] associated to the given [`ElementSegment`]. + /// + /// # Note + /// + /// This method exists to properly handle use cases where + /// otherwise the Rust borrow-checker would not accept. + /// + /// # Panics + /// + /// - If the [`Instance`] does not originate from this [`Store`]. + /// - If the [`Instance`] cannot be resolved to its entity. + /// - If the [`Table`] does not originate from this [`Store`]. + /// - If the [`Table`] cannot be resolved to its entity. + /// - If the [`ElementSegment`] does not originate from this [`Store`]. + /// - If the [`ElementSegment`] cannot be resolved to its entity. + pub(super) fn resolve_instance_table_element( + &mut self, + instance: &Instance, + memory: &Table, + segment: &ElementSegment, + ) -> (&InstanceEntity, &mut TableEntity, &ElementSegmentEntity) { + let mem_idx = self.unwrap_stored(memory.as_inner()); + let data_idx = segment.as_inner(); + let instance_idx = instance.as_inner(); + let instance = self.resolve(instance_idx, &self.instances); + let data = self.resolve(data_idx, &self.elems); + let mem = Self::resolve_mut(mem_idx, &mut self.tables); + (instance, mem, data) + } + + /// Returns a shared reference to the [`ElementSegmentEntity`] associated to the given [`ElementSegment`]. + /// + /// # Panics + /// + /// - If the [`ElementSegment`] does not originate from this [`Store`]. + /// - If the [`ElementSegment`] cannot be resolved to its entity. + #[allow(unused)] // Note: We allow this unused API to exist to uphold code symmetry. + pub fn resolve_element_segment(&self, segment: &ElementSegment) -> &ElementSegmentEntity { + self.resolve(segment.as_inner(), &self.elems) + } + + /// Returns an exclusive reference to the [`ElementSegmentEntity`] associated to the given [`ElementSegment`]. + /// + /// # Panics + /// + /// - If the [`ElementSegment`] does not originate from this [`Store`]. + /// - If the [`ElementSegment`] cannot be resolved to its entity. + pub fn resolve_element_segment_mut( + &mut self, + segment: &ElementSegment, + ) -> &mut ElementSegmentEntity { + let idx = self.unwrap_stored(segment.as_inner()); + Self::resolve_mut(idx, &mut self.elems) + } + /// Returns a shared reference to the [`MemoryEntity`] associated to the given [`Memory`]. /// /// # Panics @@ -379,6 +483,56 @@ impl StoreInner { Self::resolve_mut(idx, &mut self.memories) } + /// Returns a pair of: + /// + /// - An exclusive reference to the [`MemoryEntity`] associated to the given [`Memory`]. + /// - A shared reference to the [`DataSegmentEntity`] associated to the given [`DataSegment`]. + /// + /// # Note + /// + /// This method exists to properly handle use cases where + /// otherwise the Rust borrow-checker would not accept. + /// + /// # Panics + /// + /// - If the [`Memory`] does not originate from this [`Store`]. + /// - If the [`Memory`] cannot be resolved to its entity. + /// - If the [`DataSegment`] does not originate from this [`Store`]. + /// - If the [`DataSegment`] cannot be resolved to its entity. + pub(super) fn resolve_memory_mut_and_data_segment( + &mut self, + memory: &Memory, + segment: &DataSegment, + ) -> (&mut MemoryEntity, &DataSegmentEntity) { + let mem_idx = self.unwrap_stored(memory.as_inner()); + let data_idx = segment.as_inner(); + let data = self.resolve(data_idx, &self.datas); + let mem = Self::resolve_mut(mem_idx, &mut self.memories); + (mem, data) + } + + /// Returns a shared reference to the [`DataSegmentEntity`] associated to the given [`DataSegment`]. + /// + /// # Panics + /// + /// - If the [`DataSegment`] does not originate from this [`Store`]. + /// - If the [`DataSegment`] cannot be resolved to its entity. + #[allow(unused)] // Note: We allow this unused API to exist to uphold code symmetry. + pub fn resolve_data_segment(&self, segment: &DataSegment) -> &DataSegmentEntity { + self.resolve(segment.as_inner(), &self.datas) + } + + /// Returns an exclusive reference to the [`DataSegmentEntity`] associated to the given [`DataSegment`]. + /// + /// # Panics + /// + /// - If the [`DataSegment`] does not originate from this [`Store`]. + /// - If the [`DataSegment`] cannot be resolved to its entity. + pub fn resolve_data_segment_mut(&mut self, segment: &DataSegment) -> &mut DataSegmentEntity { + let idx = self.unwrap_stored(segment.as_inner()); + Self::resolve_mut(idx, &mut self.datas) + } + /// Returns a shared reference to the [`InstanceEntity`] associated to the given [`Instance`]. /// /// # Panics @@ -432,6 +586,11 @@ impl Store { /// Returns an exclusive reference to the [`MemoryEntity`] associated to the given [`Memory`] /// and an exclusive reference to the user provided host state. /// + /// # Note + /// + /// This method exists to properly handle use cases where + /// otherwise the Rust borrow-checker would not accept. + /// /// # Panics /// /// - If the [`Memory`] does not originate from this [`Store`]. diff --git a/crates/wasmi/src/table.rs b/crates/wasmi/src/table.rs index 9e768a4984..41a1b0b975 100644 --- a/crates/wasmi/src/table.rs +++ b/crates/wasmi/src/table.rs @@ -1,9 +1,11 @@ #![allow(clippy::len_without_is_empty)] use super::{AsContext, AsContextMut, Func, Stored}; +use crate::{element::ElementSegmentEntity, instance::InstanceEntity}; use alloc::vec::Vec; -use core::{fmt, fmt::Display}; +use core::{cmp::max, fmt, fmt::Display}; use wasmi_arena::ArenaIndex; +use wasmi_core::TrapCode; /// A raw index to a table entity. #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] @@ -231,6 +233,115 @@ impl TableEntity { *element = value; Ok(()) } + + /// Initialize `len` elements from `src_element[src_index..]` into + /// `dst_table[dst_index..]`. + /// + /// Uses the `instance` to resolve function indices of the element to [`Func`]. + /// + /// # Errors + /// + /// Returns an error if the range is out of bounds of either the source or + /// destination tables. + /// + /// # Panics + /// + /// Panics if the `instance` cannot resolve all the `element` func indices. + pub fn init( + &mut self, + instance: &InstanceEntity, + dst_index: u32, + element: &ElementSegmentEntity, + src_index: u32, + len: u32, + ) -> Result<(), TrapCode> { + // Turn parameters into proper slice indices. + let src_index = src_index as usize; + let dst_index = dst_index as usize; + let len = len as usize; + // Perform bounds check before anything else. + let dst_items = self + .elements + .get_mut(dst_index..) + .and_then(|items| items.get_mut(..len)) + .ok_or(TrapCode::TableOutOfBounds)?; + let src_items = element + .items() + .get(src_index..) + .and_then(|items| items.get(..len)) + .ok_or(TrapCode::TableOutOfBounds)?; + // Perform the initialization by copying from `src` to `dst`: + for (dst, src) in dst_items.iter_mut().zip(src_items) { + *dst = src.map(|src| { + let src_index = src.into_u32(); + instance.get_func(src_index).unwrap_or_else(|| { + panic!("missing function at index {src_index} in instance {instance:?}") + }) + }); + } + Ok(()) + } + + /// Copy `len` elements from `src_table[src_index..]` into + /// `dst_table[dst_index..]`. + /// + /// # Errors + /// + /// Returns an error if the range is out of bounds of either the source or + /// destination tables. + pub fn copy( + dst_table: &mut Self, + dst_index: u32, + src_table: &Self, + src_index: u32, + len: u32, + ) -> Result<(), TrapCode> { + // Turn parameters into proper slice indices. + let src_index = src_index as usize; + let dst_index = dst_index as usize; + let len = len as usize; + // Perform bounds check before anything else. + let dst_items = dst_table + .elements + .get_mut(dst_index..) + .and_then(|items| items.get_mut(..len)) + .ok_or(TrapCode::TableOutOfBounds)?; + let src_items = src_table + .elements + .get(src_index..) + .and_then(|items| items.get(..len)) + .ok_or(TrapCode::TableOutOfBounds)?; + // Finally, copy elements in-place for the table. + dst_items.copy_from_slice(src_items); + Ok(()) + } + + /// Copy `len` elements from `self[src_index..]` into `self[dst_index..]`. + /// + /// # Errors + /// + /// Returns an error if the range is out of bounds of the table. + pub fn copy_within( + &mut self, + dst_index: u32, + src_index: u32, + len: u32, + ) -> Result<(), TrapCode> { + // These accesses just perform the bounds checks required by the Wasm spec. + let max_offset = max(dst_index, src_index); + max_offset + .checked_add(len) + .filter(|&offset| offset < self.size()) + .ok_or(TrapCode::TableOutOfBounds)?; + // Turn parameters into proper indices. + let src_index = src_index as usize; + let dst_index = dst_index as usize; + let len = len as usize; + // Finally, copy elements in-place for the table. + self.elements + .copy_within(src_index..src_index.wrapping_add(len), dst_index); + Ok(()) + } } /// A Wasm table reference. @@ -330,4 +441,46 @@ impl Table { .resolve_table_mut(self) .set(index, value) } + + /// Copy `len` elements from `src_table[src_index..]` into + /// `dst_table[dst_index..]`. + /// + /// # Errors + /// + /// Returns an error if the range is out of bounds of either the source or + /// destination tables. + /// + /// # Panics + /// + /// Panics if `store` does not own either `dst_table` or `src_table`. + pub fn copy( + mut store: impl AsContextMut, + dst_table: &Table, + dst_index: u32, + src_table: &Table, + src_index: u32, + len: u32, + ) -> Result<(), TrapCode> { + let dst_id = dst_table.as_inner(); + let src_id = src_table.as_inner(); + if dst_id == src_id { + // The `dst_table` and `src_table` are the same table + // therefore we have to copy within the same table. + let table = store + .as_context_mut() + .store + .inner + .resolve_table_mut(dst_table); + table.copy_within(dst_index, src_index, len) + } else { + // The `dst_table` and `src_table` are different entities + // therefore we have to copy from one table to the other. + let (dst_table, src_table) = store + .as_context_mut() + .store + .inner + .resolve_table_pair_mut(dst_table, src_table); + TableEntity::copy(dst_table, dst_index, src_table, src_index, len) + } + } } diff --git a/crates/wasmi/tests/spec/mod.rs b/crates/wasmi/tests/spec/mod.rs index 18a5d66d7b..39b47d2d07 100644 --- a/crates/wasmi/tests/spec/mod.rs +++ b/crates/wasmi/tests/spec/mod.rs @@ -12,17 +12,6 @@ use self::{ }; use wasmi::Config; -/// Creates the proper [`Config`] for testing. -fn mvp_config() -> Config { - let mut config = Config::default(); - config - .wasm_mutable_global(false) - .wasm_saturating_float_to_int(false) - .wasm_sign_extension(false) - .wasm_multi_value(false); - config -} - macro_rules! define_tests { ( let folder = $test_folder:literal; @@ -41,39 +30,6 @@ macro_rules! define_tests { }; } -macro_rules! define_local_tests { - ( - let config = $get_config:expr; - let runner = $runner_fn:path; - - $( $(#[$attr:meta])* fn $test_name:ident($file_name:expr); )* - ) => { - define_tests! { - let folder = "local"; - let config = $get_config; - let runner = $runner_fn; - - $( - $( #[$attr] )* - fn $test_name($file_name); - )* - } - }; -} - -mod missing_features { - use super::{mvp_config, run::run_wasm_spec_test}; - - define_local_tests! { - let config = mvp_config(); - let runner = run_wasm_spec_test; - - fn wasm_mutable_global("missing-features/mutable-global-disabled"); - fn wasm_sign_extension("missing-features/sign-extension-disabled"); - fn wasm_saturating_float_to_int("missing-features/saturating-float-to-int-disabled"); - } -} - macro_rules! define_spec_tests { ( let config = $get_config:expr; @@ -94,126 +50,32 @@ macro_rules! define_spec_tests { }; } -mod saturating_float_to_int { - use super::{mvp_config, run::run_wasm_spec_test}; - - fn make_config() -> wasmi::Config { - let mut config = mvp_config(); - config.wasm_saturating_float_to_int(true); - config - } - - define_spec_tests! { - let config = make_config(); - let runner = run_wasm_spec_test; - - fn wasm_conversions("proposals/nontrapping-float-to-int-conversions/conversions"); - } -} - -mod sign_extension_ops { - use super::{mvp_config, run::run_wasm_spec_test}; - - fn make_config() -> wasmi::Config { - let mut config = mvp_config(); - config.wasm_sign_extension(true); - config - } - - define_spec_tests! { - let config = make_config(); - let runner = run_wasm_spec_test; - - fn wasm_i32("proposals/sign-extension-ops/i32"); - fn wasm_i64("proposals/sign-extension-ops/i64"); - } -} - -mod multi_value { - use super::{mvp_config, run::run_wasm_spec_test}; - - fn make_config() -> wasmi::Config { - let mut config = mvp_config(); - config.wasm_multi_value(true); - config - } - - define_spec_tests! { - let config = make_config(); - let runner = run_wasm_spec_test; - - fn wasm_binary("proposals/multi-value/binary"); - fn wasm_block("proposals/multi-value/block"); - fn wasm_br("proposals/multi-value/br"); - fn wasm_call("proposals/multi-value/call"); - fn wasm_call_indirect("proposals/multi-value/call_indirect"); - fn wasm_fac("proposals/multi-value/fac"); - fn wasm_func("proposals/multi-value/func"); - fn wasm_if("proposals/multi-value/if"); - fn wasm_loop("proposals/multi-value/loop"); - fn wasm_type("proposals/multi-value/type"); - } -} - -mod mutable_global { - use super::{mvp_config, run::run_wasm_spec_test}; - - fn make_config() -> wasmi::Config { - let mut config = mvp_config(); - config.wasm_mutable_global(true); - config - } - - define_spec_tests! { - let config = make_config(); - let runner = run_wasm_spec_test; - - fn wasm_globals("proposals/mutable-global/globals"); - // We expect failure for this test case temporarily because `wasmi` already implements - // the intended behavior using the semantics introduced in the `bulk-memory` - // Wasm proposal withot having `bulk-memory` Wasm proposal support: - // https://github.com/WebAssembly/bulk-memory-operations/blob/master/proposals/bulk-memory-operations/Overview.md#segment-initialization - // - // Unignore the test case and remove this comment once `bulk-memory` has been implemented. - #[should_panic] fn wasm_linking("proposals/mutable-global/linking"); - } -} - -mod bulk_memory { - use super::{mvp_config, run::run_wasm_spec_test}; - - fn make_config() -> wasmi::Config { - let mut config = mvp_config(); - // For some reason we need to enable `mutable-global` Wasm proposal - // to properly pass all the `bulk-memory` Wasm spec tests. - config.wasm_mutable_global(true); - config - } - - define_spec_tests! { - let config = make_config(); - let runner = run_wasm_spec_test; - - // Even though `wasmi` does not implement the `bulk-memory` Wasm proposal - // at the time of this writing we run this specific test from its test suite - // because `wasmi` already uses the changed semantics for segment instantiation - // coming from the `bulk-memory` Wasm proposal. - // This is why the other `linking` tests are expected to fail. - fn wasm_linking("proposals/bulk-memory-operations/linking"); - } +/// Create a [`Config`] for the Wasm MVP feature set. +fn mvp_config() -> Config { + let mut config = Config::default(); + config + .wasm_mutable_global(false) + .wasm_saturating_float_to_int(false) + .wasm_sign_extension(false) + .wasm_multi_value(false); + config } -/// Run Wasm spec test suite using MVP `wasmi` configuration. +/// Create a [`Config`] with all Wasm feature supported by `wasmi` enabled. /// /// # Note /// /// The Wasm MVP has no Wasm proposals enabled. -fn make_config() -> wasmi::Config { +fn make_config() -> Config { let mut config = mvp_config(); // We have to enable the `mutable-global` Wasm proposal because // it seems that the entire Wasm spec test suite is already built // on the basis of its semantics. config.wasm_mutable_global(true); + config.wasm_saturating_float_to_int(true); + config.wasm_sign_extension(true); + config.wasm_multi_value(true); + config.wasm_bulk_memory(true); config } @@ -223,23 +85,23 @@ define_spec_tests! { fn wasm_address("address"); fn wasm_align("align"); - fn wasm_binary("binary"); fn wasm_binary_leb128("binary-leb128"); + #[should_panic(expected = "reference types support is not enabled")] fn wasm_binary("binary"); fn wasm_block("block"); fn wasm_br("br"); fn wasm_br_if("br_if"); - fn wasm_br_table("br_table"); - fn wasm_break_drop("break-drop"); + #[should_panic(expected = "reference types support is not enabled")] fn wasm_br_table("br_table"); + #[should_panic(expected = "reference types support is not enabled")] fn wasm_bulk("bulk"); fn wasm_call("call"); - fn wasm_call_indirect("call_indirect"); + #[should_panic(expected = "multiple tables")] fn wasm_call_indirect("call_indirect"); fn wasm_comments("comments"); fn wasm_const("const"); fn wasm_conversions("conversions"); fn wasm_custom("custom"); fn wasm_data("data"); - fn wasm_elem("elem"); + #[should_panic(expected = "reference types support is not enabled")] fn wasm_elem("elem"); fn wasm_endianness("endianness"); - fn wasm_exports("exports"); + #[should_panic(expected = "multiple tables")] fn wasm_exports("exports"); fn wasm_f32("f32"); fn wasm_f32_bitwise("f32_bitwise"); fn wasm_f32_cmp("f32_cmp"); @@ -254,53 +116,57 @@ define_spec_tests! { fn wasm_forward("forward"); fn wasm_func("func"); fn wasm_func_ptrs("func_ptrs"); - fn wasm_global("global"); - fn wasm_globals("globals"); + #[should_panic(expected = "reference types support is not enabled")] fn wasm_global("global"); fn wasm_i32("i32"); fn wasm_i64("i64"); fn wasm_if("if"); - fn wasm_imports("imports"); - fn inline_module("inline-module"); + #[should_panic(expected = "multiple tables")] fn wasm_imports("imports"); + fn wasm_inline_module("inline-module"); fn wasm_int_exprs("int_exprs"); fn wasm_int_literals("int_literals"); fn wasm_labels("labels"); fn wasm_left_to_right("left-to-right"); - // We expect failure for this test case temporarily because `wasmi` already implements - // the intended behavior using the semantics introduced in the `bulk-memory` - // Wasm proposal withot having `bulk-memory` Wasm proposal support: - // https://github.com/WebAssembly/bulk-memory-operations/blob/master/proposals/bulk-memory-operations/Overview.md#segment-initialization - // - // Unignore the test case and remove this comment once `bulk-memory` has been implemented. - #[should_panic] fn wasm_linking("linking"); - fn wasm_loop("loop"); + #[should_panic(expected = "reference types support is not enabled")] fn wasm_linking("linking"); fn wasm_load("load"); fn wasm_local_get("local_get"); fn wasm_local_set("local_set"); fn wasm_local_tee("local_tee"); + fn wasm_loop("loop"); fn wasm_memory("memory"); - fn wasm_memory_redundancy("memory_redundancy"); - fn wasm_memory_trap("memory_trap"); + fn wasm_memory_copy("memory_copy"); + fn wasm_memory_fill("memory_fill"); fn wasm_memory_grow("memory_grow"); + fn wasm_memory_init("memory_init"); + fn wasm_memory_redundancy("memory_redundancy"); fn wasm_memory_size("memory_size"); + fn wasm_memory_trap("memory_trap"); fn wasm_names("names"); fn wasm_nop("nop"); + #[should_panic(expected = "reference types support is not enabled")] fn wasm_ref_func("ref_func"); + #[should_panic(expected = "reference types support is not enabled")] fn wasm_ref_is_null("ref_is_null"); + #[should_panic(expected = "reference types support is not enabled")] fn wasm_ref_null("ref_null"); fn wasm_return("return"); - fn wasm_select("select"); + #[should_panic(expected = "reference types support is not enabled")] fn wasm_select("select"); fn wasm_skip_stack_guard_page("skip-stack-guard-page"); fn wasm_stack("stack"); fn wasm_start("start"); fn wasm_store("store"); fn wasm_switch("switch"); - fn wasm_table("table"); + fn wasm_table_sub("table-sub"); + #[should_panic(expected = "multiple tables")] fn wasm_table("table"); + #[should_panic(expected = "multiple tables")] fn wasm_table_copy("table_copy"); + #[should_panic(expected = "reference types support is not enabled")] fn wasm_table_fill("table_fill"); + #[should_panic(expected = "reference types support is not enabled")] fn wasm_table_get("table_get"); + #[should_panic(expected = "reference types support is not enabled")] fn wasm_table_grow("table_grow"); + #[should_panic(expected = "multiple tables")] fn wasm_table_init("table_init"); + #[should_panic(expected = "reference types support is not enabled")] fn wasm_table_set("table_set"); + #[should_panic(expected = "multiple tables")] fn wasm_table_size("table_size"); fn wasm_token("token"); fn wasm_traps("traps"); fn wasm_type("type"); - fn wasm_typecheck("typecheck"); fn wasm_unreachable("unreachable"); - // I do not remember why we ignored this test but it might either be - // due to some updated semantics in Wasm proposals that we already - // implement or due to actual bugs in `wasmi`. Need to investigate. - #[ignore] fn wasm_unreached_invalid("unreached-invalid"); + fn wasm_unreached_invalid("unreached-invalid"); + #[should_panic(expected = "reference types support is not enabled")] fn wasm_unreached_valid("unreached-valid"); fn wasm_unwind("unwind"); fn wasm_utf8_custom_section_id("utf8-custom-section-id"); fn wasm_utf8_import_field("utf8-import-field"); diff --git a/crates/wasmi/tests/spec/testsuite b/crates/wasmi/tests/spec/testsuite index 0e7987efba..cade5b7e5b 160000 --- a/crates/wasmi/tests/spec/testsuite +++ b/crates/wasmi/tests/spec/testsuite @@ -1 +1 @@ -Subproject commit 0e7987efba9c13c5a65c2c14a8f2f04b3820e8d3 +Subproject commit cade5b7e5b19429c3d9a2bee796ef96fffe6641b