Add support for table.copy

This adds support for the `table.copy` instruction from the bulk memory
proposal. It also supports multiple tables, which were introduced by the
reference types proposal.

Part of #928
This commit is contained in:
Nick Fitzgerald
2020-02-07 14:05:33 -08:00
parent 6d01fd4103
commit 33b4a37bcb
15 changed files with 696 additions and 44 deletions

View File

@@ -526,6 +526,30 @@ impl Instance {
let align = mem::align_of_val(self);
Layout::from_size_align(size, align).unwrap()
}
/// 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 {
if let Some(defined_table_index) = self.module.local.defined_table_index(table_index) {
&self.tables[defined_table_index]
} else {
self.get_foreign_table(table_index)
}
}
/// Get a locally-defined table.
pub(crate) fn get_defined_table(&self, index: DefinedTableIndex) -> &Table {
&self.tables[index]
}
/// Get an imported, foreign table.
pub(crate) fn get_foreign_table(&self, index: TableIndex) -> &Table {
let import = self.imported_table(index);
let foreign_instance = unsafe { (&mut *(import).vmctx).instance() };
let foreign_table = unsafe { &mut *(import).from };
let foreign_index = foreign_instance.table_index(foreign_table);
&foreign_instance.tables[foreign_index]
}
}
/// A handle holding an `Instance` of a WebAssembly module.
@@ -778,6 +802,11 @@ impl InstanceHandle {
self.instance().table_set(table_index, index, val)
}
/// Get a table defined locally within this module.
pub fn get_defined_table(&self, index: DefinedTableIndex) -> &Table {
self.instance().get_defined_table(index)
}
/// Return a reference to the contained `Instance`.
pub(crate) fn instance(&self) -> &Instance {
unsafe { &*(self.instance as *const Instance) }
@@ -813,7 +842,7 @@ fn check_table_init_bounds(instance: &Instance) -> Result<(), InstantiationError
let module = Arc::clone(&instance.module);
for init in &module.table_elements {
let start = get_table_init_start(init, instance);
let table = get_table(init, instance);
let table = instance.get_table(init.table_index);
let size = usize::try_from(table.size()).unwrap();
if size < start + init.elements.len() {
@@ -913,26 +942,13 @@ fn get_table_init_start(init: &TableElements, instance: &Instance) -> usize {
start
}
/// Return a byte-slice view of a table's data.
fn get_table<'instance>(init: &TableElements, instance: &'instance Instance) -> &'instance Table {
if let Some(defined_table_index) = instance.module.local.defined_table_index(init.table_index) {
&instance.tables[defined_table_index]
} else {
let import = instance.imported_table(init.table_index);
let foreign_instance = unsafe { (&mut *(import).vmctx).instance() };
let foreign_table = unsafe { &mut *(import).from };
let foreign_index = foreign_instance.table_index(foreign_table);
&foreign_instance.tables[foreign_index]
}
}
/// 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 = get_table(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];

View File

@@ -42,9 +42,12 @@ pub use crate::instance::{InstanceHandle, InstantiationError, LinkError};
pub use crate::jit_int::GdbJitImageRegistration;
pub use crate::mmap::Mmap;
pub use crate::sig_registry::SignatureRegistry;
pub use crate::table::Table;
pub use crate::trap_registry::{TrapDescription, TrapRegistration, TrapRegistry};
pub use crate::traphandlers::resume_panic;
pub use crate::traphandlers::{catch_traps, raise_user_trap, wasmtime_call_trampoline, Trap};
pub use crate::traphandlers::{
catch_traps, raise_lib_trap, raise_user_trap, wasmtime_call_trampoline, Trap,
};
pub use crate::vmcontext::{
VMCallerCheckedAnyfunc, VMContext, VMFunctionBody, VMFunctionImport, VMGlobalDefinition,
VMGlobalImport, VMInvokeArgument, VMMemoryDefinition, VMMemoryImport, VMSharedSignatureIndex,

View File

@@ -2,8 +2,11 @@
//! inline rather than calling them, particularly when CPUs have special
//! instructions which compute them directly.
use crate::table::Table;
use crate::traphandlers::raise_lib_trap;
use crate::vmcontext::VMContext;
use wasmtime_environ::wasm::{DefinedMemoryIndex, MemoryIndex};
use wasmtime_environ::ir;
use wasmtime_environ::wasm::{DefinedMemoryIndex, DefinedTableIndex, MemoryIndex, TableIndex};
/// Implementation of f32.ceil
pub extern "C" fn wasmtime_f32_ceil(x: f32) -> f32 {
@@ -137,3 +140,93 @@ pub unsafe extern "C" fn wasmtime_imported_memory32_size(
instance.imported_memory_size(memory_index)
}
/// Implementation of `table.copy` when both tables are locally defined.
#[no_mangle]
pub unsafe extern "C" fn wasmtime_table_copy_defined_defined(
vmctx: *mut VMContext,
dst_table_index: u32,
src_table_index: u32,
dst: u32,
src: u32,
len: u32,
source_loc: u32,
) {
let dst_table_index = DefinedTableIndex::from_u32(dst_table_index);
let src_table_index = DefinedTableIndex::from_u32(src_table_index);
let source_loc = ir::SourceLoc::new(source_loc);
let instance = (&mut *vmctx).instance();
let dst_table = instance.get_defined_table(dst_table_index);
let src_table = instance.get_defined_table(src_table_index);
if let Err(trap) = Table::copy(dst_table, src_table, dst, src, len, source_loc) {
raise_lib_trap(trap);
}
}
/// Implementation of `table.copy` when the destination table is locally defined
/// and the source table is imported.
#[no_mangle]
pub unsafe extern "C" fn wasmtime_table_copy_defined_imported(
vmctx: *mut VMContext,
dst_table_index: u32,
src_table_index: u32,
dst: u32,
src: u32,
len: u32,
source_loc: u32,
) {
let dst_table_index = DefinedTableIndex::from_u32(dst_table_index);
let src_table_index = TableIndex::from_u32(src_table_index);
let source_loc = ir::SourceLoc::new(source_loc);
let instance = (&mut *vmctx).instance();
let dst_table = instance.get_defined_table(dst_table_index);
let src_table = instance.get_foreign_table(src_table_index);
if let Err(trap) = Table::copy(dst_table, src_table, dst, src, len, source_loc) {
raise_lib_trap(trap);
}
}
/// Implementation of `table.copy` when the destination table is imported
/// and the source table is locally defined.
#[no_mangle]
pub unsafe extern "C" fn wasmtime_table_copy_imported_defined(
vmctx: *mut VMContext,
dst_table_index: u32,
src_table_index: u32,
dst: u32,
src: u32,
len: u32,
source_loc: u32,
) {
let dst_table_index = TableIndex::from_u32(dst_table_index);
let src_table_index = DefinedTableIndex::from_u32(src_table_index);
let source_loc = ir::SourceLoc::new(source_loc);
let instance = (&mut *vmctx).instance();
let dst_table = instance.get_foreign_table(dst_table_index);
let src_table = instance.get_defined_table(src_table_index);
if let Err(trap) = Table::copy(dst_table, src_table, dst, src, len, source_loc) {
raise_lib_trap(trap);
}
}
/// Implementation of `table.copy` when both tables are imported.
#[no_mangle]
pub unsafe extern "C" fn wasmtime_table_copy_imported_imported(
vmctx: *mut VMContext,
dst_table_index: u32,
src_table_index: u32,
dst: u32,
src: u32,
len: u32,
source_loc: u32,
) {
let dst_table_index = TableIndex::from_u32(dst_table_index);
let src_table_index = TableIndex::from_u32(src_table_index);
let source_loc = ir::SourceLoc::new(source_loc);
let instance = (&mut *vmctx).instance();
let dst_table = instance.get_foreign_table(dst_table_index);
let src_table = instance.get_foreign_table(src_table_index);
if let Err(trap) = Table::copy(dst_table, src_table, dst, src, len, source_loc) {
raise_lib_trap(trap);
}
}

View File

@@ -3,10 +3,12 @@
//! `Table` is to WebAssembly tables what `LinearMemory` is to WebAssembly linear memories.
use crate::vmcontext::{VMCallerCheckedAnyfunc, VMTableDefinition};
use crate::{Trap, TrapDescription};
use backtrace::Backtrace;
use std::cell::RefCell;
use std::convert::{TryFrom, TryInto};
use wasmtime_environ::wasm::TableElementType;
use wasmtime_environ::{TablePlan, TableStyle};
use wasmtime_environ::{ir, TablePlan, TableStyle};
/// A table instance.
#[derive(Debug)]
@@ -87,6 +89,56 @@ impl Table {
}
}
/// Copy `len` elements from `src_table[src_index..]` into `dst_table[dst_index..]`.
///
/// # Errors
///
/// Returns an error if the range is out of bounds of either the source or
/// destination tables.
pub fn copy(
dst_table: &Self,
src_table: &Self,
dst_index: u32,
src_index: u32,
len: u32,
source_loc: ir::SourceLoc,
) -> Result<(), Trap> {
// https://webassembly.github.io/bulk-memory-operations/core/exec/instructions.html#exec-table-copy
if src_index
.checked_add(len)
.map_or(true, |n| n > src_table.size())
|| dst_index
.checked_add(len)
.map_or(true, |m| m > dst_table.size())
{
return Err(Trap::Wasm {
desc: TrapDescription {
source_loc,
trap_code: ir::TrapCode::TableOutOfBounds,
},
backtrace: Backtrace::new(),
});
}
let srcs = src_index..src_index + len;
let dsts = dst_index..dst_index + len;
// Note on the unwraps: the bounds check above means that these will
// never panic.
if dst_index <= src_index {
for (s, d) in (srcs).zip(dsts) {
dst_table.set(d, src_table.get(s).unwrap()).unwrap();
}
} else {
for (s, d) in srcs.rev().zip(dsts.rev()) {
dst_table.set(d, src_table.get(s).unwrap()).unwrap();
}
}
Ok(())
}
/// Return a `VMTableDefinition` for exposing the table to compiled wasm code.
pub fn vmtable(&self) -> VMTableDefinition {
let mut vec = self.vec.borrow_mut();

View File

@@ -84,7 +84,7 @@ fn trap_code_to_expected_string(trap_code: ir::TrapCode) -> String {
match trap_code {
StackOverflow => "call stack exhausted".to_string(),
HeapOutOfBounds => "out of bounds memory access".to_string(),
TableOutOfBounds => "undefined element".to_string(),
TableOutOfBounds => "undefined element: out of bounds".to_string(),
OutOfBounds => "out of bounds".to_string(), // Note: not covered by the test suite
IndirectCallToNull => "uninitialized element".to_string(),
BadSignature => "indirect call type mismatch".to_string(),

View File

@@ -73,6 +73,20 @@ pub unsafe fn raise_user_trap(data: Box<dyn Error + Send + Sync>) -> ! {
tls::with(|info| info.unwrap().unwind_with(UnwindReason::UserTrap(data)))
}
/// Raises a trap from inside library code immediately.
///
/// This function performs as-if a wasm trap was just executed. This trap
/// payload is then returned from `wasmtime_call` and `wasmtime_call_trampoline`
/// below.
///
/// # Safety
///
/// Only safe to call when wasm code is on the stack, aka `wasmtime_call` or
/// `wasmtime_call_trampoline` must have been previously called.
pub unsafe fn raise_lib_trap(trap: Trap) -> ! {
tls::with(|info| info.unwrap().unwind_with(UnwindReason::LibTrap(trap)))
}
/// Carries a Rust panic across wasm code and resumes the panic on the other
/// side.
///
@@ -192,6 +206,7 @@ enum UnwindReason {
None,
Panic(Box<dyn Any + Send>),
UserTrap(Box<dyn Error + Send + Sync>),
LibTrap(Trap),
Trap { backtrace: Backtrace, pc: usize },
}
@@ -219,6 +234,7 @@ impl CallThreadState {
debug_assert_eq!(ret, 0);
Err(Trap::User(data))
}
UnwindReason::LibTrap(trap) => Err(trap),
UnwindReason::Trap { backtrace, pc } => {
debug_assert_eq!(ret, 0);
let instance = unsafe { InstanceHandle::from_vmctx(self.vmctx) };

View File

@@ -537,6 +537,7 @@ 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] =
@@ -545,6 +546,21 @@ impl VMBuiltinFunctionsArray {
wasmtime_memory32_size as usize;
ptrs[BuiltinFunctionIndex::get_imported_memory32_size_index().index() as usize] =
wasmtime_imported_memory32_size as usize;
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;
debug_assert!(ptrs.iter().cloned().all(|p| p != 0));
Self { ptrs }
}
}