diff --git a/.gitmodules b/.gitmodules index f9cf57cd97..eed27e0f71 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "lib/wast/spec_testsuite"] - path = lib/wast/spec_testsuite + path = spec_testsuite url = https://github.com/WebAssembly/testsuite diff --git a/Cargo.toml b/Cargo.toml index cf3098cbeb..d6ffa494cc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,8 +14,8 @@ name = "wasmtime" path = "src/wasmtime.rs" [[bin]] -name = "run_wast" -path = "src/run_wast.rs" +name = "wast" +path = "src/wast.rs" [[bin]] name = "wasm2obj" diff --git a/build.rs b/build.rs new file mode 100644 index 0000000000..34f33e6db4 --- /dev/null +++ b/build.rs @@ -0,0 +1,116 @@ +//! Build program to generate a program which runs all the testsuites. +//! +//! By generating a separate `#[test]` test for each file, we allow cargo test +//! to automatically run the files in parallel. + +use std::env; +use std::fs::{read_dir, DirEntry, File}; +use std::io::{self, Write}; +use std::path::{Path, PathBuf}; + +fn main() { + let out_dir = + PathBuf::from(env::var("OUT_DIR").expect("The OUT_DIR environment variable must be set")); + let mut out = File::create(out_dir.join("wast_testsuite_tests.rs")) + .expect("error generating test source file"); + + test_directory(&mut out, "misc_testsuite").expect("generating tests"); + test_directory(&mut out, "spec_testsuite").expect("generating tests"); +} + +fn test_directory(out: &mut File, testsuite: &str) -> io::Result<()> { + let mut dir_entries: Vec<_> = read_dir(testsuite) + .expect("reading testsuite directory") + .map(|r| r.expect("reading testsuite directory entry")) + .filter(|dir_entry| { + let p = dir_entry.path(); + if let Some(ext) = p.extension() { + // Only look at wast files. + if ext == "wast" { + // Ignore files starting with `.`, which could be editor temporary files + if let Some(stem) = p.file_stem() { + if let Some(stemstr) = stem.to_str() { + if !stemstr.starts_with('.') { + return true; + } + } + } + } + } + false + }) + .collect(); + + dir_entries.sort_by_key(|dir| dir.path()); + + writeln!( + out, + "mod {} {{", + Path::new(testsuite) + .file_stem() + .expect("testsuite filename should have a stem") + .to_str() + .expect("testsuite filename should be representable as a string") + .replace("-", "_") + )?; + writeln!(out, " use super::{{native_isa, Path, WastContext}};")?; + for dir_entry in dir_entries { + write_testsuite_tests(out, dir_entry, testsuite)?; + } + writeln!(out, "}}")?; + Ok(()) +} + +fn write_testsuite_tests(out: &mut File, dir_entry: DirEntry, testsuite: &str) -> io::Result<()> { + let path = dir_entry.path(); + let stemstr = path + .file_stem() + .expect("file_stem") + .to_str() + .expect("to_str"); + + writeln!(out, " #[test]")?; + if ignore(testsuite, stemstr) { + writeln!(out, " #[ignore]")?; + } + writeln!( + out, + " fn {}() {{", + avoid_keywords(&stemstr.replace("-", "_")) + )?; + writeln!(out, " let mut wast_context = WastContext::new();")?; + writeln!(out, " let isa = native_isa();")?; + writeln!(out, " wast_context")?; + writeln!(out, " .register_spectest()")?; + writeln!( + out, + " .expect(\"instantiating \\\"spectest\\\"\");" + )?; + writeln!(out, " wast_context")?; + writeln!( + out, + " .run_file(&*isa, Path::new(\"{}\"))", + path.display() + )?; + writeln!(out, " .expect(\"error running wast file\");",)?; + writeln!(out, " }}")?; + writeln!(out)?; + Ok(()) +} + +/// Rename tests which have the same name as Rust keywords. +fn avoid_keywords(name: &str) -> &str { + match name { + "if" => "if_", + "loop" => "loop_", + "type" => "type_", + "const" => "const_", + "return" => "return_", + other => other, + } +} + +/// Ignore tests that aren't supported yet. +fn ignore(_testsuite: &str, _name: &str) -> bool { + false +} diff --git a/lib/environ/src/func_environ.rs b/lib/environ/src/func_environ.rs index 27297734eb..508e77d635 100644 --- a/lib/environ/src/func_environ.rs +++ b/lib/environ/src/func_environ.rs @@ -132,14 +132,6 @@ impl<'module_environment> FuncEnvironment<'module_environment> { self.isa.frontend_config().pointer_type() } - /// Transform the call argument list in preparation for making a call. - fn get_real_call_args(func: &Function, call_args: &[ir::Value]) -> Vec { - let mut real_call_args = Vec::with_capacity(call_args.len() + 1); - real_call_args.extend_from_slice(call_args); - real_call_args.push(func.special_param(ArgumentPurpose::VMContext).unwrap()); - real_call_args - } - fn vmctx(&mut self, func: &mut Function) -> ir::GlobalValue { self.vmctx.unwrap_or_else(|| { let vmctx = func.create_global_value(ir::GlobalValueData::VMContext); @@ -539,15 +531,6 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m let table_entry_addr = pos.ins().table_addr(pointer_type, table, callee, 0); - // Dereference table_entry_addr to get the function address. - let mem_flags = ir::MemFlags::trusted(); - let func_addr = pos.ins().load( - pointer_type, - mem_flags, - table_entry_addr, - i32::from(self.offsets.vmcaller_checked_anyfunc_func_ptr()), - ); - // If necessary, check the signature. match self.module.table_plans[table_index].style { TableStyle::CallerChecksSignature => { @@ -597,7 +580,27 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m } } - let real_call_args = FuncEnvironment::get_real_call_args(pos.func, call_args); + // Dereference table_entry_addr to get the function address. + let mem_flags = ir::MemFlags::trusted(); + let func_addr = pos.ins().load( + pointer_type, + mem_flags, + table_entry_addr, + i32::from(self.offsets.vmcaller_checked_anyfunc_func_ptr()), + ); + + let mut real_call_args = Vec::with_capacity(call_args.len() + 1); + real_call_args.extend_from_slice(call_args); + + // Append the callee vmctx address. + let vmctx = pos.ins().load( + pointer_type, + mem_flags, + table_entry_addr, + i32::from(self.offsets.vmcaller_checked_anyfunc_vmctx()), + ); + real_call_args.push(vmctx); + Ok(pos.ins().call_indirect(sig_ref, func_addr, &real_call_args)) } @@ -608,10 +611,12 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m callee: ir::FuncRef, call_args: &[ir::Value], ) -> WasmResult { - let real_call_args = FuncEnvironment::get_real_call_args(pos.func, call_args); + let mut real_call_args = Vec::with_capacity(call_args.len() + 1); + real_call_args.extend_from_slice(call_args); // Handle direct calls to locally-defined functions. if !self.module.is_imported_function(callee_index) { + real_call_args.push(pos.func.special_param(ArgumentPurpose::VMContext).unwrap()); return Ok(pos.ins().call(callee, &real_call_args)); } @@ -623,9 +628,18 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m let base = pos .ins() .global_value(pointer_type, imported_functions_base); - let offset = self.offsets.index_vmfunction_body_import(callee_index); + let mem_flags = ir::MemFlags::trusted(); - let func_addr = pos.ins().load(pointer_type, mem_flags, base, offset); + + // Load the callee address. + let body_offset = self.offsets.index_vmfunction_import_body(callee_index); + let func_addr = pos.ins().load(pointer_type, mem_flags, base, body_offset); + + // Append the callee vmctx address. + let vmctx_offset = self.offsets.index_vmfunction_import_vmctx(callee_index); + let vmctx = pos.ins().load(pointer_type, mem_flags, base, vmctx_offset); + real_call_args.push(vmctx); + Ok(pos.ins().call_indirect(sig_ref, func_addr, &real_call_args)) } diff --git a/lib/environ/src/lib.rs b/lib/environ/src/lib.rs index 84d3ec9243..3ea2f91720 100644 --- a/lib/environ/src/lib.rs +++ b/lib/environ/src/lib.rs @@ -48,10 +48,10 @@ mod vmoffsets; pub use compilation::{ compile_module, Compilation, CompileError, RelocSink, Relocation, RelocationTarget, Relocations, }; -pub use module::{ - DataInitializer, Export, MemoryPlan, MemoryStyle, Module, TableElements, TablePlan, TableStyle, +pub use module::{Export, MemoryPlan, MemoryStyle, Module, TableElements, TablePlan, TableStyle}; +pub use module_environ::{ + translate_signature, DataInitializer, ModuleEnvironment, ModuleTranslation, }; -pub use module_environ::{translate_signature, ModuleEnvironment, ModuleTranslation}; pub use tunables::Tunables; pub use vmoffsets::VMOffsets; diff --git a/lib/environ/src/module.rs b/lib/environ/src/module.rs index d0d911006d..4e6ac74d90 100644 --- a/lib/environ/src/module.rs +++ b/lib/environ/src/module.rs @@ -75,7 +75,7 @@ impl MemoryStyle { /// A WebAssembly linear memory description along with our chosen style for /// implementing it. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct MemoryPlan { /// The WebAssembly linear memory description. pub memory: Memory, @@ -113,7 +113,7 @@ impl TableStyle { /// A WebAssembly table description along with our chosen style for /// implementing it. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct TablePlan { /// The WebAssembly table description. pub table: cranelift_wasm::Table, @@ -277,34 +277,3 @@ impl Module { index.index() < self.imported_globals.len() } } - -/// A data initializer for linear memory. -pub struct DataInitializer<'data> { - /// The index of the memory to initialize. - pub memory_index: MemoryIndex, - /// Optionally a globalvar base to initialize at. - pub base: Option, - /// A constant offset to initialize at. - pub offset: usize, - /// The initialization data. - pub data: &'data [u8], -} - -/// References to the input wasm data buffer to be decoded and processed later, -/// separately from the main module translation. -pub struct LazyContents<'data> { - /// References to the function bodies. - pub function_body_inputs: PrimaryMap, - - /// References to the data initializers. - pub data_initializers: Vec>, -} - -impl<'data> LazyContents<'data> { - pub fn new() -> Self { - Self { - function_body_inputs: PrimaryMap::new(), - data_initializers: Vec::new(), - } - } -} diff --git a/lib/environ/src/module_environ.rs b/lib/environ/src/module_environ.rs index a5ec4230a3..4c85f18618 100644 --- a/lib/environ/src/module_environ.rs +++ b/lib/environ/src/module_environ.rs @@ -1,12 +1,13 @@ use cranelift_codegen::ir; use cranelift_codegen::ir::{AbiParam, ArgumentPurpose}; use cranelift_codegen::isa; +use cranelift_entity::PrimaryMap; use cranelift_wasm::{ - self, translate_module, FuncIndex, Global, GlobalIndex, Memory, MemoryIndex, SignatureIndex, - Table, TableIndex, WasmResult, + self, translate_module, DefinedFuncIndex, FuncIndex, Global, GlobalIndex, Memory, MemoryIndex, + SignatureIndex, Table, TableIndex, WasmResult, }; use func_environ::FuncEnvironment; -use module::{DataInitializer, Export, LazyContents, MemoryPlan, Module, TableElements, TablePlan}; +use module::{Export, MemoryPlan, Module, TableElements, TablePlan}; use std::clone::Clone; use std::string::String; use std::vec::Vec; @@ -259,3 +260,34 @@ pub fn translate_signature(mut sig: ir::Signature, pointer_type: ir::Type) -> ir .push(AbiParam::special(pointer_type, ArgumentPurpose::VMContext)); sig } + +/// A data initializer for linear memory. +pub struct DataInitializer<'data> { + /// The index of the memory to initialize. + pub memory_index: MemoryIndex, + /// Optionally a globalvar base to initialize at. + pub base: Option, + /// A constant offset to initialize at. + pub offset: usize, + /// The initialization data. + pub data: &'data [u8], +} + +/// References to the input wasm data buffer to be decoded and processed later, +/// separately from the main module translation. +pub struct LazyContents<'data> { + /// References to the function bodies. + pub function_body_inputs: PrimaryMap, + + /// References to the data initializers. + pub data_initializers: Vec>, +} + +impl<'data> LazyContents<'data> { + pub fn new() -> Self { + Self { + function_body_inputs: PrimaryMap::new(), + data_initializers: Vec::new(), + } + } +} diff --git a/lib/environ/src/vmoffsets.rs b/lib/environ/src/vmoffsets.rs index f80428d33a..33934cc72a 100644 --- a/lib/environ/src/vmoffsets.rs +++ b/lib/environ/src/vmoffsets.rs @@ -20,6 +20,26 @@ impl VMOffsets { } } +/// Offsets for `VMFunctionImport`. +impl VMOffsets { + /// The offset of the `body` field. + #[allow(clippy::erasing_op)] + pub fn vmfunction_import_body(&self) -> u8 { + 0 * self.pointer_size + } + + /// The offset of the `vmctx` field. + #[allow(clippy::identity_op)] + pub fn vmfunction_import_vmctx(&self) -> u8 { + 1 * self.pointer_size + } + + /// Return the size of `VMFunctionImport`. + pub fn size_of_vmfunction_import(&self) -> u8 { + 2 * self.pointer_size + } +} + /// Offsets for `*const VMFunctionBody`. impl VMOffsets { /// The size of the `current_elements` field. @@ -174,9 +194,14 @@ impl VMOffsets { 1 * self.pointer_size } + /// The offset of the `vmctx` field. + pub fn vmcaller_checked_anyfunc_vmctx(&self) -> u8 { + 2 * self.pointer_size + } + /// Return the size of `VMCallerCheckedAnyfunc`. pub fn size_of_vmcaller_checked_anyfunc(&self) -> u8 { - 2 * self.pointer_size + 3 * self.pointer_size } } @@ -230,6 +255,17 @@ impl VMOffsets { 8 * self.pointer_size } + /// Return the offset from the `imported_functions` pointer to `VMFunctionImport` index `index`. + fn index_vmfunction_import(&self, index: FuncIndex) -> i32 { + cast::i32( + index + .as_u32() + .checked_mul(u32::from(self.size_of_vmfunction_import())) + .unwrap(), + ) + .unwrap() + } + /// Return the offset from the `imported_tables` pointer to `VMTableImport` index `index`. fn index_vmtable_import(&self, index: TableIndex) -> i32 { cast::i32( @@ -286,15 +322,19 @@ impl VMOffsets { } /// Return the offset from the `imported_functions` pointer to the - /// `*const VMFunctionBody` index `index`. - pub fn index_vmfunction_body_import(&self, index: FuncIndex) -> i32 { - cast::i32( - index - .as_u32() - .checked_mul(u32::from(self.size_of_vmfunction_body_ptr())) - .unwrap(), - ) - .unwrap() + /// `body` field in `*const VMFunctionBody` index `index`. + pub fn index_vmfunction_import_body(&self, index: FuncIndex) -> i32 { + self.index_vmfunction_import(index) + .checked_add(i32::from(self.vmfunction_import_body())) + .unwrap() + } + + /// Return the offset from the `imported_functions` pointer to the + /// `vmctx` field in `*const VMFunctionBody` index `index`. + pub fn index_vmfunction_import_vmctx(&self, index: FuncIndex) -> i32 { + self.index_vmfunction_import(index) + .checked_add(i32::from(self.vmfunction_import_vmctx())) + .unwrap() } /// Return the offset from the `tables` pointer to the `from` field in diff --git a/lib/execute/src/action.rs b/lib/execute/src/action.rs index 00bd4bd657..0194bf495a 100644 --- a/lib/execute/src/action.rs +++ b/lib/execute/src/action.rs @@ -6,6 +6,7 @@ use std::fmt; use std::string::String; use std::vec::Vec; use wasmtime_environ::CompileError; +use wasmtime_runtime::InstantiationError; /// A runtime value. #[derive(Copy, Clone, Debug, Eq, PartialEq)] @@ -110,10 +111,6 @@ pub enum ActionError { #[fail(display = "Unknown field: {}", _0)] Field(String), - /// An index was out of bounds. - #[fail(display = "Index out of bounds: {}", _0)] - Index(u64), - /// The field was present but was the wrong kind (eg. function, table, global, or memory). #[fail(display = "Kind error: {}", _0)] Kind(String), @@ -126,9 +123,10 @@ pub enum ActionError { #[fail(display = "WebAssembly compilation error: {}", _0)] Compile(CompileError), - /// Some runtime resource was unavailable or insufficient. - #[fail(display = "Runtime resource error: {}", _0)] - Resource(String), + /// Some runtime resource was unavailable or insufficient, or the start function + /// trapped. + #[fail(display = "Instantiation error: {}", _0)] + Instantiate(InstantiationError), /// Link error. #[fail(display = "Link error: {}", _0)] diff --git a/lib/execute/src/instance_plus.rs b/lib/execute/src/instance_plus.rs new file mode 100644 index 0000000000..1f731aef16 --- /dev/null +++ b/lib/execute/src/instance_plus.rs @@ -0,0 +1,287 @@ +use action::{ActionError, ActionOutcome, RuntimeValue}; +use cranelift_codegen::{ir, isa}; +use cranelift_entity::{BoxedSlice, PrimaryMap}; +use cranelift_wasm::DefinedFuncIndex; +use jit_code::JITCode; +use link::link_module; +use resolver::Resolver; +use std::cmp::max; +use std::rc::Rc; +use std::slice; +use std::string::String; +use std::vec::Vec; +use std::{mem, ptr}; +use trampoline_park::TrampolinePark; +use wasmtime_environ::{ + compile_module, Compilation, CompileError, DataInitializer, Module, ModuleEnvironment, Tunables, +}; +use wasmtime_runtime::{ + wasmtime_call_trampoline, Export, Imports, Instance, InstantiationError, VMFunctionBody, +}; + +/// `InstancePlus` holds an `Instance` and adds support for performing actions +/// such as the "invoke" command in wast. +/// +/// TODO: Think of a better name. +#[derive(Debug)] +pub struct InstancePlus { + /// The contained instance. + pub instance: Box, + + /// Trampolines for calling into JIT code. + trampolines: TrampolinePark, +} + +impl InstancePlus { + /// Create a new `InstancePlus` by compiling the wasm module in `data` and instatiating it. + pub fn new( + jit_code: &mut JITCode, + isa: &isa::TargetIsa, + data: &[u8], + resolver: &mut Resolver, + ) -> Result { + let mut module = Module::new(); + + // TODO: Allow the tunables to be overridden. + let tunables = Tunables::default(); + + let (lazy_function_body_inputs, lazy_data_initializers) = { + let environ = ModuleEnvironment::new(isa, &mut module, tunables); + + let translation = environ + .translate(&data) + .map_err(|error| ActionError::Compile(CompileError::Wasm(error)))?; + + ( + translation.lazy.function_body_inputs, + translation.lazy.data_initializers, + ) + }; + + let (compilation, relocations) = compile_module(&module, &lazy_function_body_inputs, isa) + .map_err(ActionError::Compile)?; + + let allocated_functions = allocate_functions(jit_code, compilation).map_err(|message| { + ActionError::Instantiate(InstantiationError::Resource(format!( + "failed to allocate memory for functions: {}", + message + ))) + })?; + + let imports = link_module(&module, &allocated_functions, relocations, resolver) + .map_err(ActionError::Link)?; + + // Gather up the pointers to the compiled functions. + let finished_functions: BoxedSlice = + allocated_functions + .into_iter() + .map(|(_index, allocated)| { + let fatptr: *const [VMFunctionBody] = *allocated; + fatptr as *const VMFunctionBody + }) + .collect::>() + .into_boxed_slice(); + + // Make all code compiled thus far executable. + jit_code.publish(); + + Self::with_parts( + Rc::new(module), + finished_functions, + imports, + lazy_data_initializers, + ) + } + + /// Construct a new `InstancePlus` from the parts needed to produce an `Instance`. + pub fn with_parts( + module: Rc, + finished_functions: BoxedSlice, + imports: Imports, + data_initializers: Vec, + ) -> Result { + let instance = Instance::new(module, finished_functions, imports, data_initializers) + .map_err(ActionError::Instantiate)?; + + Ok(Self::with_instance(instance)) + } + + /// Construct a new `InstancePlus` from an existing instance. + pub fn with_instance(instance: Box) -> Self { + Self { + instance, + trampolines: TrampolinePark::new(), + } + } + + /// Invoke a function in this `Instance` identified by an export name. + pub fn invoke( + &mut self, + jit_code: &mut JITCode, + isa: &isa::TargetIsa, + function_name: &str, + args: &[RuntimeValue], + ) -> Result { + let (address, signature, callee_vmctx) = match self.instance.lookup(function_name) { + Some(Export::Function { + address, + signature, + vmctx, + }) => (address, signature, vmctx), + Some(_) => { + return Err(ActionError::Kind(format!( + "exported item \"{}\" is not a function", + function_name + ))) + } + None => { + return Err(ActionError::Field(format!( + "no export named \"{}\"", + function_name + ))) + } + }; + + for (index, value) in args.iter().enumerate() { + assert_eq!(value.value_type(), signature.params[index].value_type); + } + + // TODO: Support values larger than u64. + let mut values_vec: Vec = Vec::new(); + let value_size = mem::size_of::(); + values_vec.resize(max(signature.params.len(), signature.returns.len()), 0u64); + + // Store the argument values into `values_vec`. + for (index, arg) in args.iter().enumerate() { + unsafe { + let ptr = values_vec.as_mut_ptr().add(index); + + match arg { + RuntimeValue::I32(x) => ptr::write(ptr as *mut i32, *x), + RuntimeValue::I64(x) => ptr::write(ptr as *mut i64, *x), + RuntimeValue::F32(x) => ptr::write(ptr as *mut u32, *x), + RuntimeValue::F64(x) => ptr::write(ptr as *mut u64, *x), + } + } + } + + // Get the trampoline to call for this function. + let exec_code_buf = self + .trampolines + .get(jit_code, isa, address, &signature, value_size)?; + + // Make all JIT code produced thus far executable. + jit_code.publish(); + + // Call the trampoline. + if let Err(message) = unsafe { + wasmtime_call_trampoline( + exec_code_buf, + values_vec.as_mut_ptr() as *mut u8, + callee_vmctx, + ) + } { + return Ok(ActionOutcome::Trapped { message }); + } + + // Load the return values out of `values_vec`. + let values = signature + .returns + .iter() + .enumerate() + .map(|(index, abi_param)| unsafe { + let ptr = values_vec.as_ptr().add(index); + + match abi_param.value_type { + ir::types::I32 => RuntimeValue::I32(ptr::read(ptr as *const i32)), + ir::types::I64 => RuntimeValue::I64(ptr::read(ptr as *const i64)), + ir::types::F32 => RuntimeValue::F32(ptr::read(ptr as *const u32)), + ir::types::F64 => RuntimeValue::F64(ptr::read(ptr as *const u64)), + other => panic!("unsupported value type {:?}", other), + } + }) + .collect(); + + Ok(ActionOutcome::Returned { values }) + } + + /// Returns a slice of the contents of allocated linear memory. + pub fn inspect_memory( + &self, + memory_name: &str, + start: usize, + len: usize, + ) -> Result<&[u8], ActionError> { + let address = match unsafe { self.instance.lookup_immutable(memory_name) } { + Some(Export::Memory { + address, + memory: _memory, + vmctx: _vmctx, + }) => address, + Some(_) => { + return Err(ActionError::Kind(format!( + "exported item \"{}\" is not a linear memory", + memory_name + ))) + } + None => { + return Err(ActionError::Field(format!( + "no export named \"{}\"", + memory_name + ))) + } + }; + + Ok(unsafe { + let memory_def = &*address; + &slice::from_raw_parts(memory_def.base, memory_def.current_length)[start..start + len] + }) + } + + /// Read a global in this `Instance` identified by an export name. + pub fn get(&self, global_name: &str) -> Result { + let (address, global) = match unsafe { self.instance.lookup_immutable(global_name) } { + Some(Export::Global { address, global }) => (address, global), + Some(_) => { + return Err(ActionError::Kind(format!( + "exported item \"{}\" is not a global variable", + global_name + ))) + } + None => { + return Err(ActionError::Field(format!( + "no export named \"{}\"", + global_name + ))) + } + }; + + unsafe { + let global_def = &*address; + Ok(match global.ty { + ir::types::I32 => RuntimeValue::I32(*global_def.as_i32()), + ir::types::I64 => RuntimeValue::I64(*global_def.as_i64()), + ir::types::F32 => RuntimeValue::F32(*global_def.as_f32_bits()), + ir::types::F64 => RuntimeValue::F64(*global_def.as_f64_bits()), + other => { + return Err(ActionError::Type(format!( + "global with type {} not supported", + other + ))) + } + }) + } + } +} + +fn allocate_functions( + jit_code: &mut JITCode, + compilation: Compilation, +) -> Result, String> { + let mut result = PrimaryMap::with_capacity(compilation.functions.len()); + for (_, body) in compilation.functions.into_iter() { + let fatptr: *mut [VMFunctionBody] = jit_code.allocate_copy_of_byte_slice(body)?; + result.push(fatptr); + } + Ok(result) +} diff --git a/lib/execute/src/code.rs b/lib/execute/src/jit_code.rs similarity index 97% rename from lib/execute/src/code.rs rename to lib/execute/src/jit_code.rs index 6461474cf3..3598e27c12 100644 --- a/lib/execute/src/code.rs +++ b/lib/execute/src/jit_code.rs @@ -7,15 +7,15 @@ use std::{cmp, mem}; use wasmtime_runtime::{Mmap, VMFunctionBody}; /// Memory manager for executable code. -pub struct Code { +pub struct JITCode { current: Mmap, mmaps: Vec, position: usize, published: usize, } -impl Code { - /// Create a new `Code` instance. +impl JITCode { + /// Create a new `JITCode` instance. pub fn new() -> Self { Self { current: Mmap::new(), diff --git a/lib/execute/src/lib.rs b/lib/execute/src/lib.rs index 052041599c..4a8ce2fea6 100644 --- a/lib/execute/src/lib.rs +++ b/lib/execute/src/lib.rs @@ -39,16 +39,17 @@ extern crate failure; extern crate failure_derive; mod action; -mod code; -mod export; +mod instance_plus; +mod jit_code; mod link; -mod world; +mod resolver; +mod trampoline_park; pub use action::{ActionError, ActionOutcome, RuntimeValue}; -pub use code::Code; -pub use export::{Export, NullResolver, Resolver}; +pub use instance_plus::InstancePlus; +pub use jit_code::JITCode; pub use link::link_module; -pub use world::InstanceWorld; +pub use resolver::{NullResolver, Resolver}; #[cfg(not(feature = "std"))] mod std { diff --git a/lib/execute/src/link.rs b/lib/execute/src/link.rs index 724847ac81..4f69280259 100644 --- a/lib/execute/src/link.rs +++ b/lib/execute/src/link.rs @@ -1,16 +1,18 @@ use cranelift_codegen::binemit::Reloc; use cranelift_entity::PrimaryMap; use cranelift_wasm::{DefinedFuncIndex, Global, GlobalInit, Memory, Table, TableElementType}; -use export::{Export, FunctionExport, Resolver}; +use resolver::Resolver; use std::ptr::write_unaligned; use std::string::String; use std::vec::Vec; use wasmtime_environ::{ MemoryPlan, MemoryStyle, Module, Relocation, RelocationTarget, Relocations, TablePlan, - TableStyle, }; use wasmtime_runtime::libcalls; -use wasmtime_runtime::{Imports, VMFunctionBody, VMGlobalImport, VMMemoryImport, VMTableImport}; +use wasmtime_runtime::{ + Export, Imports, VMFunctionBody, VMFunctionImport, VMGlobalImport, VMMemoryImport, + VMTableImport, +}; /// A link error, such as incompatible or unmatched imports/exports. #[derive(Fail, Debug)] @@ -28,7 +30,11 @@ pub fn link_module( for (index, (ref module_name, ref field)) in module.imported_funcs.iter() { match resolver.resolve(module_name, field) { Some(export_value) => match export_value { - Export::Function(FunctionExport { address, signature }) => { + Export::Function { + address, + signature, + vmctx, + } => { let import_signature = &module.signatures[module.functions[index]]; if signature != *import_signature { // TODO: If the difference is in the calling convention, @@ -39,7 +45,10 @@ pub fn link_module( signature, import_signature) )); } - function_imports.push(address); + function_imports.push(VMFunctionImport { + body: address, + vmctx, + }); } Export::Table { .. } | Export::Memory { .. } | Export::Global { .. } => { return Err(LinkError(format!( @@ -104,12 +113,28 @@ pub fn link_module( memory, } => { let import_memory = &module.memory_plans[index]; - if is_memory_compatible(&memory, import_memory) { + if !is_memory_compatible(&memory, import_memory) { return Err(LinkError(format!( "{}/{}: exported memory incompatible with memory import", module_name, field ))); } + + // Sanity-check: Ensure that the imported memory has at least + // guard-page protections the importing module expects it to have. + match (memory.style, &import_memory.style) { + ( + MemoryStyle::Static { bound }, + MemoryStyle::Static { + bound: import_bound, + }, + ) => { + assert!(bound >= *import_bound); + } + _ => (), + } + assert!(memory.offset_guard_size >= import_memory.offset_guard_size); + memory_imports.push(VMMemoryImport { from: address, vmctx, @@ -161,17 +186,15 @@ pub fn link_module( } } - let imports = Imports::new( + // Apply relocations, now that we have virtual addresses for everything. + relocate(allocated_functions, relocations, &module); + + Ok(Imports::new( function_imports, table_imports, memory_imports, global_imports, - ); - - // Apply relocations, now that we have virtual addresses for everything. - relocate(&imports, allocated_functions, relocations, &module); - - Ok(imports) + )) } fn is_global_compatible(exported: &Global, imported: &Global) -> bool { @@ -193,14 +216,6 @@ fn is_global_compatible(exported: &Global, imported: &Global) -> bool { exported_ty == imported_ty && imported_mutability == exported_mutability } -fn is_table_style_compatible(exported_style: &TableStyle, imported_style: &TableStyle) -> bool { - match exported_style { - TableStyle::CallerChecksSignature => match imported_style { - TableStyle::CallerChecksSignature => true, - }, - } -} - fn is_table_element_type_compatible( exported_type: TableElementType, imported_type: TableElementType, @@ -225,7 +240,7 @@ fn is_table_compatible(exported: &TablePlan, imported: &TablePlan) -> bool { minimum: exported_minimum, maximum: exported_maximum, }, - style: exported_style, + style: _exported_style, } = exported; let TablePlan { table: @@ -234,30 +249,14 @@ fn is_table_compatible(exported: &TablePlan, imported: &TablePlan) -> bool { minimum: imported_minimum, maximum: imported_maximum, }, - style: imported_style, + style: _imported_style, } = imported; is_table_element_type_compatible(*exported_ty, *imported_ty) - && imported_minimum >= exported_minimum - && imported_maximum <= exported_maximum - && is_table_style_compatible(imported_style, exported_style) -} - -fn is_memory_style_compatible(exported_style: &MemoryStyle, imported_style: &MemoryStyle) -> bool { - match exported_style { - MemoryStyle::Dynamic => match imported_style { - MemoryStyle::Dynamic => true, - _ => false, - }, - MemoryStyle::Static { - bound: imported_bound, - } => match imported_style { - MemoryStyle::Static { - bound: exported_bound, - } => exported_bound >= imported_bound, - _ => false, - }, - } + && imported_minimum <= exported_minimum + && (imported_maximum.is_none() + || (!exported_maximum.is_none() + && imported_maximum.unwrap() >= exported_maximum.unwrap())) } fn is_memory_compatible(exported: &MemoryPlan, imported: &MemoryPlan) -> bool { @@ -268,8 +267,8 @@ fn is_memory_compatible(exported: &MemoryPlan, imported: &MemoryPlan) -> bool { maximum: exported_maximum, shared: exported_shared, }, - style: exported_style, - offset_guard_size: exported_offset_guard_size, + style: _exported_style, + offset_guard_size: _exported_offset_guard_size, } = exported; let MemoryPlan { memory: @@ -278,20 +277,19 @@ fn is_memory_compatible(exported: &MemoryPlan, imported: &MemoryPlan) -> bool { maximum: imported_maximum, shared: imported_shared, }, - style: imported_style, - offset_guard_size: imported_offset_guard_size, + style: _imported_style, + offset_guard_size: _imported_offset_guard_size, } = imported; - imported_minimum >= exported_minimum - && imported_maximum <= exported_maximum + imported_minimum <= exported_minimum + && (imported_maximum.is_none() + || (!exported_maximum.is_none() + && imported_maximum.unwrap() >= exported_maximum.unwrap())) && exported_shared == imported_shared - && is_memory_style_compatible(exported_style, imported_style) - && exported_offset_guard_size >= imported_offset_guard_size } /// Performs the relocations inside the function bytecode, provided the necessary metadata. fn relocate( - imports: &Imports, allocated_functions: &PrimaryMap, relocations: PrimaryMap>, module: &Module, @@ -305,7 +303,7 @@ fn relocate( let fatptr: *const [VMFunctionBody] = allocated_functions[f]; fatptr as *const VMFunctionBody as usize } - None => imports.functions[index] as usize, + None => panic!("direct call to import"), }, RelocationTarget::Memory32Grow => wasmtime_memory32_grow as usize, RelocationTarget::Memory32Size => wasmtime_memory32_size as usize, diff --git a/lib/execute/src/resolver.rs b/lib/execute/src/resolver.rs new file mode 100644 index 0000000000..452cd148a9 --- /dev/null +++ b/lib/execute/src/resolver.rs @@ -0,0 +1,16 @@ +use wasmtime_runtime::Export; + +/// Import resolver connects imports with available exported values. +pub trait Resolver { + /// Resolve the given module/field combo. + fn resolve(&mut self, module: &str, field: &str) -> Option; +} + +/// `Resolver` implementation that always resolves to `None`. +pub struct NullResolver {} + +impl Resolver for NullResolver { + fn resolve(&mut self, _module: &str, _field: &str) -> Option { + None + } +} diff --git a/lib/execute/src/trampoline_park.rs b/lib/execute/src/trampoline_park.rs new file mode 100644 index 0000000000..34fecd85e5 --- /dev/null +++ b/lib/execute/src/trampoline_park.rs @@ -0,0 +1,152 @@ +use action::ActionError; +use cranelift_codegen::ir::InstBuilder; +use cranelift_codegen::Context; +use cranelift_codegen::{binemit, ir, isa}; +use cranelift_frontend::{FunctionBuilder, FunctionBuilderContext}; +use jit_code::JITCode; +use std::collections::HashMap; +use std::fmt; +use wasmtime_environ::{CompileError, RelocSink}; +use wasmtime_runtime::{InstantiationError, VMFunctionBody}; + +pub struct TrampolinePark { + /// Memoized per-function trampolines. + memoized: HashMap<*const VMFunctionBody, *const VMFunctionBody>, + + /// The `FunctionBuilderContext`, shared between function compilations. + fn_builder_ctx: FunctionBuilderContext, +} + +impl TrampolinePark { + pub fn new() -> Self { + Self { + memoized: HashMap::new(), + fn_builder_ctx: FunctionBuilderContext::new(), + } + } + + pub fn get( + &mut self, + jit_code: &mut JITCode, + isa: &isa::TargetIsa, + callee_address: *const VMFunctionBody, + signature: &ir::Signature, + value_size: usize, + ) -> Result<*const VMFunctionBody, ActionError> { + use std::collections::hash_map::Entry::{Occupied, Vacant}; + Ok(match self.memoized.entry(callee_address) { + Occupied(entry) => *entry.get(), + Vacant(entry) => { + let body = make_trampoline( + &mut self.fn_builder_ctx, + jit_code, + isa, + callee_address, + signature, + value_size, + )?; + entry.insert(body); + body + } + }) + } +} + +impl fmt::Debug for TrampolinePark { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + // The `fn_builder_ctx` field is just a cache and has no logical state. + write!(f, "{:?}", self.memoized) + } +} + +fn make_trampoline( + fn_builder_ctx: &mut FunctionBuilderContext, + jit_code: &mut JITCode, + isa: &isa::TargetIsa, + callee_address: *const VMFunctionBody, + signature: &ir::Signature, + value_size: usize, +) -> Result<*const VMFunctionBody, ActionError> { + let pointer_type = isa.pointer_type(); + let mut wrapper_sig = ir::Signature::new(isa.frontend_config().default_call_conv); + + // Add the `values_vec` parameter. + wrapper_sig.params.push(ir::AbiParam::new(pointer_type)); + // Add the `vmctx` parameter. + wrapper_sig.params.push(ir::AbiParam::special( + pointer_type, + ir::ArgumentPurpose::VMContext, + )); + + let mut context = Context::new(); + context.func = ir::Function::with_name_signature(ir::ExternalName::user(0, 0), wrapper_sig); + + { + let mut builder = FunctionBuilder::new(&mut context.func, fn_builder_ctx); + let block0 = builder.create_ebb(); + + builder.append_ebb_params_for_function_params(block0); + builder.switch_to_block(block0); + builder.seal_block(block0); + + let mut callee_args = Vec::new(); + let pointer_type = isa.pointer_type(); + + let (values_vec_ptr_val, vmctx_ptr_val) = { + let params = builder.func.dfg.ebb_params(block0); + (params[0], params[1]) + }; + + // Load the argument values out of `values_vec`. + let mflags = ir::MemFlags::trusted(); + for (i, r) in signature.params.iter().enumerate() { + let value = match r.purpose { + ir::ArgumentPurpose::Normal => builder.ins().load( + r.value_type, + mflags, + values_vec_ptr_val, + (i * value_size) as i32, + ), + ir::ArgumentPurpose::VMContext => vmctx_ptr_val, + other => panic!("unsupported argument purpose {}", other), + }; + callee_args.push(value); + } + + let new_sig = builder.import_signature(signature.clone()); + + // TODO: It's possible to make this a direct call. We just need Cranelift + // to support functions declared with an immediate integer address. + // ExternalName::Absolute(u64). Let's do it. + let callee_value = builder.ins().iconst(pointer_type, callee_address as i64); + let call = builder + .ins() + .call_indirect(new_sig, callee_value, &callee_args); + + let results = builder.func.dfg.inst_results(call).to_vec(); + + // Store the return values into `values_vec`. + let mflags = ir::MemFlags::trusted(); + for (i, r) in results.iter().enumerate() { + builder + .ins() + .store(mflags, *r, values_vec_ptr_val, (i * value_size) as i32); + } + + builder.ins().return_(&[]); + builder.finalize() + } + + let mut code_buf: Vec = Vec::new(); + let mut reloc_sink = RelocSink::new(); + let mut trap_sink = binemit::NullTrapSink {}; + context + .compile_and_emit(isa, &mut code_buf, &mut reloc_sink, &mut trap_sink) + .map_err(|error| ActionError::Compile(CompileError::Codegen(error)))?; + assert!(reloc_sink.func_relocs.is_empty()); + + Ok(jit_code + .allocate_copy_of_byte_slice(&code_buf) + .map_err(|message| ActionError::Instantiate(InstantiationError::Resource(message)))? + .as_ptr()) +} diff --git a/lib/execute/src/world.rs b/lib/execute/src/world.rs deleted file mode 100644 index 2a14858c1f..0000000000 --- a/lib/execute/src/world.rs +++ /dev/null @@ -1,553 +0,0 @@ -use action::{ActionError, ActionOutcome, RuntimeValue}; -use code::Code; -use cranelift_codegen::ir::InstBuilder; -use cranelift_codegen::Context; -use cranelift_codegen::{binemit, ir, isa}; -use cranelift_entity::{BoxedSlice, EntityRef, PrimaryMap}; -use cranelift_frontend::{FunctionBuilder, FunctionBuilderContext}; -use cranelift_wasm::{ - DefinedFuncIndex, DefinedGlobalIndex, DefinedMemoryIndex, DefinedTableIndex, FuncIndex, - GlobalIndex, MemoryIndex, TableIndex, -}; -use export::Resolver; -use link::link_module; -use std::cmp::max; -use std::collections::HashMap; -use std::slice; -use std::string::String; -use std::vec::Vec; -use std::{mem, ptr}; -use wasmtime_environ::{ - compile_module, Compilation, CompileError, Export, Module, ModuleEnvironment, RelocSink, - Tunables, -}; -use wasmtime_runtime::{ - wasmtime_call_trampoline, wasmtime_init_eager, wasmtime_init_finish, Instance, VMContext, - VMFunctionBody, VMGlobalDefinition, VMGlobalImport, VMMemoryDefinition, VMMemoryImport, - VMTableDefinition, VMTableImport, -}; - -/// A module, an instance of that module, and accompanying compilation artifacts. -/// -/// TODO: Rename and reorganize this. -pub struct InstanceWorld { - module: Module, - instance: Instance, - - /// Pointers to functions in executable memory. - finished_functions: BoxedSlice, - - /// Trampolines for calling into JIT code. - trampolines: TrampolinePark, -} - -impl InstanceWorld { - /// Create a new `InstanceWorld` by compiling the wasm module in `data` and instatiating it. - /// - /// `finished_functions` holds the function bodies - /// which have been placed in executable memory and linked. - pub fn new( - code: &mut Code, - isa: &isa::TargetIsa, - data: &[u8], - resolver: &mut Resolver, - ) -> Result { - let mut module = Module::new(); - // TODO: Allow the tunables to be overridden. - let tunables = Tunables::default(); - let (lazy_function_body_inputs, lazy_data_initializers) = { - let environ = ModuleEnvironment::new(isa, &mut module, tunables); - - let translation = environ - .translate(&data) - .map_err(|error| ActionError::Compile(CompileError::Wasm(error)))?; - - ( - translation.lazy.function_body_inputs, - translation.lazy.data_initializers, - ) - }; - - let (compilation, relocations) = compile_module(&module, &lazy_function_body_inputs, isa) - .map_err(ActionError::Compile)?; - - let allocated_functions = - allocate_functions(code, compilation).map_err(ActionError::Resource)?; - - let imports = link_module(&module, &allocated_functions, relocations, resolver) - .map_err(ActionError::Link)?; - - let finished_functions: BoxedSlice = - allocated_functions - .into_iter() - .map(|(_index, allocated)| { - let fatptr: *const [VMFunctionBody] = *allocated; - fatptr as *const VMFunctionBody - }) - .collect::>() - .into_boxed_slice(); - - let instance = Instance::new( - &module, - &finished_functions, - imports, - &lazy_data_initializers, - ) - .map_err(ActionError::Resource)?; - - let fn_builder_ctx = FunctionBuilderContext::new(); - - let mut result = Self { - module, - instance, - finished_functions, - trampolines: TrampolinePark { - memo: HashMap::new(), - fn_builder_ctx, - }, - }; - - // The WebAssembly spec specifies that the start function is - // invoked automatically at instantiation time. - match result.invoke_start_function(code, isa)? { - ActionOutcome::Returned { .. } => {} - ActionOutcome::Trapped { message } => { - // Instantiation fails if the start function traps. - return Err(ActionError::Start(message)); - } - } - - Ok(result) - } - - fn get_imported_function(&self, index: FuncIndex) -> Option<*const VMFunctionBody> { - if index.index() < self.module.imported_funcs.len() { - Some(unsafe { self.instance.vmctx().imported_function(index) }) - } else { - None - } - } - - // TODO: Add an accessor for table elements. - #[allow(dead_code)] - fn get_imported_table(&self, index: TableIndex) -> Option<&VMTableImport> { - if index.index() < self.module.imported_tables.len() { - Some(unsafe { self.instance.vmctx().imported_table(index) }) - } else { - None - } - } - - fn get_imported_memory(&self, index: MemoryIndex) -> Option<&VMMemoryImport> { - if index.index() < self.module.imported_memories.len() { - Some(unsafe { self.instance.vmctx().imported_memory(index) }) - } else { - None - } - } - - fn get_imported_global(&self, index: GlobalIndex) -> Option<&VMGlobalImport> { - if index.index() < self.module.imported_globals.len() { - Some(unsafe { self.instance.vmctx().imported_global(index) }) - } else { - None - } - } - - fn get_finished_function(&self, index: DefinedFuncIndex) -> Option<*const VMFunctionBody> { - self.finished_functions.get(index).cloned() - } - - // TODO: Add an accessor for table elements. - #[allow(dead_code)] - fn get_defined_table(&self, index: DefinedTableIndex) -> Option<&VMTableDefinition> { - if self.module.table_index(index).index() < self.module.table_plans.len() { - Some(unsafe { self.instance.vmctx().table(index) }) - } else { - None - } - } - - fn get_defined_memory(&self, index: DefinedMemoryIndex) -> Option<&VMMemoryDefinition> { - if self.module.memory_index(index).index() < self.module.memory_plans.len() { - Some(unsafe { self.instance.vmctx().memory(index) }) - } else { - None - } - } - - fn get_defined_global(&self, index: DefinedGlobalIndex) -> Option<&VMGlobalDefinition> { - if self.module.global_index(index).index() < self.module.globals.len() { - Some(unsafe { self.instance.vmctx().global(index) }) - } else { - None - } - } - - /// Invoke a function in this `InstanceWorld` by name. - pub fn invoke( - &mut self, - code: &mut Code, - isa: &isa::TargetIsa, - function_name: &str, - args: &[RuntimeValue], - ) -> Result { - let fn_index = match self.module.exports.get(function_name) { - Some(Export::Function(index)) => *index, - Some(_) => { - return Err(ActionError::Kind(format!( - "exported item \"{}\" is not a function", - function_name - ))) - } - None => { - return Err(ActionError::Field(format!( - "no export named \"{}\"", - function_name - ))) - } - }; - - self.invoke_by_index(code, isa, fn_index, args) - } - - /// Invoke the WebAssembly start function of the instance, if one is present. - fn invoke_start_function( - &mut self, - code: &mut Code, - isa: &isa::TargetIsa, - ) -> Result { - if let Some(start_index) = self.module.start_func { - self.invoke_by_index(code, isa, start_index, &[]) - } else { - // No start function, just return nothing. - Ok(ActionOutcome::Returned { values: vec![] }) - } - } - - /// Calls the given indexed function, passing its return values and returning - /// its results. - fn invoke_by_index( - &mut self, - code: &mut Code, - isa: &isa::TargetIsa, - fn_index: FuncIndex, - args: &[RuntimeValue], - ) -> Result { - let callee_address = match self.module.defined_func_index(fn_index) { - Some(def_fn_index) => self - .get_finished_function(def_fn_index) - .ok_or_else(|| ActionError::Index(def_fn_index.index() as u64))?, - None => self - .get_imported_function(fn_index) - .ok_or_else(|| ActionError::Index(fn_index.index() as u64))?, - }; - - // Rather than writing inline assembly to jump to the code region, we use the fact that - // the Rust ABI for calling a function with no arguments and no return values matches the one - // of the generated code. Thanks to this, we can transmute the code region into a first-class - // Rust function and call it. - // Ensure that our signal handlers are ready for action. - wasmtime_init_eager(); - wasmtime_init_finish(self.instance.vmctx_mut()); - - let signature = &self.module.signatures[self.module.functions[fn_index]]; - let vmctx: *mut VMContext = self.instance.vmctx_mut(); - - for (index, value) in args.iter().enumerate() { - assert_eq!(value.value_type(), signature.params[index].value_type); - } - - // TODO: Support values larger than u64. - let mut values_vec: Vec = Vec::new(); - let value_size = mem::size_of::(); - values_vec.resize(max(signature.params.len(), signature.returns.len()), 0u64); - - // Store the argument values into `values_vec`. - for (index, arg) in args.iter().enumerate() { - unsafe { - let ptr = values_vec.as_mut_ptr().add(index); - - match arg { - RuntimeValue::I32(x) => ptr::write(ptr as *mut i32, *x), - RuntimeValue::I64(x) => ptr::write(ptr as *mut i64, *x), - RuntimeValue::F32(x) => ptr::write(ptr as *mut u32, *x), - RuntimeValue::F64(x) => ptr::write(ptr as *mut u64, *x), - } - } - } - - // Store the vmctx value into `values_vec`. - unsafe { - let ptr = values_vec.as_mut_ptr().add(args.len()); - ptr::write(ptr as *mut usize, vmctx as usize) - } - - // Get the trampoline to call for this function. - let exec_code_buf = - self.trampolines - .get(code, isa, callee_address, &signature, value_size)?; - - // Make all JIT code produced thus far executable. - code.publish(); - - // Call the trampoline. - if let Err(message) = unsafe { - wasmtime_call_trampoline( - exec_code_buf, - values_vec.as_mut_ptr() as *mut u8, - self.instance.vmctx_mut(), - ) - } { - return Ok(ActionOutcome::Trapped { message }); - } - - // Load the return values out of `values_vec`. - let values = signature - .returns - .iter() - .enumerate() - .map(|(index, abi_param)| unsafe { - let ptr = values_vec.as_ptr().add(index); - - match abi_param.value_type { - ir::types::I32 => RuntimeValue::I32(ptr::read(ptr as *const i32)), - ir::types::I64 => RuntimeValue::I64(ptr::read(ptr as *const i64)), - ir::types::F32 => RuntimeValue::F32(ptr::read(ptr as *const u32)), - ir::types::F64 => RuntimeValue::F64(ptr::read(ptr as *const u64)), - other => panic!("unsupported value type {:?}", other), - } - }) - .collect(); - - Ok(ActionOutcome::Returned { values }) - } - - /// Read a global in this `InstanceWorld` by name. - pub fn get(&self, global_name: &str) -> Result { - let global_index = match self.module.exports.get(global_name) { - Some(Export::Global(index)) => *index, - Some(_) => { - return Err(ActionError::Kind(format!( - "exported item \"{}\" is not a global", - global_name - ))) - } - None => { - return Err(ActionError::Field(format!( - "no export named \"{}\"", - global_name - ))) - } - }; - - self.get_by_index(global_index) - } - - /// Reads the value of the indexed global variable in `module`. - pub fn get_by_index(&self, global_index: GlobalIndex) -> Result { - let global_address = match self.module.defined_global_index(global_index) { - Some(def_global_index) => self - .get_defined_global(def_global_index) - .ok_or_else(|| ActionError::Index(def_global_index.index() as u64))?, - None => { - let from: *const VMGlobalDefinition = self - .get_imported_global(global_index) - .ok_or_else(|| ActionError::Index(global_index.index() as u64))? - .from; - from - } - }; - let global_def = unsafe { &*global_address }; - - unsafe { - Ok( - match self - .module - .globals - .get(global_index) - .ok_or_else(|| ActionError::Index(global_index.index() as u64))? - .ty - { - ir::types::I32 => RuntimeValue::I32(*global_def.as_i32()), - ir::types::I64 => RuntimeValue::I64(*global_def.as_i64()), - ir::types::F32 => RuntimeValue::F32(*global_def.as_f32_bits()), - ir::types::F64 => RuntimeValue::F64(*global_def.as_f64_bits()), - other => { - return Err(ActionError::Type(format!( - "global with type {} not supported", - other - ))) - } - }, - ) - } - } - - /// Returns a slice of the contents of allocated linear memory. - pub fn inspect_memory( - &self, - memory_index: MemoryIndex, - address: usize, - len: usize, - ) -> Result<&[u8], ActionError> { - let memory_address = match self.module.defined_memory_index(memory_index) { - Some(def_memory_index) => self - .get_defined_memory(def_memory_index) - .ok_or_else(|| ActionError::Index(def_memory_index.index() as u64))?, - None => { - let from: *const VMMemoryDefinition = self - .get_imported_memory(memory_index) - .ok_or_else(|| ActionError::Index(memory_index.index() as u64))? - .from; - from - } - }; - let memory_def = unsafe { &*memory_address }; - - Ok(unsafe { - &slice::from_raw_parts(memory_def.base, memory_def.current_length) - [address..address + len] - }) - } -} - -fn allocate_functions( - code: &mut Code, - compilation: Compilation, -) -> Result, String> { - let mut result = PrimaryMap::with_capacity(compilation.functions.len()); - for (_, body) in compilation.functions.into_iter() { - let fatptr: *mut [VMFunctionBody] = code.allocate_copy_of_byte_slice(body)?; - result.push(fatptr); - } - Ok(result) -} - -struct TrampolinePark { - /// Memorized per-function trampolines. - memo: HashMap<*const VMFunctionBody, *const VMFunctionBody>, - - /// The `FunctionBuilderContext`, shared between function compilations. - fn_builder_ctx: FunctionBuilderContext, -} - -impl TrampolinePark { - fn get( - &mut self, - code: &mut Code, - isa: &isa::TargetIsa, - callee_address: *const VMFunctionBody, - signature: &ir::Signature, - value_size: usize, - ) -> Result<*const VMFunctionBody, ActionError> { - use std::collections::hash_map::Entry::{Occupied, Vacant}; - Ok(match self.memo.entry(callee_address) { - Occupied(entry) => *entry.get(), - Vacant(entry) => { - let body = make_trampoline( - &mut self.fn_builder_ctx, - code, - isa, - callee_address, - signature, - value_size, - )?; - entry.insert(body); - body - } - }) - } -} - -fn make_trampoline( - fn_builder_ctx: &mut FunctionBuilderContext, - code: &mut Code, - isa: &isa::TargetIsa, - callee_address: *const VMFunctionBody, - signature: &ir::Signature, - value_size: usize, -) -> Result<*const VMFunctionBody, ActionError> { - let pointer_type = isa.pointer_type(); - let mut wrapper_sig = ir::Signature::new(isa.frontend_config().default_call_conv); - - // Add the `values_vec` parameter. - wrapper_sig.params.push(ir::AbiParam::new(pointer_type)); - // Add the `vmctx` parameter. - wrapper_sig.params.push(ir::AbiParam::special( - pointer_type, - ir::ArgumentPurpose::VMContext, - )); - - let mut context = Context::new(); - context.func = ir::Function::with_name_signature(ir::ExternalName::user(0, 0), wrapper_sig); - - { - let mut builder = FunctionBuilder::new(&mut context.func, fn_builder_ctx); - let block0 = builder.create_ebb(); - - builder.append_ebb_params_for_function_params(block0); - builder.switch_to_block(block0); - builder.seal_block(block0); - - let mut callee_args = Vec::new(); - let pointer_type = isa.pointer_type(); - - let (values_vec_ptr_val, vmctx_ptr_val) = { - let params = builder.func.dfg.ebb_params(block0); - (params[0], params[1]) - }; - - // Load the argument values out of `values_vec`. - let mflags = ir::MemFlags::trusted(); - for (i, r) in signature.params.iter().enumerate() { - let value = match r.purpose { - ir::ArgumentPurpose::Normal => builder.ins().load( - r.value_type, - mflags, - values_vec_ptr_val, - (i * value_size) as i32, - ), - ir::ArgumentPurpose::VMContext => vmctx_ptr_val, - other => panic!("unsupported argument purpose {}", other), - }; - callee_args.push(value); - } - - let new_sig = builder.import_signature(signature.clone()); - - // TODO: It's possible to make this a direct call. We just need Cranelift - // to support functions declared with an immediate integer address. - // ExternalName::Absolute(u64). Let's do it. - let callee_value = builder.ins().iconst(pointer_type, callee_address as i64); - let call = builder - .ins() - .call_indirect(new_sig, callee_value, &callee_args); - - let results = builder.func.dfg.inst_results(call).to_vec(); - - // Store the return values into `values_vec`. - let mflags = ir::MemFlags::trusted(); - for (i, r) in results.iter().enumerate() { - builder - .ins() - .store(mflags, *r, values_vec_ptr_val, (i * value_size) as i32); - } - - builder.ins().return_(&[]); - builder.finalize() - } - - let mut code_buf: Vec = Vec::new(); - let mut reloc_sink = RelocSink::new(); - let mut trap_sink = binemit::NullTrapSink {}; - context - .compile_and_emit(isa, &mut code_buf, &mut reloc_sink, &mut trap_sink) - .map_err(|error| ActionError::Compile(CompileError::Codegen(error)))?; - assert!(reloc_sink.func_relocs.is_empty()); - - Ok(code - .allocate_copy_of_byte_slice(&code_buf) - .map_err(ActionError::Resource)? - .as_ptr()) -} diff --git a/lib/execute/src/export.rs b/lib/runtime/src/export.rs similarity index 68% rename from lib/execute/src/export.rs rename to lib/runtime/src/export.rs index d1e1f4e78f..56e256bf48 100644 --- a/lib/execute/src/export.rs +++ b/lib/runtime/src/export.rs @@ -1,22 +1,21 @@ use cranelift_codegen::ir; use cranelift_wasm::Global; -use wasmtime_environ::{MemoryPlan, TablePlan}; -use wasmtime_runtime::{ +use vmcontext::{ VMContext, VMFunctionBody, VMGlobalDefinition, VMMemoryDefinition, VMTableDefinition, }; - -/// An exported function. -pub struct FunctionExport { - /// The address of the native-code function. - pub address: *const VMFunctionBody, - /// The function signature declaration, used for compatibilty checking. - pub signature: ir::Signature, -} +use wasmtime_environ::{MemoryPlan, TablePlan}; /// The value of an export passed from one instance to another. pub enum Export { /// A function export value. - Function(FunctionExport), + Function { + /// The address of the native-code function. + address: *const VMFunctionBody, + /// The function signature declaration, used for compatibilty checking. + signature: ir::Signature, + /// Pointer to the containing VMContext. + vmctx: *mut VMContext, + }, /// A table export value. Table { @@ -49,8 +48,16 @@ pub enum Export { impl Export { /// Construct a function export value. - pub fn function(address: *const VMFunctionBody, signature: ir::Signature) -> Self { - Export::Function(FunctionExport { address, signature }) + pub fn function( + address: *const VMFunctionBody, + signature: ir::Signature, + vmctx: *mut VMContext, + ) -> Self { + Export::Function { + address, + signature, + vmctx, + } } /// Construct a table export value. @@ -80,18 +87,3 @@ impl Export { Export::Global { address, global } } } - -/// Import resolver connects imports with available exported values. -pub trait Resolver { - /// Resolve the given module/field combo. - fn resolve(&mut self, module: &str, field: &str) -> Option; -} - -/// `Resolver` implementation that always resolves to `None`. -pub struct NullResolver {} - -impl Resolver for NullResolver { - fn resolve(&mut self, _module: &str, _field: &str) -> Option { - None - } -} diff --git a/lib/runtime/src/imports.rs b/lib/runtime/src/imports.rs index 363edac7a8..c42d7e9e44 100644 --- a/lib/runtime/src/imports.rs +++ b/lib/runtime/src/imports.rs @@ -1,12 +1,12 @@ use cranelift_entity::{BoxedSlice, PrimaryMap}; use cranelift_wasm::{FuncIndex, GlobalIndex, MemoryIndex, TableIndex}; -use vmcontext::{VMFunctionBody, VMGlobalImport, VMMemoryImport, VMTableImport}; +use vmcontext::{VMFunctionImport, VMGlobalImport, VMMemoryImport, VMTableImport}; /// Resolved import pointers. #[derive(Debug)] pub struct Imports { /// Resolved addresses for imported functions. - pub functions: BoxedSlice, + pub functions: BoxedSlice, /// Resolved addresses for imported tables. pub tables: BoxedSlice, @@ -21,7 +21,7 @@ pub struct Imports { impl Imports { /// Construct a new `Imports` instance. pub fn new( - function_imports: PrimaryMap, + function_imports: PrimaryMap, table_imports: PrimaryMap, memory_imports: PrimaryMap, global_imports: PrimaryMap, diff --git a/lib/runtime/src/instance.rs b/lib/runtime/src/instance.rs index 046e62681b..4322cddbe5 100644 --- a/lib/runtime/src/instance.rs +++ b/lib/runtime/src/instance.rs @@ -4,13 +4,18 @@ use cranelift_entity::EntityRef; use cranelift_entity::{BoxedSlice, PrimaryMap}; use cranelift_wasm::{ - DefinedFuncIndex, DefinedGlobalIndex, DefinedMemoryIndex, DefinedTableIndex, FuncIndex, + DefinedFuncIndex, DefinedGlobalIndex, DefinedMemoryIndex, DefinedTableIndex, GlobalInit, }; +use export::Export; use imports::Imports; use memory::LinearMemory; use sig_registry::SignatureRegistry; +use signalhandlers::{wasmtime_init_eager, wasmtime_init_finish}; +use std::rc::Rc; +use std::slice; use std::string::String; use table::Table; +use traphandlers::wasmtime_call; use vmcontext::{ VMCallerCheckedAnyfunc, VMContext, VMFunctionBody, VMGlobalDefinition, VMMemoryDefinition, VMTableDefinition, @@ -20,6 +25,9 @@ use wasmtime_environ::{DataInitializer, Module}; /// An Instance of a WebAssemby module. #[derive(Debug)] pub struct Instance { + /// The `Module` this `Instance` was instantiated from. + module: Rc, + /// WebAssembly linear memory data. memories: BoxedSlice, @@ -33,6 +41,9 @@ pub struct Instance { /// Resolved imports. vmctx_imports: Imports, + /// Pointers to functions in executable memory. + finished_functions: BoxedSlice, + /// Table storage base address vector pointed to by vmctx. vmctx_tables: BoxedSlice, @@ -49,19 +60,20 @@ pub struct Instance { impl Instance { /// Create a new `Instance`. pub fn new( - module: &Module, - finished_functions: &BoxedSlice, + module: Rc, + finished_functions: BoxedSlice, mut vmctx_imports: Imports, - data_initializers: &[DataInitializer], - ) -> Result { - let mut sig_registry = instantiate_signatures(module); - let mut memories = instantiate_memories(module, data_initializers)?; - let mut tables = instantiate_tables( - module, - finished_functions, - &vmctx_imports.functions, - &mut sig_registry, - ); + data_initializers: Vec, + ) -> Result, InstantiationError> { + let mut sig_registry = create_and_initialize_signatures(&module); + let mut tables = create_tables(&module); + let mut memories = create_memories(&module)?; + + let mut vmctx_tables = tables + .values_mut() + .map(Table::vmtable) + .collect::>() + .into_boxed_slice(); let mut vmctx_memories = memories .values_mut() @@ -69,13 +81,7 @@ impl Instance { .collect::>() .into_boxed_slice(); - let mut vmctx_globals = instantiate_globals(module); - - let mut vmctx_tables = tables - .values_mut() - .map(Table::vmtable) - .collect::>() - .into_boxed_slice(); + let mut vmctx_globals = create_globals(&module); let vmctx_imported_functions_ptr = vmctx_imports .functions @@ -90,19 +96,21 @@ impl Instance { .as_mut_ptr(); let vmctx_imported_globals_ptr = vmctx_imports.globals.values_mut().into_slice().as_mut_ptr(); + let vmctx_tables_ptr = vmctx_tables.values_mut().into_slice().as_mut_ptr(); let vmctx_memories_ptr = vmctx_memories.values_mut().into_slice().as_mut_ptr(); let vmctx_globals_ptr = vmctx_globals.values_mut().into_slice().as_mut_ptr(); - let vmctx_tables_ptr = vmctx_tables.values_mut().into_slice().as_mut_ptr(); let vmctx_shared_signatures_ptr = sig_registry.vmshared_signatures(); - Ok(Self { + let mut result = Box::new(Self { + module, memories, tables, sig_registry, vmctx_imports, + finished_functions, + vmctx_tables, vmctx_memories, vmctx_globals, - vmctx_tables, vmctx: VMContext::new( vmctx_imported_functions_ptr, vmctx_imported_tables_ptr, @@ -113,7 +121,31 @@ impl Instance { vmctx_globals_ptr, vmctx_shared_signatures_ptr, ), - }) + }); + + // Check initializer bounds before initializing anything. + check_table_init_bounds(&mut *result)?; + check_memory_init_bounds(&mut *result, &data_initializers)?; + + // Apply the initializers. + initialize_tables(&mut *result)?; + initialize_memories(&mut *result, data_initializers)?; + initialize_globals(&mut *result); + + // Rather than writing inline assembly to jump to the code region, we use the fact that + // the Rust ABI for calling a function with no arguments and no return values matches the one + // of the generated code. Thanks to this, we can transmute the code region into a first-class + // Rust function and call it. + // Ensure that our signal handlers are ready for action. + // TODO: Move these calls out of `Instance`. + wasmtime_init_eager(); + wasmtime_init_finish(result.vmctx_mut()); + + // The WebAssembly spec specifies that the start function is + // invoked automatically at instantiation time. + result.invoke_start_function()?; + + Ok(result) } /// Return a reference to the vmctx used by JIT code. @@ -121,11 +153,21 @@ impl Instance { &self.vmctx } + /// Return a raw pointer to the vmctx used by JIT code. + pub fn vmctx_ptr(&self) -> *const VMContext { + self.vmctx() + } + /// Return a mutable reference to the vmctx used by JIT code. pub fn vmctx_mut(&mut self) -> &mut VMContext { &mut self.vmctx } + /// Return a mutable raw pointer to the vmctx used by JIT code. + pub fn vmctx_mut_ptr(&mut self) -> *mut VMContext { + self.vmctx_mut() + } + /// Return the offset from the vmctx pointer to its containing Instance. pub(crate) fn vmctx_offset() -> isize { offset_of!(Self, vmctx) as isize @@ -166,11 +208,195 @@ impl Instance { /// Return the number of imported memories. pub(crate) fn num_imported_memories(&self) -> usize { - self.vmctx_imports.functions.len() + self.vmctx_imports.memories.len() + } + + /// Invoke the WebAssembly start function of the instance, if one is present. + fn invoke_start_function(&mut self) -> Result<(), InstantiationError> { + if let Some(start_index) = self.module.start_func { + let (callee_address, callee_vmctx) = match self.module.defined_func_index(start_index) { + Some(defined_start_index) => { + let body = self + .finished_functions + .get(defined_start_index) + .expect("start function index is out of bounds") + .clone(); + (body, self.vmctx_mut() as *mut VMContext) + } + None => { + assert!(start_index.index() < self.module.imported_funcs.len()); + let import = unsafe { self.vmctx.imported_function(start_index) }; + (import.body, import.vmctx) + } + }; + + // Make the call. + unsafe { wasmtime_call(callee_address, callee_vmctx) } + .map_err(InstantiationError::StartTrap)?; + } + + Ok(()) + } + + /// Lookup an export with the given name. + pub fn lookup(&mut self, field: &str) -> Option { + if let Some(export) = self.module.exports.get(field) { + Some(match export { + wasmtime_environ::Export::Function(index) => { + let signature = self.module.signatures[self.module.functions[*index]].clone(); + let (address, vmctx) = + if let Some(def_index) = self.module.defined_func_index(*index) { + ( + self.finished_functions[def_index], + &mut self.vmctx as *mut VMContext, + ) + } else { + let import = unsafe { self.vmctx.imported_function(*index) }; + (import.body, import.vmctx) + }; + Export::Function { + address, + signature, + vmctx, + } + } + wasmtime_environ::Export::Table(index) => { + let (address, vmctx) = if let Some(def_index) = + self.module.defined_table_index(*index) + { + ( + unsafe { self.vmctx.table_mut(def_index) } as *mut VMTableDefinition, + &mut self.vmctx as *mut VMContext, + ) + } else { + let import = unsafe { self.vmctx.imported_table(*index) }; + (import.from, import.vmctx) + }; + Export::Table { + address, + vmctx, + table: self.module.table_plans[*index].clone(), + } + } + wasmtime_environ::Export::Memory(index) => { + let (address, vmctx) = if let Some(def_index) = + self.module.defined_memory_index(*index) + { + ( + unsafe { self.vmctx.memory_mut(def_index) } as *mut VMMemoryDefinition, + &mut self.vmctx as *mut VMContext, + ) + } else { + let import = unsafe { self.vmctx.imported_memory(*index) }; + (import.from, import.vmctx) + }; + Export::Memory { + address, + vmctx, + memory: self.module.memory_plans[*index].clone(), + } + } + wasmtime_environ::Export::Global(index) => Export::Global { + address: if let Some(def_index) = self.module.defined_global_index(*index) { + unsafe { self.vmctx.global_mut(def_index) } + } else { + unsafe { self.vmctx.imported_global(*index).from } + }, + global: self.module.globals[*index].clone(), + }, + }) + } else { + None + } + } + + /// Lookup an export with the given name. This takes an immutable reference, + /// and the result is an `Export` that can only be used to read, not write. + /// This requirement is not enforced in the type system, so this function is + /// unsafe. + pub unsafe fn lookup_immutable(&self, field: &str) -> Option { + let temporary_mut = &mut *(self as *const Instance as *mut Instance); + temporary_mut.lookup(field) } } -fn instantiate_signatures(module: &Module) -> SignatureRegistry { +fn check_table_init_bounds(instance: &mut Instance) -> Result<(), InstantiationError> { + for init in &instance.module.table_elements { + // TODO: Refactor this. + let mut start = init.offset; + if let Some(base) = init.base { + let global = if let Some(def_index) = instance.module.defined_global_index(base) { + unsafe { instance.vmctx.global_mut(def_index) } + } else { + unsafe { instance.vmctx.imported_global(base).from } + }; + start += unsafe { *(&*global).as_i32() } as u32 as usize; + } + + // TODO: Refactor this. + let slice = if let Some(defined_table_index) = + instance.module.defined_table_index(init.table_index) + { + instance.tables[defined_table_index].as_mut() + } else { + let import = &instance.vmctx_imports.tables[init.table_index]; + let foreign_instance = unsafe { (&mut *(import).vmctx).instance() }; + let foreign_table = unsafe { &mut *(import).from }; + let foreign_index = foreign_instance.vmctx().table_index(foreign_table); + foreign_instance.tables[foreign_index].as_mut() + }; + + if slice.get_mut(start..start + init.elements.len()).is_none() { + return Err(InstantiationError::Link( + "elements segment does not fit".to_owned(), + )); + } + } + + Ok(()) +} + +fn check_memory_init_bounds( + instance: &mut Instance, + data_initializers: &[DataInitializer], +) -> Result<(), InstantiationError> { + for init in data_initializers { + // TODO: Refactor this. + let mut start = init.offset; + if let Some(base) = init.base { + let global = if let Some(def_index) = instance.module.defined_global_index(base) { + unsafe { instance.vmctx.global_mut(def_index) } + } else { + unsafe { instance.vmctx.imported_global(base).from } + }; + start += unsafe { *(&*global).as_i32() } as u32 as usize; + } + + // TODO: Refactor this. + let memory = if let Some(defined_memory_index) = + instance.module.defined_memory_index(init.memory_index) + { + unsafe { instance.vmctx.memory(defined_memory_index) } + } else { + let import = &instance.vmctx_imports.memories[init.memory_index]; + let foreign_instance = unsafe { (&mut *(import).vmctx).instance() }; + let foreign_memory = unsafe { &mut *(import).from }; + let foreign_index = foreign_instance.vmctx().memory_index(foreign_memory); + unsafe { foreign_instance.vmctx.memory(foreign_index) } + }; + let mem_slice = unsafe { slice::from_raw_parts_mut(memory.base, memory.current_length) }; + + if mem_slice.get_mut(start..start + init.data.len()).is_none() { + return Err(InstantiationError::Link( + "data segment does not fit".to_owned(), + )); + } + } + + Ok(()) +} + +fn create_and_initialize_signatures(module: &Module) -> SignatureRegistry { let mut sig_registry = SignatureRegistry::new(); for (sig_index, sig) in module.signatures.iter() { sig_registry.register(sig_index, sig); @@ -179,78 +405,169 @@ fn instantiate_signatures(module: &Module) -> SignatureRegistry { } /// Allocate memory for just the tables of the current module. -fn instantiate_tables( - module: &Module, - finished_functions: &BoxedSlice, - imported_functions: &BoxedSlice, - sig_registry: &mut SignatureRegistry, -) -> BoxedSlice { - let num_imports = module.imported_memories.len(); +fn create_tables(module: &Module) -> BoxedSlice { + let num_imports = module.imported_tables.len(); let mut tables: PrimaryMap = PrimaryMap::with_capacity(module.table_plans.len() - num_imports); for table in &module.table_plans.values().as_slice()[num_imports..] { tables.push(Table::new(table)); } - - for init in &module.table_elements { - debug_assert!(init.base.is_none(), "globalvar base not supported yet"); - let defined_table_index = module - .defined_table_index(init.table_index) - .expect("Initializers for imported tables not supported yet"); - let slice = tables[defined_table_index].as_mut(); - let subslice = &mut slice[init.offset..init.offset + init.elements.len()]; - for (i, func_idx) in init.elements.iter().enumerate() { - let callee_sig = module.functions[*func_idx]; - let func_ptr = if let Some(index) = module.defined_func_index(*func_idx) { - finished_functions[index] - } else { - imported_functions[*func_idx] - }; - let type_index = sig_registry.lookup(callee_sig); - subslice[i] = VMCallerCheckedAnyfunc { - func_ptr, - type_index, - }; - } - } - tables.into_boxed_slice() } +/// Initialize the table memory from the provided initializers. +fn initialize_tables(instance: &mut Instance) -> Result<(), InstantiationError> { + let vmctx: *mut VMContext = instance.vmctx_mut(); + for init in &instance.module.table_elements { + let mut start = init.offset; + if let Some(base) = init.base { + let global = if let Some(def_index) = instance.module.defined_global_index(base) { + unsafe { instance.vmctx.global_mut(def_index) } + } else { + unsafe { instance.vmctx.imported_global(base).from } + }; + start += unsafe { *(&*global).as_i32() } as u32 as usize; + } + + let slice = if let Some(defined_table_index) = + instance.module.defined_table_index(init.table_index) + { + instance.tables[defined_table_index].as_mut() + } else { + let import = &instance.vmctx_imports.tables[init.table_index]; + let foreign_instance = unsafe { (&mut *(import).vmctx).instance() }; + let foreign_table = unsafe { &mut *(import).from }; + let foreign_index = foreign_instance.vmctx().table_index(foreign_table); + foreign_instance.tables[foreign_index].as_mut() + }; + if let Some(subslice) = slice.get_mut(start..start + init.elements.len()) { + for (i, func_idx) in init.elements.iter().enumerate() { + let callee_sig = instance.module.functions[*func_idx]; + let (callee_ptr, callee_vmctx) = + if let Some(index) = instance.module.defined_func_index(*func_idx) { + (instance.finished_functions[index], vmctx) + } else { + let imported_func = &instance.vmctx_imports.functions[*func_idx]; + (imported_func.body, imported_func.vmctx) + }; + let type_index = instance.sig_registry.lookup(callee_sig); + subslice[i] = VMCallerCheckedAnyfunc { + func_ptr: callee_ptr, + type_index, + vmctx: callee_vmctx, + }; + } + } else { + return Err(InstantiationError::Link( + "elements segment does not fit".to_owned(), + )); + } + } + + Ok(()) +} + /// Allocate memory for just the memories of the current module. -fn instantiate_memories( +fn create_memories( module: &Module, - data_initializers: &[DataInitializer], -) -> Result, String> { +) -> Result, InstantiationError> { let num_imports = module.imported_memories.len(); let mut memories: PrimaryMap = PrimaryMap::with_capacity(module.memory_plans.len() - num_imports); for plan in &module.memory_plans.values().as_slice()[num_imports..] { - memories.push(LinearMemory::new(&plan)?); + memories.push(LinearMemory::new(&plan).map_err(InstantiationError::Resource)?); } - - for init in data_initializers { - debug_assert!(init.base.is_none(), "globalvar base not supported yet"); - let defined_memory_index = module - .defined_memory_index(init.memory_index) - .expect("Initializers for imported memories not supported yet"); - let mem_mut = memories[defined_memory_index].as_mut(); - let to_init = &mut mem_mut[init.offset..init.offset + init.data.len()]; - to_init.copy_from_slice(init.data); - } - Ok(memories.into_boxed_slice()) } +/// Initialize the table memory from the provided initializers. +fn initialize_memories( + instance: &mut Instance, + data_initializers: Vec, +) -> Result<(), InstantiationError> { + for init in data_initializers { + let mut start = init.offset; + if let Some(base) = init.base { + let global = if let Some(def_index) = instance.module.defined_global_index(base) { + unsafe { instance.vmctx.global_mut(def_index) } + } else { + unsafe { instance.vmctx.imported_global(base).from } + }; + start += unsafe { *(&*global).as_i32() } as u32 as usize; + } + + let memory = if let Some(defined_memory_index) = + instance.module.defined_memory_index(init.memory_index) + { + unsafe { instance.vmctx.memory(defined_memory_index) } + } else { + let import = &instance.vmctx_imports.memories[init.memory_index]; + let foreign_instance = unsafe { (&mut *(import).vmctx).instance() }; + let foreign_memory = unsafe { &mut *(import).from }; + let foreign_index = foreign_instance.vmctx().memory_index(foreign_memory); + unsafe { foreign_instance.vmctx.memory(foreign_index) } + }; + let mem_slice = unsafe { slice::from_raw_parts_mut(memory.base, memory.current_length) }; + if let Some(to_init) = mem_slice.get_mut(start..start + init.data.len()) { + to_init.copy_from_slice(init.data); + } else { + return Err(InstantiationError::Link( + "data segment does not fit".to_owned(), + )); + } + } + + Ok(()) +} + /// Allocate memory for just the globals of the current module, -/// without any initializers applied yet. -fn instantiate_globals(module: &Module) -> BoxedSlice { +/// with initializers applied. +fn create_globals(module: &Module) -> BoxedSlice { let num_imports = module.imported_globals.len(); let mut vmctx_globals = PrimaryMap::with_capacity(module.globals.len() - num_imports); - for global in &module.globals.values().as_slice()[num_imports..] { - vmctx_globals.push(VMGlobalDefinition::new(global)); + for _ in &module.globals.values().as_slice()[num_imports..] { + vmctx_globals.push(VMGlobalDefinition::new()); } vmctx_globals.into_boxed_slice() } + +fn initialize_globals(instance: &mut Instance) { + let num_imports = instance.module.imported_globals.len(); + for (index, global) in instance.module.globals.iter().skip(num_imports) { + let def_index = instance.module.defined_global_index(index).unwrap(); + let to: *mut VMGlobalDefinition = unsafe { instance.vmctx.global_mut(def_index) }; + match global.initializer { + GlobalInit::I32Const(x) => *unsafe { (*to).as_i32_mut() } = x, + GlobalInit::I64Const(x) => *unsafe { (*to).as_i64_mut() } = x, + GlobalInit::F32Const(x) => *unsafe { (*to).as_f32_bits_mut() } = x, + GlobalInit::F64Const(x) => *unsafe { (*to).as_f64_bits_mut() } = x, + GlobalInit::GetGlobal(x) => { + let from = if let Some(def_x) = instance.module.defined_global_index(x) { + unsafe { instance.vmctx.global_mut(def_x) } + } else { + unsafe { instance.vmctx.imported_global(x).from } + }; + unsafe { *to = *from }; + } + GlobalInit::Import => panic!("locally-defined global initialized as import"), + } + } +} + +/// An error while instantiating a module. +#[derive(Fail, Debug)] +pub enum InstantiationError { + /// Insufficient resources available for execution. + #[fail(display = "Insufficient resources: {}", _0)] + Resource(String), + + /// A wasm translation error occured. + #[fail(display = "Link error: {}", _0)] + Link(String), + + /// A compilation error occured. + #[fail(display = "Trap occurred while invoking start function: {}", _0)] + StartTrap(String), +} diff --git a/lib/runtime/src/lib.rs b/lib/runtime/src/lib.rs index efe2746465..6d3245896c 100644 --- a/lib/runtime/src/lib.rs +++ b/lib/runtime/src/lib.rs @@ -39,7 +39,11 @@ extern crate libc; #[macro_use] extern crate memoffset; extern crate cast; +extern crate failure; +#[macro_use] +extern crate failure_derive; +mod export; mod imports; mod instance; mod memory; @@ -52,14 +56,15 @@ mod vmcontext; pub mod libcalls; +pub use export::Export; pub use imports::Imports; -pub use instance::Instance; +pub use instance::{Instance, InstantiationError}; pub use mmap::Mmap; pub use signalhandlers::{wasmtime_init_eager, wasmtime_init_finish}; -pub use traphandlers::wasmtime_call_trampoline; +pub use traphandlers::{wasmtime_call, wasmtime_call_trampoline}; pub use vmcontext::{ - VMContext, VMFunctionBody, VMGlobalDefinition, VMGlobalImport, VMMemoryDefinition, - VMMemoryImport, VMTableDefinition, VMTableImport, + VMContext, VMFunctionBody, VMFunctionImport, VMGlobalDefinition, VMGlobalImport, + VMMemoryDefinition, VMMemoryImport, VMTableDefinition, VMTableImport, }; #[cfg(not(feature = "std"))] diff --git a/lib/runtime/src/libcalls.rs b/lib/runtime/src/libcalls.rs index 789cbd0c73..8a0fb0de1d 100644 --- a/lib/runtime/src/libcalls.rs +++ b/lib/runtime/src/libcalls.rs @@ -116,7 +116,7 @@ pub unsafe extern "C" fn wasmtime_imported_memory32_grow( ); let memory_index = MemoryIndex::from_u32(memory_index); - let import = instance.vmctx_mut().imported_memory_mut(memory_index); + let import = instance.vmctx().imported_memory(memory_index); let foreign_instance = (&mut *import.vmctx).instance(); let foreign_memory = &mut *import.from; let foreign_index = foreign_instance.vmctx().memory_index(foreign_memory); @@ -148,7 +148,7 @@ pub unsafe extern "C" fn wasmtime_imported_memory32_size( ); let memory_index = MemoryIndex::from_u32(memory_index); - let import = instance.vmctx_mut().imported_memory_mut(memory_index); + let import = instance.vmctx().imported_memory(memory_index); let foreign_instance = (&mut *import.vmctx).instance(); let foreign_memory = &mut *import.from; let foreign_index = foreign_instance.vmctx().memory_index(foreign_memory); diff --git a/lib/runtime/src/memory.rs b/lib/runtime/src/memory.rs index e84e649b9b..5413dc6204 100644 --- a/lib/runtime/src/memory.rs +++ b/lib/runtime/src/memory.rs @@ -65,14 +65,16 @@ impl LinearMemory { let mmap = Mmap::with_size(request_bytes)?; // Make the unmapped and offset-guard pages inaccessible. - unsafe { - region::protect( - mmap.as_ptr().add(mapped_bytes), - inaccessible_bytes, - region::Protection::None, - ) + if request_bytes != 0 { + unsafe { + region::protect( + mmap.as_ptr().add(mapped_bytes), + inaccessible_bytes, + region::Protection::None, + ) + } + .expect("unable to make memory inaccessible"); } - .expect("unable to make memory inaccessible"); Ok(Self { mmap, @@ -150,19 +152,7 @@ impl LinearMemory { pub fn vmmemory(&mut self) -> VMMemoryDefinition { VMMemoryDefinition { base: self.mmap.as_mut_ptr(), - current_length: self.mmap.len(), + current_length: self.current as usize * WASM_PAGE_SIZE as usize, } } } - -impl AsRef<[u8]> for LinearMemory { - fn as_ref(&self) -> &[u8] { - self.mmap.as_slice() - } -} - -impl AsMut<[u8]> for LinearMemory { - fn as_mut(&mut self) -> &mut [u8] { - self.mmap.as_mut_slice() - } -} diff --git a/lib/runtime/src/mmap.rs b/lib/runtime/src/mmap.rs index 15bda5b5fe..03537fdbcb 100644 --- a/lib/runtime/src/mmap.rs +++ b/lib/runtime/src/mmap.rs @@ -34,6 +34,12 @@ impl Mmap { /// suitably sized and aligned for memory protection. #[cfg(not(target_os = "windows"))] pub fn with_size(size: usize) -> Result { + // Mmap may return EINVAL if the size is zero, so just + // special-case that. + if size == 0 { + return Ok(Self::new()); + } + let page_size = region::page::size(); let alloc_size = round_up_to_page_size(size, page_size); let ptr = unsafe { diff --git a/lib/runtime/src/traphandlers.rs b/lib/runtime/src/traphandlers.rs index d357fd09c6..3b384cca07 100644 --- a/lib/runtime/src/traphandlers.rs +++ b/lib/runtime/src/traphandlers.rs @@ -107,3 +107,27 @@ pub unsafe extern "C" fn wasmtime_call_trampoline( Ok(()) }) } + +/// Call the wasm function pointed to by `callee`, which has no arguments or +/// return values. +#[no_mangle] +pub unsafe extern "C" fn wasmtime_call( + callee: *const VMFunctionBody, + vmctx: *mut VMContext, +) -> Result<(), String> { + // In case wasm code calls Rust that panics and unwinds past this point, + // ensure that JMP_BUFS is unwound to its incoming state. + let _guard = ScopeGuard::new(); + + let func: fn(*mut VMContext) = mem::transmute(callee); + + JMP_BUFS.with(|bufs| { + let mut buf = mem::uninitialized(); + if setjmp(&mut buf) != 0 { + return TRAP_DATA.with(|data| Err(format!("wasm trap at {:?}", data.get().pc))); + } + bufs.borrow_mut().push(buf); + func(vmctx); + Ok(()) + }) +} diff --git a/lib/runtime/src/vmcontext.rs b/lib/runtime/src/vmcontext.rs index 62fb3cf990..c3327f8be0 100644 --- a/lib/runtime/src/vmcontext.rs +++ b/lib/runtime/src/vmcontext.rs @@ -3,12 +3,47 @@ use cranelift_entity::EntityRef; use cranelift_wasm::{ - DefinedGlobalIndex, DefinedMemoryIndex, DefinedTableIndex, FuncIndex, Global, GlobalIndex, - GlobalInit, MemoryIndex, TableIndex, + DefinedGlobalIndex, DefinedMemoryIndex, DefinedTableIndex, FuncIndex, GlobalIndex, MemoryIndex, + TableIndex, }; use instance::Instance; use std::{mem, ptr, u32}; +/// An imported function. +#[derive(Debug, Copy, Clone)] +#[repr(C)] +pub struct VMFunctionImport { + /// A pointer to the imported function body. + pub body: *const VMFunctionBody, + + /// A pointer to the VMContext that owns the function. + pub vmctx: *mut VMContext, +} + +#[cfg(test)] +mod test_vmfunction_import { + use super::VMFunctionImport; + use std::mem::size_of; + use wasmtime_environ::VMOffsets; + + #[test] + fn check_vmfunction_import_offsets() { + let offsets = VMOffsets::new(size_of::<*mut u8>() as u8); + assert_eq!( + size_of::(), + usize::from(offsets.size_of_vmfunction_import()) + ); + assert_eq!( + offset_of!(VMFunctionImport, body), + usize::from(offsets.vmfunction_import_body()) + ); + assert_eq!( + offset_of!(VMFunctionImport, vmctx), + usize::from(offsets.vmfunction_import_vmctx()) + ); + } +} + /// A placeholder byte-sized type which is just used to provide some amount of type /// safety when dealing with pointers to JIT-compiled function bodies. Note that it's /// deliberately not Copy, as we shouldn't be carelessly copying function body bytes @@ -244,17 +279,8 @@ mod test_vmglobal_definition { impl VMGlobalDefinition { /// Construct a `VMGlobalDefinition`. - pub fn new(global: &Global) -> Self { - let mut result = Self { storage: [0; 8] }; - match global.initializer { - GlobalInit::I32Const(x) => *unsafe { result.as_i32_mut() } = x, - GlobalInit::I64Const(x) => *unsafe { result.as_i64_mut() } = x, - GlobalInit::F32Const(x) => *unsafe { result.as_f32_bits_mut() } = x, - GlobalInit::F64Const(x) => *unsafe { result.as_f64_bits_mut() } = x, - GlobalInit::GetGlobal(_x) => unimplemented!("globals init with get_global"), - GlobalInit::Import => panic!("attempting to initialize imported global"), - } - result + pub fn new() -> Self { + Self { storage: [0; 8] } } /// Return a reference to the value as an i32. @@ -366,6 +392,7 @@ impl VMSharedSignatureIndex { pub struct VMCallerCheckedAnyfunc { pub func_ptr: *const VMFunctionBody, pub type_index: VMSharedSignatureIndex, + pub vmctx: *mut VMContext, // If more elements are added here, remember to add offset_of tests below! } @@ -390,6 +417,10 @@ mod test_vmcaller_checked_anyfunc { offset_of!(VMCallerCheckedAnyfunc, type_index), usize::from(offsets.vmcaller_checked_anyfunc_type_index()) ); + assert_eq!( + offset_of!(VMCallerCheckedAnyfunc, vmctx), + usize::from(offsets.vmcaller_checked_anyfunc_vmctx()) + ); } } @@ -398,6 +429,7 @@ impl Default for VMCallerCheckedAnyfunc { Self { func_ptr: ptr::null_mut(), type_index: VMSharedSignatureIndex::new(u32::MAX), + vmctx: ptr::null_mut(), } } } @@ -413,16 +445,16 @@ impl Default for VMCallerCheckedAnyfunc { #[repr(C)] pub struct VMContext { /// A pointer to an array of `*const VMFunctionBody` instances, indexed by `FuncIndex`. - imported_functions: *const *const VMFunctionBody, + imported_functions: *const VMFunctionImport, /// A pointer to an array of `VMTableImport` instances, indexed by `TableIndex`. - imported_tables: *mut VMTableImport, + imported_tables: *const VMTableImport, /// A pointer to an array of `VMMemoryImport` instances, indexed by `MemoryIndex`. - imported_memories: *mut VMMemoryImport, + imported_memories: *const VMMemoryImport, /// A pointer to an array of `VMGlobalImport` instances, indexed by `GlobalIndex`. - imported_globals: *mut VMGlobalImport, + imported_globals: *const VMGlobalImport, /// A pointer to an array of locally-defined `VMTableDefinition` instances, /// indexed by `DefinedTableIndex`. @@ -473,10 +505,10 @@ mod test { impl VMContext { /// Create a new `VMContext` instance. pub fn new( - imported_functions: *const *const VMFunctionBody, - imported_tables: *mut VMTableImport, - imported_memories: *mut VMMemoryImport, - imported_globals: *mut VMGlobalImport, + imported_functions: *const VMFunctionImport, + imported_tables: *const VMTableImport, + imported_memories: *const VMMemoryImport, + imported_globals: *const VMGlobalImport, tables: *mut VMTableDefinition, memories: *mut VMMemoryDefinition, globals: *mut VMGlobalDefinition, @@ -495,8 +527,8 @@ impl VMContext { } /// Return a reference to imported function `index`. - pub unsafe fn imported_function(&self, index: FuncIndex) -> *const VMFunctionBody { - *self.imported_functions.add(index.index()) + pub unsafe fn imported_function(&self, index: FuncIndex) -> &VMFunctionImport { + &*self.imported_functions.add(index.index()) } /// Return a reference to imported table `index`. @@ -504,31 +536,16 @@ impl VMContext { &*self.imported_tables.add(index.index()) } - /// Return a mutable reference to imported table `index`. - pub unsafe fn imported_table_mut(&mut self, index: TableIndex) -> &mut VMTableImport { - &mut *self.imported_tables.add(index.index()) - } - /// Return a reference to imported memory `index`. pub unsafe fn imported_memory(&self, index: MemoryIndex) -> &VMMemoryImport { &*self.imported_memories.add(index.index()) } - /// Return a mutable reference to imported memory `index`. - pub unsafe fn imported_memory_mut(&mut self, index: MemoryIndex) -> &mut VMMemoryImport { - &mut *self.imported_memories.add(index.index()) - } - /// Return a reference to imported global `index`. pub unsafe fn imported_global(&self, index: GlobalIndex) -> &VMGlobalImport { &*self.imported_globals.add(index.index()) } - /// Return a mutable reference to imported global `index`. - pub unsafe fn imported_global_mut(&mut self, index: GlobalIndex) -> &mut VMGlobalImport { - &mut *self.imported_globals.add(index.index()) - } - /// Return a reference to locally-defined table `index`. pub unsafe fn table(&self, index: DefinedTableIndex) -> &VMTableDefinition { &*self.tables.add(index.index()) @@ -565,6 +582,16 @@ impl VMContext { &mut *((self as *mut Self as *mut u8).offset(-Instance::vmctx_offset()) as *mut Instance) } + /// Return the table index for the given `VMTableDefinition`. + pub fn table_index(&self, table: &mut VMTableDefinition) -> DefinedTableIndex { + // TODO: Use `offset_from` once it stablizes. + let begin = self.tables; + let end: *mut VMTableDefinition = table; + DefinedTableIndex::new( + (end as usize - begin as usize) / mem::size_of::(), + ) + } + /// Return the memory index for the given `VMMemoryDefinition`. pub fn memory_index(&self, memory: &mut VMMemoryDefinition) -> DefinedMemoryIndex { // TODO: Use `offset_from` once it stablizes. diff --git a/lib/wast/build.rs b/lib/wast/build.rs deleted file mode 100644 index 6c4c442c04..0000000000 --- a/lib/wast/build.rs +++ /dev/null @@ -1,103 +0,0 @@ -use std::env; -use std::fs::{read_dir, File}; -use std::io::{self, Write}; -use std::path::{Path, PathBuf}; - -fn main() { - let out_dir = - PathBuf::from(env::var("OUT_DIR").expect("The OUT_DIR environment variable must be set")); - let mut out = - File::create(out_dir.join("run_wast_files.rs")).expect("error creating run_wast_files.rs"); - - test_directory(&mut out, "misc_testsuite").unwrap(); - test_directory(&mut out, "spec_testsuite").unwrap(); -} - -fn test_directory(out: &mut File, testsuite: &str) -> io::Result<()> { - let mut dir_entries: Vec<_> = read_dir(testsuite) - .unwrap() - .map(|r| r.unwrap()) - .filter(|dir_entry| { - let p = dir_entry.path(); - if let Some(ext) = p.extension() { - // Only look at wast files. - if ext == "wast" { - // Ignore files starting with `.`, which could be editor temporary files - if let Some(stem) = p.file_stem() { - if let Some(stemstr) = stem.to_str() { - if !stemstr.starts_with('.') { - return true; - } - } - } - } - } - false - }) - .collect(); - - dir_entries.sort_by_key(|dir| dir.path()); - - writeln!( - out, - "mod {} {{", - Path::new(testsuite) - .file_stem() - .unwrap() - .to_str() - .unwrap() - .replace("-", "_") - )?; - writeln!(out, " use super::{{native_isa, WastContext, Path}};")?; - for dir_entry in dir_entries { - let path = dir_entry.path(); - let stemstr = path - .file_stem() - .expect("file_stem") - .to_str() - .expect("to_str"); - - writeln!(out, " #[test]")?; - if ignore(testsuite, stemstr) { - writeln!(out, " #[ignore]")?; - } - writeln!( - out, - " fn {}() {{", - avoid_keywords(&stemstr.replace("-", "_")) - )?; - writeln!(out, " let mut wast_context = WastContext::new().expect(\"error constructing WastContext\");")?; - writeln!( - out, - " wast_context.run_file(&*native_isa(), Path::new(\"{}\")).expect(\"error running wast file: {}\");", - path.display(), - path.display() - )?; - writeln!(out, " }}")?; - writeln!(out)?; - } - writeln!(out, "}}")?; - Ok(()) -} - -fn avoid_keywords(name: &str) -> &str { - match name { - "if" => "if_", - "loop" => "loop_", - "type" => "type_", - "const" => "const_", - "return" => "return_", - other => other, - } -} - -fn ignore(testsuite: &str, name: &str) -> bool { - match testsuite { - "spec_testsuite" => match name { - // These are the remaining spec testsuite failures. - "data" | "elem" | "imports" | "linking" => true, - _ => false, - }, - _ => false, - } -} diff --git a/lib/wast/src/spectest.rs b/lib/wast/src/spectest.rs index d9d98dca15..062ab7b0f3 100644 --- a/lib/wast/src/spectest.rs +++ b/lib/wast/src/spectest.rs @@ -1,16 +1,14 @@ use cranelift_codegen::ir::types; use cranelift_codegen::{ir, isa}; use cranelift_entity::PrimaryMap; -use cranelift_wasm::{Global, GlobalInit, Memory, Table, TableElementType}; -use std::ptr; +use cranelift_wasm::{DefinedFuncIndex, Global, GlobalInit, Memory, Table, TableElementType}; +use std::rc::Rc; use target_lexicon::HOST; use wasmtime_environ::{ - translate_signature, MemoryPlan, MemoryStyle, Module, TablePlan, TableStyle, -}; -use wasmtime_execute::{Export, Resolver}; -use wasmtime_runtime::{ - Imports, Instance, VMFunctionBody, VMGlobalDefinition, VMMemoryDefinition, VMTableDefinition, + translate_signature, Export, MemoryPlan, MemoryStyle, Module, TablePlan, TableStyle, }; +use wasmtime_execute::{ActionError, InstancePlus}; +use wasmtime_runtime::{Imports, VMFunctionBody}; extern "C" fn spectest_print() {} @@ -46,195 +44,181 @@ extern "C" fn spectest_print_f64_f64(x: f64, y: f64) { println!("{}: f64", y); } -pub struct SpecTest { - instance: Instance, - spectest_global_i32: VMGlobalDefinition, - spectest_global_f32: VMGlobalDefinition, - spectest_global_f64: VMGlobalDefinition, - spectest_table: VMTableDefinition, - spectest_memory: VMMemoryDefinition, -} +/// Return an instance implementing the "spectest" interface used in the +/// spec testsuite. +pub fn instantiate_spectest() -> Result { + let call_conv = isa::CallConv::triple_default(&HOST); + let pointer_type = types::Type::triple_pointer_type(&HOST); + let mut module = Module::new(); + let mut finished_functions: PrimaryMap = + PrimaryMap::new(); -impl SpecTest { - pub fn new() -> Result { - let finished_functions = PrimaryMap::new(); - let imports = Imports::none(); - let data_initializers = Vec::new(); - Ok(Self { - instance: Instance::new( - &Module::new(), - &finished_functions.into_boxed_slice(), - imports, - &data_initializers, - )?, - spectest_global_i32: VMGlobalDefinition::new(&Global { - ty: types::I32, - mutability: true, - initializer: GlobalInit::I32Const(0), - }), - spectest_global_f32: VMGlobalDefinition::new(&Global { - ty: types::I32, - mutability: true, - initializer: GlobalInit::F32Const(0), - }), - spectest_global_f64: VMGlobalDefinition::new(&Global { - ty: types::I32, - mutability: true, - initializer: GlobalInit::F64Const(0), - }), - spectest_table: VMTableDefinition { - base: ptr::null_mut(), - current_elements: 0, - }, - spectest_memory: VMMemoryDefinition { - base: ptr::null_mut(), - current_length: 0, - }, - }) - } -} + let sig = module.signatures.push(translate_signature( + ir::Signature { + params: vec![], + returns: vec![], + call_conv, + }, + pointer_type, + )); + let func = module.functions.push(sig); + module + .exports + .insert("print".to_owned(), Export::Function(func)); + finished_functions.push(spectest_print as *const VMFunctionBody); -impl Resolver for SpecTest { - fn resolve(&mut self, module: &str, field: &str) -> Option { - let call_conv = isa::CallConv::triple_default(&HOST); - let pointer_type = types::Type::triple_pointer_type(&HOST); - match module { - "spectest" => match field { - "print" => Some(Export::function( - spectest_print as *const VMFunctionBody, - translate_signature( - ir::Signature { - params: vec![], - returns: vec![], - call_conv, - }, - pointer_type, - ), - )), - "print_i32" => Some(Export::function( - spectest_print_i32 as *const VMFunctionBody, - translate_signature( - ir::Signature { - params: vec![ir::AbiParam::new(types::I32)], - returns: vec![], - call_conv, - }, - pointer_type, - ), - )), - "print_i64" => Some(Export::function( - spectest_print_i64 as *const VMFunctionBody, - translate_signature( - ir::Signature { - params: vec![ir::AbiParam::new(types::I64)], - returns: vec![], - call_conv, - }, - pointer_type, - ), - )), - "print_f32" => Some(Export::function( - spectest_print_f32 as *const VMFunctionBody, - translate_signature( - ir::Signature { - params: vec![ir::AbiParam::new(types::F32)], - returns: vec![], - call_conv, - }, - pointer_type, - ), - )), - "print_f64" => Some(Export::function( - spectest_print_f64 as *const VMFunctionBody, - translate_signature( - ir::Signature { - params: vec![ir::AbiParam::new(types::F64)], - returns: vec![], - call_conv, - }, - pointer_type, - ), - )), - "print_i32_f32" => Some(Export::function( - spectest_print_i32_f32 as *const VMFunctionBody, - translate_signature( - ir::Signature { - params: vec![ - ir::AbiParam::new(types::I32), - ir::AbiParam::new(types::F32), - ], - returns: vec![], - call_conv, - }, - pointer_type, - ), - )), - "print_f64_f64" => Some(Export::function( - spectest_print_f64_f64 as *const VMFunctionBody, - translate_signature( - ir::Signature { - params: vec![ - ir::AbiParam::new(types::F64), - ir::AbiParam::new(types::F64), - ], - returns: vec![], - call_conv, - }, - pointer_type, - ), - )), - "global_i32" => Some(Export::global( - &mut self.spectest_global_i32, - Global { - ty: ir::types::I32, - mutability: false, - initializer: GlobalInit::I32Const(0), - }, - )), - "global_f32" => Some(Export::global( - &mut self.spectest_global_f32, - Global { - ty: ir::types::F32, - mutability: false, - initializer: GlobalInit::F32Const(0), - }, - )), - "global_f64" => Some(Export::global( - &mut self.spectest_global_f64, - Global { - ty: ir::types::F64, - mutability: false, - initializer: GlobalInit::F64Const(0), - }, - )), - "table" => Some(Export::table( - &mut self.spectest_table, - self.instance.vmctx_mut(), - TablePlan { - table: Table { - ty: TableElementType::Func, - minimum: 0, - maximum: None, - }, - style: TableStyle::CallerChecksSignature, - }, - )), - "memory" => Some(Export::memory( - &mut self.spectest_memory, - self.instance.vmctx_mut(), - MemoryPlan { - memory: Memory { - minimum: 0, - maximum: None, - shared: false, - }, - style: MemoryStyle::Dynamic, - offset_guard_size: 0, - }, - )), - _ => None, - }, - _ => None, - } - } + let sig = module.signatures.push(translate_signature( + ir::Signature { + params: vec![ir::AbiParam::new(types::I32)], + returns: vec![], + call_conv, + }, + pointer_type, + )); + let func = module.functions.push(sig); + module + .exports + .insert("print_i32".to_owned(), Export::Function(func)); + finished_functions.push(spectest_print_i32 as *const VMFunctionBody); + + let sig = module.signatures.push(translate_signature( + ir::Signature { + params: vec![ir::AbiParam::new(types::I64)], + returns: vec![], + call_conv, + }, + pointer_type, + )); + let func = module.functions.push(sig); + module + .exports + .insert("print_i64".to_owned(), Export::Function(func)); + finished_functions.push(spectest_print_i64 as *const VMFunctionBody); + + let sig = module.signatures.push(translate_signature( + ir::Signature { + params: vec![ir::AbiParam::new(types::F32)], + returns: vec![], + call_conv, + }, + pointer_type, + )); + let func = module.functions.push(sig); + module + .exports + .insert("print_f32".to_owned(), Export::Function(func)); + finished_functions.push(spectest_print_f32 as *const VMFunctionBody); + + let sig = module.signatures.push(translate_signature( + ir::Signature { + params: vec![ir::AbiParam::new(types::F64)], + returns: vec![], + call_conv, + }, + pointer_type, + )); + let func = module.functions.push(sig); + module + .exports + .insert("print_f64".to_owned(), Export::Function(func)); + finished_functions.push(spectest_print_f64 as *const VMFunctionBody); + + let sig = module.signatures.push(translate_signature( + ir::Signature { + params: vec![ir::AbiParam::new(types::I32), ir::AbiParam::new(types::F32)], + returns: vec![], + call_conv, + }, + pointer_type, + )); + let func = module.functions.push(sig); + module + .exports + .insert("print_i32_f32".to_owned(), Export::Function(func)); + finished_functions.push(spectest_print_i32_f32 as *const VMFunctionBody); + + let sig = module.signatures.push(translate_signature( + ir::Signature { + params: vec![ir::AbiParam::new(types::F64), ir::AbiParam::new(types::F64)], + returns: vec![], + call_conv, + }, + pointer_type, + )); + let func = module.functions.push(sig); + module + .exports + .insert("print_f64_f64".to_owned(), Export::Function(func)); + finished_functions.push(spectest_print_f64_f64 as *const VMFunctionBody); + + let global = module.globals.push(Global { + ty: types::I32, + mutability: false, + initializer: GlobalInit::I32Const(666), + }); + module + .exports + .insert("global_i32".to_owned(), Export::Global(global)); + + let global = module.globals.push(Global { + ty: types::I64, + mutability: false, + initializer: GlobalInit::I64Const(666), + }); + module + .exports + .insert("global_i64".to_owned(), Export::Global(global)); + + let global = module.globals.push(Global { + ty: types::F32, + mutability: false, + initializer: GlobalInit::F32Const(0x44268000), + }); + module + .exports + .insert("global_f32".to_owned(), Export::Global(global)); + + let global = module.globals.push(Global { + ty: types::F64, + mutability: false, + initializer: GlobalInit::F64Const(0x4084d00000000000), + }); + module + .exports + .insert("global_f64".to_owned(), Export::Global(global)); + + let table = module.table_plans.push(TablePlan { + table: Table { + ty: TableElementType::Func, + minimum: 10, + maximum: Some(20), + }, + style: TableStyle::CallerChecksSignature, + }); + module + .exports + .insert("table".to_owned(), Export::Table(table)); + + let memory = module.memory_plans.push(MemoryPlan { + memory: Memory { + minimum: 1, + maximum: Some(2), + shared: false, + }, + style: MemoryStyle::Static { bound: 65536 }, + offset_guard_size: 0x80000000, + }); + module + .exports + .insert("memory".to_owned(), Export::Memory(memory)); + + let imports = Imports::none(); + let data_initializers = Vec::new(); + + InstancePlus::with_parts( + Rc::new(module), + finished_functions.into_boxed_slice(), + imports, + data_initializers, + ) } diff --git a/lib/wast/src/wast.rs b/lib/wast/src/wast.rs index 32d46f9d8b..5289183a17 100644 --- a/lib/wast/src/wast.rs +++ b/lib/wast/src/wast.rs @@ -1,12 +1,13 @@ use cranelift_codegen::isa; use cranelift_entity::PrimaryMap; -use spectest::SpecTest; +use spectest::instantiate_spectest; use std::collections::HashMap; use std::io::Read; use std::path::Path; use std::{fmt, fs, io, str}; use wabt::script::{Action, Command, CommandKind, ModuleBinary, ScriptParser, Value}; -use wasmtime_execute::{ActionError, ActionOutcome, Code, InstanceWorld, RuntimeValue}; +use wasmtime_execute::{ActionError, ActionOutcome, InstancePlus, JITCode, Resolver, RuntimeValue}; +use wasmtime_runtime::Export; /// Translate from a script::Value to a RuntimeValue. fn runtime_value(v: Value) -> RuntimeValue { @@ -72,45 +73,70 @@ pub struct WastFileError { error: WastError, } -/// An opaque reference to an `InstanceWorld`. +/// An opaque reference to an `InstancePlus`. #[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct WorldIndex(u32); -entity_impl!(WorldIndex, "world"); +pub struct InstancePlusIndex(u32); +entity_impl!(InstancePlusIndex, "instance"); + +struct WasmNamespace { + names: HashMap, + instances: PrimaryMap, +} + +impl WasmNamespace { + fn new() -> Self { + Self { + names: HashMap::new(), + instances: PrimaryMap::new(), + } + } +} + +impl Resolver for WasmNamespace { + fn resolve(&mut self, module: &str, field: &str) -> Option { + if let Some(index) = self.names.get(module) { + self.instances[*index].instance.lookup(field) + } else { + None + } + } +} /// The wast test script language allows modules to be defined and actions /// to be performed on them. pub struct WastContext { /// A namespace of wasm modules, keyed by an optional name. - worlds: PrimaryMap, - current: Option, - namespace: HashMap, - code: Code, - spectest: SpecTest, + current: Option, + namespace: WasmNamespace, + jit_code: JITCode, } impl WastContext { /// Construct a new instance of `WastContext`. - pub fn new() -> Result { - Ok(Self { - worlds: PrimaryMap::new(), + pub fn new() -> Self { + Self { current: None, - namespace: HashMap::new(), - code: Code::new(), - spectest: SpecTest::new()?, - }) + namespace: WasmNamespace::new(), + jit_code: JITCode::new(), + } } fn instantiate( &mut self, isa: &isa::TargetIsa, module: ModuleBinary, - ) -> Result { - InstanceWorld::new(&mut self.code, isa, &module.into_vec(), &mut self.spectest) + ) -> Result { + InstancePlus::new( + &mut self.jit_code, + isa, + &module.into_vec(), + &mut self.namespace, + ) } - fn get_world(&mut self, module: &Option) -> Result { + fn get_instance(&mut self, module: &Option) -> Result { let index = *if let Some(name) = module { - self.namespace.get_mut(name).ok_or_else(|| { + self.namespace.names.get_mut(name).ok_or_else(|| { WastError::Module(UnknownModule { module: Some(name.to_owned()), }) @@ -124,6 +150,14 @@ impl WastContext { Ok(index) } + /// Register "spectest" which is used by the spec testsuite. + pub fn register_spectest(&mut self) -> Result<(), ActionError> { + let instance = instantiate_spectest()?; + let index = self.namespace.instances.push(instance); + self.register("spectest".to_owned(), index); + Ok(()) + } + /// Define a module and register it. pub fn module( &mut self, @@ -131,21 +165,18 @@ impl WastContext { name: Option, module: ModuleBinary, ) -> Result<(), ActionError> { - let world = self.instantiate(isa, module)?; - let index = if let Some(name) = name { - self.register(name, world) - } else { - self.worlds.push(world) - }; + let instance = self.instantiate(isa, module)?; + let index = self.namespace.instances.push(instance); + if let Some(name) = name { + self.register(name, index); + } self.current = Some(index); Ok(()) } /// Register a module to make it available for performing actions. - pub fn register(&mut self, name: String, world: InstanceWorld) -> WorldIndex { - let index = self.worlds.push(world); - self.namespace.insert(name, index); - index + pub fn register(&mut self, name: String, index: InstancePlusIndex) { + self.namespace.names.insert(name, index); } /// Invoke an exported function from a defined module. @@ -160,16 +191,18 @@ impl WastContext { for arg in args { value_args.push(runtime_value(*arg)); } - let index = self.get_world(&module)?; - self.worlds[index] - .invoke(&mut self.code, isa, &field, &value_args) + let index = self.get_instance(&module)?; + self.namespace.instances[index] + .invoke(&mut self.jit_code, isa, &field, &value_args) .map_err(WastError::Action) } /// Get the value of an exported global from a defined module. pub fn get(&mut self, module: Option, field: &str) -> Result { - let index = self.get_world(&module)?; - self.worlds[index].get(&field).map_err(WastError::Action) + let index = self.get_instance(&module)?; + self.namespace.instances[index] + .get(&field) + .map_err(WastError::Action) } fn perform_action( @@ -211,11 +244,13 @@ impl WastContext { error: WastError::Action(error), })?; } - CommandKind::Register { - name: _name, - as_name: _as_name, - } => { - println!("{}:{}: TODO: Implement register", filename, line); + CommandKind::Register { name, as_name } => { + let index = self.get_instance(&name).map_err(|error| WastFileError { + filename: filename.to_string(), + line, + error, + })?; + self.register(as_name, index); } CommandKind::PerformAction(action) => match self .perform_action(isa, action) diff --git a/lib/wast/misc_testsuite/misc_traps.wast b/misc_testsuite/misc_traps.wast similarity index 100% rename from lib/wast/misc_testsuite/misc_traps.wast rename to misc_testsuite/misc_traps.wast diff --git a/lib/wast/misc_testsuite/stack_overflow.wast b/misc_testsuite/stack_overflow.wast similarity index 100% rename from lib/wast/misc_testsuite/stack_overflow.wast rename to misc_testsuite/stack_overflow.wast diff --git a/lib/wast/spec_testsuite b/spec_testsuite similarity index 100% rename from lib/wast/spec_testsuite rename to spec_testsuite diff --git a/src/wasmtime.rs b/src/wasmtime.rs index fbde516b52..e4272ed575 100644 --- a/src/wasmtime.rs +++ b/src/wasmtime.rs @@ -31,9 +31,7 @@ )] extern crate cranelift_codegen; -extern crate cranelift_entity; extern crate cranelift_native; -extern crate cranelift_wasm; extern crate docopt; extern crate wasmtime_execute; #[macro_use] @@ -45,18 +43,15 @@ extern crate wabt; use cranelift_codegen::isa::TargetIsa; use cranelift_codegen::settings; use cranelift_codegen::settings::Configurable; -use cranelift_entity::EntityRef; -use cranelift_wasm::MemoryIndex; use docopt::Docopt; use std::error::Error; use std::fs::File; use std::io; use std::io::prelude::*; -use std::io::stdout; use std::path::Path; use std::path::PathBuf; use std::process::exit; -use wasmtime_execute::{ActionOutcome, Code, InstanceWorld, NullResolver}; +use wasmtime_execute::{ActionOutcome, InstancePlus, JITCode, NullResolver}; static LOG_FILENAME_PREFIX: &str = "cranelift.dbg."; @@ -68,14 +63,13 @@ including calling the start function if one is present. Additional functions given with --invoke are then called. Usage: - wasmtime [-omd] ... - wasmtime [-omd] ... --invoke= + wasmtime [-od] ... + wasmtime [-od] ... --invoke= wasmtime --help | --version Options: --invoke= name of function to run -o, --optimize runs optimization passes on the translated functions - -m, --memory interactive memory inspector after execution -d, --debug enable debug output on stderr/stdout -h, --help print this help message --version print the Cranelift version @@ -84,7 +78,6 @@ Options: #[derive(Deserialize, Debug, Clone)] struct Args { arg_file: Vec, - flag_memory: bool, flag_optimize: bool, flag_debug: bool, flag_invoke: Option, @@ -150,13 +143,13 @@ fn handle_module(args: &Args, path: &Path, isa: &TargetIsa) -> Result<(), String data = wabt::wat2wasm(data).map_err(|err| String::from(err.description()))?; } let mut resolver = NullResolver {}; - let mut code = Code::new(); - let mut world = - InstanceWorld::new(&mut code, isa, &data, &mut resolver).map_err(|e| e.to_string())?; + let mut jit_code = JITCode::new(); + let mut instance_plus = + InstancePlus::new(&mut jit_code, isa, &data, &mut resolver).map_err(|e| e.to_string())?; if let Some(ref f) = args.flag_invoke { - match world - .invoke(&mut code, isa, &f, &[]) + match instance_plus + .invoke(&mut jit_code, isa, &f, &[]) .map_err(|e| e.to_string())? { ActionOutcome::Returned { .. } => {} @@ -166,42 +159,6 @@ fn handle_module(args: &Args, path: &Path, isa: &TargetIsa) -> Result<(), String } } - if args.flag_memory { - let mut input = String::new(); - println!("Inspecting memory"); - println!("Type 'quit' to exit."); - loop { - input.clear(); - print!("Memory index, offset, length (e.g. 0,0,4): "); - let _ = stdout().flush(); - match io::stdin().read_line(&mut input) { - Ok(_) => { - input.pop(); - if input == "quit" { - break; - } - let split: Vec<&str> = input.split(',').collect(); - if split.len() != 3 { - break; - } - let memory = world - .inspect_memory( - MemoryIndex::new(str::parse(split[0]).unwrap()), - str::parse(split[1]).unwrap(), - str::parse(split[2]).unwrap(), - ) - .map_err(|e| e.to_string())?; - let mut s = memory.iter().fold(String::from("#"), |mut acc, byte| { - acc.push_str(format!("{:02x}_", byte).as_str()); - acc - }); - s.pop(); - println!("{}", s); - } - Err(error) => return Err(String::from(error.description())), - } - } - } Ok(()) } @@ -211,7 +168,7 @@ mod tests { use cranelift_codegen::settings::Configurable; use std::path::PathBuf; use wabt; - use wasmtime_execute::{Code, InstanceWorld, NullResolver}; + use wasmtime_execute::{InstancePlus, JITCode, NullResolver}; const PATH_MODULE_RS2WASM_ADD_FUNC: &str = r"filetests/rs2wasm-add-func.wat"; @@ -234,8 +191,8 @@ mod tests { let isa = isa_builder.finish(settings::Flags::new(flag_builder)); let mut resolver = NullResolver {}; - let mut code = Code::new(); - let world = InstanceWorld::new(&mut code, &*isa, &data, &mut resolver); - assert!(world.is_ok()); + let mut code = JITCode::new(); + let instance = InstancePlus::new(&mut code, &*isa, &data, &mut resolver); + assert!(instance.is_ok()); } } diff --git a/src/run_wast.rs b/src/wast.rs similarity index 94% rename from src/run_wast.rs rename to src/wast.rs index 6a84e34d4d..c9568a154f 100644 --- a/src/run_wast.rs +++ b/src/wast.rs @@ -94,7 +94,12 @@ fn main() { } let isa = isa_builder.finish(settings::Flags::new(flag_builder)); - let mut wast_context = WastContext::new().expect("Error creating WastContext"); + let mut wast_context = WastContext::new(); + + wast_context + .register_spectest() + .expect("error instantiating \"spectest\""); + for filename in &args.arg_file { wast_context .run_file(&*isa, Path::new(&filename)) diff --git a/lib/wast/tests/wast_files.rs b/tests/wast_testsuites.rs similarity index 90% rename from lib/wast/tests/wast_files.rs rename to tests/wast_testsuites.rs index 8e15fe73b2..7ee178bcf9 100644 --- a/lib/wast/tests/wast_files.rs +++ b/tests/wast_testsuites.rs @@ -8,7 +8,7 @@ use cranelift_codegen::settings::Configurable; use std::path::Path; use wasmtime_wast::WastContext; -include!(concat!(env!("OUT_DIR"), "/run_wast_files.rs")); +include!(concat!(env!("OUT_DIR"), "/wast_testsuite_tests.rs")); #[cfg(test)] fn native_isa() -> Box { @@ -18,5 +18,6 @@ fn native_isa() -> Box { let isa_builder = cranelift_native::builder().unwrap_or_else(|_| { panic!("host machine is not a supported target"); }); + isa_builder.finish(settings::Flags::new(flag_builder)) }