diff --git a/crates/arena/src/lib.rs b/crates/arena/src/lib.rs index df4d7f49a7..fe8306b29a 100644 --- a/crates/arena/src/lib.rs +++ b/crates/arena/src/lib.rs @@ -32,7 +32,6 @@ 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}, @@ -159,8 +158,15 @@ where 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); + if fst_index == snd_index { + return None; + } + if fst_index > snd_index { + let (fst, snd) = self.get_pair_mut(snd, fst)?; + return Some((snd, fst)); + } + // At this point we know that fst_index < snd_index. + let (fst_set, snd_set) = self.entities.split_at_mut(snd_index); let fst = fst_set.get_mut(fst_index)?; let snd = snd_set.get_mut(0)?; Some((fst, snd)) diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index a5bd5edacc..14e3fe3d74 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -1,8 +1,8 @@ use anyhow::{anyhow, bail, Result}; use clap::Parser; -use core::fmt::Write; use std::{ ffi::OsStr, + fmt::{self, Write}, fs, path::{Path, PathBuf}, }; @@ -221,8 +221,9 @@ fn type_check_arguments( ) -> Result> { if func_type.params().len() != func_args.len() { bail!( - "invalid number of arguments given for {func_name} of type {func_type}. \ + "invalid number of arguments given for {func_name} of type {}. \ expected {} argument but got {}", + DisplayFuncType(func_type), func_type.params().len(), func_args.len() ); @@ -256,6 +257,12 @@ fn type_check_arguments( .map(F64::from) .map(Value::from) .map_err(make_err!()), + ValueType::FuncRef => { + bail!("the wasmi CLI cannot take arguments of type funcref") + } + ValueType::ExternRef => { + bail!("the wasmi CLI cannot take arguments of type externref") + } } }) .collect::, _>>()?; @@ -276,17 +283,39 @@ fn prepare_results_buffer(func_type: &FuncType) -> Vec { fn print_execution_start(wasm_file: &Path, func_name: &str, func_args: &[Value]) { print!("executing {wasm_file:?}::{func_name}("); if let Some((first_arg, rest_args)) = func_args.split_first() { - print!("{first_arg}"); - for arg in rest_args { + print!("{}", DisplayValue(first_arg)); + for arg in rest_args.iter().map(DisplayValue) { print!(", {arg}"); } } println!(") ..."); } +/// Wrapper type that implements `Display` for [`Value`]. +struct DisplayValue<'a>(&'a Value); + +impl<'a> fmt::Display for DisplayValue<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match &self.0 { + Value::I32(value) => write!(f, "{value}"), + Value::I64(value) => write!(f, "{value}"), + Value::F32(value) => write!(f, "{value}"), + Value::F64(value) => write!(f, "{value}"), + Value::FuncRef(value) => panic!("cannot display funcref values but found {value:?}"), + Value::ExternRef(value) => { + panic!("cannot display externref values but found {value:?}") + } + } + } +} + /// Prints the results of the Wasm computation in a human readable form. fn print_pretty_results(results: &[Value]) { - let pretty_results = results.iter().map(Value::to_string).collect::>(); + let pretty_results = results + .iter() + .map(DisplayValue) + .map(|v| v.to_string()) + .collect::>(); match pretty_results.len() { 1 => { println!("{}", pretty_results[0]); @@ -303,3 +332,133 @@ fn print_pretty_results(results: &[Value]) { } } } + +/// Wrapper type around [`FuncType`] that implements `Display` for it. +struct DisplayFuncType<'a>(&'a FuncType); + +impl fmt::Display for DisplayFuncType<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "fn(")?; + let params = self.0.params(); + let results = self.0.results(); + write_slice(f, params, ",")?; + write!(f, ")")?; + if let Some((first, rest)) = results.split_first() { + write!(f, " -> ")?; + if !rest.is_empty() { + write!(f, "(")?; + } + write!(f, "{first}")?; + for result in rest { + write!(f, ", {result}")?; + } + if !rest.is_empty() { + write!(f, ")")?; + } + } + Ok(()) + } +} + +/// Writes the elements of a `slice` separated by the `separator`. +fn write_slice(f: &mut fmt::Formatter, slice: &[T], separator: &str) -> fmt::Result +where + T: fmt::Display, +{ + if let Some((first, rest)) = slice.split_first() { + write!(f, "{first}")?; + for param in rest { + write!(f, "{separator} {param}")?; + } + } + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use core::borrow::Borrow; + + fn assert_display(func_type: impl Borrow, expected: &str) { + assert_eq!( + format!("{}", DisplayFuncType(func_type.borrow())), + String::from(expected), + ); + } + + #[test] + fn display_0in_0out() { + assert_display(FuncType::new([], []), "fn()"); + } + + #[test] + fn display_1in_0out() { + assert_display(FuncType::new([ValueType::I32], []), "fn(i32)"); + } + + #[test] + fn display_0in_1out() { + assert_display(FuncType::new([], [ValueType::I32]), "fn() -> i32"); + } + + #[test] + fn display_1in_1out() { + assert_display( + FuncType::new([ValueType::I32], [ValueType::I32]), + "fn(i32) -> i32", + ); + } + + #[test] + fn display_4in_0out() { + assert_display( + FuncType::new( + [ + ValueType::I32, + ValueType::I64, + ValueType::F32, + ValueType::F64, + ], + [], + ), + "fn(i32, i64, f32, f64)", + ); + } + + #[test] + fn display_0in_4out() { + assert_display( + FuncType::new( + [], + [ + ValueType::I32, + ValueType::I64, + ValueType::F32, + ValueType::F64, + ], + ), + "fn() -> (i32, i64, f32, f64)", + ); + } + + #[test] + fn display_4in_4out() { + assert_display( + FuncType::new( + [ + ValueType::I32, + ValueType::I64, + ValueType::F32, + ValueType::F64, + ], + [ + ValueType::I32, + ValueType::I64, + ValueType::F32, + ValueType::F64, + ], + ), + "fn(i32, i64, f32, f64) -> (i32, i64, f32, f64)", + ); + } +} diff --git a/crates/core/src/trap.rs b/crates/core/src/trap.rs index 43b66d5f17..e4b69cd15d 100644 --- a/crates/core/src/trap.rs +++ b/crates/core/src/trap.rs @@ -243,10 +243,10 @@ impl TrapCode { /// other uses since it avoid heap memory allocation in certain cases. pub fn trap_message(&self) -> &'static str { match self { - Self::UnreachableCodeReached => "unreachable", + Self::UnreachableCodeReached => "wasm `unreachable` instruction executed", Self::MemoryOutOfBounds => "out of bounds memory access", - Self::TableOutOfBounds => "undefined element", - Self::IndirectCallToNull => "uninitialized element", + Self::TableOutOfBounds => "undefined element: out of bounds table access", + Self::IndirectCallToNull => "uninitialized element 2", // TODO: fixme, remove the trailing " 2" again Self::IntegerDivisionByZero => "integer divide by zero", Self::IntegerOverflow => "integer overflow", Self::BadConversionToInteger => "invalid conversion to integer", diff --git a/crates/core/src/value.rs b/crates/core/src/value.rs index 8005049108..feaced839b 100644 --- a/crates/core/src/value.rs +++ b/crates/core/src/value.rs @@ -19,6 +19,27 @@ pub enum ValueType { F32, /// 64-bit IEEE 754-2008 floating point number. F64, + /// A nullable function reference. + FuncRef, + /// A nullable external reference. + ExternRef, +} + +impl ValueType { + /// Returns `true` if [`ValueType`] is a Wasm numeric type. + /// + /// This is `true` for [`ValueType::I32`], [`ValueType::I64`], + /// [`ValueType::F32`] and [`ValueType::F64`]. + pub fn is_num(&self) -> bool { + matches!(self, Self::I32 | Self::I64 | Self::F32 | Self::F64) + } + + /// Returns `true` if [`ValueType`] is a Wasm reference type. + /// + /// This is `true` for [`ValueType::FuncRef`] and [`ValueType::ExternRef`]. + pub fn is_ref(&self) -> bool { + matches!(self, Self::ExternRef | Self::FuncRef) + } } impl Display for ValueType { @@ -28,6 +49,8 @@ impl Display for ValueType { Self::I64 => write!(f, "i64"), Self::F32 => write!(f, "f32"), Self::F64 => write!(f, "f64"), + Self::FuncRef => write!(f, "funcref"), + Self::ExternRef => write!(f, "externref"), } } } diff --git a/crates/wasmi/benches/benches.rs b/crates/wasmi/benches/benches.rs index 10694009a8..c384e5885c 100644 --- a/crates/wasmi/benches/benches.rs +++ b/crates/wasmi/benches/benches.rs @@ -7,8 +7,8 @@ use self::bench::{ load_wasm_from_file, wat2wasm, }; +use core::{slice, time::Duration}; use criterion::{criterion_group, criterion_main, Bencher, Criterion}; -use std::{slice, time::Duration}; use wasmi::{core::TrapCode, Engine, Extern, Func, Linker, Memory, Module, Store, Value}; use wasmi_core::{Pages, ValueType, F32, F64}; @@ -293,8 +293,8 @@ fn bench_execute_rev_comp(c: &mut Criterion) { prepare_rev_complement .call(&mut store, &[input_size], slice::from_mut(&mut result)) .unwrap(); - let test_data_ptr = match result { - value @ Value::I32(_) => value, + let test_data_ptr = match &result { + Value::I32(value) => Value::I32(*value), _ => panic!("unexpected non-I32 result found for prepare_rev_complement"), }; @@ -304,10 +304,14 @@ fn bench_execute_rev_comp(c: &mut Criterion) { .and_then(Extern::into_func) .unwrap(); rev_complement_input_ptr - .call(&mut store, &[test_data_ptr], slice::from_mut(&mut result)) + .call( + &mut store, + slice::from_ref(&test_data_ptr), + slice::from_mut(&mut result), + ) .unwrap(); - let input_data_mem_offset = match result { - Value::I32(value) => value, + let input_data_mem_offset = match &result { + Value::I32(value) => *value, _ => panic!("unexpected non-I32 result found for prepare_rev_complement"), }; @@ -327,7 +331,7 @@ fn bench_execute_rev_comp(c: &mut Criterion) { b.iter(|| { bench_rev_complement - .call(&mut store, &[test_data_ptr], &mut []) + .call(&mut store, slice::from_ref(&test_data_ptr), &mut []) .unwrap(); }); @@ -337,10 +341,14 @@ fn bench_execute_rev_comp(c: &mut Criterion) { .and_then(Extern::into_func) .unwrap(); rev_complement_output_ptr - .call(&mut store, &[test_data_ptr], slice::from_mut(&mut result)) + .call( + &mut store, + slice::from_ref(&test_data_ptr), + slice::from_mut(&mut result), + ) .unwrap(); - let output_data_mem_offset = match result { - Value::I32(value) => value, + let output_data_mem_offset = match &result { + Value::I32(value) => *value, _ => panic!("unexpected non-I32 result found for prepare_rev_complement"), }; @@ -366,8 +374,8 @@ fn bench_execute_regex_redux(c: &mut Criterion) { prepare_regex_redux .call(&mut store, &[input_size], slice::from_mut(&mut result)) .unwrap(); - let test_data_ptr = match result { - value @ Value::I32(_) => value, + let test_data_ptr = match &result { + Value::I32(value) => Value::I32(*value), _ => panic!("unexpected non-I32 result found for prepare_regex_redux"), }; @@ -377,10 +385,14 @@ fn bench_execute_regex_redux(c: &mut Criterion) { .and_then(Extern::into_func) .unwrap(); regex_redux_input_ptr - .call(&mut store, &[test_data_ptr], slice::from_mut(&mut result)) + .call( + &mut store, + slice::from_ref(&test_data_ptr), + slice::from_mut(&mut result), + ) .unwrap(); - let input_data_mem_offset = match result { - Value::I32(value) => value, + let input_data_mem_offset = match &result { + Value::I32(value) => *value, _ => panic!("unexpected non-I32 result found for regex_redux_input_ptr"), }; @@ -400,7 +412,7 @@ fn bench_execute_regex_redux(c: &mut Criterion) { b.iter(|| { bench_regex_redux - .call(&mut store, &[test_data_ptr], &mut []) + .call(&mut store, slice::from_ref(&test_data_ptr), &mut []) .unwrap(); }) }); @@ -627,7 +639,7 @@ fn bench_execute_bare_call_4(c: &mut Criterion) { Value::default(ValueType::F32), Value::default(ValueType::F64), ]; - let results = &mut [Value::I32(0); 4]; + let results = &mut [0; 4].map(Value::I32); b.iter(|| { for _ in 0..REPETITIONS { bare_call.call(&mut store, params, results).unwrap(); @@ -662,7 +674,7 @@ fn bench_execute_bare_call_16(c: &mut Criterion) { Value::default(ValueType::F32), Value::default(ValueType::F64), ]; - let results = &mut [Value::I32(0); 16]; + let results = &mut [0; 16].map(Value::I32); b.iter(|| { for _ in 0..REPETITIONS { bare_call.call(&mut store, params, results).unwrap(); @@ -679,13 +691,17 @@ fn bench_execute_global_bump(c: &mut Criterion) { .get_export(&store, "bump") .and_then(Extern::into_func) .unwrap(); - let mut result = [Value::I32(0)]; + let mut result = Value::I32(0); b.iter(|| { count_until - .call(&mut store, &[Value::I32(BUMP_AMOUNT)], &mut result) + .call( + &mut store, + &[Value::I32(BUMP_AMOUNT)], + slice::from_mut(&mut result), + ) .unwrap(); - assert_eq!(result, [Value::I32(BUMP_AMOUNT)]); + assert_eq!(result.i32(), Some(BUMP_AMOUNT)); }) }); } @@ -698,13 +714,17 @@ fn bench_execute_global_const(c: &mut Criterion) { .get_export(&store, "call") .and_then(Extern::into_func) .unwrap(); - let mut result = [Value::I32(0)]; + let mut result = Value::I32(0); b.iter(|| { count_until - .call(&mut store, &[Value::I32(LIMIT)], &mut result) + .call( + &mut store, + &[Value::I32(LIMIT)], + slice::from_mut(&mut result), + ) .unwrap(); - assert_eq!(result, [Value::I32(LIMIT)]); + assert_eq!(result.i32(), Some(LIMIT)); }) }); } @@ -741,13 +761,17 @@ fn bench_execute_recursive_ok(c: &mut Criterion) { .get_export(&store, "call") .and_then(Extern::into_func) .unwrap(); - let mut result = [Value::I32(0)]; + let mut result = Value::I32(0); b.iter(|| { bench_call - .call(&mut store, &[Value::I32(RECURSIVE_DEPTH)], &mut result) + .call( + &mut store, + &[Value::I32(RECURSIVE_DEPTH)], + slice::from_mut(&mut result), + ) .unwrap(); - assert_eq!(result, [Value::I32(0)]); + assert_eq!(result.i32(), Some(0)); }) }); } @@ -763,13 +787,17 @@ fn bench_execute_recursive_scan(c: &mut Criterion) { .get_export(&store, "func") .and_then(Extern::into_func) .unwrap(); - let mut result = [Value::I32(0)]; + let mut result = Value::I32(0); b.iter(|| { bench_call - .call(&mut store, &[Value::I32(RECURSIVE_SCAN_DEPTH)], &mut result) + .call( + &mut store, + &[Value::I32(RECURSIVE_SCAN_DEPTH)], + slice::from_mut(&mut result), + ) .unwrap(); - assert_eq!(result, [Value::I32(RECURSIVE_SCAN_EXPECTED)]); + assert_eq!(result.i32(), Some(RECURSIVE_SCAN_EXPECTED)); }) }); } @@ -806,15 +834,18 @@ fn bench_execute_recursive_is_even(c: &mut Criterion) { .get_export(&store, "is_even") .and_then(Extern::into_func) .unwrap(); - let mut result = [Value::I32(0)]; + let mut result = Value::I32(0); b.iter(|| { bench_call - .call(&mut store, &[Value::I32(50_000)], &mut result) + .call( + &mut store, + &[Value::I32(50_000)], + slice::from_mut(&mut result), + ) .unwrap(); }); - - assert_eq!(result, [Value::I32(1)]); + assert_eq!(result.i32(), Some(1)); }); } @@ -839,16 +870,16 @@ fn bench_execute_host_calls(c: &mut Criterion) { .get_export(&store, "call") .and_then(Extern::into_func) .unwrap(); - let mut result = [Value::I64(0)]; + let mut result = Value::I64(0); b.iter(|| { call.call( &mut store, &[Value::I64(HOST_CALLS_REPETITIONS)], - &mut result, + slice::from_mut(&mut result), ) .unwrap(); - assert_eq!(result, [Value::I64(0)]); + assert_eq!(result.i64(), Some(0)); }) }); } @@ -921,12 +952,16 @@ fn bench_execute_memory_sum(c: &mut Criterion) { *byte = new_byte; expected_sum += new_byte as u64 as i64; } - let mut result = [Value::I32(0)]; + let mut result = Value::I64(0); b.iter(|| { - sum.call(&mut store, &[Value::I32(len as i32)], &mut result) - .unwrap(); + sum.call( + &mut store, + &[Value::I32(len as i32)], + slice::from_mut(&mut result), + ) + .unwrap(); }); - assert_eq!(result, [Value::I64(expected_sum)]); + assert_eq!(result.i64(), Some(expected_sum)); }); } diff --git a/crates/wasmi/src/element.rs b/crates/wasmi/src/element.rs index c4251bc00d..1d7226de1c 100644 --- a/crates/wasmi/src/element.rs +++ b/crates/wasmi/src/element.rs @@ -1,6 +1,12 @@ -use crate::{module, module::FuncIdx, store::Stored, AsContextMut}; -use alloc::sync::Arc; +use crate::{ + module, + module::{ElementSegmentItems, InitExpr}, + store::Stored, + AsContext, + AsContextMut, +}; use wasmi_arena::ArenaIndex; +use wasmi_core::ValueType; /// A raw index to a element segment entity. #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] @@ -47,6 +53,24 @@ impl ElementSegment { .inner .alloc_element_segment(entity) } + + /// Returns the number of items in the [`ElementSegment`]. + pub fn size(&self, ctx: impl AsContext) -> u32 { + ctx.as_context() + .store + .inner + .resolve_element_segment(self) + .size() + } + + /// Drops the items of the [`ElementSegment`]. + pub fn drop_items(&self, mut ctx: impl AsContextMut) { + ctx.as_context_mut() + .store + .inner + .resolve_element_segment_mut(self) + .drop_items() + } } /// An instantiated [`ElementSegmentEntity`]. @@ -58,6 +82,8 @@ impl ElementSegment { /// a need to have an instantiated representation of data segments. #[derive(Debug)] pub struct ElementSegmentEntity { + /// The [`ValueType`] of elements of this [`ElementSegmentEntity`]. + ty: ValueType, /// The underlying items of the instance element segment. /// /// # Note @@ -65,32 +91,44 @@ pub struct ElementSegmentEntity { /// These items are just readable after instantiation. /// Using Wasm `elem.drop` simply replaces the instance /// with an empty one. - items: Option]>>, + items: Option, } impl From<&'_ module::ElementSegment> for ElementSegmentEntity { fn from(segment: &'_ module::ElementSegment) -> Self { + let ty = segment.ty(); match segment.kind() { - module::ElementSegmentKind::Passive => Self { - items: Some(segment.clone_items()), + module::ElementSegmentKind::Passive | module::ElementSegmentKind::Active(_) => Self { + ty, + items: Some(segment.items_cloned()), }, - module::ElementSegmentKind::Active(_) => Self::empty(), + module::ElementSegmentKind::Declared => Self::empty(ty), } } } impl ElementSegmentEntity { /// Create an empty [`ElementSegmentEntity`] representing dropped element segments. - fn empty() -> Self { - Self { items: None } + fn empty(ty: ValueType) -> Self { + Self { ty, items: None } + } + + /// Returns the [`ValueType`] of elements in the [`ElementSegmentEntity`]. + pub fn ty(&self) -> ValueType { + self.ty + } + + /// Returns the number of items in the [`ElementSegment`]. + pub fn size(&self) -> u32 { + self.items().len() as u32 } /// Returns the items of the [`ElementSegmentEntity`]. - pub fn items(&self) -> &[Option] { + pub fn items(&self) -> &[InitExpr] { self.items .as_ref() - .map(|items| &items[..]) - .unwrap_or_else(|| &[]) + .map(ElementSegmentItems::items) + .unwrap_or(&[]) } /// Drops the items of the [`ElementSegmentEntity`]. diff --git a/crates/wasmi/src/engine/bytecode/mod.rs b/crates/wasmi/src/engine/bytecode/mod.rs index c010d688dd..f2c01628bd 100644 --- a/crates/wasmi/src/engine/bytecode/mod.rs +++ b/crates/wasmi/src/engine/bytecode/mod.rs @@ -17,6 +17,7 @@ pub use self::utils::{ LocalDepth, Offset, SignatureIdx, + TableIdx, }; use core::fmt::Debug; use wasmi_core::UntypedValue; @@ -31,18 +32,29 @@ use wasmi_core::UntypedValue; /// each representing either the `BrTable` head or one of its branching targets. #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum Instruction { - LocalGet { local_depth: LocalDepth }, - LocalSet { local_depth: LocalDepth }, - LocalTee { local_depth: LocalDepth }, + LocalGet { + local_depth: LocalDepth, + }, + LocalSet { + local_depth: LocalDepth, + }, + LocalTee { + local_depth: LocalDepth, + }, Br(BranchParams), BrIfEqz(BranchParams), BrIfNez(BranchParams), - BrTable { len_targets: usize }, + BrTable { + len_targets: usize, + }, Unreachable, Return(DropKeep), ReturnIfNez(DropKeep), Call(FuncIdx), - CallIndirect(SignatureIdx), + CallIndirect { + table: TableIdx, + func_type: SignatureIdx, + }, Drop, Select, GlobalGet(GlobalIdx), @@ -76,9 +88,33 @@ pub enum Instruction { MemoryCopy, MemoryInit(DataSegmentIdx), DataDrop(DataSegmentIdx), - TableCopy, - TableInit(ElementSegmentIdx), + TableSize { + table: TableIdx, + }, + TableGrow { + table: TableIdx, + }, + TableFill { + table: TableIdx, + }, + TableGet { + table: TableIdx, + }, + TableSet { + table: TableIdx, + }, + TableCopy { + dst: TableIdx, + src: TableIdx, + }, + TableInit { + table: TableIdx, + elem: ElementSegmentIdx, + }, ElemDrop(ElementSegmentIdx), + RefFunc { + func_index: FuncIdx, + }, Const(UntypedValue), I32Eqz, I32Eq, diff --git a/crates/wasmi/src/engine/bytecode/utils.rs b/crates/wasmi/src/engine/bytecode/utils.rs index 478dd76e27..13b5e73862 100644 --- a/crates/wasmi/src/engine/bytecode/utils.rs +++ b/crates/wasmi/src/engine/bytecode/utils.rs @@ -82,6 +82,24 @@ impl FuncIdx { } } +/// A table index. +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +#[repr(transparent)] +pub struct TableIdx(u32); + +impl From for TableIdx { + fn from(index: u32) -> Self { + Self(index) + } +} + +impl TableIdx { + /// Returns the inner `u32` index. + pub fn into_inner(self) -> u32 { + self.0 + } +} + /// An index of a unique function signature. #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[repr(transparent)] diff --git a/crates/wasmi/src/engine/cache.rs b/crates/wasmi/src/engine/cache.rs index b315ca7387..2e924c23c4 100644 --- a/crates/wasmi/src/engine/cache.rs +++ b/crates/wasmi/src/engine/cache.rs @@ -2,7 +2,7 @@ use crate::{ element::{ElementSegment, ElementSegmentEntity}, instance::InstanceEntity, memory::DataSegment, - module::{DEFAULT_MEMORY_INDEX, DEFAULT_TABLE_INDEX}, + module::DEFAULT_MEMORY_INDEX, table::TableEntity, Func, Instance, @@ -13,7 +13,7 @@ use crate::{ use core::ptr::NonNull; use wasmi_core::UntypedValue; -use super::bytecode::{DataSegmentIdx, ElementSegmentIdx}; +use super::bytecode::{DataSegmentIdx, ElementSegmentIdx, TableIdx}; /// A cache for frequently used entities of an [`Instance`]. #[derive(Debug)] @@ -22,8 +22,8 @@ pub struct InstanceCache { instance: Instance, /// The default linear memory of the currently used [`Instance`]. default_memory: Option, - /// The default table of the currently used [`Instance`]. - default_table: Option, + /// The last accessed table of the currently used [`Instance`]. + last_table: Option<(u32, Table)>, /// The last accessed function of the currently used [`Instance`]. last_func: Option<(u32, Func)>, /// The last accessed global variable value of the currently used [`Instance`]. @@ -37,7 +37,7 @@ impl From<&'_ Instance> for InstanceCache { Self { instance: *instance, default_memory: None, - default_table: None, + last_table: None, last_func: None, last_global: None, default_memory_bytes: None, @@ -55,7 +55,7 @@ impl InstanceCache { fn set_instance(&mut self, instance: &Instance) { self.instance = *instance; self.default_memory = None; - self.default_table = None; + self.last_table = None; self.last_func = None; self.last_global = None; self.default_memory_bytes = None; @@ -126,16 +126,17 @@ impl InstanceCache { /// /// If there is no [`ElementSegment`] for the [`Instance`] at the `index`. #[inline] - pub fn get_default_table_and_element_segment<'a>( + pub fn get_table_and_element_segment<'a>( &mut self, ctx: &'a mut StoreInner, + table: TableIdx, segment: ElementSegmentIdx, ) -> ( &'a InstanceEntity, &'a mut TableEntity, &'a ElementSegmentEntity, ) { - let tab = self.default_table(ctx); + let tab = self.get_table(ctx, table); let seg = self.get_element_segment(ctx, segment); let inst = self.instance(); ctx.resolve_instance_table_element(inst, &tab, &seg) @@ -156,21 +157,6 @@ impl InstanceCache { default_memory } - /// Loads the default [`Table`] of the currently used [`Instance`]. - /// - /// # Panics - /// - /// 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(instance) - .get_table(DEFAULT_TABLE_INDEX) - .unwrap_or_else(|| panic!("missing default table for instance: {instance:?}")); - self.default_table = Some(default_table); - default_table - } - /// Returns the default [`Memory`] of the currently used [`Instance`]. /// /// # Panics @@ -222,24 +208,44 @@ impl InstanceCache { self.default_memory_bytes = None; } - /// Returns the default [`Table`] of the currently used [`Instance`]. + /// Returns the [`Table`] at the `index` of the currently used [`Instance`]. /// /// # Panics /// /// If the currently used [`Instance`] does not have a default table. #[inline] - pub fn default_table(&mut self, ctx: &StoreInner) -> Table { - match self.default_table { - Some(default_table) => default_table, - None => self.load_default_table(ctx), + pub fn get_table(&mut self, ctx: &StoreInner, index: TableIdx) -> Table { + let index = index.into_inner(); + match self.last_table { + Some((table_index, table)) if index == table_index => table, + _ => self.load_table_at(ctx, index), } } + /// Loads the [`Table`] at `index` of the currently used [`Instance`]. + /// + /// # Panics + /// + /// If the currently used [`Instance`] does not have the table. + fn load_table_at(&mut self, ctx: &StoreInner, index: u32) -> Table { + let table = ctx + .resolve_instance(self.instance()) + .get_table(index) + .unwrap_or_else(|| { + panic!( + "missing table at index {index} for instance: {:?}", + self.instance + ) + }); + self.last_table = Some((index, table)); + table + } + /// Loads the [`Func`] at `index` of the currently used [`Instance`]. /// /// # Panics /// - /// If the currently used [`Instance`] does not have a default table. + /// If the currently used [`Instance`] does not have the function. fn load_func_at(&mut self, ctx: &StoreInner, index: u32) -> Func { let func = ctx .resolve_instance(self.instance()) diff --git a/crates/wasmi/src/engine/config.rs b/crates/wasmi/src/engine/config.rs index c75a9ffa1b..350f3779db 100644 --- a/crates/wasmi/src/engine/config.rs +++ b/crates/wasmi/src/engine/config.rs @@ -23,6 +23,8 @@ pub struct Config { multi_value: bool, /// Is `true` if the [`bulk-memory`] Wasm proposal is enabled. bulk_memory: bool, + /// Is `true` if the [`reference-types`] Wasm proposal is enabled. + reference_types: bool, } impl Default for Config { @@ -35,6 +37,7 @@ impl Default for Config { saturating_float_to_int: true, multi_value: true, bulk_memory: true, + reference_types: true, } } } @@ -127,6 +130,18 @@ impl Config { self } + /// Enable or disable the [`reference-types`] Wasm proposal for the [`Config`]. + /// + /// # Note + /// + /// Enabled by default. + /// + /// [`multi-value`]: https://github.com/WebAssembly/reference-types + pub fn wasm_reference_types(&mut self, enable: bool) -> &mut Self { + self.reference_types = enable; + self + } + /// Returns the [`WasmFeatures`] represented by the [`Config`]. pub fn wasm_features(&self) -> WasmFeatures { WasmFeatures { @@ -135,7 +150,7 @@ impl Config { saturating_float_to_int: self.saturating_float_to_int, sign_extension: self.sign_extension, bulk_memory: self.bulk_memory, - reference_types: false, + reference_types: self.reference_types, 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 e3b73992e0..2c2ba60ca6 100644 --- a/crates/wasmi/src/engine/executor.rs +++ b/crates/wasmi/src/engine/executor.rs @@ -10,6 +10,7 @@ use super::{ LocalDepth, Offset, SignatureIdx, + TableIdx, }, cache::InstanceCache, code_map::InstructionPtr, @@ -19,7 +20,7 @@ use super::{ FuncFrame, ValueStack, }; -use crate::{core::TrapCode, Func, StoreInner}; +use crate::{core::TrapCode, table::TableEntity, Func, FuncRef, StoreInner}; use core::cmp::{self}; use wasmi_core::{Pages, UntypedValue}; @@ -114,7 +115,9 @@ impl<'ctx, 'engine, 'func> Executor<'ctx, 'engine, 'func> { } } Instr::Call(func) => return self.visit_call(func), - Instr::CallIndirect(signature) => return self.visit_call_indirect(signature), + Instr::CallIndirect { table, func_type } => { + return self.visit_call_indirect(table, func_type) + } Instr::Drop => self.visit_drop(), Instr::Select => self.visit_select(), Instr::GlobalGet(global_idx) => self.visit_global_get(global_idx), @@ -148,9 +151,15 @@ impl<'ctx, 'engine, 'func> Executor<'ctx, 'engine, 'func> { 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::TableSize { table } => self.visit_table_size(table), + Instr::TableGrow { table } => self.visit_table_grow(table), + Instr::TableFill { table } => self.visit_table_fill(table)?, + Instr::TableGet { table } => self.visit_table_get(table)?, + Instr::TableSet { table } => self.visit_table_set(table)?, + Instr::TableCopy { dst, src } => self.visit_table_copy(dst, src)?, + Instr::TableInit { table, elem } => self.visit_table_init(table, elem)?, Instr::ElemDrop(segment) => self.visit_element_drop(segment), + Instr::RefFunc { func_index } => self.visit_ref_func(func_index), Instr::Const(bytes) => self.visit_const(bytes), Instr::I32Eqz => self.visit_i32_eqz(), Instr::I32Eq => self.visit_i32_eq(), @@ -307,16 +316,6 @@ impl<'ctx, 'engine, 'func> Executor<'ctx, 'engine, 'func> { self.cache.default_memory(self.ctx) } - /// Returns the default table. - /// - /// # Panics - /// - /// If there exists is no table for the instance. - #[inline] - fn default_table(&mut self) -> Table { - self.cache.default_table(self.ctx) - } - /// Returns the global variable at the given index. /// /// # Panics @@ -428,11 +427,11 @@ impl<'ctx, 'engine, 'func> Executor<'ctx, 'engine, 'func> { self.value_stack.sync(); } - fn call_func(&mut self, func: Func) -> Result { + fn call_func(&mut self, func: &Func) -> Result { self.next_instr(); self.frame.update_ip(self.ip); self.sync_stack_ptr(); - Ok(CallOutcome::NestedCall(func)) + Ok(CallOutcome::NestedCall(*func)) } fn ret(&mut self, drop_keep: DropKeep) { @@ -534,28 +533,30 @@ impl<'ctx, 'engine, 'func> Executor<'ctx, 'engine, 'func> { fn visit_call(&mut self, func_index: FuncIdx) -> Result { let callee = self.cache.get_func(self.ctx, func_index.into_inner()); - self.call_func(callee) + self.call_func(&callee) } fn visit_call_indirect( &mut self, - signature_index: SignatureIdx, + table: TableIdx, + func_type: SignatureIdx, ) -> Result { let func_index: u32 = self.value_stack.pop_as(); - let table = self.default_table(); - let func = self + let table = self.cache.get_table(self.ctx, table); + let funcref = self .ctx .resolve_table(&table) - .get(func_index) - .map_err(|_| TrapCode::TableOutOfBounds)? - .ok_or(TrapCode::IndirectCallToNull)?; + .get_untyped(func_index) + .map(FuncRef::from) + .ok_or(TrapCode::TableOutOfBounds)?; + let func = funcref.func().ok_or(TrapCode::IndirectCallToNull)?; let actual_signature = self.ctx.get_func_type(func); let expected_signature = self .ctx .resolve_instance(self.frame.instance()) - .get_signature(signature_index.into_inner()) + .get_signature(func_type.into_inner()) .unwrap_or_else(|| { - panic!("missing signature for call_indirect at index: {signature_index:?}") + panic!("missing signature for call_indirect at index: {func_type:?}") }); if actual_signature != expected_signature { return Err(TrapCode::BadSignature).map_err(Into::into); @@ -678,22 +679,94 @@ impl<'ctx, 'engine, 'func> Executor<'ctx, 'engine, 'func> { self.next_instr(); } - fn visit_table_copy(&mut self) -> Result<(), TrapCode> { + fn visit_table_size(&mut self, table_index: TableIdx) { + let table = self.cache.get_table(self.ctx, table_index); + let size = self.ctx.resolve_table(&table).size(); + self.value_stack.push(size); + self.next_instr() + } + + fn visit_table_grow(&mut self, table_index: TableIdx) { + // As demanded by the Wasm specification this value is returned + // by `table.grow` if the growth operation was unsuccessful. + const ERROR_CODE: u32 = u32::MAX; + let (init, delta) = self.value_stack.pop2(); + let delta: u32 = delta.into(); + let table = self.cache.get_table(self.ctx, table_index); + let result = self + .ctx + .resolve_table_mut(&table) + .grow_untyped(delta, init) + .unwrap_or(ERROR_CODE); + self.value_stack.push(result); + self.next_instr() + } + + fn visit_table_fill(&mut self, table_index: TableIdx) -> Result<(), TrapCode> { + // The `n`, `s` and `d` variable bindings are extracted from the Wasm specification. + let (i, val, n) = self.value_stack.pop3(); + let dst: u32 = i.into(); + let len: u32 = n.into(); + let table = self.cache.get_table(self.ctx, table_index); + self.ctx + .resolve_table_mut(&table) + .fill_untyped(dst, val, len)?; + self.next_instr(); + Ok(()) + } + + fn visit_table_get(&mut self, table_index: TableIdx) -> Result<(), TrapCode> { + self.value_stack.try_eval_top(|index| { + let index: u32 = index.into(); + let table = self.cache.get_table(self.ctx, table_index); + self.ctx + .resolve_table(&table) + .get_untyped(index) + .ok_or(TrapCode::TableOutOfBounds) + })?; + self.next_instr(); + Ok(()) + } + + fn visit_table_set(&mut self, table_index: TableIdx) -> Result<(), TrapCode> { + let (index, value) = self.value_stack.pop2(); + let index: u32 = index.into(); + let table = self.cache.get_table(self.ctx, table_index); + self.ctx + .resolve_table_mut(&table) + .set_untyped(index, value) + .map_err(|_| TrapCode::TableOutOfBounds)?; + self.next_instr(); + Ok(()) + } + + fn visit_table_copy(&mut self, dst: TableIdx, src: TableIdx) -> 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)?; + // Query both tables and check if they are the same: + let dst = self.cache.get_table(self.ctx, dst); + let src = self.cache.get_table(self.ctx, src); + if Table::eq(&dst, &src) { + // Copy within the same table: + let table = self.ctx.resolve_table_mut(&dst); + table.copy_within(dst_index, src_index, len)?; + } else { + // Copy from one table to another table: + let (dst, src) = self.ctx.resolve_table_pair_mut(&dst, &src); + TableEntity::copy(dst, dst_index, src, src_index, len)?; + } self.next_instr(); Ok(()) } - fn visit_table_init(&mut self, segment: ElementSegmentIdx) -> Result<(), TrapCode> { + fn visit_table_init( + &mut self, + table: TableIdx, + elem: 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); @@ -701,8 +774,12 @@ impl<'ctx, 'engine, 'func> Executor<'ctx, 'engine, 'func> { 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)?; + .get_table_and_element_segment(self.ctx, table, elem); + table.init(dst_index, element, src_index, len, |func_index| { + instance + .get_func(func_index) + .unwrap_or_else(|| panic!("missing function at index {func_index}")) + })?; self.next_instr(); Ok(()) } @@ -713,6 +790,13 @@ impl<'ctx, 'engine, 'func> Executor<'ctx, 'engine, 'func> { self.next_instr(); } + fn visit_ref_func(&mut self, func_index: FuncIdx) { + let func = self.cache.get_func(self.ctx, func_index.into_inner()); + let funcref = FuncRef::new(func); + self.value_stack.push(funcref); + 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 473133e964..bd842dcf0e 100644 --- a/crates/wasmi/src/engine/func_builder/mod.rs +++ b/crates/wasmi/src/engine/func_builder/mod.rs @@ -25,9 +25,16 @@ pub use self::{ error::TranslationError, inst_builder::{Instr, InstructionsBuilder, RelativeDepth}, }; -use super::{DropKeep, FuncBody, Instruction}; +use super::{bytecode, DropKeep, FuncBody, Instruction}; use crate::{ - engine::bytecode::{BranchParams, DataSegmentIdx, ElementSegmentIdx, Offset}, + engine::bytecode::{ + BranchParams, + DataSegmentIdx, + ElementSegmentIdx, + Offset, + SignatureIdx, + TableIdx, + }, module::{ BlockType, FuncIdx, @@ -37,11 +44,11 @@ use crate::{ MemoryIdx, ModuleResources, ReusableAllocations, - TableIdx, DEFAULT_MEMORY_INDEX, }, Engine, FuncType, + GlobalType, Mutability, Value, }; @@ -139,7 +146,8 @@ impl<'parser> FuncBuilder<'parser> { } /// Resolves the [`FuncType`] of the given [`FuncTypeIdx`]. - fn func_type_at(&self, func_type_index: FuncTypeIdx) -> FuncType { + fn func_type_at(&self, func_type_index: SignatureIdx) -> FuncType { + let func_type_index = FuncTypeIdx(func_type_index.into_inner()); // TODO: use the same type let dedup_func_type = self.res.get_func_type(func_type_index); self.res .engine() @@ -717,24 +725,23 @@ impl<'parser> FuncBuilder<'parser> { /// Translates a Wasm `call_indirect` instruction. pub fn translate_call_indirect( &mut self, - index: u32, + func_type_index: u32, table_index: u32, _table_byte: u8, ) -> Result<(), TranslationError> { self.translate_if_reachable(|builder| { - /// The default Wasm MVP table index. - const DEFAULT_TABLE_INDEX: u32 = 0; - let func_type_idx = FuncTypeIdx(index); - let table_idx = TableIdx(table_index); - assert_eq!(table_idx.into_u32(), DEFAULT_TABLE_INDEX); + let func_type_index = SignatureIdx::from(func_type_index); + let table = TableIdx::from(table_index); builder.stack_height.pop1(); - let func_type = builder.func_type_at(func_type_idx); + let func_type = builder.func_type_at(func_type_index); builder.adjust_value_stack_for_call(&func_type); - let func_type_idx = func_type_idx.into_u32().into(); builder .alloc .inst_builder - .push_inst(Instruction::CallIndirect(func_type_idx)); + .push_inst(Instruction::CallIndirect { + table, + func_type: func_type_index, + }); Ok(()) }) } @@ -758,6 +765,44 @@ impl<'parser> FuncBuilder<'parser> { }) } + pub fn translate_typed_select( + &mut self, + _ty: wasmparser::ValType, + ) -> Result<(), TranslationError> { + // The `ty` parameter is only important for Wasm validation. + // Since `wasmi` bytecode is untyped we are not interested in this additional information. + self.translate_select() + } + + /// Translate a Wasm `ref.null` instruction. + pub fn translate_ref_null(&mut self, _ty: wasmparser::ValType) -> Result<(), TranslationError> { + // Since `wasmi` bytecode is untyped we have no special `null` instructions + // but simply reuse the `constant` instruction with an immediate value of 0. + // Note that `FuncRef` and `ExternRef` are encoded as 64-bit values in `wasmi`. + self.translate_const(0i64) + } + + /// Translate a Wasm `ref.is_null` instruction. + pub fn translate_ref_is_null(&mut self) -> Result<(), TranslationError> { + // Since `wasmi` bytecode is untyped we have no special `null` instructions + // but simply reuse the `i64.eqz` instruction with an immediate value of 0. + // Note that `FuncRef` and `ExternRef` are encoded as 64-bit values in `wasmi`. + self.translate_i64_eqz() + } + + /// Translate a Wasm `ref.func` instruction. + pub fn translate_ref_func(&mut self, func_index: u32) -> Result<(), TranslationError> { + self.translate_if_reachable(|builder| { + let func_index = bytecode::FuncIdx::from(func_index); + builder + .alloc + .inst_builder + .push_inst(Instruction::RefFunc { func_index }); + builder.stack_height.push(); + Ok(()) + }) + } + /// Translate a Wasm `local.get` instruction. pub fn translate_local_get(&mut self, local_idx: u32) -> Result<(), TranslationError> { self.translate_if_reachable(|builder| { @@ -802,15 +847,40 @@ 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::to_const) { - Some(value) if global_type.mutability().is_const() => Instruction::constant(value), - _ => Instruction::GlobalGet(global_idx.into_u32().into()), - }; + let instr = Self::optimize_global_get(&global_type, init_value).unwrap_or_else(|| { + // No optimization took place in this case. + Instruction::GlobalGet(global_idx.into_u32().into()) + }); builder.alloc.inst_builder.push_inst(instr); Ok(()) }) } + /// Returns `Some` equivalent instruction if the `global.get` can be optimzied. + /// + /// # Note + /// + /// Only internal (non-imported) and constant (non-mutable) globals + /// have a chance to be optimized to more efficient instructions. + fn optimize_global_get( + global_type: &GlobalType, + init_value: Option<&InitExpr>, + ) -> Option { + let content_type = global_type.content(); + if let (Mutability::Const, Some(init_expr)) = (global_type.mutability(), init_value) { + if let Some(value) = init_expr.to_const(content_type) { + // We can optimize `global.get` to the constant value. + return Some(Instruction::constant(value)); + } + if let Some(func_index) = init_expr.func_ref() { + // We can optimize `global.get` to the equivalent `ref.func x` instruction. + let func_index = bytecode::FuncIdx::from(func_index.into_u32()); + return Some(Instruction::RefFunc { func_index }); + } + } + None + } + /// Translate a Wasm `global.set` instruction. pub fn translate_global_set(&mut self, global_idx: u32) -> Result<(), TranslationError> { self.translate_if_reachable(|builder| { @@ -1181,85 +1251,84 @@ impl<'parser> FuncBuilder<'parser> { } /// 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") + pub fn translate_table_size(&mut self, table_index: u32) -> Result<(), TranslationError> { + self.translate_if_reachable(|builder| { + let table = TableIdx::from(table_index); + builder.stack_height.push(); + builder + .alloc + .inst_builder + .push_inst(Instruction::TableSize { table }); + Ok(()) }) } /// 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_grow(&mut self, table_index: u32) -> Result<(), TranslationError> { + self.translate_if_reachable(|builder| { + let table = TableIdx::from(table_index); + builder.stack_height.pop1(); + builder + .alloc + .inst_builder + .push_inst(Instruction::TableGrow { table }); + Ok(()) }) } + /// Translate a Wasm `table.copy` instruction. pub fn translate_table_copy( &mut self, - _dst_table: u32, - _src_table: u32, + 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); + let dst = TableIdx::from(dst_table); + let src = TableIdx::from(src_table); builder.stack_height.pop3(); - builder.alloc.inst_builder.push_inst(Instruction::TableCopy); + builder + .alloc + .inst_builder + .push_inst(Instruction::TableCopy { dst, src }); 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") + /// Translate a Wasm `table.fill` instruction. + pub fn translate_table_fill(&mut self, table_index: u32) -> Result<(), TranslationError> { + self.translate_if_reachable(|builder| { + let table = TableIdx::from(table_index); + builder.stack_height.pop3(); + builder + .alloc + .inst_builder + .push_inst(Instruction::TableFill { table }); + Ok(()) }) } - 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") + /// Translate a Wasm `table.get` instruction. + pub fn translate_table_get(&mut self, table_index: u32) -> Result<(), TranslationError> { + self.translate_if_reachable(|builder| { + let table = TableIdx::from(table_index); + builder + .alloc + .inst_builder + .push_inst(Instruction::TableGet { table }); + Ok(()) }) } - 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.set` instruction. + pub fn translate_table_set(&mut self, table_index: u32) -> Result<(), TranslationError> { + self.translate_if_reachable(|builder| { + let table = TableIdx::from(table_index); + builder.stack_height.pop2(); + builder + .alloc + .inst_builder + .push_inst(Instruction::TableSet { table }); + Ok(()) }) } @@ -1270,14 +1339,13 @@ impl<'parser> FuncBuilder<'parser> { table_index: u32, ) -> Result<(), TranslationError> { self.translate_if_reachable(|builder| { - debug_assert_eq!(table_index, DEFAULT_MEMORY_INDEX); builder.stack_height.pop3(); + let table = TableIdx::from(table_index); + let elem = ElementSegmentIdx::from(segment_index); builder .alloc .inst_builder - .push_inst(Instruction::TableInit(ElementSegmentIdx::from( - segment_index, - ))); + .push_inst(Instruction::TableInit { table, elem }); Ok(()) }) } diff --git a/crates/wasmi/src/engine/func_builder/visit.rs b/crates/wasmi/src/engine/func_builder/visit.rs index d595e11114..f5d5b603d3 100644 --- a/crates/wasmi/src/engine/func_builder/visit.rs +++ b/crates/wasmi/src/engine/func_builder/visit.rs @@ -37,6 +37,10 @@ macro_rules! for_each_supported_operator { fn visit_call_indirect(index: u32, table_index: u32, table_byte: u8) => fn translate_call_indirect fn visit_drop() => fn translate_drop fn visit_select() => fn translate_select + fn visit_typed_select(ty: wasmparser::ValType) => fn translate_typed_select + fn visit_ref_null(ty: wasmparser::ValType) => fn translate_ref_null + fn visit_ref_is_null() => fn translate_ref_is_null + fn visit_ref_func(func_index: u32) => fn translate_ref_func fn visit_local_get(local_index: u32) => fn translate_local_get fn visit_local_set(local_index: u32) => fn translate_local_set fn visit_local_tee(local_index: u32) => fn translate_local_tee @@ -272,6 +276,10 @@ macro_rules! define_unsupported_visit_operator { // Supported operators are handled by `define_supported_visit_operator`. define_unsupported_visit_operator!($($rest)*); }; + ( @reference_types $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/tests.rs b/crates/wasmi/src/engine/tests.rs index b08bc147cd..911a4aaa00 100644 --- a/crates/wasmi/src/engine/tests.rs +++ b/crates/wasmi/src/engine/tests.rs @@ -57,7 +57,7 @@ fn assert_func_body( assert_eq!( actual, expected, - "encountered instruction mismatch for {} at position {index}", + "encountered instruction mismatch for {:?} at position {index}", engine.resolve_func_type(&func_type, Clone::clone), ); } diff --git a/crates/wasmi/src/engine/traits.rs b/crates/wasmi/src/engine/traits.rs index 09530fbcf4..b6f7ea629a 100644 --- a/crates/wasmi/src/engine/traits.rs +++ b/crates/wasmi/src/engine/traits.rs @@ -25,7 +25,7 @@ impl<'a> CallParams for &'a [Value] { #[inline] fn call_params(self) -> Self::Params { CallParamsValueIter { - iter: self.iter().copied(), + iter: self.iter().cloned(), } } } @@ -33,7 +33,7 @@ impl<'a> CallParams for &'a [Value] { /// An iterator over the [`UntypedValue`] call parameters. #[derive(Debug)] pub struct CallParamsValueIter<'a> { - iter: iter::Copied>, + iter: iter::Cloned>, } impl<'a> Iterator for CallParamsValueIter<'a> { diff --git a/crates/wasmi/src/externref.rs b/crates/wasmi/src/externref.rs new file mode 100644 index 0000000000..02dfd09449 --- /dev/null +++ b/crates/wasmi/src/externref.rs @@ -0,0 +1,218 @@ +use crate::{store::Stored, AsContextMut, StoreContext}; +use alloc::boxed::Box; +use core::{any::Any, num::NonZeroU32}; +use wasmi_arena::ArenaIndex; +use wasmi_core::UntypedValue; + +/// A raw index to a function entity. +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct ExternObjectIdx(NonZeroU32); + +impl ArenaIndex for ExternObjectIdx { + fn into_usize(self) -> usize { + self.0.get().wrapping_sub(1) as usize + } + + fn from_usize(index: usize) -> Self { + index + .try_into() + .ok() + .map(|index: u32| index.wrapping_add(1)) + .and_then(NonZeroU32::new) + .map(Self) + .unwrap_or_else(|| panic!("out of bounds extern object index {index}")) + } +} + +/// An externally defined object. +#[derive(Debug)] +pub struct ExternObjectEntity { + inner: Box, +} + +impl ExternObjectEntity { + /// Creates a new instance of `ExternRef` wrapping the given value. + pub fn new(object: T) -> Self + where + T: 'static + Any + Send + Sync, + { + Self { + inner: Box::new(object), + } + } + + /// Returns a shared reference to the external object. + pub fn data(&self) -> &dyn Any { + &*self.inner + } +} + +/// Represents an opaque reference to any data within WebAssembly. +#[derive(Debug, Copy, Clone)] +#[repr(transparent)] +pub struct ExternObject(Stored); + +impl ExternObject { + /// Creates a new [`ExternObject`] reference from its raw representation. + pub(crate) fn from_inner(stored: Stored) -> Self { + Self(stored) + } + + /// Returns the raw representation of the [`ExternObject`]. + pub(crate) fn as_inner(&self) -> &Stored { + &self.0 + } + + /// Creates a new instance of `ExternRef` wrapping the given value. + pub fn new(mut ctx: impl AsContextMut, object: T) -> Self + where + T: 'static + Any + Send + Sync, + { + ctx.as_context_mut() + .store + .inner + .alloc_extern_object(ExternObjectEntity::new(object)) + } + + /// Returns a shared reference to the underlying data for this [`ExternRef`]. + /// + /// # Panics + /// + /// Panics if `ctx` does not own this [`ExternObject`]. + pub fn data<'a, T: 'a>(&self, ctx: impl Into>) -> &'a dyn Any { + ctx.into().store.inner.resolve_external_object(self).data() + } +} + +/// Represents a nullable opaque reference to any data within WebAssembly. +#[derive(Debug, Default, Copy, Clone)] +#[repr(transparent)] +pub struct ExternRef { + inner: Option, +} + +/// Type used to convert between [`ExternRef`] and [`UntypedValue`]. +union Transposer { + externref: ExternRef, + untyped: UntypedValue, +} + +#[test] +fn externref_sizeof() { + // These assertions are important in order to convert `FuncRef` + // from and to 64-bit `UntypedValue` instances. + // + // The following equation must be true: + // size_of(ExternRef) == size_of(ExternObject) == size_of(UntypedValue) + use core::mem::size_of; + assert_eq!(size_of::(), size_of::()); + assert_eq!(size_of::(), size_of::()); +} + +#[test] +fn externref_null_to_zero() { + assert_eq!(UntypedValue::from(ExternRef::null()), UntypedValue::from(0)); + assert!(ExternRef::from(UntypedValue::from(0)).is_null()); +} + +impl From for ExternRef { + fn from(untyped: UntypedValue) -> Self { + // Safety: This operation is safe since there are no invalid + // bit patterns for [`ExternRef`] instances. Therefore + // this operation cannot produce invalid [`ExternRef`] + // instances even though the input [`UntypedValue`] + // was modified arbitrarily. + unsafe { Transposer { untyped }.externref }.canonicalize() + } +} + +impl From for UntypedValue { + fn from(externref: ExternRef) -> Self { + let externref = externref.canonicalize(); + // Safety: This operation is safe since there are no invalid + // bit patterns for [`UntypedValue`] instances. Therefore + // this operation cannot produce invalid [`UntypedValue`] + // instances even if it was possible to arbitrarily modify + // the input [`ExternRef`] instance. + unsafe { Transposer { externref }.untyped } + } +} + +impl ExternRef { + /// Creates a new [`ExternRef`] wrapping the given value. + pub fn new(ctx: impl AsContextMut, object: impl Into>) -> Self + where + T: 'static + Any + Send + Sync, + { + object + .into() + .map(|object| ExternObject::new(ctx, object)) + .map(Self::from_object) + .unwrap_or_else(Self::null) + .canonicalize() + } + + /// Canonicalize `self` so that all `null` values have the same representation. + /// + /// # Note + /// + /// The underlying issue is that `ExternRef` has many possible values for the + /// `null` value. However, to simplify operating on encoded `ExternRef` instances + /// (encoded as `UntypedValue`) we want it to encode to exactly one `null` + /// value. The most trivial of all possible `null` values is `0_u64`, therefore + /// we canonicalize all `null` values to be represented by `0_u64`. + fn canonicalize(self) -> Self { + if self.is_null() { + // Safety: This is safe since `0u64` can be bit + // interpreted as a valid `ExternRef` value. + return unsafe { + Transposer { + untyped: UntypedValue::from(0u64), + } + .externref + }; + } + self + } + + /// Creates a new [`ExternRef`] to the given [`ExternObject`]. + fn from_object(object: ExternObject) -> Self { + Self { + inner: Some(object), + } + } + + /// Creates a new [`ExternRef`] which is `null`. + pub fn null() -> Self { + Self { inner: None }.canonicalize() + } + + /// Returns `true` if [`ExternRef`] is `null`. + pub fn is_null(&self) -> bool { + self.inner.is_none() + } + + /// Returns a shared reference to the underlying data for this [`ExternRef`]. + /// + /// # Panics + /// + /// Panics if `ctx` does not own this [`ExternRef`]. + pub fn data<'a, T: 'a>(&self, ctx: impl Into>) -> Option<&'a dyn Any> { + self.inner.map(|object| object.data(ctx)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{Engine, Store}; + + #[test] + fn it_works() { + let engine = Engine::default(); + let mut store = >::new(&engine, ()); + let value = 42_i32; + let obj = ExternObject::new::(&mut store, value); + assert_eq!(obj.data(&store).downcast_ref::(), Some(&value),); + } +} diff --git a/crates/wasmi/src/func/mod.rs b/crates/wasmi/src/func/mod.rs index 4a780606ae..5a131034c9 100644 --- a/crates/wasmi/src/func/mod.rs +++ b/crates/wasmi/src/func/mod.rs @@ -20,23 +20,27 @@ use super::{ }; use crate::{core::Trap, engine::ResumableCall, Error, FuncType, Value}; use alloc::sync::Arc; -use core::{fmt, fmt::Debug}; +use core::{fmt, fmt::Debug, num::NonZeroU32}; use wasmi_arena::ArenaIndex; +use wasmi_core::UntypedValue; /// A raw index to a function entity. #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] -pub struct FuncIdx(u32); +pub struct FuncIdx(NonZeroU32); impl ArenaIndex for FuncIdx { fn into_usize(self) -> usize { - self.0 as usize + self.0.get().wrapping_sub(1) 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 func index: {error}") - }); - Self(value) + fn from_usize(index: usize) -> Self { + index + .try_into() + .ok() + .map(|index: u32| index.wrapping_add(1)) + .and_then(NonZeroU32::new) + .map(Self) + .unwrap_or_else(|| panic!("out of bounds func index {index}")) } } @@ -407,3 +411,123 @@ impl Func { ctx.into().store.resolve_func(self).as_internal() } } + +/// A nullable [`Func`] reference. +#[derive(Debug, Default, Copy, Clone)] +#[repr(transparent)] +pub struct FuncRef { + inner: Option, +} + +impl From for FuncRef { + fn from(func: Func) -> Self { + Self::new(func) + } +} + +/// Type used to convert between [`FuncRef`] and [`UntypedValue`]. +union Transposer { + funcref: FuncRef, + untyped: UntypedValue, +} + +#[test] +fn funcref_sizeof() { + // These assertions are important in order to convert `FuncRef` + // from and to 64-bit `UntypedValue` instances. + // + // The following equation must be true: + // size_of(Func) == size_of(UntypedValue) == size_of(FuncRef) + use core::mem::size_of; + assert_eq!(size_of::(), size_of::()); + assert_eq!(size_of::(), size_of::()); +} + +#[test] +fn funcref_null_to_zero() { + assert_eq!(UntypedValue::from(FuncRef::null()), UntypedValue::from(0)); + assert!(FuncRef::from(UntypedValue::from(0)).is_null()); +} + +impl From for FuncRef { + fn from(untyped: UntypedValue) -> Self { + // Safety: This operation is safe since there are no invalid + // bit patterns for [`FuncRef`] instances. Therefore + // this operation cannot produce invalid [`FuncRef`] + // instances even though the input [`UntypedValue`] + // was modified arbitrarily. + unsafe { Transposer { untyped }.funcref }.canonicalize() + } +} + +impl From for UntypedValue { + fn from(funcref: FuncRef) -> Self { + let funcref = funcref.canonicalize(); + // Safety: This operation is safe since there are no invalid + // bit patterns for [`UntypedValue`] instances. Therefore + // this operation cannot produce invalid [`UntypedValue`] + // instances even if it was possible to arbitrarily modify + // the input [`FuncRef`] instance. + unsafe { Transposer { funcref }.untyped } + } +} + +impl FuncRef { + /// Returns `true` if [`FuncRef`] is `null`. + pub fn is_null(&self) -> bool { + self.inner.is_none() + } + + /// Canonicalize `self` so that all `null` values have the same representation. + /// + /// # Note + /// + /// The underlying issue is that `FuncRef` has many possible values for the + /// `null` value. However, to simplify operating on encoded `FuncRef` instances + /// (encoded as `UntypedValue`) we want it to encode to exactly one `null` + /// value. The most trivial of all possible `null` values is `0_u64`, therefore + /// we canonicalize all `null` values to be represented by `0_u64`. + fn canonicalize(self) -> Self { + if self.is_null() { + // Safety: This is safe since `0u64` can be bit + // interpreted as a valid `FuncRef` value. + return unsafe { + Transposer { + untyped: UntypedValue::from(0u64), + } + .funcref + }; + } + self + } + + /// Creates a new [`FuncRef`]. + /// + /// # Examples + /// + /// ```rust + /// # use wasmi::{Func, FuncRef, Store, Engine}; + /// # let engine = Engine::default(); + /// # let mut store = >::new(&engine, ()); + /// assert!(FuncRef::new(None).is_null()); + /// assert!(FuncRef::new(Func::wrap(&mut store, |x: i32| x)).func().is_some()); + /// ``` + pub fn new(nullable_func: impl Into>) -> Self { + Self { + inner: nullable_func.into(), + } + .canonicalize() + } + + /// Returns the inner [`Func`] if [`FuncRef`] is not `null`. + /// + /// Otherwise returns `None`. + pub fn func(&self) -> Option<&Func> { + self.inner.as_ref() + } + + /// Creates a `null` [`FuncRef`]. + pub fn null() -> Self { + Self::new(None).canonicalize() + } +} diff --git a/crates/wasmi/src/func_type.rs b/crates/wasmi/src/func_type.rs index aa0d4c6918..a360b00fe1 100644 --- a/crates/wasmi/src/func_type.rs +++ b/crates/wasmi/src/func_type.rs @@ -1,6 +1,5 @@ use crate::{core::ValueType, func::FuncError, Value}; use alloc::{sync::Arc, vec::Vec}; -use core::fmt::{self, Display}; /// A function type representing a function's parameter and result types. /// @@ -23,43 +22,6 @@ pub struct FuncType { params_results: Arc<[ValueType]>, } -impl Display for FuncType { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "fn(")?; - let (params, results) = self.params_results(); - write_slice(f, params, ",")?; - write!(f, ")")?; - if let Some((first, rest)) = results.split_first() { - write!(f, " -> ")?; - if !rest.is_empty() { - write!(f, "(")?; - } - write!(f, "{first}")?; - for result in rest { - write!(f, ", {result}")?; - } - if !rest.is_empty() { - write!(f, ")")?; - } - } - Ok(()) - } -} - -/// Writes the elements of a `slice` separated by the `separator`. -fn write_slice(f: &mut fmt::Formatter, slice: &[T], separator: &str) -> fmt::Result -where - T: Display, -{ - if let Some((first, rest)) = slice.split_first() { - write!(f, "{first}")?; - for param in rest { - write!(f, "{separator} {param}")?; - } - } - Ok(()) -} - impl FuncType { /// Creates a new [`FuncType`]. pub fn new(params: P, results: R) -> Self @@ -193,7 +155,6 @@ impl Ty for Value { #[cfg(test)] mod tests { use super::*; - use core::borrow::Borrow; #[test] fn new_empty_works() { @@ -246,84 +207,4 @@ mod tests { } } } - - fn assert_display(func_type: impl Borrow, expected: &str) { - assert_eq!(format!("{}", func_type.borrow()), String::from(expected),); - } - - #[test] - fn display_0in_0out() { - assert_display(FuncType::new([], []), "fn()"); - } - - #[test] - fn display_1in_0out() { - assert_display(FuncType::new([ValueType::I32], []), "fn(i32)"); - } - - #[test] - fn display_0in_1out() { - assert_display(FuncType::new([], [ValueType::I32]), "fn() -> i32"); - } - - #[test] - fn display_1in_1out() { - assert_display( - FuncType::new([ValueType::I32], [ValueType::I32]), - "fn(i32) -> i32", - ); - } - - #[test] - fn display_4in_0out() { - assert_display( - FuncType::new( - [ - ValueType::I32, - ValueType::I64, - ValueType::F32, - ValueType::F64, - ], - [], - ), - "fn(i32, i64, f32, f64)", - ); - } - - #[test] - fn display_0in_4out() { - assert_display( - FuncType::new( - [], - [ - ValueType::I32, - ValueType::I64, - ValueType::F32, - ValueType::F64, - ], - ), - "fn() -> (i32, i64, f32, f64)", - ); - } - - #[test] - fn display_4in_4out() { - assert_display( - FuncType::new( - [ - ValueType::I32, - ValueType::I64, - ValueType::F32, - ValueType::F64, - ], - [ - ValueType::I32, - ValueType::I64, - ValueType::F32, - ValueType::F64, - ], - ), - "fn(i32, i64, f32, f64) -> (i32, i64, f32, f64)", - ); - } } diff --git a/crates/wasmi/src/global.rs b/crates/wasmi/src/global.rs index 7acb7cb2e2..192b657d13 100644 --- a/crates/wasmi/src/global.rs +++ b/crates/wasmi/src/global.rs @@ -150,8 +150,8 @@ impl GlobalEntity { /// Creates a new global entity with the given initial value and mutability. pub fn new(initial_value: Value, mutability: Mutability) -> Self { Self { - value: initial_value.into(), ty: GlobalType::new(initial_value.ty(), mutability), + value: initial_value.into(), } } diff --git a/crates/wasmi/src/lib.rs b/crates/wasmi/src/lib.rs index 30ab771c79..54539456e8 100644 --- a/crates/wasmi/src/lib.rs +++ b/crates/wasmi/src/lib.rs @@ -91,6 +91,7 @@ mod element; mod engine; mod error; mod external; +mod externref; mod func; mod func_type; mod global; @@ -139,9 +140,11 @@ pub use self::{ }, error::Error, external::{Extern, ExternType}, + externref::ExternRef, func::{ Caller, Func, + FuncRef, IntoFunc, TypedFunc, WasmParams, diff --git a/crates/wasmi/src/linker.rs b/crates/wasmi/src/linker.rs index 895cf304f8..d058570251 100644 --- a/crates/wasmi/src/linker.rs +++ b/crates/wasmi/src/linker.rs @@ -1,16 +1,11 @@ -use super::{ - errors::{MemoryError, TableError}, - AsContextMut, - Error, - Extern, - InstancePre, - Module, -}; +use super::{AsContextMut, Error, Extern, InstancePre, Module}; use crate::{ module::{ImportName, ImportType}, ExternType, FuncType, GlobalType, + MemoryType, + TableType, }; use alloc::{ collections::{btree_map::Entry, BTreeMap}, @@ -35,62 +30,114 @@ pub enum LinkerError { /// The duplicated imported item. /// /// This refers to the second inserted item. - import_item: Extern, + duplicate: Extern, }, /// Encountered when no definition for an import is found. - CannotFindDefinitionForImport { + MissingDefinition { /// The name of the import for which no definition was found. name: ImportName, - // /// The module name of the import for which no definition has been found. - // module_name: String, - // /// The field name of the import for which no definition has been found. - // field_name: Option, /// The type of the import for which no definition has been found. - item_type: ExternType, + ty: ExternType, }, - /// Encountered when a function signature does not match the expected signature. + /// Encountered when a definition with invalid type is found. + InvalidTypeDefinition { + /// The name of the import for which no definition was found. + name: ImportName, + /// The expected import type. + expected: ExternType, + /// The found definition type. + found: ExternType, + }, + /// Encountered when a [`FuncType`] does not match the expected [`FuncType`]. FuncTypeMismatch { /// The name of the import with the mismatched type. name: ImportName, - /// The expected function type. + /// The expected [`FuncType`]. expected: FuncType, - /// The actual function signature found. - actual: FuncType, + /// The mismatching [`FuncType`] found. + found: FuncType, + }, + /// Encountered when a [`TableType`] does not match the expected [`TableType`]. + InvalidTableSubtype { + /// The name of the import with the invalid [`TableType`]. + name: ImportName, + /// The [`TableType`] that is supposed to be a subtype of `other`. + ty: TableType, + /// The [`TableType`] this is supposed to be a supertype of `ty`. + other: TableType, + }, + /// Encountered when a [`MemoryType`] does not match the expected [`MemoryType`]. + InvalidMemorySubtype { + /// The name of the import with the invalid [`MemoryType`]. + name: ImportName, + /// The [`MemoryType`] that is supposed to be a subtype of `other`. + ty: MemoryType, + /// The [`MemoryType`] this is supposed to be a supertype of `ty`. + other: MemoryType, }, - /// Occurs when an imported table does not satisfy the required table type. - Table(TableError), - /// Occurs when an imported memory does not satisfy the required memory type. - Memory(MemoryError), - /// Encountered when an imported global variable has a mismatching global variable type. + /// Encountered when a [`GlobalType`] does not match the expected [`GlobalType`]. GlobalTypeMismatch { /// The name of the import with the mismatched type. name: ImportName, - /// The expected global variable type. + /// The expected [`GlobalType`]. expected: GlobalType, - /// The actual global variable type found. - actual: GlobalType, + /// The mismatching [`GlobalType`] found. + found: GlobalType, }, } impl LinkerError { /// Creates a new [`LinkerError`] for when an imported definition was not found. - pub fn cannot_find_definition_of_import(import: &ImportType) -> Self { - Self::CannotFindDefinitionForImport { + fn missing_definition(import: &ImportType) -> Self { + Self::MissingDefinition { name: import.import_name().clone(), - item_type: import.ty().clone(), + ty: import.ty().clone(), } } -} -impl From for LinkerError { - fn from(error: TableError) -> Self { - Self::Table(error) + /// Creates a new [`LinkerError`] for when an imported definition has an invalid type. + fn invalid_type_definition(import: &ImportType, found: &ExternType) -> Self { + Self::InvalidTypeDefinition { + name: import.import_name().clone(), + expected: import.ty().clone(), + found: found.clone(), + } + } + + /// Create a new [`LinkerError`] for when a [`FuncType`] mismatched. + fn func_type_mismatch(name: &ImportName, expected: &FuncType, found: &FuncType) -> Self { + Self::FuncTypeMismatch { + name: name.clone(), + expected: expected.clone(), + found: found.clone(), + } } -} -impl From for LinkerError { - fn from(error: MemoryError) -> Self { - Self::Memory(error) + /// Create a new [`LinkerError`] for when a [`TableType`] `ty` unexpectedly is not a subtype of `other`. + fn table_type_mismatch(name: &ImportName, ty: &TableType, other: &TableType) -> Self { + Self::InvalidTableSubtype { + name: name.clone(), + ty: *ty, + other: *other, + } + } + + /// Create a new [`LinkerError`] for when a [`MemoryType`] `ty` unexpectedly is not a subtype of `other`. + fn invalid_memory_subtype(name: &ImportName, ty: &MemoryType, other: &MemoryType) -> Self { + Self::InvalidMemorySubtype { + name: name.clone(), + ty: *ty, + other: *other, + } + } + + /// Create a new [`LinkerError`] for when a [`GlobalType`] mismatched. + fn global_type_mismatch(name: &ImportName, expected: &GlobalType, found: &GlobalType) -> Self { + Self::GlobalTypeMismatch { + name: name.clone(), + expected: *expected, + found: *found, + } } } @@ -102,40 +149,60 @@ impl Display for LinkerError { match self { Self::DuplicateDefinition { import_name, - import_item, + duplicate, } => { write!( f, - "encountered duplicate definition `{import_name}` of {import_item:?}", + "encountered duplicate definition `{import_name}` of {duplicate:?}", ) } - Self::CannotFindDefinitionForImport { name, item_type } => { - write!(f, "cannot find definition for import {name}: {item_type:?}",) + Self::MissingDefinition { name, ty } => { + write!( + f, + "cannot find definition for import {name} with type {ty:?}", + ) + } + Self::InvalidTypeDefinition { + name, + expected, + found, + } => { + write!(f, "found definition for import {name} with type {expected:?} but found type {found:?}") } Self::FuncTypeMismatch { name, expected, - actual, + found, } => { write!( f, "function type mismatch for import {name}: \ - expected {expected:?} but found {actual:?}", + expected {expected:?} but found {found:?}", + ) + } + Self::InvalidTableSubtype { name, ty, other } => { + write!( + f, + "import {name}: table type {ty:?} is not a subtype of {other:?}" + ) + } + Self::InvalidMemorySubtype { name, ty, other } => { + write!( + f, + "import {name}: memory type {ty:?} is not a subtype of {other:?}" ) } Self::GlobalTypeMismatch { name, expected, - actual, + found, } => { write!( f, "global variable type mismatch for import {name}: \ - expected {expected:?} but found {actual:?}", + expected {expected:?} but found {found:?}", ) } - Self::Table(error) => Display::fmt(error, f), - Self::Memory(error) => Display::fmt(error, f), } } } @@ -313,7 +380,7 @@ impl Linker { let import_name = ImportName::new(module_name, field_name); return Err(LinkerError::DuplicateDefinition { import_name, - import_item: item, + duplicate: item, }); } Entry::Vacant(v) => { @@ -363,51 +430,52 @@ impl Linker { context: impl AsContextMut, import: ImportType, ) -> Result { - let make_err = || LinkerError::cannot_find_definition_of_import(&import); + let import_name = import.import_name(); let module_name = import.module(); let field_name = import.name(); - let resolved = self.resolve(module_name, field_name); - let context = context.as_context(); + let resolved = self + .resolve(module_name, field_name) + .ok_or_else(|| LinkerError::missing_definition(&import))?; + let invalid_type = || LinkerError::invalid_type_definition(&import, &resolved.ty(&context)); match import.ty() { - ExternType::Func(expected_func_type) => { - let func = resolved.and_then(Extern::into_func).ok_or_else(make_err)?; - let actual_func_type = func.ty_dedup(&context); - let actual_func_type = context.store.inner.resolve_func_type(actual_func_type); - if &actual_func_type != expected_func_type { - return Err(LinkerError::FuncTypeMismatch { - name: import.import_name().clone(), - expected: expected_func_type.clone(), - actual: actual_func_type, - }) + ExternType::Func(expected_type) => { + let func = resolved.into_func().ok_or_else(invalid_type)?; + let found_type = func.ty(&context); + if &found_type != expected_type { + return Err(LinkerError::func_type_mismatch( + import_name, + expected_type, + &found_type, + )) .map_err(Into::into); } Ok(Extern::Func(func)) } - ExternType::Table(expected_table_type) => { - let table = resolved.and_then(Extern::into_table).ok_or_else(make_err)?; - let actual_table_type = table.ty(context); - actual_table_type.satisfies(expected_table_type)?; + ExternType::Table(expected_type) => { + let table = resolved.into_table().ok_or_else(invalid_type)?; + let found_type = table.dynamic_ty(context); + found_type.is_subtype_or_err(expected_type).map_err(|_| { + LinkerError::table_type_mismatch(import_name, expected_type, &found_type) + })?; Ok(Extern::Table(table)) } - ExternType::Memory(expected_memory_type) => { - let memory = resolved - .and_then(Extern::into_memory) - .ok_or_else(make_err)?; - let actual_memory_type = memory.ty(context); - actual_memory_type.satisfies(expected_memory_type)?; + ExternType::Memory(expected_type) => { + let memory = resolved.into_memory().ok_or_else(invalid_type)?; + let found_type = memory.import_ty(context); + found_type.is_subtype_or_err(expected_type).map_err(|_| { + LinkerError::invalid_memory_subtype(import_name, expected_type, &found_type) + })?; Ok(Extern::Memory(memory)) } - ExternType::Global(expected_global_type) => { - let global = resolved - .and_then(Extern::into_global) - .ok_or_else(make_err)?; - let actual_global_type = global.ty(context); - if &actual_global_type != expected_global_type { - return Err(LinkerError::GlobalTypeMismatch { - name: import.import_name().clone(), - expected: *expected_global_type, - actual: actual_global_type, - }) + ExternType::Global(expected_type) => { + let global = resolved.into_global().ok_or_else(invalid_type)?; + let found_type = global.ty(context); + if &found_type != expected_type { + return Err(LinkerError::global_type_mismatch( + import_name, + expected_type, + &found_type, + )) .map_err(Into::into); } Ok(Extern::Global(global)) diff --git a/crates/wasmi/src/memory/mod.rs b/crates/wasmi/src/memory/mod.rs index c8e716513f..90716bcdc3 100644 --- a/crates/wasmi/src/memory/mod.rs +++ b/crates/wasmi/src/memory/mod.rs @@ -37,12 +37,12 @@ pub enum MemoryError { OutOfBoundsAccess, /// Tried to create an invalid linear memory type. InvalidMemoryType, - /// Occurs when a memory type does not satisfy the constraints of another. - UnsatisfyingMemoryType { - /// The unsatisfying [`MemoryType`]. - unsatisfying: MemoryType, - /// The required [`MemoryType`]. - required: MemoryType, + /// Occurs when `ty` is not a subtype of `other`. + InvalidSubtype { + /// The [`MemoryType`] which is not a subtype of `other`. + ty: MemoryType, + /// The [`MemoryType`] which is supposed to be a supertype of `ty`. + other: MemoryType, }, } @@ -61,15 +61,8 @@ impl Display for MemoryError { Self::InvalidMemoryType => { write!(f, "tried to create an invalid virtual memory type") } - Self::UnsatisfyingMemoryType { - unsatisfying, - required, - } => { - write!( - f, - "memory type {unsatisfying:?} does not \ - satisfy requirements of {required:?}", - ) + Self::InvalidSubtype { ty, other } => { + write!(f, "memory type {ty:?} is not a subtype of {other:?}",) } } } @@ -118,30 +111,46 @@ impl MemoryType { self.maximum_pages } - /// Checks if `self` satisfies the given `MemoryType`. + /// Checks if `self` is a subtype of `other`. + /// + /// # Note + /// + /// This implements the [subtyping rules] according to the WebAssembly spec. + /// + /// [import subtyping]: + /// https://webassembly.github.io/spec/core/valid/types.html#import-subtyping /// /// # Errors /// - /// - If the initial limits of the `required` [`MemoryType`] are greater than `self`. - /// - If the maximum limits of the `required` [`MemoryType`] are greater than `self`. - pub(crate) fn satisfies(&self, required: &MemoryType) -> Result<(), MemoryError> { - if required.initial_pages() > self.initial_pages() { - return Err(MemoryError::UnsatisfyingMemoryType { - unsatisfying: *self, - required: *required, - }); + /// - If the `minimum` size of `self` is less than or equal to the `minimum` size of `other`. + /// - If the `maximum` size of `self` is greater than the `maximum` size of `other`. + pub(crate) fn is_subtype_or_err(&self, other: &MemoryType) -> Result<(), MemoryError> { + match self.is_subtype_of(other) { + true => Ok(()), + false => Err(MemoryError::InvalidSubtype { + ty: *self, + other: *other, + }), } - match (required.maximum_pages(), self.maximum_pages()) { - (None, _) => (), - (Some(max_required), Some(max)) if max_required >= max => (), - _ => { - return Err(MemoryError::UnsatisfyingMemoryType { - unsatisfying: *self, - required: *required, - }); - } + } + + /// Returns `true` if the [`MemoryType`] is a subtype of the `other` [`MemoryType`]. + /// + /// # Note + /// + /// This implements the [subtyping rules] according to the WebAssembly spec. + /// + /// [import subtyping]: + /// https://webassembly.github.io/spec/core/valid/types.html#import-subtyping + pub(crate) fn is_subtype_of(&self, other: &MemoryType) -> bool { + if self.initial_pages() < other.initial_pages() { + return false; + } + match (self.maximum_pages(), other.maximum_pages()) { + (_, None) => true, + (Some(max), Some(other_max)) => max <= other_max, + _ => false, } - Ok(()) } } @@ -173,6 +182,19 @@ impl MemoryEntity { self.memory_type } + /// The current [`MemoryType`] of the linear memory. + /// + /// # Note + /// + /// This respects the current size of the [`Memory`] as its minimum size + /// and is useful for import subtyping checks. + pub fn import_ty(&self) -> MemoryType { + let current_pages = self.current_pages().into(); + let maximum_pages = self.ty().maximum_pages().map(Into::into); + MemoryType::new(current_pages, maximum_pages) + .unwrap_or_else(|_| panic!("must result in valid memory type due to invariants")) + } + /// Returns the amount of pages in use by the linear memory. pub fn current_pages(&self) -> Pages { self.current_pages @@ -286,6 +308,24 @@ impl Memory { ctx.as_context().store.inner.resolve_memory(self).ty() } + /// The current [`MemoryType`] of the linear memory. + /// + /// # Note + /// + /// This respects the current size of the [`Memory`] as its minimum size + /// and is useful for import subtyping checks. + /// + /// # Panics + /// + /// Panics if `ctx` does not own this [`Memory`]. + pub(crate) fn import_ty(&self, ctx: impl AsContext) -> MemoryType { + ctx.as_context() + .store + .inner + .resolve_memory(self) + .import_ty() + } + /// Returns the amount of pages in use by the linear memory. /// /// # Panics @@ -401,3 +441,23 @@ impl Memory { .write(offset, buffer) } } + +#[cfg(test)] +mod tests { + use super::*; + + fn memory_type(minimum: u32, maximum: impl Into>) -> MemoryType { + MemoryType::new(minimum, maximum.into()).unwrap() + } + + #[test] + fn subtyping_works() { + assert!(memory_type(0, 1).is_subtype_of(&memory_type(0, 1))); + assert!(memory_type(0, 1).is_subtype_of(&memory_type(0, 2))); + assert!(!memory_type(0, 2).is_subtype_of(&memory_type(0, 1))); + assert!(memory_type(2, None).is_subtype_of(&memory_type(1, None))); + assert!(memory_type(0, None).is_subtype_of(&memory_type(0, None))); + assert!(memory_type(0, 1).is_subtype_of(&memory_type(0, None))); + assert!(!memory_type(0, None).is_subtype_of(&memory_type(0, 1))); + } +} diff --git a/crates/wasmi/src/module/builder.rs b/crates/wasmi/src/module/builder.rs index 9cb3915ec7..60d6279633 100644 --- a/crates/wasmi/src/module/builder.rs +++ b/crates/wasmi/src/module/builder.rs @@ -92,10 +92,16 @@ impl<'a> ModuleResources<'a> { /// Returns the global variable type and optional initial value. pub fn get_global(&self, global_idx: GlobalIdx) -> (GlobalType, Option<&InitExpr>) { let index = global_idx.into_u32() as usize; - let offset = self.res.imports.len_globals(); - let global_type = self.res.globals[index]; - let init_expr = self.res.globals_init.get(index + offset); - (global_type, init_expr) + let len_imports = self.res.imports.len_globals(); + let global_type = self.get_type_of_global(global_idx); + if index < len_imports { + // The index refers to an imported global without init value. + (global_type, None) + } else { + // The index refers to an internal global with init value. + let init_expr = &self.res.globals_init[index - len_imports]; + (global_type, Some(init_expr)) + } } } diff --git a/crates/wasmi/src/module/element.rs b/crates/wasmi/src/module/element.rs index 385c461505..3dc2ab40a8 100644 --- a/crates/wasmi/src/module/element.rs +++ b/crates/wasmi/src/module/element.rs @@ -1,6 +1,7 @@ -use super::{FuncIdx, InitExpr, TableIdx}; -use crate::errors::ModuleError; +use super::{InitExpr, TableIdx}; +use crate::{errors::ModuleError, module::utils::WasmiValueType}; use alloc::sync::Arc; +use wasmi_core::ValueType; /// A table element segment within a [`Module`]. /// @@ -9,8 +10,46 @@ use alloc::sync::Arc; pub struct ElementSegment { /// The kind of the [`ElementSegment`]. kind: ElementSegmentKind, + /// The type of elements of the [`ElementSegment`]. + ty: ValueType, /// The items of the [`ElementSegment`]. - items: Arc<[Option]>, + items: ElementSegmentItems, +} + +/// The items of an [`ElementSegment`]. +#[derive(Debug, Clone)] +pub struct ElementSegmentItems { + exprs: Arc<[InitExpr]>, +} + +impl ElementSegmentItems { + /// Creates new [`ElementSegmentItems`] from the given [`wasmparser::ElementItems`]. + /// + /// # Panics + /// + /// If the given [`wasmparser::ElementItem`] is invalid. + fn new(items: &wasmparser::ElementItems) -> Self { + let exprs = items + .get_items_reader() + .unwrap_or_else(|error| panic!("failed to parse element items: {error}")) + .into_iter() + .map(|item| { + let item = item.as_ref().unwrap_or_else(|error| { + panic!("failed to parse element item {item:?}: {error}") + }); + match item { + wasmparser::ElementItem::Func(func_index) => InitExpr::new_funcref(*func_index), + wasmparser::ElementItem::Expr(expr) => InitExpr::new(*expr), + } + }) + .collect::>(); + Self { exprs } + } + + /// Returns a shared reference to the items of the [`ElementSegmentItems`]. + pub fn items(&self) -> &[InitExpr] { + &self.exprs + } } /// The kind of a Wasm [`ElementSegment`]. @@ -20,6 +59,8 @@ pub enum ElementSegmentKind { Passive, /// An active [`ElementSegment`]. Active(ActiveElementSegment), + /// A declared [`ElementSegment`] from the `reference-types` Wasm proposal. + Declared, } /// An active Wasm element segment. @@ -60,9 +101,7 @@ impl TryFrom> for ElementSegmentKind { })) } wasmparser::ElementKind::Passive => Ok(Self::Passive), - wasmparser::ElementKind::Declared => { - panic!("wasmi does not support the `reference-types` Wasm proposal but found declared element segment") - } + wasmparser::ElementKind::Declared => Ok(Self::Declared), } } } @@ -71,25 +110,15 @@ 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" + assert!( + element.ty.is_reference_type(), + "only reftypes are allowed as element types but found: {:?}", + element.ty ); let kind = ElementSegmentKind::try_from(element.kind)?; - let items = element - .items - .get_items_reader()? - .into_iter() - .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::, _>>()?; - Ok(ElementSegment { kind, items }) + let ty = WasmiValueType::from(element.ty).into_inner(); + let items = ElementSegmentItems::new(&element.items); + Ok(ElementSegment { kind, ty, items }) } } @@ -99,13 +128,13 @@ impl ElementSegment { &self.kind } - /// Returns the element items of the [`ElementSegment`]. - pub fn items(&self) -> &[Option] { - &self.items[..] + /// Returns the [`ValueType`] of the [`ElementSegment`]. + pub fn ty(&self) -> ValueType { + self.ty } - /// Clone the underlying items of the [`ElementSegment`]. - pub fn clone_items(&self) -> Arc<[Option]> { + /// Returns the element items of the [`ElementSegment`]. + pub fn items_cloned(&self) -> ElementSegmentItems { self.items.clone() } } diff --git a/crates/wasmi/src/module/import.rs b/crates/wasmi/src/module/import.rs index ebafcafe54..dec3e3b113 100644 --- a/crates/wasmi/src/module/import.rs +++ b/crates/wasmi/src/module/import.rs @@ -123,15 +123,6 @@ pub enum ExternTypeIdx { pub struct FuncTypeIdx(pub(crate) u32); impl FuncTypeIdx { - /// Returns the [`FuncTypeIdx`] as `u32`. - /// - /// # Note - /// - /// This is mostly useful for indexing into buffers. - pub fn into_u32(self) -> u32 { - self.0 - } - /// Returns the [`FuncTypeIdx`] as `usize`. /// /// # Note diff --git a/crates/wasmi/src/module/init_expr.rs b/crates/wasmi/src/module/init_expr.rs index a73cf88109..11fcc5302d 100644 --- a/crates/wasmi/src/module/init_expr.rs +++ b/crates/wasmi/src/module/init_expr.rs @@ -1,6 +1,6 @@ use super::{FuncIdx, GlobalIdx}; -use crate::{errors::ModuleError, Value}; -use wasmi_core::{F32, F64}; +use crate::{errors::ModuleError, ExternRef, FuncRef, Value}; +use wasmi_core::{ValueType, F32, F64}; /// An initializer expression. /// @@ -40,23 +40,42 @@ impl InitExpr { Self { op } } - /// Convert the [`InitExpr`] into the underlying Wasm `elemexpr` if possible. + /// Create a new `ref.func x` [`InitExpr`]. + pub fn new_funcref(func_index: u32) -> Self { + Self { + op: InitExprOperand::FuncRef(func_index), + } + } + + /// Convert the [`InitExpr`] into a Wasm `funcexpr` index 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 { + /// If the [`InitExpr`] cannot be interpreted as `funcref` index. + pub fn as_funcref(&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) + panic!("encountered non-funcref Wasm expression {:?}", self.op) } } } + /// Converts the [`InitExpr`] into an `externref`. + /// + /// # Panics + /// + /// If the [`InitExpr`] cannot be interpreted as `externref`. + pub fn as_externref(&self) -> ExternRef { + match self.op { + InitExprOperand::RefNull => ExternRef::null(), + _ => panic!("encountered non-externref Wasm expression: {:?}", self.op), + } + } + /// Return the `Const` [`InitExpr`] if any. /// /// Returns `None` if the underlying operand is not `Const`. @@ -64,17 +83,41 @@ impl InitExpr { /// # Panics /// /// If a non-const expression operand is encountered. - pub fn to_const(&self) -> Option { - match self.op { - InitExprOperand::Const(value) => Some(value), - // 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, + pub fn to_const(&self, ty: ValueType) -> Option { + match &self.op { + InitExprOperand::Const(value) => Some(value.clone()), + InitExprOperand::RefNull => match ty { + ValueType::FuncRef => Some(Value::from(FuncRef::null())), + ValueType::ExternRef => Some(Value::from(ExternRef::null())), + _ => panic!("cannot have null reference for non-reftype but found {ty:?}"), + }, + 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 + } + InitExprOperand::FuncRef(_) => { + // Note: We do not need to handle `global.get` here + // since we can do this somewhere else where we + // can properly handle the constant `func.ref`. + // In the function builder we want to replace + // `global.get` of constant `FuncRef(x)` with + // the Wasm `ref.func x` instruction. + None + } + } + } + + /// Returns `Some(index)` if the [`InitExpr`] is a `FuncRef(index)`. + /// + /// Otherwise returns `None`. + pub fn func_ref(&self) -> Option { + if let InitExprOperand::FuncRef(index) = self.op { + return Some(FuncIdx(index)); } + None } /// Evaluates the [`InitExpr`] given the context for global variables. @@ -82,14 +125,24 @@ impl InitExpr { /// # 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, + pub fn to_const_with_context( + &self, + value_type: ValueType, + global_get: impl Fn(u32) -> Value, + func_get: impl Fn(u32) -> Value, + ) -> Value { + let value = match &self.op { + InitExprOperand::Const(value) => value.clone(), InitExprOperand::GlobalGet(index) => global_get(index.into_u32()), - ref error @ (InitExprOperand::FuncRef(_) | InitExprOperand::RefNull) => { - panic!("encountered non-const expression operand: {error:?}") - } - } + InitExprOperand::RefNull => match value_type { + ValueType::FuncRef => Value::from(FuncRef::null()), + ValueType::ExternRef => Value::from(ExternRef::null()), + _ => panic!("expected reftype for InitExpr but found {value_type:?}"), + }, + InitExprOperand::FuncRef(index) => func_get(*index), + }; + assert_eq!(value.ty(), value_type); + value } } diff --git a/crates/wasmi/src/module/instantiate/error.rs b/crates/wasmi/src/module/instantiate/error.rs index d692e44ae0..5a7f219980 100644 --- a/crates/wasmi/src/module/instantiate/error.rs +++ b/crates/wasmi/src/module/instantiate/error.rs @@ -77,7 +77,7 @@ impl Display for InstantiationError { amount, } => write!( f, - "out of bounds: table {table:?} does not fit {amount} elements starting from offset {offset}", + "out of bounds table access: {table:?} does not fit {amount} elements starting from offset {offset}", ), Self::FoundStartFn { index } => { write!(f, "found an unexpected start function with index {index}") diff --git a/crates/wasmi/src/module/instantiate/mod.rs b/crates/wasmi/src/module/instantiate/mod.rs index 9b59debed0..30f031d9cc 100644 --- a/crates/wasmi/src/module/instantiate/mod.rs +++ b/crates/wasmi/src/module/instantiate/mod.rs @@ -15,6 +15,7 @@ use crate::{ Extern, ExternType, FuncEntity, + FuncRef, FuncType, Global, Instance, @@ -24,6 +25,7 @@ use crate::{ Table, Value, }; +use wasmi_core::{Trap, ValueType}; impl Module { /// Instantiates a new [`Instance`] from the given compiled [`Module`]. @@ -57,7 +59,7 @@ impl Module { self.extract_func_types(&mut context, &mut builder); self.extract_imports(&mut context, &mut builder, externals)?; self.extract_functions(&mut context, &mut builder, handle); - self.extract_tables(&mut context, &mut builder); + self.extract_tables(&mut context, &mut builder)?; self.extract_memories(&mut context, &mut builder); self.extract_globals(&mut context, &mut builder); self.extract_exports(&mut builder); @@ -143,18 +145,18 @@ impl Module { builder.push_func(func); } (ExternType::Table(required), Extern::Table(table)) => { - let imported = table.ty(context.as_context()); - imported.satisfies(required)?; + let imported = table.dynamic_ty(context.as_context()); + imported.is_subtype_or_err(required)?; builder.push_table(table); } (ExternType::Memory(required), Extern::Memory(memory)) => { - let imported = memory.ty(context.as_context()); - imported.satisfies(required)?; + let imported = memory.import_ty(context.as_context()); + imported.is_subtype_or_err(required)?; builder.push_memory(memory); } (ExternType::Global(required), Extern::Global(global)) => { let imported = global.ty(context.as_context()); - imported.satisfies(required)?; + required.satisfies(&imported)?; builder.push_global(global); } (expected_import, actual_extern_val) => { @@ -194,10 +196,17 @@ impl Module { /// This also stores [`Table`] references into the [`Instance`] under construction. /// /// [`Store`]: struct.Store.html - fn extract_tables(&self, context: &mut impl AsContextMut, builder: &mut InstanceEntityBuilder) { + fn extract_tables( + &self, + context: &mut impl AsContextMut, + builder: &mut InstanceEntityBuilder, + ) -> Result<(), InstantiationError> { for table_type in self.internal_tables().copied() { - builder.push_table(Table::new(context.as_context_mut(), table_type)); + let init = Value::default(table_type.element()); + let table = Table::new(context.as_context_mut(), table_type, init)?; + builder.push_table(table); } + Ok(()) } /// Extracts the Wasm linear memories from the module and stores them into the [`Store`]. @@ -233,7 +242,9 @@ impl Module { builder: &mut InstanceEntityBuilder, ) { for (global_type, global_init) in self.internal_globals() { - let init_value = Self::eval_init_expr(context.as_context_mut(), builder, global_init); + let value_type = global_type.content(); + let init_value = + Self::eval_init_expr(context.as_context_mut(), builder, value_type, global_init); let mutability = global_type.mutability(); let global = Global::new(context.as_context_mut(), init_value, mutability); builder.push_global(global); @@ -244,10 +255,14 @@ impl Module { fn eval_init_expr( context: impl AsContext, builder: &InstanceEntityBuilder, + value_type: ValueType, init_expr: &InitExpr, ) -> Value { - init_expr - .to_const_with_context(|global_index| builder.get_global(global_index).get(&context)) + init_expr.to_const_with_context( + value_type, + |global_index| builder.get_global(global_index).get(&context), + |func_index| Value::from(FuncRef::new(builder.get_func(func_index))), + ) } /// Extracts the Wasm exports from the module and registers them into the [`Instance`]. @@ -289,42 +304,53 @@ impl Module { /// Initializes the [`Instance`] tables with the Wasm element segments of the [`Module`]. fn initialize_table_elements( &self, - context: &mut impl AsContextMut, + mut context: &mut impl AsContextMut, builder: &mut InstanceEntityBuilder, ) -> Result<(), Error> { 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()); + let element = ElementSegment::new(context.as_context_mut(), segment); + if let ElementSegmentKind::Active(active) = segment.kind() { + let dst_index = + Self::eval_init_expr(&mut *context, builder, ValueType::I32, active.offset()) + .i32() + .unwrap_or_else(|| { + panic!( + "expected offset value of type `i32` due to \ + Wasm validation but found: {:?}", + active.offset(), + ) + }) as u32; + let table = builder.get_table(active.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) + let len_items = element.size(&context); + dst_index + .checked_add(len_items) + .filter(|&max_index| max_index <= len_table) .ok_or(InstantiationError::ElementSegmentDoesNotFit { table, - offset, + offset: dst_index, 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)?; + { + let (table, element) = context + .as_context_mut() + .store + .inner + .resolve_table_element(&table, &element); + table + .init(dst_index, element, 0, len_items, |func_index| { + builder.get_func(func_index) + }) + .map_err(Trap::from)?; } + // Now drop the active element segment as commanded by the Wasm spec. + element.drop_items(&mut context); } - builder.push_element_segment(ElementSegment::new(context.as_context_mut(), segment)); + builder.push_element_segment(element); } Ok(()) } @@ -339,14 +365,15 @@ impl Module { 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 \ + let offset = + Self::eval_init_expr(&mut *context, builder, ValueType::I32, offset_expr) + .i32() + .unwrap_or_else(|| { + panic!( + "expected offset value of type `i32` due to \ Wasm validation but found: {offset_expr:?}", - ) - }) as usize; + ) + }) as u32 as usize; let memory = builder.get_memory(segment.memory_index().into_u32()); memory.write(&mut *context, offset, bytes)?; } diff --git a/crates/wasmi/src/module/instantiate/tests.rs b/crates/wasmi/src/module/instantiate/tests.rs index 2b9091eb1e..48ce863439 100644 --- a/crates/wasmi/src/module/instantiate/tests.rs +++ b/crates/wasmi/src/module/instantiate/tests.rs @@ -7,6 +7,8 @@ //! instances with more than 1 memory (or table) if the Wasm module imported //! those entities. +use wasmi_core::ValueType; + use crate::{ instance::InstanceEntity, Engine, @@ -19,6 +21,7 @@ use crate::{ Store, Table, TableType, + Value, }; fn try_instantiate_from_wat(wat: &str) -> Result<(Store<()>, Instance), Error> { @@ -32,8 +35,9 @@ fn try_instantiate_from_wat(wat: &str) -> Result<(Store<()>, Instance), Error> { let memory = Memory::new(&mut store, memory_type)?; linker.define("env", "memory", memory)?; // Define one table that can be used by the tests as import. - let table_type = TableType::new(4, None); - let table = Table::new(&mut store, table_type); + let table_type = TableType::new(ValueType::FuncRef, 4, None); + let init = Value::default(table_type.element()); + let table = Table::new(&mut store, table_type, init)?; linker.define("env", "table", table)?; let instance = linker .instantiate(&mut store, &module) diff --git a/crates/wasmi/src/module/mod.rs b/crates/wasmi/src/module/mod.rs index c7fb521584..d343d07c52 100644 --- a/crates/wasmi/src/module/mod.rs +++ b/crates/wasmi/src/module/mod.rs @@ -33,7 +33,7 @@ pub use self::{ }; pub(crate) use self::{ data::{DataSegment, DataSegmentKind}, - element::{ElementSegment, ElementSegmentKind}, + element::{ElementSegment, ElementSegmentItems, ElementSegmentKind}, init_expr::InitExpr, }; use crate::{ @@ -70,9 +70,6 @@ pub struct Module { /// The index of the default Wasm linear memory. pub(crate) const DEFAULT_MEMORY_INDEX: u32 = 0; -/// The index of the default Wasm table. -pub(crate) const DEFAULT_TABLE_INDEX: u32 = 0; - /// An imported item declaration in the [`Module`]. #[derive(Debug)] pub enum Imported { diff --git a/crates/wasmi/src/module/utils.rs b/crates/wasmi/src/module/utils.rs index c6f8806f55..f86df8ddc0 100644 --- a/crates/wasmi/src/module/utils.rs +++ b/crates/wasmi/src/module/utils.rs @@ -5,14 +5,10 @@ impl TryFrom for TableType { type Error = ModuleError; fn try_from(table_type: wasmparser::TableType) -> Result { - assert_eq!( - table_type.element_type, - wasmparser::ValType::FuncRef, - "wasmi does not support the `reference-types` Wasm proposal" - ); + let element = WasmiValueType::from(table_type.element_type).into_inner(); let minimum = table_type.initial; let maximum = table_type.maximum; - Ok(TableType::new(minimum, maximum)) + Ok(TableType::new(element, minimum, maximum)) } } @@ -104,9 +100,8 @@ impl From for WasmiValueType { wasmparser::ValType::F32 => Self::from(ValueType::F32), wasmparser::ValType::F64 => Self::from(ValueType::F64), wasmparser::ValType::V128 => panic!("wasmi does not support the `simd` Wasm proposal"), - wasmparser::ValType::FuncRef | wasmparser::ValType::ExternRef => { - panic!("wasmi does not support the `reference-types` Wasm proposal") - } + wasmparser::ValType::FuncRef => Self::from(ValueType::FuncRef), + wasmparser::ValType::ExternRef => Self::from(ValueType::ExternRef), } } } diff --git a/crates/wasmi/src/store.rs b/crates/wasmi/src/store.rs index 24cb6c1b88..a7a70fd65c 100644 --- a/crates/wasmi/src/store.rs +++ b/crates/wasmi/src/store.rs @@ -23,7 +23,10 @@ use super::{ TableEntity, TableIdx, }; -use crate::memory::DataSegment; +use crate::{ + externref::{ExternObject, ExternObjectEntity, ExternObjectIdx}, + memory::DataSegment, +}; use core::{ fmt::Debug, sync::atomic::{AtomicU32, Ordering}, @@ -106,6 +109,10 @@ pub struct StoreInner { datas: Arena, /// Stored data segments. elems: Arena, + /// Stored external objects for [`ExternRef`] types. + /// + /// [`ExternRef`]: [`crate::ExternRef`] + extern_objects: Arena, /// The [`Engine`] in use by the [`Store`]. /// /// Amongst others the [`Engine`] stores the Wasm function definitions. @@ -135,6 +142,7 @@ impl StoreInner { instances: Arena::new(), datas: Arena::new(), elems: Arena::new(), + extern_objects: Arena::new(), } } @@ -196,7 +204,7 @@ impl StoreInner { /// # Note /// /// Panics if no [`DedupFuncType`] for the given [`Func`] was registered. - pub fn get_func_type(&self, func: Func) -> DedupFuncType { + pub fn get_func_type(&self, func: &Func) -> DedupFuncType { let idx = self.unwrap_stored(func.as_inner()); self.func_types .get(idx) @@ -237,6 +245,12 @@ impl StoreInner { ElementSegment::from_inner(self.wrap_stored(segment)) } + /// Allocates a new [`ExternObjectEntity`] and returns a [`ExternObject`] reference to it. + pub(super) fn alloc_extern_object(&mut self, object: ExternObjectEntity) -> ExternObject { + let object = self.extern_objects.alloc(object); + ExternObject::from_inner(self.wrap_stored(object)) + } + /// Allocates a new uninitialized [`InstanceEntity`] and returns an [`Instance`] reference to it. /// /// # Note @@ -403,6 +417,34 @@ impl StoreInner { }) } + /// Returns a triple of: + /// + /// - 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 [`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_table_element( + &mut self, + table: &Table, + segment: &ElementSegment, + ) -> (&mut TableEntity, &ElementSegmentEntity) { + let table_idx = self.unwrap_stored(table.as_inner()); + let elem_idx = segment.as_inner(); + let elem = self.resolve(elem_idx, &self.elems); + let table = Self::resolve_mut(table_idx, &mut self.tables); + (table, elem) + } + /// Returns a triple of: /// /// - A shared reference to the [`InstanceEntity`] associated to the given [`Instance`]. @@ -425,10 +467,10 @@ impl StoreInner { pub(super) fn resolve_instance_table_element( &mut self, instance: &Instance, - memory: &Table, + table: &Table, segment: &ElementSegment, ) -> (&InstanceEntity, &mut TableEntity, &ElementSegmentEntity) { - let mem_idx = self.unwrap_stored(memory.as_inner()); + let mem_idx = self.unwrap_stored(table.as_inner()); let data_idx = segment.as_inner(); let instance_idx = instance.as_inner(); let instance = self.resolve(instance_idx, &self.instances); @@ -542,6 +584,16 @@ impl StoreInner { pub fn resolve_instance(&self, instance: &Instance) -> &InstanceEntity { self.resolve(instance.as_inner(), &self.instances) } + + /// Returns a shared reference to the [`ExternObjectEntity`] associated to the given [`ExternObject`]. + /// + /// # Panics + /// + /// - If the [`ExternObject`] does not originate from this [`Store`]. + /// - If the [`ExternObject`] cannot be resolved to its entity. + pub fn resolve_external_object(&self, object: &ExternObject) -> &ExternObjectEntity { + self.resolve(object.as_inner(), &self.extern_objects) + } } impl Store { diff --git a/crates/wasmi/src/table.rs b/crates/wasmi/src/table.rs index 41a1b0b975..a899b3541a 100644 --- a/crates/wasmi/src/table.rs +++ b/crates/wasmi/src/table.rs @@ -1,11 +1,16 @@ -#![allow(clippy::len_without_is_empty)] - -use super::{AsContext, AsContextMut, Func, Stored}; -use crate::{element::ElementSegmentEntity, instance::InstanceEntity}; +use super::{AsContext, AsContextMut, Stored}; +use crate::{ + element::ElementSegmentEntity, + module::FuncIdx, + value::WithType, + Func, + FuncRef, + Value, +}; use alloc::vec::Vec; use core::{cmp::max, fmt, fmt::Display}; use wasmi_arena::ArenaIndex; -use wasmi_core::TrapCode; +use wasmi_core::{TrapCode, UntypedValue, ValueType}; /// A raw index to a table entity. #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] @@ -37,6 +42,13 @@ pub enum TableError { /// The amount of requested invalid growth. delta: u32, }, + /// Occurs when operating with a [`Table`] and mismatching element types. + ElementTypeMismatch { + /// Expected element type for the [`Table`]. + expected: ValueType, + /// Encountered element type. + actual: ValueType, + }, /// Occurs when accessing the table out of bounds. AccessOutOfBounds { /// The current size of the table. @@ -44,12 +56,14 @@ pub enum TableError { /// The accessed index that is out of bounds. offset: u32, }, - /// Occurs when a table type does not satisfy the constraints of another. - UnsatisfyingTableType { - /// The unsatisfying [`TableType`]. - unsatisfying: TableType, - /// The required [`TableType`]. - required: TableType, + /// Occur when coping elements of tables out of bounds. + CopyOutOfBounds, + /// Occurs when `ty` is not a subtype of `other`. + InvalidSubtype { + /// The [`TableType`] which is not a subtype of `other`. + ty: TableType, + /// The [`TableType`] which is supposed to be a supertype of `ty`. + other: TableType, }, } @@ -67,6 +81,9 @@ impl Display for TableError { {maximum} by {delta} out of bounds", ) } + Self::ElementTypeMismatch { expected, actual } => { + write!(f, "encountered mismatching table element type, expected {expected:?} but found {actual:?}") + } Self::AccessOutOfBounds { current, offset } => { write!( f, @@ -74,15 +91,11 @@ impl Display for TableError { of table with size {current}", ) } - Self::UnsatisfyingTableType { - unsatisfying, - required, - } => { - write!( - f, - "table type {unsatisfying:?} does not satisfy requirements \ - of {required:?}", - ) + Self::CopyOutOfBounds => { + write!(f, "out of bounds access of table elements while copying") + } + Self::InvalidSubtype { ty, other } => { + write!(f, "table type {ty:?} is not a subtype of {other:?}",) } } } @@ -91,6 +104,8 @@ impl Display for TableError { /// A descriptor for a [`Table`] instance. #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub struct TableType { + /// The type of values stored in the [`Table`]. + element: ValueType, /// The minimum number of elements the [`Table`] must have. min: u32, /// The optional maximum number of elements the [`Table`] can have. @@ -105,49 +120,84 @@ impl TableType { /// # Panics /// /// If `min` is greater than `max`. - pub fn new(min: u32, max: Option) -> Self { + pub fn new(element: ValueType, min: u32, max: Option) -> Self { if let Some(max) = max { assert!(min <= max); } - Self { min, max } + Self { element, min, max } + } + + /// Returns the [`ValueType`] of elements stored in the [`Table`]. + pub fn element(&self) -> ValueType { + self.element } /// Returns minimum number of elements the [`Table`] must have. - pub fn minimum(self) -> u32 { + pub fn minimum(&self) -> u32 { self.min } /// The optional maximum number of elements the [`Table`] can have. /// /// If this returns `None` then the [`Table`] is not limited in size. - pub fn maximum(self) -> Option { + pub fn maximum(&self) -> Option { self.max } - /// Checks if `self` satisfies the given `TableType`. + /// Returns a [`TableError`] if `ty` does not match the [`Table`] element [`ValueType`]. + fn matches_element_type(&self, ty: ValueType) -> Result<(), TableError> { + let expected = self.element(); + let actual = ty; + if actual != expected { + return Err(TableError::ElementTypeMismatch { expected, actual }); + } + Ok(()) + } + + /// Checks if `self` is a subtype of `other`. + /// + /// # Note + /// + /// This implements the [subtyping rules] according to the WebAssembly spec. + /// + /// [import subtyping]: + /// https://webassembly.github.io/spec/core/valid/types.html#import-subtyping /// /// # Errors /// - /// - If the initial limits of the `required` [`TableType`] are greater than `self`. - /// - If the maximum limits of the `required` [`TableType`] are greater than `self`. - pub(crate) fn satisfies(&self, required: &TableType) -> Result<(), TableError> { - if required.minimum() > self.minimum() { - return Err(TableError::UnsatisfyingTableType { - unsatisfying: *self, - required: *required, - }); + /// - If the `element` type of `self` does not match the `element` type of `other`. + /// - If the `minimum` size of `self` is less than or equal to the `minimum` size of `other`. + /// - If the `maximum` size of `self` is greater than the `maximum` size of `other`. + pub(crate) fn is_subtype_or_err(&self, other: &TableType) -> Result<(), TableError> { + match self.is_subtype_of(other) { + true => Ok(()), + false => Err(TableError::InvalidSubtype { + ty: *self, + other: *other, + }), } - match (required.maximum(), self.maximum()) { - (None, _) => (), - (Some(max_required), Some(max)) if max_required >= max => (), - _ => { - return Err(TableError::UnsatisfyingTableType { - unsatisfying: *self, - required: *required, - }); - } + } + + /// Returns `true` if the [`TableType`] is a subtype of the `other` [`TableType`]. + /// + /// # Note + /// + /// This implements the [subtyping rules] according to the WebAssembly spec. + /// + /// [import subtyping]: + /// https://webassembly.github.io/spec/core/valid/types.html#import-subtyping + pub(crate) fn is_subtype_of(&self, other: &Self) -> bool { + if self.matches_element_type(other.element()).is_err() { + return false; + } + if self.minimum() < other.minimum() { + return false; + } + match (self.maximum(), other.maximum()) { + (_, None) => true, + (Some(max), Some(other_max)) => max <= other_max, + _ => false, } - Ok(()) } } @@ -155,16 +205,19 @@ impl TableType { #[derive(Debug)] pub struct TableEntity { ty: TableType, - elements: Vec>, + elements: Vec, } impl TableEntity { /// Creates a new table entity with the given resizable limits. - pub fn new(ty: TableType) -> Self { - Self { - elements: vec![None; ty.minimum() as usize], - ty, - } + /// + /// # Errors + /// + /// If `init` does not match the [`TableType`] element type. + pub fn new(ty: TableType, init: Value) -> Result { + ty.matches_element_type(init.ty())?; + let elements = vec![init.into(); ty.minimum() as usize]; + Ok(Self { ty, elements }) } /// Returns the resizable limits of the table. @@ -172,6 +225,16 @@ impl TableEntity { self.ty } + /// Returns the dynamic [`TableType`] of the [`TableEntity`]. + /// + /// # Note + /// + /// This respects the current size of the [`TableEntity`] + /// as its minimum size and is useful for import subtyping checks. + pub fn dynamic_ty(&self) -> TableType { + TableType::new(self.ty().element(), self.size(), self.ty().maximum()) + } + /// Returns the current size of the [`Table`]. pub fn size(&self) -> u32 { self.elements.len() as u32 @@ -179,14 +242,35 @@ impl TableEntity { /// Grows the table by the given amount of elements. /// + /// Returns the old size of the [`Table`] upon success. + /// + /// # Note + /// + /// The newly added elements are initialized to the `init` [`Value`]. + /// + /// # Errors + /// + /// - If the table is grown beyond its maximum limits. + /// - If `value` does not match the [`Table`] element type. + pub fn grow(&mut self, delta: u32, init: Value) -> Result { + self.ty().matches_element_type(init.ty())?; + self.grow_untyped(delta, init.into()) + } + + /// Grows the table by the given amount of elements. + /// + /// Returns the old size of the [`Table`] upon success. + /// /// # Note /// - /// The newly added elements are initialized to `None`. + /// This is an internal API that exists for efficiency purposes. + /// + /// The newly added elements are initialized to the `init` [`Value`]. /// /// # Errors /// /// If the table is grown beyond its maximum limits. - pub fn grow(&mut self, delta: u32) -> Result<(), TableError> { + pub fn grow_untyped(&mut self, delta: u32, init: UntypedValue) -> Result { let maximum = self.ty.maximum().unwrap_or(u32::MAX); let current = self.size(); let new_len = current @@ -197,67 +281,98 @@ impl TableEntity { current, delta, })? as usize; - self.elements.resize(new_len, None); - Ok(()) + self.elements.resize(new_len, init); + Ok(current) + } + + /// Converts the internal [`UntypedValue`] into a [`Value`] for this [`Table`] element type. + fn make_typed(&self, untyped: UntypedValue) -> Value { + untyped.with_type(self.ty().element()) } /// Returns the [`Table`] element value at `index`. /// + /// Returns `None` if `index` is out of bounds. + pub fn get(&self, index: u32) -> Option { + self.get_untyped(index) + .map(|untyped| self.make_typed(untyped)) + } + + /// Returns the untyped [`Table`] element value at `index`. + /// + /// Returns `None` if `index` is out of bounds. + /// + /// # Note + /// + /// This is a more efficient version of [`Table::get`] for + /// internal use only. + pub fn get_untyped(&self, index: u32) -> Option { + self.elements.get(index as usize).copied() + } + + /// Sets the [`Value`] of this [`Table`] at `index`. + /// /// # Errors /// - /// If `index` is out of bounds. - pub fn get(&self, index: u32) -> Result, TableError> { - let element = self.elements.get(index as usize).copied().ok_or_else(|| { - TableError::AccessOutOfBounds { - current: self.size(), - offset: index, - } - })?; - Ok(element) + /// - If `index` is out of bounds. + /// - If `value` does not match the [`Table`] element type. + pub fn set(&mut self, index: u32, value: Value) -> Result<(), TableError> { + self.ty().matches_element_type(value.ty())?; + self.set_untyped(index, value.into()) } - /// Writes the `value` provided into `index` within this [`Table`]. + /// Returns the [`UntypedValue`] of the [`Table`] at `index`. /// /// # Errors /// /// If `index` is out of bounds. - pub fn set(&mut self, index: u32, value: Option) -> Result<(), TableError> { + pub fn set_untyped(&mut self, index: u32, value: UntypedValue) -> Result<(), TableError> { let current = self.size(); - let element = + let untyped = self.elements .get_mut(index as usize) .ok_or(TableError::AccessOutOfBounds { current, offset: index, })?; - *element = value; + *untyped = 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`]. + /// Uses the `instance` to resolve function indices of the element to [`Func`][`crate::Func`]. /// /// # Errors /// - /// Returns an error if the range is out of bounds of either the source or - /// destination tables. + /// 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. + /// - Panics if the `instance` cannot resolve all the `element` func indices. + /// - If the [`ElementSegmentEntity`] element type does not match the [`Table`] element type. + /// Note: This is a panic instead of an error since it is asserted at Wasm validation time. pub fn init( &mut self, - instance: &InstanceEntity, dst_index: u32, element: &ElementSegmentEntity, src_index: u32, len: u32, + get_func: impl Fn(u32) -> Func, ) -> Result<(), TrapCode> { - // Turn parameters into proper slice indices. - let src_index = src_index as usize; + let table_type = self.ty(); + assert!( + table_type.element().is_ref(), + "table.init currently only works on reftypes" + ); + table_type + .matches_element_type(element.ty()) + .map_err(|_| TrapCode::BadSignature)?; + // Convert parameters to indices. let dst_index = dst_index as usize; + let src_index = src_index as usize; let len = len as usize; // Perform bounds check before anything else. let dst_items = self @@ -270,15 +385,29 @@ impl TableEntity { .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:?}") - }) - }); + if len == 0 { + // Bail out early if nothing needs to be initialized. + // The Wasm spec demands to still perform the bounds check + // so we cannot bail out earlier. + return Ok(()); } + // Perform the actual table initialization. + match table_type.element() { + ValueType::FuncRef => { + // Initialize element interpreted as Wasm `funrefs`. + dst_items.iter_mut().zip(src_items).for_each(|(dst, src)| { + let func_or_null = src.as_funcref().map(FuncIdx::into_u32).map(&get_func); + *dst = FuncRef::new(func_or_null).into(); + }); + } + ValueType::ExternRef => { + // Initialize element interpreted as Wasm `externrefs`. + dst_items.iter_mut().zip(src_items).for_each(|(dst, src)| { + *dst = UntypedValue::from(src.as_externref()); + }); + } + _ => panic!("table.init currently only works on reftypes"), + }; Ok(()) } @@ -331,7 +460,7 @@ impl TableEntity { let max_offset = max(dst_index, src_index); max_offset .checked_add(len) - .filter(|&offset| offset < self.size()) + .filter(|&offset| offset <= self.size()) .ok_or(TrapCode::TableOutOfBounds)?; // Turn parameters into proper indices. let src_index = src_index as usize; @@ -342,6 +471,53 @@ impl TableEntity { .copy_within(src_index..src_index.wrapping_add(len), dst_index); Ok(()) } + + /// Fill `table[dst..(dst + len)]` with the given value. + /// + /// # Errors + /// + /// - If `val` has a type mismatch with the element type of the [`Table`]. + /// - If the region to be filled is out of bounds for the [`Table`]. + /// - If `val` originates from a different [`Store`] than the [`Table`]. + /// + /// # Panics + /// + /// If `ctx` does not own `dst_table` or `src_table`. + /// + /// [`Store`]: [`crate::Store`] + pub fn fill(&mut self, dst: u32, val: Value, len: u32) -> Result<(), TrapCode> { + self.ty() + .matches_element_type(val.ty()) + .map_err(|_| TrapCode::BadSignature)?; + self.fill_untyped(dst, val.into(), len) + } + + /// Fill `table[dst..(dst + len)]` with the given value. + /// + /// # Note + /// + /// This is an API for internal use only and exists for efficiency reasons. + /// + /// # Errors + /// + /// - If the region to be filled is out of bounds for the [`Table`]. + /// + /// # Panics + /// + /// If `ctx` does not own `dst_table` or `src_table`. + /// + /// [`Store`]: [`crate::Store`] + pub fn fill_untyped(&mut self, dst: u32, val: UntypedValue, len: u32) -> Result<(), TrapCode> { + let dst_index = dst as usize; + let len = len as usize; + let dst = self + .elements + .get_mut(dst_index..) + .and_then(|elements| elements.get_mut(..len)) + .ok_or(TrapCode::TableOutOfBounds)?; + dst.fill(val); + Ok(()) + } } /// A Wasm table reference. @@ -361,11 +537,14 @@ impl Table { } /// Creates a new table to the store. - pub fn new(mut ctx: impl AsContextMut, ty: TableType) -> Self { - ctx.as_context_mut() - .store - .inner - .alloc_table(TableEntity::new(ty)) + /// + /// # Errors + /// + /// If `init` does not match the [`TableType`] element type. + pub fn new(mut ctx: impl AsContextMut, ty: TableType, init: Value) -> Result { + let entity = TableEntity::new(ty, init)?; + let table = ctx.as_context_mut().store.inner.alloc_table(entity); + Ok(table) } /// Returns the type and limits of the table. @@ -377,6 +556,24 @@ impl Table { ctx.as_context().store.inner.resolve_table(self).ty() } + /// Returns the dynamic [`TableType`] of the [`Table`]. + /// + /// # Note + /// + /// This respects the current size of the [`Table`] as + /// its minimum size and is useful for import subtyping checks. + /// + /// # Panics + /// + /// Panics if `ctx` does not own this [`Table`]. + pub(crate) fn dynamic_ty(&self, ctx: impl AsContext) -> TableType { + ctx.as_context() + .store + .inner + .resolve_table(self) + .dynamic_ty() + } + /// Returns the current size of the [`Table`]. /// /// # Panics @@ -388,43 +585,50 @@ impl Table { /// Grows the table by the given amount of elements. /// + /// Returns the old size of the [`Table`] upon success. + /// /// # Note /// - /// The newly added elements are initialized to `None`. + /// The newly added elements are initialized to the `init` [`Value`]. /// /// # Errors /// - /// If the table is grown beyond its maximum limits. + /// - If the table is grown beyond its maximum limits. + /// - If `value` does not match the [`Table`] element type. /// /// # Panics /// /// Panics if `ctx` does not own this [`Table`]. - pub fn grow(&self, mut ctx: impl AsContextMut, delta: u32) -> Result<(), TableError> { + pub fn grow( + &self, + mut ctx: impl AsContextMut, + delta: u32, + init: Value, + ) -> Result { ctx.as_context_mut() .store .inner .resolve_table_mut(self) - .grow(delta) + .grow(delta, init) } /// Returns the [`Table`] element value at `index`. /// - /// # Errors - /// - /// If `index` is out of bounds. + /// Returns `None` if `index` is out of bounds. /// /// # Panics /// /// Panics if `ctx` does not own this [`Table`]. - pub fn get(&self, ctx: impl AsContext, index: u32) -> Result, TableError> { + pub fn get(&self, ctx: impl AsContext, index: u32) -> Option { ctx.as_context().store.inner.resolve_table(self).get(index) } - /// Writes the `value` provided into `index` within this [`Table`]. + /// Sets the [`Value`] of this [`Table`] at `index`. /// /// # Errors /// - /// If `index` is out of bounds. + /// - If `index` is out of bounds. + /// - If `value` does not match the [`Table`] element type. /// /// # Panics /// @@ -433,7 +637,7 @@ impl Table { &self, mut ctx: impl AsContextMut, index: u32, - value: Option, + value: Value, ) -> Result<(), TableError> { ctx.as_context_mut() .store @@ -442,6 +646,17 @@ impl Table { .set(index, value) } + /// Returns `true` if `lhs` and `rhs` [`Table`] refer to the same entity. + /// + /// # Note + /// + /// We do not implement `Eq` and `PartialEq` and + /// intentionally keep this API hidden from users. + #[inline] + pub(crate) fn eq(lhs: &Self, rhs: &Self) -> bool { + lhs.as_inner() == rhs.as_inner() + } + /// Copy `len` elements from `src_table[src_index..]` into /// `dst_table[dst_index..]`. /// @@ -460,10 +675,8 @@ impl Table { 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 { + ) -> Result<(), TableError> { + if Self::eq(dst_table, src_table) { // The `dst_table` and `src_table` are the same table // therefore we have to copy within the same table. let table = store @@ -471,16 +684,72 @@ impl Table { .store .inner .resolve_table_mut(dst_table); - table.copy_within(dst_index, src_index, len) + table + .copy_within(dst_index, src_index, len) + .map_err(|_| TableError::CopyOutOfBounds) } else { // The `dst_table` and `src_table` are different entities // therefore we have to copy from one table to the other. + let dst_ty = dst_table.ty(&store); + let src_ty = src_table.ty(&store).element(); + dst_ty.matches_element_type(src_ty)?; 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) + .map_err(|_| TableError::CopyOutOfBounds) } } + + /// Fill `table[dst..(dst + len)]` with the given value. + /// + /// # Errors + /// + /// - If `val` has a type mismatch with the element type of the [`Table`]. + /// - If the region to be filled is out of bounds for the [`Table`]. + /// - If `val` originates from a different [`Store`] than the [`Table`]. + /// + /// # Panics + /// + /// If `ctx` does not own `dst_table` or `src_table`. + /// + /// [`Store`]: [`crate::Store`] + pub fn fill( + &self, + mut ctx: impl AsContextMut, + dst: u32, + val: Value, + len: u32, + ) -> Result<(), TrapCode> { + ctx.as_context_mut() + .store + .inner + .resolve_table_mut(self) + .fill(dst, val, len) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn table_type(element: ValueType, minimum: u32, maximum: impl Into>) -> TableType { + TableType::new(element, minimum, maximum.into()) + } + + use ValueType::{F64, I32}; + + #[test] + fn subtyping_works() { + assert!(!table_type(I32, 0, 1).is_subtype_of(&table_type(F64, 0, 1))); + assert!(table_type(I32, 0, 1).is_subtype_of(&table_type(I32, 0, 1))); + assert!(table_type(I32, 0, 1).is_subtype_of(&table_type(I32, 0, 2))); + assert!(!table_type(I32, 0, 2).is_subtype_of(&table_type(I32, 0, 1))); + assert!(table_type(I32, 2, None).is_subtype_of(&table_type(I32, 1, None))); + assert!(table_type(I32, 0, None).is_subtype_of(&table_type(I32, 0, None))); + assert!(table_type(I32, 0, 1).is_subtype_of(&table_type(I32, 0, None))); + assert!(!table_type(I32, 0, None).is_subtype_of(&table_type(I32, 0, 1))); + } } diff --git a/crates/wasmi/src/value.rs b/crates/wasmi/src/value.rs index cb67e071d0..c5c61dafbd 100644 --- a/crates/wasmi/src/value.rs +++ b/crates/wasmi/src/value.rs @@ -1,4 +1,4 @@ -use core::{fmt, fmt::Display}; +use crate::{ExternRef, Func, FuncRef}; use wasmi_core::{UntypedValue, ValueType, F32, F64}; /// Untyped instances that allow to be typed. @@ -19,6 +19,8 @@ impl WithType for UntypedValue { ValueType::I64 => Value::I64(self.into()), ValueType::F32 => Value::F32(self.into()), ValueType::F64 => Value::F64(self.into()), + ValueType::FuncRef => Value::FuncRef(self.into()), + ValueType::ExternRef => Value::ExternRef(self.into()), } } } @@ -30,6 +32,8 @@ impl From for UntypedValue { Value::I64(value) => value.into(), Value::F32(value) => value.into(), Value::F64(value) => value.into(), + Value::FuncRef(value) => value.into(), + Value::ExternRef(value) => value.into(), } } } @@ -41,7 +45,7 @@ impl From for UntypedValue { /// /// There is no distinction between signed and unsigned integer types. Instead, integers are /// interpreted by respective operations as either unsigned or signed in two’s complement representation. -#[derive(Copy, Clone, Debug, PartialEq)] +#[derive(Clone, Debug)] pub enum Value { /// Value of 32-bit signed or unsigned integer. I32(i32), @@ -51,17 +55,10 @@ pub enum Value { F32(F32), /// Value of 64-bit IEEE 754-2008 floating point number. F64(F64), -} - -impl Display for Value { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Self::I32(value) => write!(f, "{value}"), - Self::I64(value) => write!(f, "{value}"), - Self::F32(value) => write!(f, "{}", f32::from(*value)), - Self::F64(value) => write!(f, "{}", f64::from(*value)), - } - } + /// A nullable [`Func`][`crate::Func`] reference, a.k.a. [`FuncRef`]. + FuncRef(FuncRef), + /// A nullable external object reference, a.k.a. [`ExternRef`]. + ExternRef(ExternRef), } impl Value { @@ -69,10 +66,12 @@ impl Value { #[inline] pub fn default(value_type: ValueType) -> Self { match value_type { - ValueType::I32 => Value::I32(0), - ValueType::I64 => Value::I64(0), - ValueType::F32 => Value::F32(0f32.into()), - ValueType::F64 => Value::F64(0f64.into()), + ValueType::I32 => Self::I32(0), + ValueType::I64 => Self::I64(0), + ValueType::F32 => Self::F32(0f32.into()), + ValueType::F64 => Self::F64(0f64.into()), + ValueType::FuncRef => Self::from(FuncRef::null()), + ValueType::ExternRef => Self::from(ExternRef::null()), } } @@ -80,220 +79,109 @@ impl Value { #[inline] pub fn ty(&self) -> ValueType { match *self { - Value::I32(_) => ValueType::I32, - Value::I64(_) => ValueType::I64, - Value::F32(_) => ValueType::F32, - Value::F64(_) => ValueType::F64, + Self::I32(_) => ValueType::I32, + Self::I64(_) => ValueType::I64, + Self::F32(_) => ValueType::F32, + Self::F64(_) => ValueType::F64, + Self::FuncRef(_) => ValueType::FuncRef, + Self::ExternRef(_) => ValueType::ExternRef, } } - /// Returns `T` if this particular [`Value`] contains - /// appropriate type. - /// - /// See [`FromValue`] for details. - /// - /// [`FromValue`]: trait.FromValue.html - /// [`Value`]: enum.Value.html - #[inline] - pub fn try_into>(self) -> Option { - >::try_from(self).ok() - } -} - -impl From for Value { - #[inline] - fn from(val: i8) -> Self { - Value::I32(val.into()) + /// Returns the underlying `i32` if the type matches otherwise returns `None`. + pub fn i32(&self) -> Option { + match self { + Self::I32(value) => Some(*value), + _ => None, + } } -} -impl From for Value { - #[inline] - fn from(val: i16) -> Self { - Value::I32(val.into()) + /// Returns the underlying `i64` if the type matches otherwise returns `None`. + pub fn i64(&self) -> Option { + match self { + Self::I64(value) => Some(*value), + _ => None, + } } -} -impl From for Value { - #[inline] - fn from(val: i32) -> Self { - Value::I32(val) + /// Returns the underlying `f32` if the type matches otherwise returns `None`. + pub fn f32(&self) -> Option { + match self { + Self::F32(value) => Some(*value), + _ => None, + } } -} -impl From for Value { - #[inline] - fn from(val: i64) -> Self { - Value::I64(val) + /// Returns the underlying `f64` if the type matches otherwise returns `None`. + pub fn f64(&self) -> Option { + match self { + Self::F64(value) => Some(*value), + _ => None, + } } -} -impl From for Value { - #[inline] - fn from(val: u8) -> Self { - Value::I32(val.into()) + /// Returns the underlying `funcref` if the type matches otherwise returns `None`. + pub fn funcref(&self) -> Option<&FuncRef> { + match self { + Self::FuncRef(value) => Some(value), + _ => None, + } } -} -impl From for Value { - #[inline] - fn from(val: u16) -> Self { - Value::I32(val.into()) + /// Returns the underlying `externref` if the type matches otherwise returns `None`. + pub fn externref(&self) -> Option<&ExternRef> { + match self { + Self::ExternRef(value) => Some(value), + _ => None, + } } } -impl From for Value { +impl From for Value { #[inline] - fn from(val: u32) -> Self { - Value::I32(val as _) + fn from(val: i32) -> Self { + Self::I32(val) } } -impl From for Value { +impl From for Value { #[inline] - fn from(val: u64) -> Self { - Value::I64(val as _) + fn from(val: i64) -> Self { + Self::I64(val) } } impl From for Value { #[inline] fn from(val: F32) -> Self { - Value::F32(val) + Self::F32(val) } } impl From for Value { #[inline] fn from(val: F64) -> Self { - Value::F64(val) + Self::F64(val) } } -macro_rules! impl_from_value { - ($expected_rt_ty: ident, $into: ty) => { - impl TryFrom for $into { - type Error = TryFromValueError; - - #[inline] - fn try_from(val: Value) -> Result { - match val { - Value::$expected_rt_ty(val) => Ok(val as _), - _ => Err(Self::Error::TypeMismatch), - } - } - } - }; -} -impl_from_value!(I32, i32); -impl_from_value!(I64, i64); -impl_from_value!(F32, F32); -impl_from_value!(F64, F64); -impl_from_value!(I32, u32); -impl_from_value!(I64, u64); - -/// Errors that may occur upon converting a [`Value`] to a primitive type. -#[derive(Debug, Copy, Clone)] -pub enum TryFromValueError { - /// The type does not match the expected type. - TypeMismatch, - /// The value is out of bounds for the expected type. - OutOfBounds, -} - -impl Display for TryFromValueError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - TryFromValueError::TypeMismatch => write!(f, "encountered type mismatch"), - TryFromValueError::OutOfBounds => write!(f, "value out of bounds"), - } - } -} - -/// This conversion assumes that boolean values are represented by -/// [`I32`] type. -/// -/// [`I32`]: enum.Value.html#variant.I32 -impl TryFrom for bool { - type Error = TryFromValueError; - +impl From for Value { #[inline] - fn try_from(val: Value) -> Result { - match val { - Value::I32(val) => Ok(val != 0), - _ => Err(Self::Error::TypeMismatch), - } + fn from(funcref: FuncRef) -> Self { + Self::FuncRef(funcref) } } -/// This conversion assumes that `i8` is represented as an [`I32`]. -/// -/// [`I32`]: enum.Value.html#variant.I32 -impl TryFrom for i8 { - type Error = TryFromValueError; - +impl From for Value { #[inline] - fn try_from(val: Value) -> Result { - let min = i32::from(i8::MIN); - let max = i32::from(i8::MAX); - match val { - Value::I32(val) if min <= val && val <= max => Ok(val as i8), - Value::I32(_) => Err(Self::Error::OutOfBounds), - _ => Err(Self::Error::TypeMismatch), - } - } -} - -/// This conversion assumes that `i16` is represented as an [`I32`]. -/// -/// [`I32`]: enum.Value.html#variant.I32 -impl TryFrom for i16 { - type Error = TryFromValueError; - - #[inline] - fn try_from(val: Value) -> Result { - let min = i32::from(i16::MIN); - let max = i32::from(i16::MAX); - match val { - Value::I32(val) if min <= val && val <= max => Ok(val as i16), - Value::I32(_) => Err(Self::Error::OutOfBounds), - _ => Err(Self::Error::TypeMismatch), - } - } -} - -/// This conversion assumes that `u8` is represented as an [`I32`]. -/// -/// [`I32`]: enum.Value.html#variant.I32 -impl TryFrom for u8 { - type Error = TryFromValueError; - - #[inline] - fn try_from(val: Value) -> Result { - let min = i32::from(u8::MIN); - let max = i32::from(u8::MAX); - match val { - Value::I32(val) if min <= val && val <= max => Ok(val as u8), - Value::I32(_) => Err(Self::Error::OutOfBounds), - _ => Err(Self::Error::TypeMismatch), - } + fn from(func: Func) -> Self { + Self::FuncRef(FuncRef::new(func)) } } -/// This conversion assumes that `u16` is represented as an [`I32`]. -/// -/// [`I32`]: enum.Value.html#variant.I32 -impl TryFrom for u16 { - type Error = TryFromValueError; - +impl From for Value { #[inline] - fn try_from(val: Value) -> Result { - let min = i32::from(u16::MIN); - let max = i32::from(u16::MAX); - match val { - Value::I32(val) if min <= val && val <= max => Ok(val as u16), - Value::I32(_) => Err(Self::Error::OutOfBounds), - _ => Err(Self::Error::TypeMismatch), - } + fn from(externref: ExternRef) -> Self { + Self::ExternRef(externref) } } diff --git a/crates/wasmi/tests/e2e/v1/func.rs b/crates/wasmi/tests/e2e/v1/func.rs index 7598e25d7b..055ef57353 100644 --- a/crates/wasmi/tests/e2e/v1/func.rs +++ b/crates/wasmi/tests/e2e/v1/func.rs @@ -1,5 +1,7 @@ //! Tests for the `Func` type in `wasmi`. +use core::slice; + use assert_matches::assert_matches; use wasmi::{errors::FuncError, Engine, Error, Func, Store, Value}; use wasmi_core::{F32, F64}; @@ -32,13 +34,14 @@ fn setup_add2() -> (Store<()>, Func) { #[test] fn dynamic_add2_works() { let (mut store, add2) = setup_add2(); - let result_add2 = { - let mut result = [Value::I32(0)]; - add2.call(&mut store, &[Value::I32(1), Value::I32(2)], &mut result) - .unwrap(); - result[0] - }; - assert_eq!(result_add2, Value::I32(3)); + let mut result = Value::I32(0); + add2.call( + &mut store, + &[Value::I32(1), Value::I32(2)], + slice::from_mut(&mut result), + ) + .unwrap(); + assert_eq!(result.i32(), Some(3)); } #[test] @@ -59,17 +62,14 @@ fn setup_add3() -> (Store<()>, Func) { #[test] fn dynamic_add3_works() { let (mut store, add3) = setup_add3(); - let result_add3 = { - let mut result = [Value::I32(0)]; - add3.call( - &mut store, - &[Value::I32(1), Value::I32(2), Value::I32(3)], - &mut result, - ) - .unwrap(); - result[0] - }; - assert_eq!(result_add3, Value::I32(6)); + let mut result = Value::I32(0); + add3.call( + &mut store, + &[Value::I32(1), Value::I32(2), Value::I32(3)], + slice::from_mut(&mut result), + ) + .unwrap(); + assert_eq!(result.i32(), Some(6)); } #[test] @@ -90,14 +90,11 @@ fn setup_duplicate() -> (Store<()>, Func) { #[test] fn dynamic_duplicate_works() { let (mut store, duplicate) = setup_duplicate(); - let result_duplicate = { - let mut result = [Value::I32(0), Value::I32(0)]; - duplicate - .call(&mut store, &[Value::I32(10)], &mut result) - .unwrap(); - (result[0], result[1]) - }; - assert_eq!(result_duplicate, (Value::I32(10), Value::I32(10))); + let mut result = [Value::I32(0), Value::I32(0)]; + duplicate + .call(&mut store, &[Value::I32(10)], &mut result) + .unwrap(); + assert_eq!((result[0].i32(), result[1].i32()), (Some(10), Some(10))); } #[test] @@ -206,7 +203,7 @@ fn setup_many_results() -> (Store<()>, Func) { #[test] fn dynamic_many_results_works() { let (mut store, func) = setup_many_results(); - let mut results = [Value::I32(0); 16]; + let mut results = [0; 16].map(Value::I32); func.call(&mut store, &[], &mut results).unwrap(); let mut i = 0; let expected = [0; 16].map(|_| { @@ -214,7 +211,10 @@ fn dynamic_many_results_works() { i += 1; value }); - assert_eq!(results, expected) + assert_eq!( + results.map(|result| result.i32().unwrap()), + expected.map(|expected| expected.i32().unwrap()) + ) } #[test] @@ -258,27 +258,13 @@ fn setup_many_params_many_results() -> (Store<()>, Func) { #[test] fn dynamic_many_params_many_results_works() { let (mut store, func) = setup_many_params_many_results(); - let mut results = [Value::I32(0); 16]; - let inputs = [ - Value::I32(0), - Value::I32(1), - Value::I32(2), - Value::I32(3), - Value::I32(4), - Value::I32(5), - Value::I32(6), - Value::I32(7), - Value::I32(8), - Value::I32(9), - Value::I32(10), - Value::I32(11), - Value::I32(12), - Value::I32(13), - Value::I32(14), - Value::I32(15), - ]; + let mut results = [0; 16].map(Value::I32); + let inputs = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15].map(Value::I32); func.call(&mut store, &inputs, &mut results).unwrap(); - assert_eq!(&results, &inputs) + assert_eq!( + results.map(|result| result.i32().unwrap()), + inputs.map(|input| input.i32().unwrap()), + ) } #[test] @@ -298,7 +284,7 @@ fn dynamic_many_types_works() { &mut store, |v0: i32, v1: u32, v2: i64, v3: u64, v4: F32, v5: F64| (v0, v1, v2, v3, v4, v5), ); - let mut results = [Value::I32(0); 6]; + let mut results = [0; 6].map(Value::I32); let inputs = [ Value::I32(0), Value::I32(1), @@ -308,7 +294,12 @@ fn dynamic_many_types_works() { Value::F64(5.0.into()), ]; func.call(&mut store, &inputs, &mut results).unwrap(); - assert_eq!(&results, &inputs) + assert_eq!(results[0].i32(), Some(0)); + assert_eq!(results[1].i32(), Some(1)); + assert_eq!(results[2].i64(), Some(2)); + assert_eq!(results[3].i64(), Some(3)); + assert_eq!(results[4].f32(), Some(4.0.into())); + assert_eq!(results[5].f64(), Some(5.0.into())); } #[test] diff --git a/crates/wasmi/tests/e2e/v1/resumable_call.rs b/crates/wasmi/tests/e2e/v1/resumable_call.rs index cfae960d90..5177abb8d9 100644 --- a/crates/wasmi/tests/e2e/v1/resumable_call.rs +++ b/crates/wasmi/tests/e2e/v1/resumable_call.rs @@ -1,5 +1,7 @@ //! Test to assert that resumable call feature works as intended. +use core::slice; + use wasmi::{ Engine, Error, @@ -140,20 +142,20 @@ impl AssertResumable for ResumableCall { } fn run_test(wasm_fn: Func, mut store: &mut Store<()>, wasm_trap: bool) { - let mut results = [Value::I32(0)]; + let mut results = Value::I32(0); let invocation = wasm_fn .call_resumable( &mut store, &[Value::I32(wasm_trap as i32)], - &mut results[..], + slice::from_mut(&mut results), ) .unwrap() .assert_resumable(store, 10, &[ValueType::I32]); let invocation = invocation - .resume(&mut store, &[Value::I32(2)], &mut results[..]) + .resume(&mut store, &[Value::I32(2)], slice::from_mut(&mut results)) .unwrap() .assert_resumable(store, 20, &[ValueType::I32]); - let call = invocation.resume(&mut store, &[Value::I32(3)], &mut results[..]); + let call = invocation.resume(&mut store, &[Value::I32(3)], slice::from_mut(&mut results)); if wasm_trap { match call.unwrap_err() { Error::Trap(trap) => { @@ -166,7 +168,7 @@ fn run_test(wasm_fn: Func, mut store: &mut Store<()>, wasm_trap: bool) { } } else { call.unwrap().assert_finish(); - assert_eq!(results, [Value::I32(4)]); + assert_eq!(results.i32(), Some(4)); } } diff --git a/crates/wasmi/tests/spec/context.rs b/crates/wasmi/tests/spec/context.rs index 78c0d02303..4533014a6a 100644 --- a/crates/wasmi/tests/spec/context.rs +++ b/crates/wasmi/tests/spec/context.rs @@ -18,7 +18,7 @@ use wasmi::{ TableType, Value, }; -use wasmi_core::{F32, F64}; +use wasmi_core::{ValueType, F32, F64}; use wast::token::{Id, Span}; /// The context of a single Wasm test spec suite run. @@ -53,8 +53,14 @@ impl<'a> TestContext<'a> { let mut linker = Linker::default(); let mut store = Store::new(&engine, ()); let default_memory = Memory::new(&mut store, MemoryType::new(1, Some(2)).unwrap()).unwrap(); - let default_table = Table::new(&mut store, TableType::new(10, Some(20))); + let default_table = Table::new( + &mut store, + TableType::new(ValueType::FuncRef, 10, Some(20)), + Value::default(ValueType::FuncRef), + ) + .unwrap(); let global_i32 = Global::new(&mut store, Value::I32(666), Mutability::Const); + let global_i64 = Global::new(&mut store, Value::I64(666), Mutability::Const); let global_f32 = Global::new(&mut store, Value::F32(666.0.into()), Mutability::Const); let global_f64 = Global::new(&mut store, Value::F64(666.0.into()), Mutability::Const); let print = Func::wrap(&mut store, || { @@ -63,6 +69,9 @@ impl<'a> TestContext<'a> { let print_i32 = Func::wrap(&mut store, |value: i32| { println!("print: {value}"); }); + let print_i64 = Func::wrap(&mut store, |value: i64| { + println!("print: {value}"); + }); let print_f32 = Func::wrap(&mut store, |value: F32| { println!("print: {value:?}"); }); @@ -78,10 +87,12 @@ impl<'a> TestContext<'a> { linker.define("spectest", "memory", default_memory).unwrap(); linker.define("spectest", "table", default_table).unwrap(); linker.define("spectest", "global_i32", global_i32).unwrap(); + linker.define("spectest", "global_i64", global_i64).unwrap(); linker.define("spectest", "global_f32", global_f32).unwrap(); linker.define("spectest", "global_f64", global_f64).unwrap(); linker.define("spectest", "print", print).unwrap(); linker.define("spectest", "print_i32", print_i32).unwrap(); + linker.define("spectest", "print_i64", print_i64).unwrap(); linker.define("spectest", "print_f32", print_f32).unwrap(); linker.define("spectest", "print_f64", print_f64).unwrap(); linker @@ -120,6 +131,16 @@ impl TestContext<'_> { &self.engine } + /// Returns a shared reference to the underlying [`Store`]. + pub fn store(&self) -> &Store<()> { + &self.store + } + + /// Returns an exclusive reference to the underlying [`Store`]. + pub fn store_mut(&mut self) -> &mut Store<()> { + &mut self.store + } + /// Returns an exclusive reference to the test profile. pub fn profile(&mut self) -> &mut TestProfile { &mut self.profile diff --git a/crates/wasmi/tests/spec/mod.rs b/crates/wasmi/tests/spec/mod.rs index 39b47d2d07..6324967851 100644 --- a/crates/wasmi/tests/spec/mod.rs +++ b/crates/wasmi/tests/spec/mod.rs @@ -76,6 +76,7 @@ fn make_config() -> Config { config.wasm_sign_extension(true); config.wasm_multi_value(true); config.wasm_bulk_memory(true); + config.wasm_reference_types(true); config } @@ -86,22 +87,22 @@ define_spec_tests! { fn wasm_address("address"); fn wasm_align("align"); fn wasm_binary_leb128("binary-leb128"); - #[should_panic(expected = "reference types support is not enabled")] fn wasm_binary("binary"); + fn wasm_binary("binary"); fn wasm_block("block"); fn wasm_br("br"); fn wasm_br_if("br_if"); - #[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_br_table("br_table"); + fn wasm_bulk("bulk"); fn wasm_call("call"); - #[should_panic(expected = "multiple tables")] fn wasm_call_indirect("call_indirect"); + 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"); - #[should_panic(expected = "reference types support is not enabled")] fn wasm_elem("elem"); + fn wasm_elem("elem"); fn wasm_endianness("endianness"); - #[should_panic(expected = "multiple tables")] fn wasm_exports("exports"); + fn wasm_exports("exports"); fn wasm_f32("f32"); fn wasm_f32_bitwise("f32_bitwise"); fn wasm_f32_cmp("f32_cmp"); @@ -116,17 +117,17 @@ define_spec_tests! { fn wasm_forward("forward"); fn wasm_func("func"); fn wasm_func_ptrs("func_ptrs"); - #[should_panic(expected = "reference types support is not enabled")] fn wasm_global("global"); + fn wasm_global("global"); fn wasm_i32("i32"); fn wasm_i64("i64"); fn wasm_if("if"); - #[should_panic(expected = "multiple tables")] fn wasm_imports("imports"); + 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"); - #[should_panic(expected = "reference types support is not enabled")] fn wasm_linking("linking"); + fn wasm_linking("linking"); fn wasm_load("load"); fn wasm_local_get("local_get"); fn wasm_local_set("local_set"); @@ -142,31 +143,31 @@ define_spec_tests! { 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_ref_func("ref_func"); + fn wasm_ref_is_null("ref_is_null"); + fn wasm_ref_null("ref_null"); fn wasm_return("return"); - #[should_panic(expected = "reference types support is not enabled")] fn wasm_select("select"); + 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_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_table("table"); + fn wasm_table_copy("table_copy"); + fn wasm_table_fill("table_fill"); + fn wasm_table_get("table_get"); + fn wasm_table_grow("table_grow"); + fn wasm_table_init("table_init"); + fn wasm_table_set("table_set"); + fn wasm_table_size("table_size"); fn wasm_token("token"); fn wasm_traps("traps"); fn wasm_type("type"); fn wasm_unreachable("unreachable"); fn wasm_unreached_invalid("unreached-invalid"); - #[should_panic(expected = "reference types support is not enabled")] fn wasm_unreached_valid("unreached-valid"); + 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/run.rs b/crates/wasmi/tests/spec/run.rs index b04ba6a389..f701323e99 100644 --- a/crates/wasmi/tests/spec/run.rs +++ b/crates/wasmi/tests/spec/run.rs @@ -1,9 +1,9 @@ use super::{error::TestError, TestContext, TestDescriptor}; use anyhow::Result; -use wasmi::{Config, Value}; +use wasmi::{Config, ExternRef, FuncRef, Instance, Value}; use wasmi_core::{F32, F64}; use wast::{ - core::{NanPattern, WastRetCore}, + core::{HeapType, NanPattern, WastRetCore}, lexer::Lexer, parser::ParseBuffer, token::Span, @@ -53,10 +53,11 @@ pub fn run_wasm_spec_test(name: &str, config: Config) { fn execute_directives(wast: Wast, test_context: &mut TestContext) -> Result<()> { 'outer: for directive in wast.directives { + let span = directive.span(); test_context.profile().bump_directives(); match directive { WastDirective::Wat(QuoteWat::Wat(Wat::Module(module))) => { - test_context.compile_and_instantiate(module)?; + module_compilation_succeeds(test_context, span, module); test_context.profile().bump_module(); } WastDirective::Wat(_) => { @@ -201,7 +202,7 @@ fn assert_trap(test_context: &TestContext, span: Span, error: TestError, message match error { TestError::Wasmi(error) => { assert!( - error.to_string().starts_with(message), + error.to_string().contains(message), "{}: the directive trapped as expected but with an unexpected message\n\ expected: {},\n\ encountered: {}", @@ -211,9 +212,10 @@ fn assert_trap(test_context: &TestContext, span: Span, error: TestError, message ); } unexpected => panic!( - "encountered unexpected error: \n\t\ + "{}: encountered unexpected error: \n\t\ found: '{unexpected}'\n\t\ expected: trap with message '{message}'", + test_context.spanned(span), ), } } @@ -224,7 +226,8 @@ fn assert_results(context: &TestContext, span: Span, results: &[Value], expected let expected = expected.iter().map(|expected| match expected { WastRet::Core(expected) => expected, WastRet::Component(expected) => panic!( - "`wasmi` does not support the Wasm `component-model` proposal but found {expected:?}" + "{:?}: `wasmi` does not support the Wasm `component-model` proposal but found {expected:?}", + context.spanned(span), ), }); for (result, expected) in results.iter().zip(expected) { @@ -259,6 +262,20 @@ fn assert_results(context: &TestContext, span: Span, results: &[Value], expected ); } }, + (Value::FuncRef(funcref), WastRetCore::RefNull(Some(HeapType::Func))) => { + assert!(funcref.is_null()); + } + (Value::ExternRef(externref), WastRetCore::RefNull(Some(HeapType::Extern))) => { + assert!(externref.is_null()); + } + (Value::ExternRef(externref), WastRetCore::RefExtern(expected)) => { + let value = externref + .data(context.store()) + .expect("unexpected null element") + .downcast_ref::() + .expect("unexpected non-u32 data"); + assert_eq!(value, expected); + } (result, expected) => panic!( "{}: encountered mismatch in evaluation. expected {:?} but found {:?}", context.spanned(span), @@ -285,6 +302,21 @@ fn extract_module(quote_wat: QuoteWat) -> Option { } } +fn module_compilation_succeeds( + context: &mut TestContext, + span: Span, + module: wast::core::Module, +) -> Instance { + match context.compile_and_instantiate(module) { + Ok(instance) => instance, + Err(error) => panic!( + "{}: failed to instantiate module but should have suceeded: {}", + context.spanned(span), + error + ), + } +} + fn module_compilation_fails( context: &mut TestContext, span: Span, @@ -332,18 +364,16 @@ fn execute_wast_invoke( let mut args = >::new(); for arg in invoke.args { let value = match arg { - wast::WastArg::Core(arg) => { - match arg { - wast::core::WastArgCore::I32(arg) => Value::I32(arg), - wast::core::WastArgCore::I64(arg) => Value::I64(arg), - wast::core::WastArgCore::F32(arg) => Value::F32(F32::from_bits(arg.bits)), - wast::core::WastArgCore::F64(arg) => Value::F64(F64::from_bits(arg.bits)), - wast::core::WastArgCore::V128(arg) => panic!("{span:?}: `wasmi` does not support the `simd` Wasm proposal but found: {arg:?}"), - wast::core::WastArgCore::RefNull(_) | - wast::core::WastArgCore::RefExtern(_) => panic!("{span:?}: `wasmi` does not support the `reference-types` Wasm proposal but found {arg:?}"), - } - } - wast::WastArg::Component(arg) => panic!("{span:?}: `wasmi` does not support the Wasm `component-model` but found {arg:?}"), + wast::WastArg::Core(arg) => value(context.store_mut(), &arg).unwrap_or_else(|| { + panic!( + "{}: encountered unsupported WastArgCore argument: {arg:?}", + context.spanned(span) + ) + }), + wast::WastArg::Component(arg) => panic!( + "{}: `wasmi` does not support the Wasm `component-model` but found {arg:?}", + context.spanned(span) + ), }; args.push(value); } @@ -351,3 +381,17 @@ fn execute_wast_invoke( .invoke(module_name, field_name, &args) .map(|results| results.to_vec()) } + +/// Converts the [`WastArgCore`][`wast::core::WastArgCore`] into a [`wasmi::Value`] if possible. +fn value(ctx: &mut wasmi::Store<()>, value: &wast::core::WastArgCore) -> Option { + Some(match value { + wast::core::WastArgCore::I32(arg) => Value::I32(*arg), + wast::core::WastArgCore::I64(arg) => Value::I64(*arg), + wast::core::WastArgCore::F32(arg) => Value::F32(F32::from_bits(arg.bits)), + wast::core::WastArgCore::F64(arg) => Value::F64(F64::from_bits(arg.bits)), + wast::core::WastArgCore::RefNull(HeapType::Func) => Value::FuncRef(FuncRef::null()), + wast::core::WastArgCore::RefNull(HeapType::Extern) => Value::ExternRef(ExternRef::null()), + wast::core::WastArgCore::RefExtern(value) => Value::ExternRef(ExternRef::new(ctx, *value)), + _ => return None, + }) +} diff --git a/crates/wasmi/tests/spec/testsuite b/crates/wasmi/tests/spec/testsuite index cade5b7e5b..3a04b2cf93 160000 --- a/crates/wasmi/tests/spec/testsuite +++ b/crates/wasmi/tests/spec/testsuite @@ -1 +1 @@ -Subproject commit cade5b7e5b19429c3d9a2bee796ef96fffe6641b +Subproject commit 3a04b2cf93bd8fce277458d419eea8d9c326345c