Implement tables and call_indirect (#12)
* Implement tables and call_indirect * Restore comment about sig checking. * Widen callee index on 64bit platforms.
This commit is contained in:
committed by
Dan Gohman
parent
e7c8d23a42
commit
7b222190f5
50
filetests/call_indirect.wat
Normal file
50
filetests/call_indirect.wat
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
(module
|
||||||
|
(type $indirect_sig (func (param i64) (result i64)))
|
||||||
|
|
||||||
|
(func $assert (param i32)
|
||||||
|
(block $ok
|
||||||
|
(br_if $ok
|
||||||
|
(get_local 0)
|
||||||
|
)
|
||||||
|
(unreachable)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
(func $plus_1 (param i64) (result i64)
|
||||||
|
get_local 0
|
||||||
|
i64.const 1
|
||||||
|
i64.add
|
||||||
|
)
|
||||||
|
(func $minus_1 (param i64) (result i64)
|
||||||
|
get_local 0
|
||||||
|
i64.const 1
|
||||||
|
i64.sub
|
||||||
|
)
|
||||||
|
|
||||||
|
(func $main
|
||||||
|
(call $call_indirect
|
||||||
|
(i32.const 0)
|
||||||
|
(i64.const 2)
|
||||||
|
)
|
||||||
|
(call $call_indirect
|
||||||
|
(i32.const 1)
|
||||||
|
(i64.const 0)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
(func $call_indirect (param $func i32) (param $expected i64)
|
||||||
|
(call $assert
|
||||||
|
(i64.eq
|
||||||
|
(call_indirect (type $indirect_sig)
|
||||||
|
(i64.const 1)
|
||||||
|
(get_local $func)
|
||||||
|
)
|
||||||
|
(get_local $expected)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(start $main)
|
||||||
|
|
||||||
|
(table 2 2 anyfunc)
|
||||||
|
(elem (i32.const 0) $plus_1 $minus_1)
|
||||||
|
)
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
use cranelift_codegen::cursor::FuncCursor;
|
use cranelift_codegen::cursor::FuncCursor;
|
||||||
use cranelift_codegen::ir;
|
use cranelift_codegen::ir;
|
||||||
use cranelift_codegen::ir::immediates::Offset32;
|
use cranelift_codegen::ir::immediates::{Imm64, Offset32};
|
||||||
use cranelift_codegen::ir::types::*;
|
use cranelift_codegen::ir::types::*;
|
||||||
use cranelift_codegen::ir::{
|
use cranelift_codegen::ir::{
|
||||||
AbiParam, ArgumentPurpose, ExtFuncData, ExternalName, FuncRef, Function, InstBuilder, Signature,
|
AbiParam, ArgumentPurpose, ExtFuncData, ExternalName, FuncRef, Function, InstBuilder, Signature,
|
||||||
@@ -312,8 +312,29 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_table(&mut self, _func: &mut ir::Function, _index: TableIndex) -> ir::Table {
|
fn make_table(&mut self, func: &mut ir::Function, _index: TableIndex) -> ir::Table {
|
||||||
unimplemented!("make_table");
|
let pointer_bytes = self.pointer_bytes();
|
||||||
|
let base_gv_addr = func.create_global_value(ir::GlobalValueData::VMContext {
|
||||||
|
offset: Offset32::new(pointer_bytes as i32 * 2),
|
||||||
|
});
|
||||||
|
let base_gv = func.create_global_value(ir::GlobalValueData::Deref {
|
||||||
|
base: base_gv_addr,
|
||||||
|
offset: 0.into(),
|
||||||
|
});
|
||||||
|
let bound_gv_addr = func.create_global_value(ir::GlobalValueData::VMContext {
|
||||||
|
offset: Offset32::new(pointer_bytes as i32 * 3),
|
||||||
|
});
|
||||||
|
let bound_gv = func.create_global_value(ir::GlobalValueData::Deref {
|
||||||
|
base: bound_gv_addr,
|
||||||
|
offset: 0.into(),
|
||||||
|
});
|
||||||
|
|
||||||
|
func.create_table(ir::TableData {
|
||||||
|
base_gv,
|
||||||
|
min_size: Imm64::new(0),
|
||||||
|
bound_gv,
|
||||||
|
element_size: Imm64::new(i64::from(self.pointer_bytes() as i64)),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_indirect_sig(&mut self, func: &mut ir::Function, index: SignatureIndex) -> ir::SigRef {
|
fn make_indirect_sig(&mut self, func: &mut ir::Function, index: SignatureIndex) -> ir::SigRef {
|
||||||
@@ -338,17 +359,38 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m
|
|||||||
&mut self,
|
&mut self,
|
||||||
mut pos: FuncCursor,
|
mut pos: FuncCursor,
|
||||||
table_index: TableIndex,
|
table_index: TableIndex,
|
||||||
_table: ir::Table,
|
table: ir::Table,
|
||||||
_sig_index: SignatureIndex,
|
_sig_index: SignatureIndex,
|
||||||
sig_ref: ir::SigRef,
|
sig_ref: ir::SigRef,
|
||||||
callee: ir::Value,
|
callee: ir::Value,
|
||||||
call_args: &[ir::Value],
|
call_args: &[ir::Value],
|
||||||
) -> WasmResult<ir::Inst> {
|
) -> WasmResult<ir::Inst> {
|
||||||
// TODO: Cranelift's call_indirect doesn't implement bounds checking
|
// TODO: Cranelift's call_indirect doesn't implement signature checking,
|
||||||
// or signature checking, so we need to implement it ourselves.
|
// so we need to implement it ourselves.
|
||||||
debug_assert_eq!(table_index, 0, "non-default tables not supported yet");
|
debug_assert_eq!(table_index, 0, "non-default tables not supported yet");
|
||||||
|
|
||||||
|
let callee_ty = pos.func.dfg.value_type(callee);
|
||||||
|
debug_assert_eq!(callee_ty, I32, "wasm call indirect index should be I32");
|
||||||
|
let callee = if self.pointer_type() == I64 {
|
||||||
|
// The current limitation of `table_addr` is that the index should be
|
||||||
|
// the same type as `self.pointer_type()`. So we just extend the given
|
||||||
|
// index to 64-bit here.
|
||||||
|
pos.ins().uextend(I64, callee)
|
||||||
|
} else {
|
||||||
|
callee
|
||||||
|
};
|
||||||
|
let table_entry_addr = pos.ins().table_addr(I64, table, callee, 0);
|
||||||
|
|
||||||
|
// Dereference table_entry_addr to get the function address.
|
||||||
|
let mut mem_flags = ir::MemFlags::new();
|
||||||
|
mem_flags.set_notrap();
|
||||||
|
mem_flags.set_aligned();
|
||||||
|
let func_addr = pos
|
||||||
|
.ins()
|
||||||
|
.load(self.pointer_type(), mem_flags, table_entry_addr, 0);
|
||||||
|
|
||||||
let real_call_args = FuncEnvironment::get_real_call_args(pos.func, call_args);
|
let real_call_args = FuncEnvironment::get_real_call_args(pos.func, call_args);
|
||||||
Ok(pos.ins().call_indirect(sig_ref, callee, &real_call_args))
|
Ok(pos.ins().call_indirect(sig_ref, func_addr, &real_call_args))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn translate_call(
|
fn translate_call(
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ use memory::LinearMemory;
|
|||||||
use region::protect;
|
use region::protect;
|
||||||
use region::Protection;
|
use region::Protection;
|
||||||
use std::mem::transmute;
|
use std::mem::transmute;
|
||||||
use std::ptr::write_unaligned;
|
use std::ptr::{self, write_unaligned};
|
||||||
use wasmtime_environ::{
|
use wasmtime_environ::{
|
||||||
compile_module, Compilation, Module, ModuleTranslation, Relocation, RelocationTarget,
|
compile_module, Compilation, Module, ModuleTranslation, Relocation, RelocationTarget,
|
||||||
};
|
};
|
||||||
@@ -66,7 +66,7 @@ fn relocate(compilation: &mut Compilation, relocations: &[Vec<Relocation>]) {
|
|||||||
|
|
||||||
extern "C" fn grow_memory(size: u32, vmctx: *mut *mut u8) -> u32 {
|
extern "C" fn grow_memory(size: u32, vmctx: *mut *mut u8) -> u32 {
|
||||||
unsafe {
|
unsafe {
|
||||||
let instance = (*vmctx.offset(2)) as *mut Instance;
|
let instance = (*vmctx.offset(4)) as *mut Instance;
|
||||||
(*instance)
|
(*instance)
|
||||||
.memory_mut(0)
|
.memory_mut(0)
|
||||||
.grow(size)
|
.grow(size)
|
||||||
@@ -76,7 +76,7 @@ extern "C" fn grow_memory(size: u32, vmctx: *mut *mut u8) -> u32 {
|
|||||||
|
|
||||||
extern "C" fn current_memory(vmctx: *mut *mut u8) -> u32 {
|
extern "C" fn current_memory(vmctx: *mut *mut u8) -> u32 {
|
||||||
unsafe {
|
unsafe {
|
||||||
let instance = (*vmctx.offset(2)) as *mut Instance;
|
let instance = (*vmctx.offset(4)) as *mut Instance;
|
||||||
(*instance).memory_mut(0).current_size()
|
(*instance).memory_mut(0).current_size()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -84,10 +84,24 @@ extern "C" fn current_memory(vmctx: *mut *mut u8) -> u32 {
|
|||||||
/// Create the VmCtx data structure for the JIT'd code to use. This must
|
/// Create the VmCtx data structure for the JIT'd code to use. This must
|
||||||
/// match the VmCtx layout in the environment.
|
/// match the VmCtx layout in the environment.
|
||||||
fn make_vmctx(instance: &mut Instance, mem_base_addrs: &mut [*mut u8]) -> Vec<*mut u8> {
|
fn make_vmctx(instance: &mut Instance, mem_base_addrs: &mut [*mut u8]) -> Vec<*mut u8> {
|
||||||
|
debug_assert!(
|
||||||
|
instance.tables.len() <= 1,
|
||||||
|
"non-default tables is not supported"
|
||||||
|
);
|
||||||
|
|
||||||
|
let (default_table_ptr, default_table_len) = instance
|
||||||
|
.tables
|
||||||
|
.get_mut(0)
|
||||||
|
.map(|table| (table.as_mut_ptr() as *mut u8, table.len()))
|
||||||
|
.unwrap_or((ptr::null_mut(), 0));
|
||||||
|
|
||||||
let mut vmctx = Vec::new();
|
let mut vmctx = Vec::new();
|
||||||
vmctx.push(instance.globals.as_mut_ptr());
|
vmctx.push(instance.globals.as_mut_ptr());
|
||||||
vmctx.push(mem_base_addrs.as_mut_ptr() as *mut u8);
|
vmctx.push(mem_base_addrs.as_mut_ptr() as *mut u8);
|
||||||
|
vmctx.push(default_table_ptr);
|
||||||
|
vmctx.push(default_table_len as *mut u8);
|
||||||
vmctx.push(instance as *mut Instance as *mut u8);
|
vmctx.push(instance as *mut Instance as *mut u8);
|
||||||
|
|
||||||
vmctx
|
vmctx
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
use cranelift_codegen::ir;
|
use cranelift_codegen::ir;
|
||||||
use cranelift_wasm::GlobalIndex;
|
use cranelift_wasm::GlobalIndex;
|
||||||
use memory::LinearMemory;
|
use memory::LinearMemory;
|
||||||
use wasmtime_environ::{DataInitializer, Module, TableElements};
|
use wasmtime_environ::{Compilation, DataInitializer, Module, TableElements};
|
||||||
|
|
||||||
/// An Instance of a WebAssemby module.
|
/// An Instance of a WebAssemby module.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -21,20 +21,29 @@ pub struct Instance {
|
|||||||
|
|
||||||
impl Instance {
|
impl Instance {
|
||||||
/// Create a new `Instance`.
|
/// Create a new `Instance`.
|
||||||
pub fn new(module: &Module, data_initializers: &[DataInitializer]) -> Self {
|
pub fn new(
|
||||||
|
module: &Module,
|
||||||
|
compilation: &Compilation,
|
||||||
|
data_initializers: &[DataInitializer],
|
||||||
|
) -> Self {
|
||||||
let mut result = Self {
|
let mut result = Self {
|
||||||
tables: Vec::new(),
|
tables: Vec::new(),
|
||||||
memories: Vec::new(),
|
memories: Vec::new(),
|
||||||
globals: Vec::new(),
|
globals: Vec::new(),
|
||||||
};
|
};
|
||||||
result.instantiate_tables(module, &module.table_elements);
|
result.instantiate_tables(module, compilation, &module.table_elements);
|
||||||
result.instantiate_memories(module, data_initializers);
|
result.instantiate_memories(module, data_initializers);
|
||||||
result.instantiate_globals(module);
|
result.instantiate_globals(module);
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Allocate memory in `self` for just the tables of the current module.
|
/// Allocate memory in `self` for just the tables of the current module.
|
||||||
fn instantiate_tables(&mut self, module: &Module, table_initializers: &[TableElements]) {
|
fn instantiate_tables(
|
||||||
|
&mut self,
|
||||||
|
module: &Module,
|
||||||
|
compilation: &Compilation,
|
||||||
|
table_initializers: &[TableElements],
|
||||||
|
) {
|
||||||
debug_assert!(self.tables.is_empty());
|
debug_assert!(self.tables.is_empty());
|
||||||
self.tables.reserve_exact(module.tables.len());
|
self.tables.reserve_exact(module.tables.len());
|
||||||
for table in &module.tables {
|
for table in &module.tables {
|
||||||
@@ -47,7 +56,10 @@ impl Instance {
|
|||||||
debug_assert!(init.base.is_none(), "globalvar base not supported yet");
|
debug_assert!(init.base.is_none(), "globalvar base not supported yet");
|
||||||
let to_init =
|
let to_init =
|
||||||
&mut self.tables[init.table_index][init.offset..init.offset + init.elements.len()];
|
&mut self.tables[init.table_index][init.offset..init.offset + init.elements.len()];
|
||||||
to_init.copy_from_slice(&init.elements);
|
for (i, func_idx) in init.elements.iter().enumerate() {
|
||||||
|
let code_buf = &compilation.functions[*func_idx];
|
||||||
|
to_init[i] = code_buf.as_ptr() as usize;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -133,8 +133,11 @@ fn handle_module(args: &Args, path: PathBuf, isa: &TargetIsa) -> Result<(), Stri
|
|||||||
let translation = environ.translate(&data).map_err(|e| e.to_string())?;
|
let translation = environ.translate(&data).map_err(|e| e.to_string())?;
|
||||||
let instance = match compile_and_link_module(isa, &translation) {
|
let instance = match compile_and_link_module(isa, &translation) {
|
||||||
Ok(compilation) => {
|
Ok(compilation) => {
|
||||||
let mut instance =
|
let mut instance = Instance::new(
|
||||||
Instance::new(translation.module, &translation.lazy.data_initializers);
|
translation.module,
|
||||||
|
&compilation,
|
||||||
|
&translation.lazy.data_initializers,
|
||||||
|
);
|
||||||
execute(&translation.module, &compilation, &mut instance)?;
|
execute(&translation.module, &compilation, &mut instance)?;
|
||||||
instance
|
instance
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user