diff --git a/filetests/call_indirect.wat b/filetests/call_indirect.wat new file mode 100644 index 0000000000..8bd8ed58c1 --- /dev/null +++ b/filetests/call_indirect.wat @@ -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) +) diff --git a/lib/environ/src/environ.rs b/lib/environ/src/environ.rs index 06980b3987..8616468c8c 100644 --- a/lib/environ/src/environ.rs +++ b/lib/environ/src/environ.rs @@ -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 { - // 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( diff --git a/lib/execute/src/execute.rs b/lib/execute/src/execute.rs index 84c1879261..6abfdf3f69 100644 --- a/lib/execute/src/execute.rs +++ b/lib/execute/src/execute.rs @@ -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]) { 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 } diff --git a/lib/execute/src/instance.rs b/lib/execute/src/instance.rs index 9096fdd2c8..abec71a1ad 100644 --- a/lib/execute/src/instance.rs +++ b/lib/execute/src/instance.rs @@ -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; + } } } diff --git a/src/main.rs b/src/main.rs index 60dc710f68..402ac05d5c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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 }