From cb97e4ec8eb4e2981747fe612bb1d21d64766b32 Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Tue, 18 Feb 2020 16:41:43 -0800 Subject: [PATCH] Implement `table.init` and `elem.drop` from the bulk memory proposal --- build.rs | 4 +- crates/api/src/instance.rs | 16 ++- crates/environ/src/data_structures.rs | 4 +- crates/environ/src/func_environ.rs | 139 ++++++++++++++++--- crates/environ/src/module.rs | 5 + crates/runtime/src/instance.rs | 172 ++++++++++++++++++++---- crates/runtime/src/libcalls.rs | 34 ++++- crates/runtime/src/vmcontext.rs | 10 +- crates/wast/src/wast.rs | 12 +- tests/misc_testsuite/elem-ref-null.wast | 2 + tests/misc_testsuite/elem_drop.wast | 7 + tests/wast_testsuites.rs | 2 + 12 files changed, 352 insertions(+), 55 deletions(-) mode change 100644 => 100755 crates/environ/src/data_structures.rs create mode 100644 tests/misc_testsuite/elem-ref-null.wast create mode 100644 tests/misc_testsuite/elem_drop.wast diff --git a/build.rs b/build.rs index 718ee96cc5..1d71b908fe 100644 --- a/build.rs +++ b/build.rs @@ -182,7 +182,9 @@ fn ignore(testsuite: &str, testname: &str, strategy: &str) -> bool { ("reference_types", _) => return true, - ("bulk_memory_operations", "table_copy") => return false, + ("bulk_memory_operations", "table_copy") + | ("bulk_memory_operations", "table_init") + | ("bulk_memory_operations", "elem") => return false, ("bulk_memory_operations", _) => return true, _ => {} diff --git a/crates/api/src/instance.rs b/crates/api/src/instance.rs index 1a44b656b2..065627dc51 100644 --- a/crates/api/src/instance.rs +++ b/crates/api/src/instance.rs @@ -1,10 +1,10 @@ use crate::externals::Extern; use crate::module::Module; -use crate::runtime::Store; +use crate::runtime::{Config, Store}; use crate::trap::Trap; use anyhow::{Error, Result}; use wasmtime_jit::{CompiledModule, Resolver}; -use wasmtime_runtime::{Export, InstanceHandle, InstantiationError}; +use wasmtime_runtime::{Export, InstanceHandle, InstantiationError, LinkError}; struct SimpleResolver<'a> { imports: &'a [Extern], @@ -19,6 +19,7 @@ impl Resolver for SimpleResolver<'_> { } fn instantiate( + config: &Config, compiled_module: &CompiledModule, imports: &[Extern], ) -> Result { @@ -29,6 +30,14 @@ fn instantiate( .map_err(|e| -> Error { match e { InstantiationError::StartTrap(trap) => Trap::from_jit(trap).into(), + e @ InstantiationError::TableOutOfBounds(_) => { + let msg = e.to_string(); + if config.validating_config.operator_config.enable_bulk_memory { + Trap::new(msg).into() + } else { + InstantiationError::Link(LinkError(msg)).into() + } + } other => other.into(), } })?; @@ -105,7 +114,8 @@ impl Instance { /// [issue]: https://github.com/bytecodealliance/wasmtime/issues/727 pub fn new(module: &Module, imports: &[Extern]) -> Result { let store = module.store(); - let instance_handle = instantiate(module.compiled_module(), imports)?; + let config = store.engine().config(); + let instance_handle = instantiate(config, module.compiled_module(), imports)?; let exports = { let mut exports = Vec::with_capacity(module.exports().len()); diff --git a/crates/environ/src/data_structures.rs b/crates/environ/src/data_structures.rs old mode 100644 new mode 100755 index 6be6c33dab..e12dee3ca7 --- a/crates/environ/src/data_structures.rs +++ b/crates/environ/src/data_structures.rs @@ -17,13 +17,13 @@ pub mod isa { } pub mod entity { - pub use cranelift_entity::{BoxedSlice, EntityRef, PrimaryMap}; + pub use cranelift_entity::{packed_option, BoxedSlice, EntityRef, PrimaryMap}; } pub mod wasm { pub use cranelift_wasm::{ get_vmctx_value_label, DefinedFuncIndex, DefinedGlobalIndex, DefinedMemoryIndex, DefinedTableIndex, FuncIndex, Global, GlobalIndex, GlobalInit, Memory, MemoryIndex, - SignatureIndex, Table, TableElementType, TableIndex, + PassiveElemIndex, SignatureIndex, Table, TableElementType, TableIndex, }; } diff --git a/crates/environ/src/func_environ.rs b/crates/environ/src/func_environ.rs index 737e762a02..f3a11ff735 100644 --- a/crates/environ/src/func_environ.rs +++ b/crates/environ/src/func_environ.rs @@ -1,7 +1,7 @@ use crate::module::{MemoryPlan, MemoryStyle, ModuleLocal, TableStyle}; use crate::vmoffsets::VMOffsets; use crate::WASM_PAGE_SIZE; -use cranelift_codegen::cursor::FuncCursor; +use cranelift_codegen::cursor::{Cursor, FuncCursor}; use cranelift_codegen::ir; use cranelift_codegen::ir::condcodes::*; use cranelift_codegen::ir::immediates::{Offset32, Uimm64}; @@ -62,9 +62,17 @@ impl BuiltinFunctionIndex { pub const fn get_table_copy_imported_imported_index() -> Self { Self(7) } + /// Returns an index for wasm's `table.init`. + pub const fn get_table_init_index() -> Self { + Self(8) + } + /// Returns an index for wasm's `elem.drop`. + pub const fn get_elem_drop_index() -> Self { + Self(9) + } /// Returns the total number of builtin functions. pub const fn builtin_functions_total_number() -> u32 { - 8 + 10 } /// Return the index as an u32 number. @@ -96,6 +104,12 @@ pub struct FuncEnvironment<'module_environment> { /// (it's the same for both local and imported tables). table_copy_sig: Option, + /// The external function signature for implementing wasm's `table.init`. + table_init_sig: Option, + + /// The external function signature for implementing wasm's `elem.drop`. + elem_drop_sig: Option, + /// Offsets to struct fields accessed by JIT code. offsets: VMOffsets, } @@ -112,6 +126,8 @@ impl<'module_environment> FuncEnvironment<'module_environment> { memory32_size_sig: None, memory_grow_sig: None, table_copy_sig: None, + table_init_sig: None, + elem_drop_sig: None, offsets: VMOffsets::new(target_config.pointer_bytes(), module), } } @@ -294,6 +310,67 @@ impl<'module_environment> FuncEnvironment<'module_environment> { } } + fn get_table_init_sig(&mut self, func: &mut Function) -> ir::SigRef { + let sig = self.table_init_sig.unwrap_or_else(|| { + func.import_signature(Signature { + params: vec![ + AbiParam::special(self.pointer_type(), ArgumentPurpose::VMContext), + // Table index. + AbiParam::new(I32), + // Segment index. + AbiParam::new(I32), + // Destination index within table. + AbiParam::new(I32), + // Source index within segment. + AbiParam::new(I32), + // Number of elements to initialize. + AbiParam::new(I32), + // Source location. + AbiParam::new(I32), + ], + returns: vec![], + call_conv: self.target_config.default_call_conv, + }) + }); + self.table_init_sig = Some(sig); + sig + } + + fn get_table_init_func( + &mut self, + func: &mut Function, + table_index: TableIndex, + ) -> (ir::SigRef, usize, BuiltinFunctionIndex) { + let sig = self.get_table_init_sig(func); + let table_index = table_index.as_u32() as usize; + ( + sig, + table_index, + BuiltinFunctionIndex::get_table_init_index(), + ) + } + + fn get_elem_drop_sig(&mut self, func: &mut Function) -> ir::SigRef { + let sig = self.elem_drop_sig.unwrap_or_else(|| { + func.import_signature(Signature { + params: vec![ + AbiParam::special(self.pointer_type(), ArgumentPurpose::VMContext), + // Element index. + AbiParam::new(I32), + ], + returns: vec![], + call_conv: self.target_config.default_call_conv, + }) + }); + self.elem_drop_sig = Some(sig); + sig + } + + fn get_elem_drop_func(&mut self, func: &mut Function) -> (ir::SigRef, BuiltinFunctionIndex) { + let sig = self.get_elem_drop_sig(func); + (sig, BuiltinFunctionIndex::get_elem_drop_index()) + } + /// Translates load of builtin function and returns a pair of values `vmctx` /// and address of the loaded function. fn translate_load_builtin_function_address( @@ -960,8 +1037,6 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m src: ir::Value, len: ir::Value, ) -> WasmResult<()> { - use cranelift_codegen::cursor::Cursor; - let (func_sig, dst_table_index_arg, src_table_index_arg, func_idx) = self.get_table_copy_func(&mut pos.func, dst_table_index, src_table_index); @@ -992,22 +1067,52 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m fn translate_table_init( &mut self, - _pos: FuncCursor, - _seg_index: u32, - _table_index: TableIndex, + mut pos: FuncCursor, + seg_index: u32, + table_index: TableIndex, _table: ir::Table, - _dst: ir::Value, - _src: ir::Value, - _len: ir::Value, + dst: ir::Value, + src: ir::Value, + len: ir::Value, ) -> WasmResult<()> { - Err(WasmError::Unsupported( - "bulk memory: `table.init`".to_string(), - )) + let (func_sig, table_index_arg, func_idx) = + self.get_table_init_func(&mut pos.func, table_index); + + let table_index_arg = pos.ins().iconst(I32, table_index_arg as i64); + let seg_index_arg = pos.ins().iconst(I32, seg_index as i64); + + let src_loc = pos.srcloc(); + let src_loc_arg = pos.ins().iconst(I32, src_loc.bits() as i64); + + let (vmctx, func_addr) = self.translate_load_builtin_function_address(&mut pos, func_idx); + + pos.ins().call_indirect( + func_sig, + func_addr, + &[ + vmctx, + table_index_arg, + seg_index_arg, + dst, + src, + len, + src_loc_arg, + ], + ); + + Ok(()) } - fn translate_elem_drop(&mut self, _pos: FuncCursor, _seg_index: u32) -> WasmResult<()> { - Err(WasmError::Unsupported( - "bulk memory: `elem.drop`".to_string(), - )) + fn translate_elem_drop(&mut self, mut pos: FuncCursor, elem_index: u32) -> WasmResult<()> { + let (func_sig, func_idx) = self.get_elem_drop_func(&mut pos.func); + + let elem_index_arg = pos.ins().iconst(I32, elem_index as i64); + + let (vmctx, func_addr) = self.translate_load_builtin_function_address(&mut pos, func_idx); + + pos.ins() + .call_indirect(func_sig, func_addr, &[vmctx, elem_index_arg]); + + Ok(()) } } diff --git a/crates/environ/src/module.rs b/crates/environ/src/module.rs index 499f470a30..aacc6fe747 100644 --- a/crates/environ/src/module.rs +++ b/crates/environ/src/module.rs @@ -237,6 +237,11 @@ impl Module { }, } } + + /// Get the given passive element, if it exists. + pub fn get_passive_element(&self, index: PassiveElemIndex) -> Option<&[FuncIndex]> { + self.passive_elements.get(&index).map(|es| &**es) + } } impl ModuleLocal { diff --git a/crates/runtime/src/instance.rs b/crates/runtime/src/instance.rs index c8e4dac5b7..4ae120d8f1 100644 --- a/crates/runtime/src/instance.rs +++ b/crates/runtime/src/instance.rs @@ -14,24 +14,25 @@ use crate::vmcontext::{ VMGlobalDefinition, VMGlobalImport, VMMemoryDefinition, VMMemoryImport, VMSharedSignatureIndex, VMTableDefinition, VMTableImport, }; -use crate::TrapRegistration; +use crate::{TrapDescription, TrapRegistration}; use memoffset::offset_of; use more_asserts::assert_lt; use std::alloc::{self, Layout}; use std::any::Any; -use std::cell::Cell; +use std::cell::{Cell, RefCell}; +use std::collections::HashMap; use std::collections::HashSet; use std::convert::TryFrom; use std::rc::Rc; use std::sync::Arc; use std::{mem, ptr, slice}; use thiserror::Error; -use wasmtime_environ::entity::{BoxedSlice, EntityRef, PrimaryMap}; +use wasmtime_environ::entity::{packed_option::ReservedValue, BoxedSlice, EntityRef, PrimaryMap}; use wasmtime_environ::wasm::{ DefinedFuncIndex, DefinedGlobalIndex, DefinedMemoryIndex, DefinedTableIndex, FuncIndex, - GlobalIndex, GlobalInit, MemoryIndex, SignatureIndex, TableIndex, + GlobalIndex, GlobalInit, MemoryIndex, PassiveElemIndex, SignatureIndex, TableIndex, }; -use wasmtime_environ::{DataInitializer, Module, TableElements, VMOffsets}; +use wasmtime_environ::{ir, DataInitializer, Module, TableElements, VMOffsets}; cfg_if::cfg_if! { if #[cfg(unix)] { @@ -86,6 +87,10 @@ pub(crate) struct Instance { /// WebAssembly table data. tables: BoxedSlice, + /// Passive elements in this instantiation. As `elem.drop`s happen, these + /// entries get replaced into empty slices. + passive_elements: RefCell>>, + /// Pointers to functions in executable memory. finished_functions: BoxedSlice, @@ -124,6 +129,14 @@ impl Instance { unsafe { *self.signature_ids_ptr().add(index) } } + pub(crate) fn module(&self) -> &Arc { + &self.module + } + + pub(crate) fn module_ref(&self) -> &Module { + &*self.module + } + /// Return a pointer to the `VMSharedSignatureIndex`s. fn signature_ids_ptr(&self) -> *mut VMSharedSignatureIndex { unsafe { self.vmctx_plus_offset(self.offsets.vmctx_signature_ids_begin()) } @@ -527,6 +540,91 @@ impl Instance { Layout::from_size_align(size, align).unwrap() } + /// Get a `VMCallerCheckedAnyfunc` for the given `FuncIndex`. + fn get_caller_checked_anyfunc(&self, index: FuncIndex) -> VMCallerCheckedAnyfunc { + if index == FuncIndex::reserved_value() { + return VMCallerCheckedAnyfunc::default(); + } + + let sig = self.module.local.functions[index]; + let type_index = self.signature_id(sig); + + let (func_ptr, vmctx) = if let Some(def_index) = self.module.local.defined_func_index(index) + { + ( + self.finished_functions[def_index] as *const _, + self.vmctx_ptr(), + ) + } else { + let import = self.imported_function(index); + (import.body, import.vmctx) + }; + VMCallerCheckedAnyfunc { + func_ptr, + type_index, + vmctx, + } + } + + /// The `table.init` operation: initializes a portion of a table with a + /// passive element. + /// + /// # Errors + /// + /// Returns a `Trap` error when the range within the table is out of bounds + /// or the range within the passive element is out of bounds. + pub(crate) fn table_init( + &self, + table_index: TableIndex, + elem_index: PassiveElemIndex, + dst: u32, + src: u32, + len: u32, + source_loc: ir::SourceLoc, + ) -> Result<(), Trap> { + // https://webassembly.github.io/bulk-memory-operations/core/exec/instructions.html#exec-table-init + + let table = self.get_table(table_index); + let passive_elements = self.passive_elements.borrow(); + let elem = passive_elements + .get(&elem_index) + .map(|e| &**e) + .unwrap_or_else(|| &[]); + + if src + .checked_add(len) + .map_or(true, |n| n as usize > elem.len()) + || dst.checked_add(len).map_or(true, |m| m > table.size()) + { + return Err(Trap::Wasm { + desc: TrapDescription { + source_loc, + trap_code: ir::TrapCode::TableOutOfBounds, + }, + backtrace: backtrace::Backtrace::new(), + }); + } + + for (dst, src) in (dst..dst + len).zip(src..src + len) { + table + .set(dst, elem[src as usize].clone()) + .expect("should never panic because we already did the bounds check above"); + } + + Ok(()) + } + + /// Drop an element. + pub(crate) fn elem_drop(&self, elem_index: PassiveElemIndex) { + // https://webassembly.github.io/reference-types/core/exec/instructions.html#exec-elem-drop + + let mut passive_elements = self.passive_elements.borrow_mut(); + // Note that dropping a non-passive element is a no-op (not a trap). + if let Some(elem) = passive_elements.get_mut(&elem_index) { + mem::replace(elem, vec![].into_boxed_slice()); + } + } + /// Get a table by index regardless of whether it is locally-defined or an /// imported, foreign table. pub(crate) fn get_table(&self, table_index: TableIndex) -> &Table { @@ -611,6 +709,7 @@ impl InstanceHandle { offsets, memories, tables, + passive_elements: Default::default(), finished_functions, dbg_jit_registration, host_state, @@ -681,6 +780,7 @@ impl InstanceHandle { // Apply the initializers. initialize_tables(instance)?; + initialize_passive_elements(instance); initialize_memories(instance, data_initializers)?; initialize_globals(instance); @@ -721,12 +821,12 @@ impl InstanceHandle { /// Return a reference-counting pointer to a module. pub fn module(&self) -> &Arc { - &self.instance().module + self.instance().module() } /// Return a reference to a module. pub fn module_ref(&self) -> &Module { - &self.instance().module + self.instance().module_ref() } /// Lookup an export with the given name. @@ -846,9 +946,9 @@ fn check_table_init_bounds(instance: &Instance) -> Result<(), InstantiationError let size = usize::try_from(table.size()).unwrap(); if size < start + init.elements.len() { - return Err(InstantiationError::Link(LinkError( + return Err(InstantiationError::TableOutOfBounds( "elements segment does not fit".to_owned(), - ))); + )); } } @@ -944,31 +1044,15 @@ fn get_table_init_start(init: &TableElements, instance: &Instance) -> usize { /// Initialize the table memory from the provided initializers. fn initialize_tables(instance: &Instance) -> Result<(), InstantiationError> { - let vmctx = instance.vmctx_ptr(); let module = Arc::clone(&instance.module); for init in &module.table_elements { let start = get_table_init_start(init, instance); let table = instance.get_table(init.table_index); for (i, func_idx) in init.elements.iter().enumerate() { - let callee_sig = instance.module.local.functions[*func_idx]; - let (callee_ptr, callee_vmctx) = - if let Some(index) = instance.module.local.defined_func_index(*func_idx) { - (instance.finished_functions[index] as *const _, vmctx) - } else { - let imported_func = instance.imported_function(*func_idx); - (imported_func.body, imported_func.vmctx) - }; - let type_index = instance.signature_id(callee_sig); + let anyfunc = instance.get_caller_checked_anyfunc(*func_idx); table - .set( - u32::try_from(start + i).unwrap(), - VMCallerCheckedAnyfunc { - func_ptr: callee_ptr, - type_index, - vmctx: callee_vmctx, - }, - ) + .set(u32::try_from(start + i).unwrap(), anyfunc) .unwrap(); } } @@ -976,6 +1060,33 @@ fn initialize_tables(instance: &Instance) -> Result<(), InstantiationError> { Ok(()) } +/// Initialize the `Instance::passive_elements` map by resolving the +/// `Module::passive_elements`'s `FuncIndex`s into `VMCallerCheckedAnyfunc`s for +/// this instance. +fn initialize_passive_elements(instance: &Instance) { + let mut passive_elements = instance.passive_elements.borrow_mut(); + debug_assert!( + passive_elements.is_empty(), + "should only be called once, at initialization time" + ); + + passive_elements.extend( + instance + .module + .passive_elements + .iter() + .map(|(idx, segments)| { + ( + *idx, + segments + .iter() + .map(|s| instance.get_caller_checked_anyfunc(*s)) + .collect(), + ) + }), + ); +} + /// Allocate memory for just the memories of the current module. fn create_memories( module: &Module, @@ -1059,6 +1170,13 @@ pub enum InstantiationError { #[error("Insufficient resources: {0}")] Resource(String), + /// A table out of bounds error. + /// + /// Depending on whether bulk memory is enabled or not, this is either a + /// link error or a trap raised during instantiation. + #[error("Table out of bounds error: {0}")] + TableOutOfBounds(String), + /// A wasm link error occured. #[error("Failed to link module")] Link(#[from] LinkError), diff --git a/crates/runtime/src/libcalls.rs b/crates/runtime/src/libcalls.rs index aa4b04f448..3e4c0f93e9 100644 --- a/crates/runtime/src/libcalls.rs +++ b/crates/runtime/src/libcalls.rs @@ -6,7 +6,9 @@ use crate::table::Table; use crate::traphandlers::raise_lib_trap; use crate::vmcontext::VMContext; use wasmtime_environ::ir; -use wasmtime_environ::wasm::{DefinedMemoryIndex, DefinedTableIndex, MemoryIndex, TableIndex}; +use wasmtime_environ::wasm::{ + DefinedMemoryIndex, DefinedTableIndex, MemoryIndex, PassiveElemIndex, TableIndex, +}; /// Implementation of f32.ceil pub extern "C" fn wasmtime_f32_ceil(x: f32) -> f32 { @@ -230,3 +232,33 @@ pub unsafe extern "C" fn wasmtime_table_copy_imported_imported( raise_lib_trap(trap); } } + +/// Implementation of `table.init`. +#[no_mangle] +pub unsafe extern "C" fn wasmtime_table_init( + vmctx: *mut VMContext, + table_index: u32, + elem_index: u32, + dst: u32, + src: u32, + len: u32, + source_loc: u32, +) { + let table_index = TableIndex::from_u32(table_index); + let source_loc = ir::SourceLoc::new(source_loc); + let elem_index = PassiveElemIndex::from_u32(elem_index); + + let instance = (&mut *vmctx).instance(); + + if let Err(trap) = instance.table_init(table_index, elem_index, dst, src, len, source_loc) { + raise_lib_trap(trap); + } +} + +/// Implementation of `elem.drop`. +#[no_mangle] +pub unsafe extern "C" fn wasmtime_elem_drop(vmctx: *mut VMContext, elem_index: u32) { + let elem_index = PassiveElemIndex::from_u32(elem_index); + let instance = (&mut *vmctx).instance(); + instance.elem_drop(elem_index); +} diff --git a/crates/runtime/src/vmcontext.rs b/crates/runtime/src/vmcontext.rs index d656e68602..613cf9af99 100644 --- a/crates/runtime/src/vmcontext.rs +++ b/crates/runtime/src/vmcontext.rs @@ -536,12 +536,14 @@ impl VMBuiltinFunctionsArray { pub fn initialized() -> Self { use crate::libcalls::*; + let mut ptrs = [0; Self::len()]; ptrs[BuiltinFunctionIndex::get_memory32_grow_index().index() as usize] = wasmtime_memory32_grow as usize; ptrs[BuiltinFunctionIndex::get_imported_memory32_grow_index().index() as usize] = wasmtime_imported_memory32_grow as usize; + ptrs[BuiltinFunctionIndex::get_memory32_size_index().index() as usize] = wasmtime_memory32_size as usize; ptrs[BuiltinFunctionIndex::get_imported_memory32_size_index().index() as usize] = @@ -549,16 +551,18 @@ impl VMBuiltinFunctionsArray { ptrs[BuiltinFunctionIndex::get_table_copy_defined_defined_index().index() as usize] = wasmtime_table_copy_defined_defined as usize; - ptrs[BuiltinFunctionIndex::get_table_copy_defined_imported_index().index() as usize] = wasmtime_table_copy_defined_imported as usize; - ptrs[BuiltinFunctionIndex::get_table_copy_imported_defined_index().index() as usize] = wasmtime_table_copy_imported_defined as usize; - ptrs[BuiltinFunctionIndex::get_table_copy_imported_imported_index().index() as usize] = wasmtime_table_copy_imported_imported as usize; + ptrs[BuiltinFunctionIndex::get_table_init_index().index() as usize] = + wasmtime_table_init as usize; + ptrs[BuiltinFunctionIndex::get_elem_drop_index().index() as usize] = + wasmtime_elem_drop as usize; + debug_assert!(ptrs.iter().cloned().all(|p| p != 0)); Self { ptrs } diff --git a/crates/wast/src/wast.rs b/crates/wast/src/wast.rs index 621177b0da..4d6ac4f4e5 100644 --- a/crates/wast/src/wast.rs +++ b/crates/wast/src/wast.rs @@ -287,7 +287,7 @@ impl WastContext { Err(e) => e, }; let error_message = format!("{:?}", err); - if !error_message.contains(&message) { + if !is_matching_assert_invalid_error_message(&message, &error_message) { bail!( "assert_invalid: expected \"{}\", got \"{}\"", message, @@ -343,6 +343,16 @@ impl WastContext { } } +fn is_matching_assert_invalid_error_message(expected: &str, actual: &str) -> bool { + actual.contains(expected) + // Waiting on https://github.com/WebAssembly/bulk-memory-operations/pull/137 + // to propagate to WebAssembly/testsuite. + || (expected.contains("unknown table") && actual.contains("unknown elem")) + // `elem.wast` and `proposals/bulk-memory-operations/elem.wast` disagree + // on the expected error message for the same error. + || (expected.contains("out of bounds") && actual.contains("does not fit")) +} + fn extract_lane_as_i8(bytes: u128, lane: usize) -> i8 { (bytes >> (lane * 8)) as i8 } diff --git a/tests/misc_testsuite/elem-ref-null.wast b/tests/misc_testsuite/elem-ref-null.wast new file mode 100644 index 0000000000..c904c332dd --- /dev/null +++ b/tests/misc_testsuite/elem-ref-null.wast @@ -0,0 +1,2 @@ +(module + (elem funcref (ref.null))) diff --git a/tests/misc_testsuite/elem_drop.wast b/tests/misc_testsuite/elem_drop.wast new file mode 100644 index 0000000000..a95804b594 --- /dev/null +++ b/tests/misc_testsuite/elem_drop.wast @@ -0,0 +1,7 @@ +(module + (table 1 1 funcref) + (elem (i32.const 0) funcref (ref.func 0)) + (func (export "elem.drop non-passive element") + (elem.drop 0))) + +(invoke "elem.drop non-passive element") diff --git a/tests/wast_testsuites.rs b/tests/wast_testsuites.rs index a1ce8d8454..cb7599bc94 100644 --- a/tests/wast_testsuites.rs +++ b/tests/wast_testsuites.rs @@ -20,6 +20,8 @@ fn run_wast(wast: &str, strategy: Strategy) -> anyhow::Result<()> { let bulk_mem = reftypes || wast.iter().any(|s| s == "bulk-memory-operations") || wast.iter().any(|s| s == "table_copy.wast") + || wast.iter().any(|s| s == "elem_drop.wast") + || wast.iter().any(|s| s == "elem-ref-null.wast") || wast .iter() .any(|s| s == "table_copy_on_imported_tables.wast");