Implement table.init and elem.drop from the bulk memory proposal
This commit is contained in:
4
build.rs
4
build.rs
@@ -182,7 +182,9 @@ fn ignore(testsuite: &str, testname: &str, strategy: &str) -> bool {
|
|||||||
|
|
||||||
("reference_types", _) => return true,
|
("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,
|
("bulk_memory_operations", _) => return true,
|
||||||
|
|
||||||
_ => {}
|
_ => {}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
use crate::externals::Extern;
|
use crate::externals::Extern;
|
||||||
use crate::module::Module;
|
use crate::module::Module;
|
||||||
use crate::runtime::Store;
|
use crate::runtime::{Config, Store};
|
||||||
use crate::trap::Trap;
|
use crate::trap::Trap;
|
||||||
use anyhow::{Error, Result};
|
use anyhow::{Error, Result};
|
||||||
use wasmtime_jit::{CompiledModule, Resolver};
|
use wasmtime_jit::{CompiledModule, Resolver};
|
||||||
use wasmtime_runtime::{Export, InstanceHandle, InstantiationError};
|
use wasmtime_runtime::{Export, InstanceHandle, InstantiationError, LinkError};
|
||||||
|
|
||||||
struct SimpleResolver<'a> {
|
struct SimpleResolver<'a> {
|
||||||
imports: &'a [Extern],
|
imports: &'a [Extern],
|
||||||
@@ -19,6 +19,7 @@ impl Resolver for SimpleResolver<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn instantiate(
|
fn instantiate(
|
||||||
|
config: &Config,
|
||||||
compiled_module: &CompiledModule,
|
compiled_module: &CompiledModule,
|
||||||
imports: &[Extern],
|
imports: &[Extern],
|
||||||
) -> Result<InstanceHandle, Error> {
|
) -> Result<InstanceHandle, Error> {
|
||||||
@@ -29,6 +30,14 @@ fn instantiate(
|
|||||||
.map_err(|e| -> Error {
|
.map_err(|e| -> Error {
|
||||||
match e {
|
match e {
|
||||||
InstantiationError::StartTrap(trap) => Trap::from_jit(trap).into(),
|
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(),
|
other => other.into(),
|
||||||
}
|
}
|
||||||
})?;
|
})?;
|
||||||
@@ -105,7 +114,8 @@ impl Instance {
|
|||||||
/// [issue]: https://github.com/bytecodealliance/wasmtime/issues/727
|
/// [issue]: https://github.com/bytecodealliance/wasmtime/issues/727
|
||||||
pub fn new(module: &Module, imports: &[Extern]) -> Result<Instance, Error> {
|
pub fn new(module: &Module, imports: &[Extern]) -> Result<Instance, Error> {
|
||||||
let store = module.store();
|
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 exports = {
|
||||||
let mut exports = Vec::with_capacity(module.exports().len());
|
let mut exports = Vec::with_capacity(module.exports().len());
|
||||||
|
|||||||
4
crates/environ/src/data_structures.rs
Normal file → Executable file
4
crates/environ/src/data_structures.rs
Normal file → Executable file
@@ -17,13 +17,13 @@ pub mod isa {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub mod entity {
|
pub mod entity {
|
||||||
pub use cranelift_entity::{BoxedSlice, EntityRef, PrimaryMap};
|
pub use cranelift_entity::{packed_option, BoxedSlice, EntityRef, PrimaryMap};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod wasm {
|
pub mod wasm {
|
||||||
pub use cranelift_wasm::{
|
pub use cranelift_wasm::{
|
||||||
get_vmctx_value_label, DefinedFuncIndex, DefinedGlobalIndex, DefinedMemoryIndex,
|
get_vmctx_value_label, DefinedFuncIndex, DefinedGlobalIndex, DefinedMemoryIndex,
|
||||||
DefinedTableIndex, FuncIndex, Global, GlobalIndex, GlobalInit, Memory, MemoryIndex,
|
DefinedTableIndex, FuncIndex, Global, GlobalIndex, GlobalInit, Memory, MemoryIndex,
|
||||||
SignatureIndex, Table, TableElementType, TableIndex,
|
PassiveElemIndex, SignatureIndex, Table, TableElementType, TableIndex,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use crate::module::{MemoryPlan, MemoryStyle, ModuleLocal, TableStyle};
|
use crate::module::{MemoryPlan, MemoryStyle, ModuleLocal, TableStyle};
|
||||||
use crate::vmoffsets::VMOffsets;
|
use crate::vmoffsets::VMOffsets;
|
||||||
use crate::WASM_PAGE_SIZE;
|
use crate::WASM_PAGE_SIZE;
|
||||||
use cranelift_codegen::cursor::FuncCursor;
|
use cranelift_codegen::cursor::{Cursor, FuncCursor};
|
||||||
use cranelift_codegen::ir;
|
use cranelift_codegen::ir;
|
||||||
use cranelift_codegen::ir::condcodes::*;
|
use cranelift_codegen::ir::condcodes::*;
|
||||||
use cranelift_codegen::ir::immediates::{Offset32, Uimm64};
|
use cranelift_codegen::ir::immediates::{Offset32, Uimm64};
|
||||||
@@ -62,9 +62,17 @@ impl BuiltinFunctionIndex {
|
|||||||
pub const fn get_table_copy_imported_imported_index() -> Self {
|
pub const fn get_table_copy_imported_imported_index() -> Self {
|
||||||
Self(7)
|
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.
|
/// Returns the total number of builtin functions.
|
||||||
pub const fn builtin_functions_total_number() -> u32 {
|
pub const fn builtin_functions_total_number() -> u32 {
|
||||||
8
|
10
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the index as an u32 number.
|
/// 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).
|
/// (it's the same for both local and imported tables).
|
||||||
table_copy_sig: Option<ir::SigRef>,
|
table_copy_sig: Option<ir::SigRef>,
|
||||||
|
|
||||||
|
/// The external function signature for implementing wasm's `table.init`.
|
||||||
|
table_init_sig: Option<ir::SigRef>,
|
||||||
|
|
||||||
|
/// The external function signature for implementing wasm's `elem.drop`.
|
||||||
|
elem_drop_sig: Option<ir::SigRef>,
|
||||||
|
|
||||||
/// Offsets to struct fields accessed by JIT code.
|
/// Offsets to struct fields accessed by JIT code.
|
||||||
offsets: VMOffsets,
|
offsets: VMOffsets,
|
||||||
}
|
}
|
||||||
@@ -112,6 +126,8 @@ impl<'module_environment> FuncEnvironment<'module_environment> {
|
|||||||
memory32_size_sig: None,
|
memory32_size_sig: None,
|
||||||
memory_grow_sig: None,
|
memory_grow_sig: None,
|
||||||
table_copy_sig: None,
|
table_copy_sig: None,
|
||||||
|
table_init_sig: None,
|
||||||
|
elem_drop_sig: None,
|
||||||
offsets: VMOffsets::new(target_config.pointer_bytes(), module),
|
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`
|
/// Translates load of builtin function and returns a pair of values `vmctx`
|
||||||
/// and address of the loaded function.
|
/// and address of the loaded function.
|
||||||
fn translate_load_builtin_function_address(
|
fn translate_load_builtin_function_address(
|
||||||
@@ -960,8 +1037,6 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m
|
|||||||
src: ir::Value,
|
src: ir::Value,
|
||||||
len: ir::Value,
|
len: ir::Value,
|
||||||
) -> WasmResult<()> {
|
) -> WasmResult<()> {
|
||||||
use cranelift_codegen::cursor::Cursor;
|
|
||||||
|
|
||||||
let (func_sig, dst_table_index_arg, src_table_index_arg, func_idx) =
|
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);
|
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(
|
fn translate_table_init(
|
||||||
&mut self,
|
&mut self,
|
||||||
_pos: FuncCursor,
|
mut pos: FuncCursor,
|
||||||
_seg_index: u32,
|
seg_index: u32,
|
||||||
_table_index: TableIndex,
|
table_index: TableIndex,
|
||||||
_table: ir::Table,
|
_table: ir::Table,
|
||||||
_dst: ir::Value,
|
dst: ir::Value,
|
||||||
_src: ir::Value,
|
src: ir::Value,
|
||||||
_len: ir::Value,
|
len: ir::Value,
|
||||||
) -> WasmResult<()> {
|
) -> WasmResult<()> {
|
||||||
Err(WasmError::Unsupported(
|
let (func_sig, table_index_arg, func_idx) =
|
||||||
"bulk memory: `table.init`".to_string(),
|
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<()> {
|
fn translate_elem_drop(&mut self, mut pos: FuncCursor, elem_index: u32) -> WasmResult<()> {
|
||||||
Err(WasmError::Unsupported(
|
let (func_sig, func_idx) = self.get_elem_drop_func(&mut pos.func);
|
||||||
"bulk memory: `elem.drop`".to_string(),
|
|
||||||
))
|
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(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
impl ModuleLocal {
|
||||||
|
|||||||
@@ -14,24 +14,25 @@ use crate::vmcontext::{
|
|||||||
VMGlobalDefinition, VMGlobalImport, VMMemoryDefinition, VMMemoryImport, VMSharedSignatureIndex,
|
VMGlobalDefinition, VMGlobalImport, VMMemoryDefinition, VMMemoryImport, VMSharedSignatureIndex,
|
||||||
VMTableDefinition, VMTableImport,
|
VMTableDefinition, VMTableImport,
|
||||||
};
|
};
|
||||||
use crate::TrapRegistration;
|
use crate::{TrapDescription, TrapRegistration};
|
||||||
use memoffset::offset_of;
|
use memoffset::offset_of;
|
||||||
use more_asserts::assert_lt;
|
use more_asserts::assert_lt;
|
||||||
use std::alloc::{self, Layout};
|
use std::alloc::{self, Layout};
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
use std::cell::Cell;
|
use std::cell::{Cell, RefCell};
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::{mem, ptr, slice};
|
use std::{mem, ptr, slice};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use wasmtime_environ::entity::{BoxedSlice, EntityRef, PrimaryMap};
|
use wasmtime_environ::entity::{packed_option::ReservedValue, BoxedSlice, EntityRef, PrimaryMap};
|
||||||
use wasmtime_environ::wasm::{
|
use wasmtime_environ::wasm::{
|
||||||
DefinedFuncIndex, DefinedGlobalIndex, DefinedMemoryIndex, DefinedTableIndex, FuncIndex,
|
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! {
|
cfg_if::cfg_if! {
|
||||||
if #[cfg(unix)] {
|
if #[cfg(unix)] {
|
||||||
@@ -86,6 +87,10 @@ pub(crate) struct Instance {
|
|||||||
/// WebAssembly table data.
|
/// WebAssembly table data.
|
||||||
tables: BoxedSlice<DefinedTableIndex, Table>,
|
tables: BoxedSlice<DefinedTableIndex, Table>,
|
||||||
|
|
||||||
|
/// Passive elements in this instantiation. As `elem.drop`s happen, these
|
||||||
|
/// entries get replaced into empty slices.
|
||||||
|
passive_elements: RefCell<HashMap<PassiveElemIndex, Box<[VMCallerCheckedAnyfunc]>>>,
|
||||||
|
|
||||||
/// Pointers to functions in executable memory.
|
/// Pointers to functions in executable memory.
|
||||||
finished_functions: BoxedSlice<DefinedFuncIndex, *mut [VMFunctionBody]>,
|
finished_functions: BoxedSlice<DefinedFuncIndex, *mut [VMFunctionBody]>,
|
||||||
|
|
||||||
@@ -124,6 +129,14 @@ impl Instance {
|
|||||||
unsafe { *self.signature_ids_ptr().add(index) }
|
unsafe { *self.signature_ids_ptr().add(index) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn module(&self) -> &Arc<Module> {
|
||||||
|
&self.module
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn module_ref(&self) -> &Module {
|
||||||
|
&*self.module
|
||||||
|
}
|
||||||
|
|
||||||
/// Return a pointer to the `VMSharedSignatureIndex`s.
|
/// Return a pointer to the `VMSharedSignatureIndex`s.
|
||||||
fn signature_ids_ptr(&self) -> *mut VMSharedSignatureIndex {
|
fn signature_ids_ptr(&self) -> *mut VMSharedSignatureIndex {
|
||||||
unsafe { self.vmctx_plus_offset(self.offsets.vmctx_signature_ids_begin()) }
|
unsafe { self.vmctx_plus_offset(self.offsets.vmctx_signature_ids_begin()) }
|
||||||
@@ -527,6 +540,91 @@ impl Instance {
|
|||||||
Layout::from_size_align(size, align).unwrap()
|
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
|
/// Get a table by index regardless of whether it is locally-defined or an
|
||||||
/// imported, foreign table.
|
/// imported, foreign table.
|
||||||
pub(crate) fn get_table(&self, table_index: TableIndex) -> &Table {
|
pub(crate) fn get_table(&self, table_index: TableIndex) -> &Table {
|
||||||
@@ -611,6 +709,7 @@ impl InstanceHandle {
|
|||||||
offsets,
|
offsets,
|
||||||
memories,
|
memories,
|
||||||
tables,
|
tables,
|
||||||
|
passive_elements: Default::default(),
|
||||||
finished_functions,
|
finished_functions,
|
||||||
dbg_jit_registration,
|
dbg_jit_registration,
|
||||||
host_state,
|
host_state,
|
||||||
@@ -681,6 +780,7 @@ impl InstanceHandle {
|
|||||||
|
|
||||||
// Apply the initializers.
|
// Apply the initializers.
|
||||||
initialize_tables(instance)?;
|
initialize_tables(instance)?;
|
||||||
|
initialize_passive_elements(instance);
|
||||||
initialize_memories(instance, data_initializers)?;
|
initialize_memories(instance, data_initializers)?;
|
||||||
initialize_globals(instance);
|
initialize_globals(instance);
|
||||||
|
|
||||||
@@ -721,12 +821,12 @@ impl InstanceHandle {
|
|||||||
|
|
||||||
/// Return a reference-counting pointer to a module.
|
/// Return a reference-counting pointer to a module.
|
||||||
pub fn module(&self) -> &Arc<Module> {
|
pub fn module(&self) -> &Arc<Module> {
|
||||||
&self.instance().module
|
self.instance().module()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return a reference to a module.
|
/// Return a reference to a module.
|
||||||
pub fn module_ref(&self) -> &Module {
|
pub fn module_ref(&self) -> &Module {
|
||||||
&self.instance().module
|
self.instance().module_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Lookup an export with the given name.
|
/// 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();
|
let size = usize::try_from(table.size()).unwrap();
|
||||||
if size < start + init.elements.len() {
|
if size < start + init.elements.len() {
|
||||||
return Err(InstantiationError::Link(LinkError(
|
return Err(InstantiationError::TableOutOfBounds(
|
||||||
"elements segment does not fit".to_owned(),
|
"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.
|
/// Initialize the table memory from the provided initializers.
|
||||||
fn initialize_tables(instance: &Instance) -> Result<(), InstantiationError> {
|
fn initialize_tables(instance: &Instance) -> Result<(), InstantiationError> {
|
||||||
let vmctx = instance.vmctx_ptr();
|
|
||||||
let module = Arc::clone(&instance.module);
|
let module = Arc::clone(&instance.module);
|
||||||
for init in &module.table_elements {
|
for init in &module.table_elements {
|
||||||
let start = get_table_init_start(init, instance);
|
let start = get_table_init_start(init, instance);
|
||||||
let table = instance.get_table(init.table_index);
|
let table = instance.get_table(init.table_index);
|
||||||
|
|
||||||
for (i, func_idx) in init.elements.iter().enumerate() {
|
for (i, func_idx) in init.elements.iter().enumerate() {
|
||||||
let callee_sig = instance.module.local.functions[*func_idx];
|
let anyfunc = instance.get_caller_checked_anyfunc(*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);
|
|
||||||
table
|
table
|
||||||
.set(
|
.set(u32::try_from(start + i).unwrap(), anyfunc)
|
||||||
u32::try_from(start + i).unwrap(),
|
|
||||||
VMCallerCheckedAnyfunc {
|
|
||||||
func_ptr: callee_ptr,
|
|
||||||
type_index,
|
|
||||||
vmctx: callee_vmctx,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -976,6 +1060,33 @@ fn initialize_tables(instance: &Instance) -> Result<(), InstantiationError> {
|
|||||||
Ok(())
|
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.
|
/// Allocate memory for just the memories of the current module.
|
||||||
fn create_memories(
|
fn create_memories(
|
||||||
module: &Module,
|
module: &Module,
|
||||||
@@ -1059,6 +1170,13 @@ pub enum InstantiationError {
|
|||||||
#[error("Insufficient resources: {0}")]
|
#[error("Insufficient resources: {0}")]
|
||||||
Resource(String),
|
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.
|
/// A wasm link error occured.
|
||||||
#[error("Failed to link module")]
|
#[error("Failed to link module")]
|
||||||
Link(#[from] LinkError),
|
Link(#[from] LinkError),
|
||||||
|
|||||||
@@ -6,7 +6,9 @@ use crate::table::Table;
|
|||||||
use crate::traphandlers::raise_lib_trap;
|
use crate::traphandlers::raise_lib_trap;
|
||||||
use crate::vmcontext::VMContext;
|
use crate::vmcontext::VMContext;
|
||||||
use wasmtime_environ::ir;
|
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
|
/// Implementation of f32.ceil
|
||||||
pub extern "C" fn wasmtime_f32_ceil(x: f32) -> f32 {
|
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);
|
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);
|
||||||
|
}
|
||||||
|
|||||||
@@ -536,12 +536,14 @@ impl VMBuiltinFunctionsArray {
|
|||||||
|
|
||||||
pub fn initialized() -> Self {
|
pub fn initialized() -> Self {
|
||||||
use crate::libcalls::*;
|
use crate::libcalls::*;
|
||||||
|
|
||||||
let mut ptrs = [0; Self::len()];
|
let mut ptrs = [0; Self::len()];
|
||||||
|
|
||||||
ptrs[BuiltinFunctionIndex::get_memory32_grow_index().index() as usize] =
|
ptrs[BuiltinFunctionIndex::get_memory32_grow_index().index() as usize] =
|
||||||
wasmtime_memory32_grow as usize;
|
wasmtime_memory32_grow as usize;
|
||||||
ptrs[BuiltinFunctionIndex::get_imported_memory32_grow_index().index() as usize] =
|
ptrs[BuiltinFunctionIndex::get_imported_memory32_grow_index().index() as usize] =
|
||||||
wasmtime_imported_memory32_grow as usize;
|
wasmtime_imported_memory32_grow as usize;
|
||||||
|
|
||||||
ptrs[BuiltinFunctionIndex::get_memory32_size_index().index() as usize] =
|
ptrs[BuiltinFunctionIndex::get_memory32_size_index().index() as usize] =
|
||||||
wasmtime_memory32_size as usize;
|
wasmtime_memory32_size as usize;
|
||||||
ptrs[BuiltinFunctionIndex::get_imported_memory32_size_index().index() 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] =
|
ptrs[BuiltinFunctionIndex::get_table_copy_defined_defined_index().index() as usize] =
|
||||||
wasmtime_table_copy_defined_defined as usize;
|
wasmtime_table_copy_defined_defined as usize;
|
||||||
|
|
||||||
ptrs[BuiltinFunctionIndex::get_table_copy_defined_imported_index().index() as usize] =
|
ptrs[BuiltinFunctionIndex::get_table_copy_defined_imported_index().index() as usize] =
|
||||||
wasmtime_table_copy_defined_imported as usize;
|
wasmtime_table_copy_defined_imported as usize;
|
||||||
|
|
||||||
ptrs[BuiltinFunctionIndex::get_table_copy_imported_defined_index().index() as usize] =
|
ptrs[BuiltinFunctionIndex::get_table_copy_imported_defined_index().index() as usize] =
|
||||||
wasmtime_table_copy_imported_defined as usize;
|
wasmtime_table_copy_imported_defined as usize;
|
||||||
|
|
||||||
ptrs[BuiltinFunctionIndex::get_table_copy_imported_imported_index().index() as usize] =
|
ptrs[BuiltinFunctionIndex::get_table_copy_imported_imported_index().index() as usize] =
|
||||||
wasmtime_table_copy_imported_imported 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));
|
debug_assert!(ptrs.iter().cloned().all(|p| p != 0));
|
||||||
|
|
||||||
Self { ptrs }
|
Self { ptrs }
|
||||||
|
|||||||
@@ -287,7 +287,7 @@ impl WastContext {
|
|||||||
Err(e) => e,
|
Err(e) => e,
|
||||||
};
|
};
|
||||||
let error_message = format!("{:?}", err);
|
let error_message = format!("{:?}", err);
|
||||||
if !error_message.contains(&message) {
|
if !is_matching_assert_invalid_error_message(&message, &error_message) {
|
||||||
bail!(
|
bail!(
|
||||||
"assert_invalid: expected \"{}\", got \"{}\"",
|
"assert_invalid: expected \"{}\", got \"{}\"",
|
||||||
message,
|
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 {
|
fn extract_lane_as_i8(bytes: u128, lane: usize) -> i8 {
|
||||||
(bytes >> (lane * 8)) as i8
|
(bytes >> (lane * 8)) as i8
|
||||||
}
|
}
|
||||||
|
|||||||
2
tests/misc_testsuite/elem-ref-null.wast
Normal file
2
tests/misc_testsuite/elem-ref-null.wast
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
(module
|
||||||
|
(elem funcref (ref.null)))
|
||||||
7
tests/misc_testsuite/elem_drop.wast
Normal file
7
tests/misc_testsuite/elem_drop.wast
Normal file
@@ -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")
|
||||||
@@ -20,6 +20,8 @@ fn run_wast(wast: &str, strategy: Strategy) -> anyhow::Result<()> {
|
|||||||
let bulk_mem = reftypes
|
let bulk_mem = reftypes
|
||||||
|| wast.iter().any(|s| s == "bulk-memory-operations")
|
|| wast.iter().any(|s| s == "bulk-memory-operations")
|
||||||
|| wast.iter().any(|s| s == "table_copy.wast")
|
|| 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
|
|| wast
|
||||||
.iter()
|
.iter()
|
||||||
.any(|s| s == "table_copy_on_imported_tables.wast");
|
.any(|s| s == "table_copy_on_imported_tables.wast");
|
||||||
|
|||||||
Reference in New Issue
Block a user