diff --git a/crates/checked/src/store.rs b/crates/checked/src/store.rs index 5793d23453..d25ef1e244 100644 --- a/crates/checked/src/store.rs +++ b/crates/checked/src/store.rs @@ -1,4 +1,4 @@ -use alloc::{string::String, vec::Vec}; +use alloc::vec::Vec; use dlr_wasm_interpreter::{ Config, FuncAddr, FuncType, GlobalAddr, GlobalType, HostResumable, Hostcode, MemAddr, MemType, Module, ModuleAddr, RuntimeError, TableAddr, TableType, WasmResumable, @@ -527,7 +527,7 @@ impl<'b, T: Config> Store<'b, T> { pub fn instance_exports( &self, module_addr: Stored, - ) -> Vec<(String, StoredExternVal)> { + ) -> impl ExactSizeIterator + '_ { // 1. try unwrap let module_addr = module_addr.try_unwrap_into_bare(self.id); // 2. call @@ -536,13 +536,10 @@ impl<'b, T: Config> Store<'b, T> { let exports = unsafe { self.inner.instance_exports(module_addr) }; // 3. rewrap // 4. return - exports - .into_iter() - .map(|(name, externval)| { - // SAFETY: The `ExternVal`s just came from the current store. - let stored_externval = unsafe { StoredExternVal::from_bare(externval, self.id) }; - (name, stored_externval) - }) - .collect() + exports.map(|(name, externval)| { + // SAFETY: The `ExternVal`s just came from the current store. + let stored_externval = unsafe { StoredExternVal::from_bare(externval, self.id) }; + (name, stored_externval) + }) } } diff --git a/crates/linker/src/lib.rs b/crates/linker/src/lib.rs index b754058f6a..388c4dc182 100644 --- a/crates/linker/src/lib.rs +++ b/crates/linker/src/lib.rs @@ -107,7 +107,7 @@ impl Linker { // SAFETY: The module and thus also its exported extern values come // from the same store used now. Therefore, the extern values must // be valid in this store. - unsafe { self.define(module_name.clone(), export.0, export.1)? }; + unsafe { self.define(module_name.clone(), export.0.to_owned(), export.1)? }; } Ok(()) diff --git a/src/core/decoding/decoder.rs b/src/core/decoding/decoder.rs index d4831fefbb..919ee3882c 100644 --- a/src/core/decoding/decoder.rs +++ b/src/core/decoding/decoder.rs @@ -106,9 +106,9 @@ impl<'a> WasmDecoder<'a> { /// /// May panic if the closure moved the [`pc`](Self::pc) backwards, e.g. when /// [move_start_to](Self::move_start_to) is called. - pub fn measure_num_read_bytes( + pub fn measure_num_read_bytes( &mut self, - f: impl FnOnce(&mut WasmDecoder) -> Result, + f: impl FnOnce(&mut WasmDecoder<'a>) -> Result, ) -> Result<(T, usize), E> { let before = self.pc; let ret = f(self)?; diff --git a/src/core/decoding/error.rs b/src/core/decoding/error.rs index dfb2049261..63b9c5b432 100644 --- a/src/core/decoding/error.rs +++ b/src/core/decoding/error.rs @@ -44,7 +44,7 @@ pub enum DecodingError { /// Therefore, 33-bit signed integers may never be negative. I33IsNegative, /// A function specifies too many locals, i.e. more than 2^32 - 1 - TooManyLocals(u64), + TooManyLocals, /// A section's contents were successfully decoded, but either too many or not enough bytes of /// the section's bytecode were consumed. SectionSizeMismatch, @@ -75,7 +75,7 @@ impl fmt::Display for DecodingError { DecodingError::MalformedVariableLengthInteger => write!(f, "Reading a variable-length integer overflowed"), DecodingError::MalformedElemKindDiscriminator(byte) => write!(f, "Failed to parse {byte:#x} as an element kind discriminator"), DecodingError::I33IsNegative => f.write_str("An i33 type is negative which is not allowed"), - DecodingError::TooManyLocals(n) => write!(f,"There are {n} locals and this exceeds the maximum allowed number of 2^32-1"), + DecodingError::TooManyLocals => write!(f,"A function specifies too many locals, i.e. more than of 2^32-1"), DecodingError::SectionSizeMismatch => f.write_str("A section's contents were successfully decoded, but either too many or not enough bytes of the section's bytecode were consumed."), DecodingError::SectionOutOfOrder(ty) => write!(f, "A section of type `{ty:?}` is defined out of order"), } diff --git a/src/core/decoding/modules/code_section.rs b/src/core/decoding/modules/code_section.rs index 2805ed4f35..efe5da47ae 100644 --- a/src/core/decoding/modules/code_section.rs +++ b/src/core/decoding/modules/code_section.rs @@ -1,4 +1,3 @@ -use alloc::vec::Vec; use core::iter; use crate::{ @@ -6,39 +5,62 @@ use crate::{ DecodingError, ValType, }; -pub fn decode_locals(wasm: &mut WasmDecoder) -> Result, DecodingError> { - let locals = wasm.decode_vec(|wasm| { - let n = wasm.decode_var_u32()?.into_usize(); - let valtype = ValType::decode(wasm)?; - - Ok((n, valtype)) - })?; - - // these checks are related to the official test suite binary.wast file, the first 2 assert_malformed's starting at line 350 - // we check to not have more than 2^32-1 locals, and if that number is okay, we then get to instantiate them all - // this is because the flat_map and collect take an insane amount of time - // in total, these 2 tests take more than 240s - let mut total_no_of_locals: u64 = 0; - for local in &locals { - let temp = local.0 as u64; - if temp > u32::MAX.into() { - return Err(DecodingError::TooManyLocals(total_no_of_locals)); - }; - total_no_of_locals = match total_no_of_locals.checked_add(temp) { - None => return Err(DecodingError::TooManyLocals(total_no_of_locals)), - Some(n) => n, - } - } +pub fn decode_locals<'a, 'wasm>( + wasm: &'a mut WasmDecoder<'wasm>, +) -> Result + use<'a, 'wasm>, DecodingError> { + // First pass to decode all locals and check if their total number exceeds 2^32-1 + let mut total_number_of_locals = 0_u32; + let mut wasm_cloned = wasm.clone(); + wasm_cloned + .decode_vec_map(|wasm| { + let n = wasm.decode_var_u32()?; + let _valtype = ValType::decode(wasm)?; + + total_number_of_locals = total_number_of_locals + .checked_add(n) + .ok_or(DecodingError::TooManyLocals)?; + + Ok(()) + })? + .collect::>()?; + + // Second pass to flatten locals + let locals = wasm + .decode_vec_map::<(u32, ValType), _, DecodingError>(|wasm| { + let n = wasm.decode_var_u32().unwrap(); + let valtype = ValType::decode(wasm).unwrap(); + + Ok((n, valtype)) + }) + .unwrap() + .map(Result::unwrap) + .flat_map(|res| iter::repeat_n(res.1, res.0.into_usize())); + + Ok(WithExactSize { + i: locals, + len: total_number_of_locals.into_usize(), + }) +} - if total_no_of_locals > u32::MAX.into() { - return Err(DecodingError::TooManyLocals(total_no_of_locals)); +struct WithExactSize { + i: I, + len: usize, +} + +impl> Iterator for WithExactSize { + type Item = T; + + fn next(&mut self) -> Option { + self.i.next() } - // Flatten local types for easier representation where n > 1 - let locals = locals - .into_iter() - .flat_map(|entry| iter::repeat_n(entry.1, entry.0)) - .collect::>(); + fn size_hint(&self) -> (usize, Option) { + (self.len, Some(self.len)) + } +} - Ok(locals) +impl> ExactSizeIterator for WithExactSize { + fn len(&self) -> usize { + self.len + } } diff --git a/src/core/decoding/modules/custom_section.rs b/src/core/decoding/modules/custom_section.rs index 2f675b8e1a..7d7f324ead 100644 --- a/src/core/decoding/modules/custom_section.rs +++ b/src/core/decoding/modules/custom_section.rs @@ -1,6 +1,6 @@ use crate::{ core::decoding::decoder::{span::Span, WasmDecoder}, - DecodingError, ValidationError, + DecodingError, }; #[derive(Debug, Clone)] @@ -10,11 +10,10 @@ pub struct CustomSection<'wasm> { } impl<'wasm> CustomSection<'wasm> { - // TODO this should return a Result<_, DecodingError> pub(crate) fn decode( wasm: &mut WasmDecoder<'wasm>, section_contents: Span, - ) -> Result, ValidationError> { + ) -> Result, DecodingError> { // customsec ::= section_0(custom) // custom ::= name byte* // name ::= b*:vec(byte) => name (if utf8(name) = b*) diff --git a/src/core/decoding/types.rs b/src/core/decoding/types.rs index 73a986bd65..1543b54524 100644 --- a/src/core/decoding/types.rs +++ b/src/core/decoding/types.rs @@ -7,6 +7,8 @@ //! [^validation]: [WebAssembly Specification 2.0 - 3.2. Types](https://www.w3.org/TR/2025/CRD-wasm-core-2-20250616/#types%E2%91%A4). //! [^binary-format]: [WebAssembly Specification 2.0 - 5.3. Types](https://www.w3.org/TR/2025/CRD-wasm-core-2-20250616/#types%E2%91%A6). +use alloc::vec::Vec; + use crate::{ core::{ decoding::decoder::WasmDecoder, @@ -101,7 +103,9 @@ impl ResultType { /// [^binary-format]: [WebAssembly Specification 2.0 - 5.3.5. Result Types](https://www.w3.org/TR/2025/CRD-wasm-core-2-20250616/#binary-resulttype). /// [^always-valid]: [WebAssembly Specification 2.0 - 3.2. Types](https://www.w3.org/TR/2025/CRD-wasm-core-2-20250616/#types%E2%91%A4). pub fn decode(wasm: &mut WasmDecoder) -> Result { - let valtypes = wasm.decode_vec(ValType::decode)?; + let valtypes = wasm + .decode_vec_map(ValType::decode)? + .collect::, DecodingError>>()?; Ok(ResultType { valtypes }) } diff --git a/src/core/decoding/values.rs b/src/core/decoding/values.rs index 5073cc7cba..39587086b7 100644 --- a/src/core/decoding/values.rs +++ b/src/core/decoding/values.rs @@ -5,8 +5,6 @@ //! Note: If any of these methods return `Err`, they may have consumed some bytes from the [WasmDecoder] object and thus consequent calls may result in unexpected behaviour. //! This is due to the fact that these methods read elemental types which cannot be split. -use alloc::vec::Vec; - use crate::{ core::{decoding::decoder::WasmDecoder, utils::ToUsizeExt}, DecodingError, @@ -366,36 +364,55 @@ impl<'wasm> WasmDecoder<'wasm> { core::str::from_utf8(utf8_str).map_err(DecodingError::MalformedUtf8) } - // TODO remove, see note on read_vec for more info - pub fn decode_vec_enumerated(&mut self, mut read_element: F) -> Result, E> + /// A version of [`WasmDecoder::decode_vec_map`] that enumerates all elements. + pub fn decode_vec_enumerate_map<'a, 'f, T, F, E>( + &'a mut self, + mut read_element: F, + ) -> Result< + impl ExactSizeIterator> + use<'a, 'wasm, 'f, F, T, E>, + DecodingError, + > where T: 'wasm, - F: FnMut(&mut WasmDecoder<'wasm>, u32) -> Result, + F: FnMut(&mut WasmDecoder<'wasm>, u32) -> Result + 'f, E: From, { - let mut idx = 0; - self.decode_vec(|wasm| { - let ret = read_element(wasm, idx); - idx = idx - .checked_add(1) - .expect("the length of vectors to be encoded as a u32"); - ret - }) + let len = self.decode_var_u32()?; + let i = (0..len).map(move |n| read_element(self, n)); + + Ok(i) } - /// Note: If `Err`, the [WasmDecoder] object is no longer guaranteed to be in a valid state - // TODO make this return `impl ExactSizeIterator` to prevent allocation. This will be - // usedful if we want to decode some information on-demand in the future. - pub fn decode_vec(&mut self, mut read_element: F) -> Result, E> + /// Decodes a vector and maps every element with the given closure. An iterator with a maximum + /// fixed size of [`u32::MAX`] is returned. + pub fn decode_vec_map<'a, 'f, T, F, E>( + &'a mut self, + mut read_element: F, + ) -> Result> + use<'a, 'wasm, 'f, F, T, E>, DecodingError> where T: 'wasm, - F: FnMut(&mut WasmDecoder<'wasm>) -> Result, + F: FnMut(&mut WasmDecoder<'wasm>) -> Result + 'f, E: From, { let len = self.decode_var_u32()?; - core::iter::repeat_with(|| read_element(self)) - .take(len.into_usize()) - .collect() + let all_elements = (0..len).map(move |_| read_element(self)); + + // End the iterator after the first error was been encountered. + let mut found_err = false; + let elements_until_first_error_inclusive = all_elements.take_while(move |result| { + if result.is_err() { + if found_err { + false + } else { + found_err = true; + true + } + } else { + true + } + }); + + Ok(elements_until_first_error_inclusive) } } diff --git a/src/core/fixed_capacity_vec.rs b/src/core/fixed_capacity_vec.rs index 241f476ca3..b73f2cdea2 100644 --- a/src/core/fixed_capacity_vec.rs +++ b/src/core/fixed_capacity_vec.rs @@ -215,6 +215,22 @@ impl FixedCapacityVec { None } } + + /// Drops all elements in this vector and sets its length to zero. The capacity of this vector + /// remains unaltered. + pub fn clear(&mut self) { + // SAFETY: self.len is always less or equal to the capacity, i.e. the length of + // self.elements. Therefore, this must always return a valid slice. + let elements_to_drop = unsafe { self.elements.get_unchecked_mut(0..self.len) }; + for element in elements_to_drop { + // SAFETY: This is guaranteed to be initialized, as `self.len` accurately tracks the + // number of initialized values and elements_to_drop is exactly the slice starting at + // index 0 and ending at index `self.len` (exclusively). + unsafe { element.assume_init_drop() }; + } + + self.len = 0; + } } impl FixedCapacityVec { diff --git a/src/core/structure/import_subtyping.rs b/src/core/structure/import_subtyping.rs index 251c062099..7b93818e8d 100644 --- a/src/core/structure/import_subtyping.rs +++ b/src/core/structure/import_subtyping.rs @@ -1,4 +1,4 @@ -use crate::{ExternType, Limits}; +use crate::{core::structure::types::ExternTypeRef, ExternType, Limits}; //https://webassembly.github.io/spec/core/valid/types.html#import-subtyping pub trait ImportSubTypeRelation { @@ -21,18 +21,25 @@ impl ImportSubTypeRelation for Limits { } impl ImportSubTypeRelation for ExternType { + // https://webassembly.github.io/spec/core/valid/types.html#match-limits + fn is_subtype_of(&self, other: &Self) -> bool { + self.as_ref().is_subtype_of(&other.as_ref()) + } +} + +impl ImportSubTypeRelation for ExternTypeRef<'_> { // https://webassembly.github.io/spec/core/valid/types.html#match-limits fn is_subtype_of(&self, other: &Self) -> bool { match self { - ExternType::Table(self_table_type) => match other { - ExternType::Table(other_table_type) => { + ExternTypeRef::Table(self_table_type) => match other { + ExternTypeRef::Table(other_table_type) => { self_table_type.lim.is_subtype_of(&other_table_type.lim) && self_table_type.et == other_table_type.et } _ => false, }, - ExternType::Mem(self_mem_type) => match other { - ExternType::Mem(other_mem_type) => { + ExternTypeRef::Mem(self_mem_type) => match other { + ExternTypeRef::Mem(other_mem_type) => { self_mem_type.limits.is_subtype_of(&other_mem_type.limits) } _ => false, diff --git a/src/core/structure/types.rs b/src/core/structure/types.rs index 17fcfbb149..76c9cba686 100644 --- a/src/core/structure/types.rs +++ b/src/core/structure/types.rs @@ -16,7 +16,7 @@ //! [^valid-types]: [WebAssembly Specification 2.0 - 3.2. Types](https://www.w3.org/TR/2025/CRD-wasm-core-2-20250616/#types%E2%91%A4). //! [^structure-instructions]: [WebAssembly Specification 2.0 - 2.4. Instructions](https://www.w3.org/TR/2025/CRD-wasm-core-2-20250616/#instructions%E2%91%A0). -use alloc::vec::Vec; +use alloc::{vec, vec::Vec}; use core::fmt; use crate::core::structure::modules::indices::TypeIdx; @@ -59,7 +59,7 @@ pub enum ValType { /// A result type /// /// See: [WebAssembly Specification 2.0 - 2.3.5. Result Types](https://www.w3.org/TR/2025/CRD-wasm-core-2-20250616/#syntax-resulttype). -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Default)] pub struct ResultType { pub valtypes: Vec, } @@ -73,6 +73,28 @@ pub struct FuncType { pub returns: ResultType, } +impl FuncType { + pub fn new_empty() -> Self { + Self { + params: ResultType::default(), + returns: ResultType::default(), + } + } + + pub fn new_returning(single_return_value: ValType) -> Self { + Self { + params: ResultType::default(), + returns: ResultType { + valtypes: vec![single_return_value], + }, + } + } + + pub fn is_empty(&self) -> bool { + self.params.valtypes.is_empty() && self.returns.valtypes.is_empty() + } +} + /// A block type /// /// See: [WebAssembly Specification 2.0 - 2.4.8. Control Instructions](https://www.w3.org/TR/2025/CRD-wasm-core-2-20250616/#syntax-blocktype). @@ -154,6 +176,20 @@ pub struct GlobalType { /// An external type /// +/// This type exists because cloning [`FuncType`]s is expensive and often unnecessary. Use +/// [`ExternType`] if cloning is wanted. +/// +/// See: [WebAssembly Specification 2.0 - 2.3.11. External Types](https://www.w3.org/TR/2025/CRD-wasm-core-2-20250616/#syntax-externtype). +#[derive(Copy, Clone, PartialEq, Eq)] +pub enum ExternTypeRef<'a> { + Func(&'a FuncType), + Table(TableType), + Mem(MemType), + Global(GlobalType), +} + +/// An owned external type +/// /// See: [WebAssembly Specification 2.0 - 2.3.11. External Types](https://www.w3.org/TR/2025/CRD-wasm-core-2-20250616/#syntax-externtype). #[derive(Debug, Clone, Eq, PartialEq)] pub enum ExternType { @@ -163,6 +199,28 @@ pub enum ExternType { Global(GlobalType), } +impl ExternType { + pub fn as_ref(&self) -> ExternTypeRef<'_> { + match self { + ExternType::Func(func_type) => ExternTypeRef::Func(func_type), + ExternType::Table(table_type) => ExternTypeRef::Table(*table_type), + ExternType::Mem(mem_type) => ExternTypeRef::Mem(*mem_type), + ExternType::Global(global_type) => ExternTypeRef::Global(*global_type), + } + } +} + +impl ExternTypeRef<'_> { + pub fn to_owned(self) -> ExternType { + match self { + ExternTypeRef::Func(func_type) => ExternType::Func(func_type.clone()), + ExternTypeRef::Table(table_type) => ExternType::Table(table_type), + ExternTypeRef::Mem(mem_type) => ExternType::Mem(mem_type), + ExternTypeRef::Global(global_type) => ExternType::Global(global_type), + } + } +} + /// A memarg /// /// See: [WebAssembly Specification 2.0 - 2.4.7. Memory Instructions](https://www.w3.org/TR/2025/CRD-wasm-core-2-20250616/#syntax-memarg). diff --git a/src/execution/instructions/const_interpreter_loop.rs b/src/execution/instructions/const_interpreter_loop.rs index 6bbb4506e6..c73c757c42 100644 --- a/src/execution/instructions/const_interpreter_loop.rs +++ b/src/execution/instructions/const_interpreter_loop.rs @@ -5,7 +5,7 @@ use crate::{ decoding::decoder::{span::Span, WasmDecoder}, structure::{ modules::indices::{FuncIdx, GlobalIdx}, - types::{FuncType, ResultType}, + types::FuncType, }, }, execution::{assert_validated::UnwrapValidatedExt, runtime_structure::value_stack::Stack}, @@ -92,26 +92,26 @@ pub(crate) unsafe fn run_const_span( span: &Span, module: ModuleAddr, store: &Store, + maybe_reusable_stack: &mut Option, ) -> Result, RuntimeError> { let mut wasm = WasmDecoder::new(wasm); wasm.move_start_to(*span).unwrap_validated(); - let mut stack = Stack::new::( - Vec::new(), - &FuncType { - params: ResultType { - valtypes: Vec::new(), - }, - returns: ResultType { - valtypes: Vec::new(), - }, - }, - &[], - )?; + // If there is a stack to use, clear and reinitialize it. Otherwise create a new stack. + let stack = match maybe_reusable_stack { + Some(existing_stack) => { + existing_stack.clear_and_reinitialize(Vec::new(), &FuncType::new_empty(), &[])?; + existing_stack + } + None => { + let new_stack = Stack::new::(Vec::new(), &FuncType::new_empty(), &[])?; + maybe_reusable_stack.insert(new_stack) + } + }; // SAFETY: The current caller makes the same safety guarantees. - unsafe { run_const(&mut wasm, &mut stack, module, store)? }; + unsafe { run_const(&mut wasm, stack, module, store)? }; Ok(stack.peek_value()) } diff --git a/src/execution/instructions/control.rs b/src/execution/instructions/control.rs index 5e709778ad..e479892698 100644 --- a/src/execution/instructions/control.rs +++ b/src/execution/instructions/control.rs @@ -205,14 +205,15 @@ pub unsafe fn br_if(state: State) -> Result, define_instruction!(super::br_table, br_table_mod, fuel_check = flat(BR_TABLE)); #[inline(always)] pub unsafe fn br_table(state: State) -> Result, RuntimeError> { - let label_vec = state + let label_vec_len = state .wasm - .decode_vec::<_, _, DecodingError>(|wasm| { + .decode_vec_map::<_, _, DecodingError>(|wasm| { // SAFETY: Validation guarantees that there is a // valid vec of label indices. Ok(unsafe { decode_label_idx_unchecked(wasm) }) }) - .unwrap(); + .unwrap() + .count(); // SAFETY: Validation guarantees there to be another label index // for the default case. @@ -227,8 +228,8 @@ pub unsafe fn br_table(state: State) -> Result= label_vec.len() { - state.resumable.stp += label_vec.len(); + if case_val >= label_vec_len { + state.resumable.stp += label_vec_len; } else { state.resumable.stp += case_val; } diff --git a/src/execution/instructions/parametric.rs b/src/execution/instructions/parametric.rs index a459cdd00a..4102a486cf 100644 --- a/src/execution/instructions/parametric.rs +++ b/src/execution/instructions/parametric.rs @@ -45,7 +45,12 @@ pub unsafe fn select(state: State) -> Result define_instruction!(super::select_t, select_t_mod, fuel_check = flat(SELECT_T)); #[inline(always)] pub unsafe fn select_t(state: State) -> Result, RuntimeError> { - let _type_vec = state.wasm.decode_vec(ValType::decode).unwrap_validated(); + // skip past type vec + state + .wasm + .decode_vec_map(ValType::decode) + .unwrap_validated() + .for_each(|_| {}); let test_val: i32 = state .resumable .stack diff --git a/src/execution/runtime_structure/export_instances.rs b/src/execution/runtime_structure/export_instances.rs index 3345074476..740a84193b 100644 --- a/src/execution/runtime_structure/export_instances.rs +++ b/src/execution/runtime_structure/export_instances.rs @@ -1,2 +1,7 @@ -// TODO add ExportInst type. Currently we store exports as a BTreeMap in the -// ModuleInst, but this is not how the spec does it +use crate::ExternVal; + +#[derive(Copy, Clone, Debug)] +pub struct ExportInst<'wasm> { + pub name: &'wasm str, + pub value: ExternVal, +} diff --git a/src/execution/runtime_structure/external_values.rs b/src/execution/runtime_structure/external_values.rs index baf05de583..7ccea70c5b 100644 --- a/src/execution/runtime_structure/external_values.rs +++ b/src/execution/runtime_structure/external_values.rs @@ -1,4 +1,6 @@ -use crate::{Config, ExternType, FuncAddr, GlobalAddr, MemAddr, Store, TableAddr}; +use crate::{ + core::structure::types::ExternTypeRef, Config, FuncAddr, GlobalAddr, MemAddr, Store, TableAddr, +}; /// #[derive(Debug, Copy, Clone, PartialEq, Eq)] @@ -16,32 +18,35 @@ impl ExternVal { /// # Safety /// The caller has to guarantee that `self` came from the same [`Store`] which /// is passed now as a reference. - pub fn extern_type(&self, store: &Store) -> ExternType { + // TODO make this fn unsafe + pub(crate) fn extern_type<'store, T: Config>( + &self, + store: &'store Store, + ) -> ExternTypeRef<'store> { match self { - // TODO: fix ugly clone in function types ExternVal::Func(func_addr) => { // SAFETY: The caller ensures that self including the function // address in self is valid in the given store. let function = unsafe { store.inner.functions.get(*func_addr) }; - ExternType::Func(function.ty().clone()) + ExternTypeRef::Func(function.ty()) } ExternVal::Table(table_addr) => { // SAFETY: The caller ensures that self including the table // address in self is valid in the given store. let table = unsafe { store.inner.tables.get(*table_addr) }; - ExternType::Table(table.ty) + ExternTypeRef::Table(table.ty) } ExternVal::Mem(mem_addr) => { // SAFETY: The caller ensures that self including the memory // address in self is valid in the given store. let memory = unsafe { store.inner.memories.get(*mem_addr) }; - ExternType::Mem(memory.ty) + ExternTypeRef::Mem(memory.ty) } ExternVal::Global(global_addr) => { // SAFETY: The caller ensures that self including the global // address in self is valid in the given store. let global = unsafe { store.inner.globals.get(*global_addr) }; - ExternType::Global(global.ty) + ExternTypeRef::Global(global.ty) } } } diff --git a/src/execution/runtime_structure/module_instances.rs b/src/execution/runtime_structure/module_instances.rs index 5497f4e171..e448bc11d2 100644 --- a/src/execution/runtime_structure/module_instances.rs +++ b/src/execution/runtime_structure/module_instances.rs @@ -1,4 +1,4 @@ -use alloc::{collections::btree_map::BTreeMap, string::String}; +use alloc::boxed::Box; use crate::{ core::{ @@ -7,7 +7,8 @@ use crate::{ DataIdx, ElemIdx, FuncIdx, GlobalIdx, IdxVec, MemIdx, TableIdx, TypeIdx, }, }, - DataAddr, ElemAddr, ExternVal, FuncAddr, FuncType, GlobalAddr, MemAddr, TableAddr, + execution::runtime_structure::export_instances::ExportInst, + DataAddr, ElemAddr, FuncAddr, FuncType, GlobalAddr, MemAddr, TableAddr, }; /// / @@ -25,11 +26,7 @@ pub struct ModuleInst<'b> { pub global_addrs: IdxVec, pub elem_addrs: IdxVec, pub data_addrs: IdxVec, - /// - /// matches the list of ExportInst structs in the spec, however the spec never uses the name attribute - /// except during linking, which is up to the embedder to implement. - /// therefore this is a map data structure instead. - pub exports: BTreeMap, + pub exports: Box<[ExportInst<'b>]>, // TODO the bytecode is not in the spec, but required for re-parsing pub wasm_bytecode: &'b [u8], diff --git a/src/execution/runtime_structure/store.rs b/src/execution/runtime_structure/store.rs index 39d2801a24..8094b6f706 100644 --- a/src/execution/runtime_structure/store.rs +++ b/src/execution/runtime_structure/store.rs @@ -1,4 +1,4 @@ -use alloc::{borrow::ToOwned, collections::btree_map::BTreeMap, string::String, vec, vec::Vec}; +use alloc::{boxed::Box, vec, vec::Vec}; use core::convert::Infallible; use crate::{ @@ -27,6 +27,7 @@ use crate::{ runtime_structure::{ data_instances::DataInst, element_instances::ElemInst, + export_instances::ExportInst, external_values::ExternFilterable, function_instances::{FuncInst, HostFuncInst, WasmFuncInst}, global_instances::GlobalInst, @@ -176,7 +177,7 @@ impl<'b, T: Config> Store<'b, T> { ), elem_addrs: IdxVec::default(), data_addrs: IdxVec::default(), - exports: BTreeMap::new(), + exports: Box::default(), wasm_bytecode: module.wasm, sidetable: module.sidetable.clone(), }; @@ -214,6 +215,7 @@ impl<'b, T: Config> Store<'b, T> { // instantiation: this roughly matches step 6,7,8 // validation guarantees these will evaluate without errors. + let mut maybe_reusable_stack = None; let local_globals_init_vals: Vec = module .globals .iter_local_definitions() @@ -223,8 +225,15 @@ impl<'b, T: Config> Store<'b, T> { // this global is valid. // 2. The module with this module address was just inserted into // this store's `AddrVec`. - let const_expr_result = - unsafe { run_const_span(module.wasm, &global.init_expr, module_addr, self) }; + let const_expr_result = unsafe { + run_const_span( + module.wasm, + &global.init_expr, + module_addr, + self, + &mut maybe_reusable_stack, + ) + }; const_expr_result.transpose().unwrap_validated() }) .collect::, _>>()?; @@ -260,8 +269,15 @@ impl<'b, T: Config> Store<'b, T> { // for elements, including this one, are valid. // 2. The module with this module address was just inserted into // this store's `AddrVec`. - let const_expr_result = - unsafe { run_const_span(module.wasm, expr, module_addr, self) }; + let const_expr_result = unsafe { + run_const_span( + module.wasm, + expr, + module_addr, + self, + &mut maybe_reusable_stack, + ) + }; const_expr_result .map(|res| res.unwrap_validated().try_into().unwrap_validated()) }) @@ -363,13 +379,13 @@ impl<'b, T: Config> Store<'b, T> { .into_inner(); // allocation: step 18,19 - let export_insts: BTreeMap = module + // SAFETY: The module with this module address was just inserted + // into this `AddrVec` + let module_inst = unsafe { self.modules.get(module_addr) }; + let export_insts = module .exports .iter() .map(|export| { - // SAFETY: The module with this module address was just inserted - // into this `AddrVec` - let module_inst = unsafe { self.modules.get(module_addr) }; let value = match export.desc { ExportDesc::Func(func_idx) => { // SAFETY: Both the function index and the functions @@ -406,7 +422,11 @@ impl<'b, T: Config> Store<'b, T> { ExternVal::Global(*global_addr) } }; - (export.name.to_owned(), value) + + ExportInst { + name: export.name, + value, + } }) .collect(); @@ -457,8 +477,15 @@ impl<'b, T: Config> Store<'b, T> { // from an element contained in the same module the // Wasm bytecode is from. Therefore, the constant expression // in that span must be validated already. - let const_expr_result = - unsafe { run_const_span(module.wasm, einstr_i, module_addr, self)? }; + let const_expr_result = unsafe { + run_const_span( + module.wasm, + einstr_i, + module_addr, + self, + &mut maybe_reusable_stack, + )? + }; let d: i32 = const_expr_result .unwrap_validated() // there is a return value .try_into() @@ -564,8 +591,15 @@ impl<'b, T: Config> Store<'b, T> { // from a data segment contained in the same module // the Wasm bytecode is from. Therefore, the constant // expression in that span must be validated already. - let const_expr_result = - unsafe { run_const_span(module.wasm, dinstr_i, module_addr, self)? }; + let const_expr_result = unsafe { + run_const_span( + module.wasm, + dinstr_i, + module_addr, + self, + &mut maybe_reusable_stack, + )? + }; let d: u32 = const_expr_result .unwrap_validated() // there is a return value .try_into() @@ -685,10 +719,12 @@ impl<'b, T: Config> Store<'b, T> { // 2. If there exists an `exportinst_i` in `moduleinst.exports` such that name `exportinst_i.name` equals `name`, then: // a. Return the external value `exportinst_i.value`. // 3. Else return `error`. + // TODO Can we optimize this linear search without using fancy data structures or sorting + // during instantiation? module_inst .exports - .get(name) - .copied() + .iter() + .find_map(|export_inst| (export_inst.name == name).then_some(export_inst.value)) .ok_or(RuntimeError::UnknownExport) } @@ -1179,7 +1215,7 @@ impl<'b, T: Config> Store<'b, T> { wasm_decoder.move_start_to(span).unwrap_validated(); let (locals, bytes_read) = wasm_decoder - .measure_num_read_bytes(decode_locals) + .measure_num_read_bytes(|wasm| decode_locals(wasm).map(Iterator::collect)) .unwrap_validated(); let code_expr = wasm_decoder @@ -1508,7 +1544,10 @@ impl<'b, T: Config> Store<'b, T> { /// /// The caller has to guarantee that the given [`ModuleAddr`] came from the /// current [`Store`] object. - pub unsafe fn instance_exports(&self, module_addr: ModuleAddr) -> Vec<(String, ExternVal)> { + pub unsafe fn instance_exports( + &self, + module_addr: ModuleAddr, + ) -> impl ExactSizeIterator + '_ { // SAFETY: The caller ensures that the given module address is valid in // the current store. let module = unsafe { self.modules.get(module_addr) }; @@ -1516,8 +1555,7 @@ impl<'b, T: Config> Store<'b, T> { module .exports .iter() - .map(|(name, externval)| (name.clone(), *externval)) - .collect() + .map(|export_inst| (export_inst.name, export_inst.value)) } } diff --git a/src/execution/runtime_structure/value_stack.rs b/src/execution/runtime_structure/value_stack.rs index a00ec3bd88..e456b0b77f 100644 --- a/src/execution/runtime_structure/value_stack.rs +++ b/src/execution/runtime_structure/value_stack.rs @@ -38,32 +38,64 @@ impl Stack { frames: FixedCapacityVec::with_capacity(T::MAX_CALL_STACK_SIZE), }; - stack.values.push_from_slice(¶ms_to_base_call_frame)?; + stack.initialize_bottom_most_frame( + params_to_base_call_frame, + base_call_frame_func_ty, + base_call_frame_remaining_locals, + )?; + + Ok(stack) + } + + /// Clears the stack and re-initializes it with a new base ball frame + pub fn clear_and_reinitialize( + &mut self, + params_to_base_call_frame: Vec, + base_call_frame_func_ty: &FuncType, + base_call_frame_remaining_locals: &[ValType], + ) -> Result<(), RuntimeError> { + self.values.clear(); + self.frames.clear(); + + self.initialize_bottom_most_frame( + params_to_base_call_frame, + base_call_frame_func_ty, + base_call_frame_remaining_locals, + ) + } + + fn initialize_bottom_most_frame( + &mut self, + params_to_base_call_frame: Vec, + base_call_frame_func_ty: &FuncType, + base_call_frame_remaining_locals: &[ValType], + ) -> Result<(), RuntimeError> { + self.values.push_from_slice(¶ms_to_base_call_frame)?; debug_assert!( - stack.values.len() >= base_call_frame_func_ty.params.valtypes.len(), + self.values.len() >= base_call_frame_func_ty.params.valtypes.len(), "when pushing a new call frame, at least as many values need to be on the stack as required by the new call frames's function" ); // the topmost `param_count` values are transferred into/consumed by this new call frame let param_count = base_call_frame_func_ty.params.valtypes.len(); - let call_frame_base_idx = stack.values.len() - param_count; + let call_frame_base_idx = self.values.len() - param_count; // verify that enough space is on the stack to push the remaining locals - if base_call_frame_remaining_locals.len() + stack.values.len() >= stack.values.capacity() { + if base_call_frame_remaining_locals.len() + self.values.len() >= self.values.capacity() { return Err(RuntimeError::StackExhaustion); } // after the params, put the additional locals for local in base_call_frame_remaining_locals { // SAFETY: the previous if checks that sufficient space is one the stack - unsafe { stack.push_value_unchecked(Value::default_from_ty(*local)) }; + unsafe { self.push_value_unchecked(Value::default_from_ty(*local)) }; } // now that the locals are all populated, the actual stack section of this call frame begins - let value_stack_base_idx = stack.values.len(); + let value_stack_base_idx = self.values.len(); - stack.frames.push(CallFrame { + self.frames.push(CallFrame { return_func_addr: MaybeUninit::uninit(), return_addr: MaybeUninit::uninit(), value_stack_base_idx, @@ -72,7 +104,7 @@ impl Stack { return_stp: MaybeUninit::uninit(), })?; - Ok(stack) + Ok(()) } pub fn into_values(mut self) -> Vec { diff --git a/src/validation/error.rs b/src/validation/error.rs index e1a140dec7..b25d2e922e 100644 --- a/src/validation/error.rs +++ b/src/validation/error.rs @@ -119,6 +119,10 @@ pub enum ValidationError { LimitsNotWithinRange(u32), /// A mutable global was referenced in some global.get instruction in a constant expression. MutGlobalInConstGlobalGet, + /// A function specifies a number of parameters and locals which exceed 2^32-1 when added + /// together. This is not defined as a validation error by the specification, but very likely to + /// be incorrect, as this makes some locals un-addressable. + TooManyParamsAndLocals, } impl core::error::Error for ValidationError {} @@ -176,6 +180,7 @@ impl Display for ValidationError { ValidationError::LimitsMinLargerThanMax { min, max } => write!(f, "Limits are invalid because min={min} is larger than max={max}"), ValidationError::LimitsNotWithinRange(range) => write!(f, "The min or max field of a limits type is not within the expected range of {range}"), ValidationError::MutGlobalInConstGlobalGet => f.write_str("A mutable global was referenced in some global.get instruction in a constant expression"), + ValidationError::TooManyParamsAndLocals => f.write_str("A function specifies a number of parameters and locals which exceed 2^32-1 when added together. This is not defined as a validation error by the specification, but very likely to be incorrect, as this makes some locals un-addressable."), } } } diff --git a/src/validation/instructions/expressions.rs b/src/validation/instructions/expressions.rs index 18b081e8cc..5b690a9816 100644 --- a/src/validation/instructions/expressions.rs +++ b/src/validation/instructions/expressions.rs @@ -142,7 +142,7 @@ pub unsafe fn decode_and_validate_expr( let block_ty = BlockType::decode_and_validate(wasm, fn_types)?; // SAFETY: The block type was just validated using the same // `IdxVec`. - unsafe { block_ty.as_func_type(fn_types) }? + unsafe { block_ty.as_func_type(fn_types) } }; let label_info = LabelInfo::Block { stps_to_backpatch: Vec::new(), @@ -157,7 +157,7 @@ pub unsafe fn decode_and_validate_expr( let block_ty = BlockType::decode_and_validate(wasm, fn_types)?; // SAFETY: The block type was just validated using the same // `IdxVec`. - unsafe { block_ty.as_func_type(fn_types) }? + unsafe { block_ty.as_func_type(fn_types) } }; let label_info = LabelInfo::Loop { ip: wasm.pc, @@ -173,7 +173,7 @@ pub unsafe fn decode_and_validate_expr( let block_ty = BlockType::decode_and_validate(wasm, fn_types)?; // SAFETY: The block type was just validated using the same // `IdxVec`. - unsafe { block_ty.as_func_type(fn_types) }? + unsafe { block_ty.as_func_type(fn_types) } }; stack.assert_pop_val_type(ValType::NumType(NumType::I32))?; @@ -253,7 +253,10 @@ pub unsafe fn decode_and_validate_expr( )?; } BR_TABLE => { - let label_vec = wasm.decode_vec(decode_label_idx)?; + let label_vec: Vec = wasm + .decode_vec_map(decode_label_idx) + .and_then(Iterator::collect)?; + let max_label_idx = decode_label_idx(wasm)?; stack.assert_pop_val_type(ValType::NumType(NumType::I32))?; for label_idx in &label_vec { @@ -425,7 +428,9 @@ pub unsafe fn decode_and_validate_expr( stack.validate_polymorphic_select()?; } SELECT_T => { - let type_vec = wasm.decode_vec(ValType::decode)?; + let type_vec: Vec = wasm + .decode_vec_map(ValType::decode) + .and_then(Iterator::collect)?; if type_vec.len() != 1 { return Err(ValidationError::InvalidSelectTypeVectorLength( type_vec.len(), diff --git a/src/validation/mod.rs b/src/validation/mod.rs index a5e2856379..eaeca5af1d 100644 --- a/src/validation/mod.rs +++ b/src/validation/mod.rs @@ -23,7 +23,7 @@ use crate::{ IdxVecOverflowError, MemIdx, TableIdx, TypeIdx, }, }, - types::{ExternType, FuncType, GlobalType, MemType, ResultType, TableType}, + types::{ExternType, FuncType, GlobalType, MemType, TableType}, }, utils::ToUsizeExt, }, @@ -111,15 +111,17 @@ pub fn decode_and_validate<'wasm, T: ValidationConfig>( let mut custom_sections = Vec::new(); read_all_custom_sections(&mut wasm, &mut custom_sections)?; - let types = decode_section_if_ty_matches(&mut wasm, SectionTy::Type, |wasm, _| { - wasm.decode_vec(FuncType::decode).map(|types| IdxVec::new(types).expect("that index space creation never fails because the length of the types vector is encoded as a 32-bit integer in the bytecode")) - }) ? - .unwrap_or_default(); + let types = decode_section_if_ty_matches::<_, DecodingError>(&mut wasm, SectionTy::Type, |wasm, _| { + let collected= wasm.decode_vec_map(FuncType::decode).and_then(Iterator::collect)?; + let idx_vec = IdxVec::new(collected).expect("that index space creation never fails because the length of the types vector is encoded as a 32-bit integer in the bytecode"); + Ok(idx_vec) + })?.unwrap_or_default(); read_all_custom_sections(&mut wasm, &mut custom_sections)?; let imports = decode_section_if_ty_matches(&mut wasm, SectionTy::Import, |wasm, _| { - wasm.decode_vec(|wasm| Import::decode_and_validate(wasm, &types)) + let elements = wasm.decode_vec_map(|wasm| Import::decode_and_validate(wasm, &types))?; + elements.collect::, ValidationError>>() })? .unwrap_or_default(); @@ -133,7 +135,9 @@ pub fn decode_and_validate<'wasm, T: ValidationConfig>( // only after that do the local functions get assigned their indices. let local_functions = decode_section_if_ty_matches(&mut wasm, SectionTy::Function, |wasm, _| { - wasm.decode_vec(|wasm| TypeIdx::decode_and_validate(wasm, &types)) + wasm.decode_vec_map(|wasm| TypeIdx::decode_and_validate(wasm, &types)) + .map_err(ValidationError::from) + .and_then(Iterator::collect) })? .unwrap_or_default(); @@ -152,7 +156,9 @@ pub fn decode_and_validate<'wasm, T: ValidationConfig>( _ => None, }); let local_tables = decode_section_if_ty_matches(&mut wasm, SectionTy::Table, |wasm, _| { - wasm.decode_vec(TableType::decode_and_validate) + wasm.decode_vec_map(TableType::decode_and_validate) + .map_err(ValidationError::from) + .and_then(Iterator::collect) })? .unwrap_or_default(); @@ -167,7 +173,9 @@ pub fn decode_and_validate<'wasm, T: ValidationConfig>( }); // let imported_memories_length = imported_memories.len(); let local_memories = decode_section_if_ty_matches(&mut wasm, SectionTy::Memory, |wasm, _| { - wasm.decode_vec(MemType::decode_and_validate) + wasm.decode_vec_map(MemType::decode_and_validate) + .map_err(ValidationError::from) + .and_then(Iterator::collect) })? .unwrap_or_default(); @@ -188,7 +196,7 @@ pub fn decode_and_validate<'wasm, T: ValidationConfig>( }) .collect(); let local_globals = decode_section_if_ty_matches(&mut wasm, SectionTy::Global, |wasm, _| { - wasm.decode_vec(|wasm| { + wasm.decode_vec_map(|wasm| { Global::decode_and_validate( wasm, &imported_global_types, @@ -196,6 +204,8 @@ pub fn decode_and_validate<'wasm, T: ValidationConfig>( functions.inner(), ) }) + .map_err(ValidationError::from) + .and_then(Iterator::collect) })? .unwrap_or_default(); @@ -210,20 +220,23 @@ pub fn decode_and_validate<'wasm, T: ValidationConfig>( read_all_custom_sections(&mut wasm, &mut custom_sections)?; - let exports = decode_section_if_ty_matches(&mut wasm, SectionTy::Export, |wasm, _| { - wasm.decode_vec(|wasm| { - Export::decode_and_validate( - wasm, - functions.inner(), - tables.inner(), - memories.inner(), - globals.inner(), - ) - }) - })? - .unwrap_or_default(); + let exports: Vec = + decode_section_if_ty_matches(&mut wasm, SectionTy::Export, |wasm, _| { + wasm.decode_vec_map(|wasm| { + Export::decode_and_validate( + wasm, + functions.inner(), + tables.inner(), + memories.inner(), + globals.inner(), + ) + }) + .map_err(ValidationError::from) + .and_then(Iterator::collect) + })? + .unwrap_or_default(); validation_context_refs.extend(exports.iter().filter_map( - |Export { name: _, desc }| match *desc { + |Export { name: _, desc }| match desc { ExportDesc::Func(func_idx) => Some(func_idx), _ => None, }, @@ -244,16 +257,7 @@ pub fn decode_and_validate<'wasm, T: ValidationConfig>( // current function. Therefore, this has to be the same one used to // create and validate this `TypeIdx`. let func_type = unsafe { types.get(*type_idx) }; - if func_type - != &(FuncType { - params: ResultType { - valtypes: Vec::new(), - }, - returns: ResultType { - valtypes: Vec::new(), - }, - }) - { + if !func_type.is_empty() { Err(ValidationError::InvalidStartFunctionSignature) } else { Ok(func_idx) @@ -262,15 +266,17 @@ pub fn decode_and_validate<'wasm, T: ValidationConfig>( read_all_custom_sections(&mut wasm, &mut custom_sections)?; - let elements = decode_section_if_ty_matches(&mut wasm, SectionTy::Element, |wasm, _| { - ElemType::decode_and_validate( + let elements = decode_section_if_ty_matches::<_, ValidationError>(&mut wasm, SectionTy::Element, |wasm, _| { + let collected = wasm.decode_vec_map(|wasm| { + ElemType::decode_and_validate( wasm, functions.inner(), &mut validation_context_refs, tables.inner(), &imported_global_types, - ) - .map(|elements| IdxVec::new(elements).expect("that index space creation never fails because the length of the elements vector is encoded as a 32-bit integer in the bytecode")) + )}).map_err(ValidationError::from).and_then(Iterator::collect)?; + let idx_vec = IdxVec::new(collected).expect("that index space creation never fails because the length of the elements vector is encoded as a 32-bit integer in the bytecode"); + Ok(idx_vec) })? .unwrap_or_default(); @@ -320,11 +326,12 @@ pub fn decode_and_validate<'wasm, T: ValidationConfig>( read_all_custom_sections(&mut wasm, &mut custom_sections)?; - let data_section = decode_section_if_ty_matches(&mut wasm, SectionTy::Data, |wasm, _| { - wasm.decode_vec(|wasm| { - DataSegment::decode_and_validate(wasm, &imported_global_types, functions.inner(), memories.inner()) - }) - .map(|data_segments| IdxVec::new(data_segments).expect("that index space creation never fails because the length of the data segments vector is encoded as a 32-bit integer in the bytecode")) + let data_section = decode_section_if_ty_matches::<_, ValidationError>(&mut wasm, SectionTy::Data, |wasm, _| { + let collected = wasm.decode_vec_map(|wasm| { + DataSegment::decode_and_validate(wasm, &imported_global_types, functions.inner(), memories.inner()) + }).map_err(ValidationError::from).and_then(Iterator::collect)?; + let idx_vec = IdxVec::new(collected).expect("that index space creation never fails because the length of the data segments vector is encoded as a 32-bit integer in the bytecode"); + Ok(idx_vec) })? .unwrap_or_default(); @@ -396,7 +403,7 @@ impl<'wasm> Module<'wasm> { self.imports.iter().map(|import| { // SAFETY: This is sound because the argument is `self` and the // import desc also comes from `self`. - let extern_type = unsafe { import.desc.extern_type(self) }; + let extern_type = unsafe { import.desc.extern_type_owned(self) }; (import.module_name, import.name, extern_type) }) } diff --git a/src/validation/modules/data_segments.rs b/src/validation/modules/data_segments.rs index 51e3b6af9b..071aa92100 100644 --- a/src/validation/modules/data_segments.rs +++ b/src/validation/modules/data_segments.rs @@ -1,3 +1,5 @@ +use alloc::vec::Vec; + use crate::{ core::{ decoding::decoder::WasmDecoder, @@ -14,7 +16,7 @@ use crate::{ instructions::constant_expressions::decode_and_validate_constant_expression, validation_stack::ValidationStack, }, - MemType, ValidationError, + DecodingError, MemType, ValidationError, }; impl DataSegment { @@ -45,7 +47,9 @@ impl DataSegment { valid_stack.assert_val_types(&[ValType::NumType(NumType::I32)], true)?; - let byte_vec = wasm.decode_vec(|el| el.decode_u8())?; + let byte_vec = wasm + .decode_vec_map(WasmDecoder::decode_u8)? + .collect::, DecodingError>>()?; // WARN: we currently don't take into consideration how we act when we are dealing with globals here DataSegment { @@ -60,9 +64,13 @@ impl DataSegment { // passive // A passive data segment's contents can be copied into a memory using the `memory.init` instruction trace!("Data section: passive"); + let byte_vec = wasm + .decode_vec_map(WasmDecoder::decode_u8)? + .collect::, DecodingError>>()?; + DataSegment { mode: DataMode::Passive, - init: wasm.decode_vec(|el| el.decode_u8())?, + init: byte_vec, } } 2 => { @@ -81,7 +89,9 @@ impl DataSegment { valid_stack.assert_val_types(&[ValType::NumType(NumType::I32)], true)?; - let byte_vec = wasm.decode_vec(|el| el.decode_u8())?; + let byte_vec = wasm + .decode_vec_map(WasmDecoder::decode_u8)? + .collect::, DecodingError>>()?; DataSegment { mode: DataMode::Active(DataModeActive { diff --git a/src/validation/modules/element_segments.rs b/src/validation/modules/element_segments.rs index eb2c4c6b55..6ba69b463f 100644 --- a/src/validation/modules/element_segments.rs +++ b/src/validation/modules/element_segments.rs @@ -32,192 +32,190 @@ impl ElemType { validation_context_refs: &mut BTreeSet, c_tables: &IdxVec, imported_global_types: &[GlobalType], - ) -> Result, ValidationError> { - wasm.decode_vec(|wasm| { - let prop = wasm.decode_var_u32()?; + ) -> Result { + let prop = wasm.decode_var_u32()?; - let elem = match prop { - 0 => { - // binary format is: 0:u32 e:expr y*:vec(funcidx) - // should parse to spec struct {type funcref, init ((ref.func y) end)*, mode active {table 0, offset e}} - // which is equivalent to ElemType{init: ElemItems::RefFuncs(y*), mode: ElemMode::Active{0, e}} here - let e = decode_and_validate_active_segment_offset_expr( - wasm, - imported_global_types, - c_funcs, - validation_context_refs, - )?; - let init = decode_and_validate_shortened_initializer_list( - wasm, - c_funcs, - validation_context_refs, - )?; - let mode = ElemMode::Active(ActiveElem { - table_idx: TableIdx::validate(0, c_tables)?, - init_expr: e, - }); - ElemType { init, mode } - } - 1 => { - // binary format is: 1:u32 et:elemkind y*:vec(funcidx) - // should parse to spec struct {type et, init ((ref.func y) end)*, mode passive} - // which is equivalent to ElemType{init: ElemItems::RefFuncs(y*), mode: ElemMode::Passive} here - let _et = ElemKind::decode(wasm)?; - let init = decode_and_validate_shortened_initializer_list( - wasm, - c_funcs, - validation_context_refs, - )?; - let mode = ElemMode::Passive; - ElemType { init, mode } - } - 2 => { - // binary format is: 2:u32 x:tableidx e:expr et:elemkind y*:vec(funcidx) - // should parse to spec struct {type et, init ((ref.func y) end)*, mode active {table x, offset e}} - // which reflects to ElemType{init: ElemItems::RefFuncs(y*), mode: ElemMode::Active{x, e}} here - let x = TableIdx::decode_and_validate(wasm, c_tables)?; - let e = decode_and_validate_active_segment_offset_expr( - wasm, - imported_global_types, - c_funcs, - validation_context_refs, - )?; - let _et = ElemKind::decode(wasm)?; - let init = decode_and_validate_shortened_initializer_list( - wasm, - c_funcs, - validation_context_refs, - )?; - let mode = ElemMode::Active(ActiveElem { - table_idx: x, - init_expr: e, - }); - ElemType { init, mode } - } - 3 => { - // binary format is: 3:u32 et:elemkind y*:vec(funcidx) - // should parse to spec struct {type et, init ((ref.func y) end)*, mode declarative} - // which is equivalent to ElemType{init: ElemItems::RefFuncs(y*), mode: ElemMode::Declarative} here - let _et = ElemKind::decode(wasm)?; - let init = decode_and_validate_shortened_initializer_list( - wasm, - c_funcs, - validation_context_refs, - )?; - let mode = ElemMode::Declarative; - ElemType { init, mode } - } - 4 => { - // binary format is: 4:u32 e:expr el*:vec(expr) - // should parse to spec struct {type funcref, init el*, mode active { table 0, offset e}} - // which is equivalent to ElemType{init: ElemItems::Exprs(funcref, el*), mode: ElemMode::Active{0, e}} - let e = decode_and_validate_active_segment_offset_expr( - wasm, - imported_global_types, - c_funcs, - validation_context_refs, - )?; - let init = decode_and_validate_generic_initializer_list( - wasm, - RefType::FuncRef, - imported_global_types, - c_funcs, - validation_context_refs, - )?; - let mode = ElemMode::Active(ActiveElem { - table_idx: TableIdx::validate(0, c_tables)?, - init_expr: e, - }); - ElemType { init, mode } - } - 5 => { - // binary format is 5:u32 et: reftype el*:vec(expr) - // should parse to spec struct {type et, init el*, mode passive} - // which is equivalent to ElemType{init: ElemItems::Exprs(et, el*), mode: ElemMode::Passive} here - let et = RefType::decode(wasm)?; - let init = decode_and_validate_generic_initializer_list( - wasm, - et, - imported_global_types, - c_funcs, - validation_context_refs, - )?; - let mode = ElemMode::Passive; - ElemType { init, mode } - } - 6 => { - // binary format is 6:u32 x:table_idx e:expr et:reftype el*:vec(expr) - // should parse to spec struct {type et, init el*, mode passive} - // which is equivalent to ElemType{init: Exprs(et, el*), mode: ElemMode::Active{table x, offset e}} here - let x = TableIdx::decode_and_validate(wasm, c_tables)?; - let e = decode_and_validate_active_segment_offset_expr( - wasm, - imported_global_types, - c_funcs, - validation_context_refs, - )?; - let et = RefType::decode(wasm)?; - let init = decode_and_validate_generic_initializer_list( - wasm, - et, - imported_global_types, - c_funcs, - validation_context_refs, - )?; - let mode = ElemMode::Active(ActiveElem { - table_idx: x, - init_expr: e, - }); - ElemType { init, mode } - } - 7 => { - // binary format is 7:u32 et:reftype el*:vec(expr) - // should parse to spec struct {type et, init el*, mode declarative} - // which is equivalent to ElemType{init: Exprs(et, el*), mode: ElemMode::Declarative} here - let et = RefType::decode(wasm)?; - let init = decode_and_validate_generic_initializer_list( - wasm, - et, - imported_global_types, - c_funcs, - validation_context_refs, - )?; - let mode = ElemMode::Declarative; - ElemType { init, mode } - } - invalid_mode @ 8.. => { - return Err(ValidationError::InvalidElementMode(invalid_mode)); - } - }; - - // assume the element segment is well formed in terms of abstract syntax from now on. - // start validating element segment of form {type t, init e*, mode elemmode}: https://webassembly.github.io/spec/core/valid/modules.html#element-segments - let t = elem.ty(); - // 1. Each e_i must be valid with type t and be const: this is already checked during the parse of initializer expressions above. - // 2. elemmode must be valid with type t - // -- start validating elemmode for type t: - match elem.mode { - ElemMode::Active(ActiveElem { + let elem = match prop { + 0 => { + // binary format is: 0:u32 e:expr y*:vec(funcidx) + // should parse to spec struct {type funcref, init ((ref.func y) end)*, mode active {table 0, offset e}} + // which is equivalent to ElemType{init: ElemItems::RefFuncs(y*), mode: ElemMode::Active{0, e}} here + let e = decode_and_validate_active_segment_offset_expr( + wasm, + imported_global_types, + c_funcs, + validation_context_refs, + )?; + let init = decode_and_validate_shortened_initializer_list( + wasm, + c_funcs, + validation_context_refs, + )?; + let mode = ElemMode::Active(ActiveElem { + table_idx: TableIdx::validate(0, c_tables)?, + init_expr: e, + }); + ElemType { init, mode } + } + 1 => { + // binary format is: 1:u32 et:elemkind y*:vec(funcidx) + // should parse to spec struct {type et, init ((ref.func y) end)*, mode passive} + // which is equivalent to ElemType{init: ElemItems::RefFuncs(y*), mode: ElemMode::Passive} here + let _et = ElemKind::decode(wasm)?; + let init = decode_and_validate_shortened_initializer_list( + wasm, + c_funcs, + validation_context_refs, + )?; + let mode = ElemMode::Passive; + ElemType { init, mode } + } + 2 => { + // binary format is: 2:u32 x:tableidx e:expr et:elemkind y*:vec(funcidx) + // should parse to spec struct {type et, init ((ref.func y) end)*, mode active {table x, offset e}} + // which reflects to ElemType{init: ElemItems::RefFuncs(y*), mode: ElemMode::Active{x, e}} here + let x = TableIdx::decode_and_validate(wasm, c_tables)?; + let e = decode_and_validate_active_segment_offset_expr( + wasm, + imported_global_types, + c_funcs, + validation_context_refs, + )?; + let _et = ElemKind::decode(wasm)?; + let init = decode_and_validate_shortened_initializer_list( + wasm, + c_funcs, + validation_context_refs, + )?; + let mode = ElemMode::Active(ActiveElem { + table_idx: x, + init_expr: e, + }); + ElemType { init, mode } + } + 3 => { + // binary format is: 3:u32 et:elemkind y*:vec(funcidx) + // should parse to spec struct {type et, init ((ref.func y) end)*, mode declarative} + // which is equivalent to ElemType{init: ElemItems::RefFuncs(y*), mode: ElemMode::Declarative} here + let _et = ElemKind::decode(wasm)?; + let init = decode_and_validate_shortened_initializer_list( + wasm, + c_funcs, + validation_context_refs, + )?; + let mode = ElemMode::Declarative; + ElemType { init, mode } + } + 4 => { + // binary format is: 4:u32 e:expr el*:vec(expr) + // should parse to spec struct {type funcref, init el*, mode active { table 0, offset e}} + // which is equivalent to ElemType{init: ElemItems::Exprs(funcref, el*), mode: ElemMode::Active{0, e}} + let e = decode_and_validate_active_segment_offset_expr( + wasm, + imported_global_types, + c_funcs, + validation_context_refs, + )?; + let init = decode_and_validate_generic_initializer_list( + wasm, + RefType::FuncRef, + imported_global_types, + c_funcs, + validation_context_refs, + )?; + let mode = ElemMode::Active(ActiveElem { + table_idx: TableIdx::validate(0, c_tables)?, + init_expr: e, + }); + ElemType { init, mode } + } + 5 => { + // binary format is 5:u32 et: reftype el*:vec(expr) + // should parse to spec struct {type et, init el*, mode passive} + // which is equivalent to ElemType{init: ElemItems::Exprs(et, el*), mode: ElemMode::Passive} here + let et = RefType::decode(wasm)?; + let init = decode_and_validate_generic_initializer_list( + wasm, + et, + imported_global_types, + c_funcs, + validation_context_refs, + )?; + let mode = ElemMode::Passive; + ElemType { init, mode } + } + 6 => { + // binary format is 6:u32 x:table_idx e:expr et:reftype el*:vec(expr) + // should parse to spec struct {type et, init el*, mode passive} + // which is equivalent to ElemType{init: Exprs(et, el*), mode: ElemMode::Active{table x, offset e}} here + let x = TableIdx::decode_and_validate(wasm, c_tables)?; + let e = decode_and_validate_active_segment_offset_expr( + wasm, + imported_global_types, + c_funcs, + validation_context_refs, + )?; + let et = RefType::decode(wasm)?; + let init = decode_and_validate_generic_initializer_list( + wasm, + et, + imported_global_types, + c_funcs, + validation_context_refs, + )?; + let mode = ElemMode::Active(ActiveElem { table_idx: x, - init_expr: _expr, - }) => { - // start validating elemmode of form active {table x, offset expr} - // 1-2. C.tables[x] must be defined with type: limits t - // SAFETY: The `ActiveElem` that is being deconstructed was - // created and also validated in this function. - let table_type = unsafe { c_tables.get(x) }; - if table_type.et != t { - return Err(ValidationError::ActiveElementSegmentTypeMismatch); - } - // 3-4. _expr must be valid with type I32 and be const: already checked during the parse of initializer expressions above. - // Then elemmode is valid with type t. + init_expr: e, + }); + ElemType { init, mode } + } + 7 => { + // binary format is 7:u32 et:reftype el*:vec(expr) + // should parse to spec struct {type et, init el*, mode declarative} + // which is equivalent to ElemType{init: Exprs(et, el*), mode: ElemMode::Declarative} here + let et = RefType::decode(wasm)?; + let init = decode_and_validate_generic_initializer_list( + wasm, + et, + imported_global_types, + c_funcs, + validation_context_refs, + )?; + let mode = ElemMode::Declarative; + ElemType { init, mode } + } + invalid_mode @ 8.. => { + return Err(ValidationError::InvalidElementMode(invalid_mode)); + } + }; + + // assume the element segment is well formed in terms of abstract syntax from now on. + // start validating element segment of form {type t, init e*, mode elemmode}: https://webassembly.github.io/spec/core/valid/modules.html#element-segments + let t = elem.ty(); + // 1. Each e_i must be valid with type t and be const: this is already checked during the parse of initializer expressions above. + // 2. elemmode must be valid with type t + // -- start validating elemmode for type t: + match elem.mode { + ElemMode::Active(ActiveElem { + table_idx: x, + init_expr: _expr, + }) => { + // start validating elemmode of form active {table x, offset expr} + // 1-2. C.tables[x] must be defined with type: limits t + // SAFETY: The `ActiveElem` that is being deconstructed was + // created and also validated in this function. + let table_type = unsafe { c_tables.get(x) }; + if table_type.et != t { + return Err(ValidationError::ActiveElementSegmentTypeMismatch); } - ElemMode::Declarative | ElemMode::Passive => (), // these are valid for any type t. + // 3-4. _expr must be valid with type I32 and be const: already checked during the parse of initializer expressions above. + // Then elemmode is valid with type t. } - // -- Then elemmmode is valid with type t. - // Then the element segment is valid with type t. - Ok(elem) - }) + ElemMode::Declarative | ElemMode::Passive => (), // these are valid for any type t. + } + // -- Then elemmmode is valid with type t. + // Then the element segment is valid with type t. + Ok(elem) } } @@ -262,12 +260,13 @@ fn decode_and_validate_shortened_initializer_list( c_funcs: &IdxVec, validation_context_refs: &mut BTreeSet, ) -> Result { - wasm.decode_vec(|w| { + let elements = wasm.decode_vec_map(|w| { let func_idx = FuncIdx::decode_and_validate(w, c_funcs)?; validation_context_refs.insert(func_idx); - Ok(func_idx) - }) - .map(ElemItems::RefFuncs) + Ok::<_, ValidationError>(func_idx) + })?; + let function_refs = elements.collect::, ValidationError>>()?; + Ok(ElemItems::RefFuncs(function_refs)) } /// Parse and validate the initializer list of an element segment for the supplied type `expected_type`. @@ -285,7 +284,7 @@ fn decode_and_validate_generic_initializer_list( c_funcs: &IdxVec, validation_context_refs: &mut BTreeSet, ) -> Result { - wasm.decode_vec(|w| { + let v_elements = wasm.decode_vec_map(|w| { let mut valid_stack = ValidationStack::new(); let (span, seen_func_refs) = decode_and_validate_constant_expression( w, @@ -295,7 +294,8 @@ fn decode_and_validate_generic_initializer_list( )?; validation_context_refs.extend(seen_func_refs); valid_stack.assert_val_types(&[ValType::RefType(expected_type)], true)?; - Ok(span) - }) - .map(|v| ElemItems::Exprs(expected_type, v)) + Ok::<_, ValidationError>(span) + })?; + let v = v_elements.collect::, ValidationError>>()?; + Ok(ElemItems::Exprs(expected_type, v)) } diff --git a/src/validation/modules/functions.rs b/src/validation/modules/functions.rs index 9c1a638369..af52f03c78 100644 --- a/src/validation/modules/functions.rs +++ b/src/validation/modules/functions.rs @@ -57,7 +57,7 @@ pub unsafe fn decode_and_validate_code_section( sidetable: &mut Sidetable, user_data: &mut T2, ) -> Result, ValidationError> { - let code_block_spans_stps = wasm.decode_vec_enumerated(|wasm, idx| { + let code_block_spans_stps = wasm.decode_vec_enumerate_map(|wasm, idx| { // We need to offset the index by the number of functions that were // imported. Imported functions always live at the start of the index // space. @@ -74,11 +74,16 @@ pub unsafe fn decode_and_validate_code_section( let func_block = wasm.make_span(func_size.into_usize())?; let previous_pc = wasm.pc; - let locals = { + let locals: Vec = { let params = func_ty.params.valtypes.iter().cloned(); let declared_locals = decode_locals(wasm)?; - params.chain(declared_locals).collect::>() + params.chain(declared_locals).collect() }; + // Note: The specification does not consider the case in which the number of parameters + + // the number of locals exceeds 2^32-1. Because this is very likely an error, we can check for it. + if locals.len() > u32::MAX.into_usize() { + return Err(ValidationError::TooManyParamsAndLocals); + } let mut stack = ValidationStack::new_for_func(func_ty); let stp = sidetable.len(); @@ -116,5 +121,5 @@ pub unsafe fn decode_and_validate_code_section( code_block_spans_stps.len() ); - Ok(code_block_spans_stps) + code_block_spans_stps.collect() } diff --git a/src/validation/modules/imports.rs b/src/validation/modules/imports.rs index 279f2cca59..b51bbd0aa2 100644 --- a/src/validation/modules/imports.rs +++ b/src/validation/modules/imports.rs @@ -1,9 +1,12 @@ use crate::{ core::{ decoding::decoder::WasmDecoder, - structure::modules::{ - imports::{Import, ImportDesc}, - indices::{IdxVec, TypeIdx}, + structure::{ + modules::{ + imports::{Import, ImportDesc}, + indices::{IdxVec, TypeIdx}, + }, + types::ExternTypeRef, }, }, DecodingError, ExternType, FuncType, GlobalType, MemType, Module, TableType, ValidationError, @@ -50,7 +53,19 @@ impl ImportDesc { /// /// The caller must ensure that `self` comes from the same /// [`Module`] that is passed as an argument here. - pub unsafe fn extern_type(&self, module: &Module) -> ExternType { + pub unsafe fn extern_type_owned(&self, module: &Module) -> ExternType { + // SAFETY: The caller makes the same safety guarantees as required for extern_type_ref. + unsafe { self.extern_type(module) }.to_owned() + } + + /// returns the external type of `self` according to typing relation, + /// taking `validation_info` as validation context C + /// + /// # Safety + /// + /// The caller must ensure that `self` comes from the same + /// [`Module`] that is passed as an argument here. + pub unsafe fn extern_type<'module>(&self, module: &'module Module) -> ExternTypeRef<'module> { match self { ImportDesc::Func(type_idx) => { // unlike ExportDescs, these directly refer to the types section @@ -61,12 +76,11 @@ impl ImportDesc { // `Module`. Because all type indices contained by a `Module` must always be valid, // this is safe. let func_type = unsafe { module.types.get(*type_idx) }; - // TODO ugly clone that should disappear when types are directly parsed from bytecode instead of vector copies - ExternType::Func(func_type.clone()) + ExternTypeRef::Func(func_type) } - ImportDesc::Table(ty) => ExternType::Table(*ty), - ImportDesc::Mem(ty) => ExternType::Mem(*ty), - ImportDesc::Global(ty) => ExternType::Global(*ty), + ImportDesc::Table(ty) => ExternTypeRef::Table(*ty), + ImportDesc::Mem(ty) => ExternTypeRef::Mem(*ty), + ImportDesc::Global(ty) => ExternTypeRef::Global(*ty), } } } diff --git a/src/validation/types.rs b/src/validation/types.rs index 529b900ad7..05bb802963 100644 --- a/src/validation/types.rs +++ b/src/validation/types.rs @@ -2,8 +2,6 @@ //! //! TODO write description for this module -use alloc::vec::Vec; - use crate::{ core::{ decoding::decoder::WasmDecoder, @@ -13,8 +11,7 @@ use crate::{ }, }, execution::assert_validated::UnwrapValidatedExt, - DecodingError, FuncType, Limits, MemType, RefType, ResultType, TableType, ValType, - ValidationError, + DecodingError, FuncType, Limits, MemType, RefType, TableType, ValType, ValidationError, }; impl BlockType { @@ -51,32 +48,14 @@ impl BlockType { /// used to validate `self` through [`BlockType::decode_and_validate`]. // TODO maybe make this function return a `Cow<'a, FuncType>`. This could // prevent one allocation per call. - pub unsafe fn as_func_type( - &self, - func_types: &IdxVec, - ) -> Result { + pub unsafe fn as_func_type(&self, func_types: &IdxVec) -> FuncType { match self { - BlockType::Empty => Ok(FuncType { - params: ResultType { - valtypes: Vec::new(), - }, - returns: ResultType { - valtypes: Vec::new(), - }, - }), - BlockType::Returns(val_type) => Ok(FuncType { - params: ResultType { - valtypes: Vec::new(), - }, - returns: ResultType { - valtypes: [*val_type].into(), - }, - }), + BlockType::Empty => FuncType::new_empty(), + BlockType::Returns(val_type) => FuncType::new_returning(*val_type), BlockType::Type(type_idx) => { // SAFETY: The caller ensures that this `IdxVec` is the same one // used to validate the `TypeIdx` in `self`. - let func_type = unsafe { func_types.get(*type_idx) }; - Ok(func_type.clone()) + unsafe { func_types.get(*type_idx) }.clone() } } } diff --git a/src/validation/validation_stack.rs b/src/validation/validation_stack.rs index 225b2c82d4..2b0e0a05bd 100644 --- a/src/validation/validation_stack.rs +++ b/src/validation/validation_stack.rs @@ -3,7 +3,7 @@ use core::iter; use crate::{ core::{ - structure::types::{FuncType, NumType, RefType, ResultType, ValType}, + structure::types::{FuncType, NumType, RefType, ValType}, utils::ToUsizeExt, }, ValidationError, @@ -23,14 +23,7 @@ impl ValidationStack { stack: Vec::new(), ctrl_stack: vec![CtrlStackEntry { label_info: LabelInfo::Untyped, - block_ty: FuncType { - params: ResultType { - valtypes: Vec::new(), - }, - returns: ResultType { - valtypes: Vec::new(), - }, - }, + block_ty: FuncType::new_empty(), height: 0, unreachable: false, }], @@ -467,19 +460,12 @@ pub enum LabelInfo { mod tests { use crate::{NumType, RefType, ValType}; - use super::{CtrlStackEntry, FuncType, LabelInfo, ResultType, ValidationStack, Vec}; + use super::{CtrlStackEntry, FuncType, LabelInfo, ValidationStack}; fn push_dummy_untyped_label(validation_stack: &mut ValidationStack) { validation_stack.ctrl_stack.push(CtrlStackEntry { label_info: LabelInfo::Untyped, - block_ty: FuncType { - params: ResultType { - valtypes: Vec::new(), - }, - returns: ResultType { - valtypes: Vec::new(), - }, - }, + block_ty: FuncType::new_empty(), height: validation_stack.len(), unreachable: false, })