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::ir;
|
||||
use cranelift_codegen::ir::immediates::Offset32;
|
||||
use cranelift_codegen::ir::immediates::{Imm64, Offset32};
|
||||
use cranelift_codegen::ir::types::*;
|
||||
use cranelift_codegen::ir::{
|
||||
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 {
|
||||
unimplemented!("make_table");
|
||||
fn make_table(&mut self, func: &mut ir::Function, _index: TableIndex) -> ir::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 {
|
||||
@@ -338,17 +359,38 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m
|
||||
&mut self,
|
||||
mut pos: FuncCursor,
|
||||
table_index: TableIndex,
|
||||
_table: ir::Table,
|
||||
table: ir::Table,
|
||||
_sig_index: SignatureIndex,
|
||||
sig_ref: ir::SigRef,
|
||||
callee: ir::Value,
|
||||
call_args: &[ir::Value],
|
||||
) -> WasmResult<ir::Inst> {
|
||||
// TODO: Cranelift's call_indirect doesn't implement bounds checking
|
||||
// or signature checking, so we need to implement it ourselves.
|
||||
// TODO: Cranelift's call_indirect doesn't implement signature checking,
|
||||
// so we need to implement it ourselves.
|
||||
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);
|
||||
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(
|
||||
|
||||
@@ -5,7 +5,7 @@ use memory::LinearMemory;
|
||||
use region::protect;
|
||||
use region::Protection;
|
||||
use std::mem::transmute;
|
||||
use std::ptr::write_unaligned;
|
||||
use std::ptr::{self, write_unaligned};
|
||||
use wasmtime_environ::{
|
||||
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 {
|
||||
unsafe {
|
||||
let instance = (*vmctx.offset(2)) as *mut Instance;
|
||||
let instance = (*vmctx.offset(4)) as *mut Instance;
|
||||
(*instance)
|
||||
.memory_mut(0)
|
||||
.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 {
|
||||
unsafe {
|
||||
let instance = (*vmctx.offset(2)) as *mut Instance;
|
||||
let instance = (*vmctx.offset(4)) as *mut Instance;
|
||||
(*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
|
||||
/// match the VmCtx layout in the environment.
|
||||
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();
|
||||
vmctx.push(instance.globals.as_mut_ptr());
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
use cranelift_codegen::ir;
|
||||
use cranelift_wasm::GlobalIndex;
|
||||
use memory::LinearMemory;
|
||||
use wasmtime_environ::{DataInitializer, Module, TableElements};
|
||||
use wasmtime_environ::{Compilation, DataInitializer, Module, TableElements};
|
||||
|
||||
/// An Instance of a WebAssemby module.
|
||||
#[derive(Debug)]
|
||||
@@ -21,20 +21,29 @@ pub struct Instance {
|
||||
|
||||
impl 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 {
|
||||
tables: Vec::new(),
|
||||
memories: 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_globals(module);
|
||||
result
|
||||
}
|
||||
|
||||
/// 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());
|
||||
self.tables.reserve_exact(module.tables.len());
|
||||
for table in &module.tables {
|
||||
@@ -47,7 +56,10 @@ impl Instance {
|
||||
debug_assert!(init.base.is_none(), "globalvar base not supported yet");
|
||||
let to_init =
|
||||
&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 instance = match compile_and_link_module(isa, &translation) {
|
||||
Ok(compilation) => {
|
||||
let mut instance =
|
||||
Instance::new(translation.module, &translation.lazy.data_initializers);
|
||||
let mut instance = Instance::new(
|
||||
translation.module,
|
||||
&compilation,
|
||||
&translation.lazy.data_initializers,
|
||||
);
|
||||
execute(&translation.module, &compilation, &mut instance)?;
|
||||
instance
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user