diff --git a/Cargo.toml b/Cargo.toml index e4b977bd69..f52308cc2b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,7 +28,7 @@ cranelift-entity = "0.26.0" cranelift-wasm = "0.26.0" wasmtime-environ = { path = "lib/environ" } wasmtime-runtime = { path = "lib/runtime" } -wasmtime-execute = { path = "lib/execute" } +wasmtime-jit = { path = "lib/jit" } wasmtime-obj = { path = "lib/obj" } wasmtime-wast = { path = "lib/wast" } docopt = "1.0.1" diff --git a/build.rs b/build.rs index d5b74a2048..9aca2b7a71 100644 --- a/build.rs +++ b/build.rs @@ -53,7 +53,10 @@ fn test_directory(out: &mut File, testsuite: &str) -> io::Result<()> { .expect("testsuite filename should be representable as a string") .replace("-", "_") )?; - writeln!(out, " use super::{{native_isa, Path, WastContext}};")?; + writeln!( + out, + " use super::{{native_isa, Path, WastContext, Compiler}};" + )?; for dir_entry in dir_entries { write_testsuite_tests(out, dir_entry, testsuite)?; } @@ -78,8 +81,12 @@ fn write_testsuite_tests(out: &mut File, dir_entry: DirEntry, testsuite: &str) - " fn {}() {{", avoid_keywords(&stemstr.replace("-", "_")) )?; - writeln!(out, " let mut wast_context = WastContext::new();")?; writeln!(out, " let isa = native_isa();")?; + writeln!(out, " let compiler = Compiler::new(isa);")?; + writeln!( + out, + " let mut wast_context = WastContext::new(Box::new(compiler));" + )?; writeln!(out, " wast_context")?; writeln!(out, " .register_spectest()")?; writeln!( @@ -87,7 +94,7 @@ fn write_testsuite_tests(out: &mut File, dir_entry: DirEntry, testsuite: &str) - " .expect(\"instantiating \\\"spectest\\\"\");" )?; writeln!(out, " wast_context")?; - write!(out, " .run_file(&*isa, Path::new(\"")?; + write!(out, " .run_file(Path::new(\"")?; // Write out the string with escape_debug to prevent special characters such // as backslash from being reinterpreted. for c in path.display().to_string().chars() { diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 8f82cb5f27..55e92463f7 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -9,7 +9,7 @@ cargo-fuzz = true [dependencies] wasmtime-environ = { path = "../lib/environ" } -wasmtime-execute = { path = "../lib/execute" } +wasmtime-jit = { path = "../lib/jit" } cranelift-codegen = "0.26.0" cranelift-wasm = "0.26.0" cranelift-native = "0.26.0" diff --git a/fuzz/fuzz_targets/compile.rs b/fuzz/fuzz_targets/compile.rs index 7848ae55ce..db49eed337 100644 --- a/fuzz/fuzz_targets/compile.rs +++ b/fuzz/fuzz_targets/compile.rs @@ -6,7 +6,7 @@ extern crate cranelift_codegen; extern crate cranelift_native; extern crate wasmparser; extern crate wasmtime_environ; -extern crate wasmtime_execute; +extern crate wasmtime_jit; use cranelift_codegen::settings; use wasmparser::validate; @@ -28,9 +28,8 @@ fuzz_target!(|data: &[u8]| { Err(_) => return, }; let imports_resolver = |_env: &str, _function: &str| None; - let _exec = - match wasmtime_execute::compile_and_link_module(&*isa, &translation, imports_resolver) { - Ok(x) => x, - Err(_) => return, - }; + let _exec = match wasmtime_jit::compile_and_link_module(&*isa, &translation, imports_resolver) { + Ok(x) => x, + Err(_) => return, + }; }); diff --git a/lib/environ/README.md b/lib/environ/README.md index 3e5389f92b..0649c5f877 100644 --- a/lib/environ/README.md +++ b/lib/environ/README.md @@ -1,5 +1,6 @@ This is the `wasmtime-environ` crate, which contains the implementations of the `ModuleEnvironment` and `FuncEnvironment` traits from [`cranelift-wasm`](https://crates.io/crates/cranelift-wasm). They effectively -implement an ABI for basic wasm compilation, which can be used for JITing, -native object files, or other purposes. +implement an ABI for basic wasm compilation that defines how linear memories +are allocated, how indirect calls work, and other details. They can be used +for JITing, native object files, or other purposes. diff --git a/lib/environ/src/compilation.rs b/lib/environ/src/compilation.rs index e4f53f8042..2ae7fb62c2 100644 --- a/lib/environ/src/compilation.rs +++ b/lib/environ/src/compilation.rs @@ -3,16 +3,9 @@ use cranelift_codegen::binemit; use cranelift_codegen::ir; -use cranelift_codegen::ir::ExternalName; -use cranelift_codegen::isa; -use cranelift_codegen::{CodegenError, Context}; +use cranelift_codegen::CodegenError; use cranelift_entity::PrimaryMap; -use cranelift_wasm::{DefinedFuncIndex, FuncIndex, FuncTranslator, WasmError}; -use func_environ::{ - get_func_name, get_imported_memory32_grow_name, get_imported_memory32_size_name, - get_memory32_grow_name, get_memory32_size_name, FuncEnvironment, -}; -use module::Module; +use cranelift_wasm::{DefinedFuncIndex, FuncIndex, WasmError}; use std::vec::Vec; /// The result of compiling a WebAssemby module's functions. @@ -29,71 +22,6 @@ impl Compilation { } } -/// Implementation of a relocation sink that just saves all the information for later -pub struct RelocSink { - /// Relocations recorded for the function. - pub func_relocs: Vec, -} - -impl binemit::RelocSink for RelocSink { - fn reloc_ebb( - &mut self, - _offset: binemit::CodeOffset, - _reloc: binemit::Reloc, - _ebb_offset: binemit::CodeOffset, - ) { - // This should use the `offsets` field of `ir::Function`. - panic!("ebb headers not yet implemented"); - } - fn reloc_external( - &mut self, - offset: binemit::CodeOffset, - reloc: binemit::Reloc, - name: &ExternalName, - addend: binemit::Addend, - ) { - let reloc_target = if *name == get_memory32_grow_name() { - RelocationTarget::Memory32Grow - } else if *name == get_imported_memory32_grow_name() { - RelocationTarget::ImportedMemory32Grow - } else if *name == get_memory32_size_name() { - RelocationTarget::Memory32Size - } else if *name == get_imported_memory32_size_name() { - RelocationTarget::ImportedMemory32Size - } else if let ExternalName::User { namespace, index } = *name { - debug_assert!(namespace == 0); - RelocationTarget::UserFunc(FuncIndex::from_u32(index)) - } else if let ExternalName::LibCall(libcall) = *name { - RelocationTarget::LibCall(libcall) - } else { - panic!("unrecognized external name") - }; - self.func_relocs.push(Relocation { - reloc, - reloc_target, - offset, - addend, - }); - } - fn reloc_jt( - &mut self, - _offset: binemit::CodeOffset, - _reloc: binemit::Reloc, - _jt: ir::JumpTable, - ) { - panic!("jump tables not yet implemented"); - } -} - -impl RelocSink { - /// Return a new `RelocSink` instance. - pub fn new() -> Self { - Self { - func_relocs: Vec::new(), - } - } -} - /// A record of a relocation to perform. #[derive(Debug, Clone)] pub struct Relocation { @@ -127,44 +55,6 @@ pub enum RelocationTarget { /// Relocations to apply to function bodies. pub type Relocations = PrimaryMap>; -/// Compile the module, producing a compilation result with associated -/// relocations. -pub fn compile_module<'data, 'module>( - module: &'module Module, - function_body_inputs: &PrimaryMap, - isa: &isa::TargetIsa, -) -> Result<(Compilation, Relocations), CompileError> { - let mut functions = PrimaryMap::new(); - let mut relocations = PrimaryMap::new(); - for (i, input) in function_body_inputs.iter() { - let func_index = module.func_index(i); - let mut context = Context::new(); - context.func.name = get_func_name(func_index); - context.func.signature = module.signatures[module.functions[func_index]].clone(); - - let mut trans = FuncTranslator::new(); - trans - .translate( - input, - &mut context.func, - &mut FuncEnvironment::new(isa, module), - ) - .map_err(CompileError::Wasm)?; - - 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(CompileError::Codegen)?; - functions.push(code_buf); - relocations.push(reloc_sink.func_relocs); - } - - // TODO: Reorganize where we create the Vec for the resolved imports. - Ok((Compilation::new(functions), relocations)) -} - /// An error while compiling WebAssembly to machine code. #[derive(Fail, Debug)] pub enum CompileError { diff --git a/lib/environ/src/cranelift.rs b/lib/environ/src/cranelift.rs new file mode 100644 index 0000000000..9832948543 --- /dev/null +++ b/lib/environ/src/cranelift.rs @@ -0,0 +1,119 @@ +//! Support for compiling with Cranelift. + +use compilation::{Compilation, CompileError, Relocation, RelocationTarget, Relocations}; +use cranelift_codegen::binemit; +use cranelift_codegen::ir; +use cranelift_codegen::ir::ExternalName; +use cranelift_codegen::isa; +use cranelift_codegen::Context; +use cranelift_entity::PrimaryMap; +use cranelift_wasm::{DefinedFuncIndex, FuncIndex, FuncTranslator}; +use func_environ::{ + get_func_name, get_imported_memory32_grow_name, get_imported_memory32_size_name, + get_memory32_grow_name, get_memory32_size_name, FuncEnvironment, +}; +use module::Module; +use std::vec::Vec; + +/// Implementation of a relocation sink that just saves all the information for later +struct RelocSink { + /// Relocations recorded for the function. + func_relocs: Vec, +} + +impl binemit::RelocSink for RelocSink { + fn reloc_ebb( + &mut self, + _offset: binemit::CodeOffset, + _reloc: binemit::Reloc, + _ebb_offset: binemit::CodeOffset, + ) { + // This should use the `offsets` field of `ir::Function`. + panic!("ebb headers not yet implemented"); + } + fn reloc_external( + &mut self, + offset: binemit::CodeOffset, + reloc: binemit::Reloc, + name: &ExternalName, + addend: binemit::Addend, + ) { + let reloc_target = if *name == get_memory32_grow_name() { + RelocationTarget::Memory32Grow + } else if *name == get_imported_memory32_grow_name() { + RelocationTarget::ImportedMemory32Grow + } else if *name == get_memory32_size_name() { + RelocationTarget::Memory32Size + } else if *name == get_imported_memory32_size_name() { + RelocationTarget::ImportedMemory32Size + } else if let ExternalName::User { namespace, index } = *name { + debug_assert!(namespace == 0); + RelocationTarget::UserFunc(FuncIndex::from_u32(index)) + } else if let ExternalName::LibCall(libcall) = *name { + RelocationTarget::LibCall(libcall) + } else { + panic!("unrecognized external name") + }; + self.func_relocs.push(Relocation { + reloc, + reloc_target, + offset, + addend, + }); + } + fn reloc_jt( + &mut self, + _offset: binemit::CodeOffset, + _reloc: binemit::Reloc, + _jt: ir::JumpTable, + ) { + panic!("jump tables not yet implemented"); + } +} + +impl RelocSink { + /// Return a new `RelocSink` instance. + pub fn new() -> Self { + Self { + func_relocs: Vec::new(), + } + } +} + +/// Compile the module using Cranelift, producing a compilation result with +/// associated relocations. +pub fn compile_module<'data, 'module>( + module: &'module Module, + function_body_inputs: PrimaryMap, + isa: &isa::TargetIsa, +) -> Result<(Compilation, Relocations), CompileError> { + let mut functions = PrimaryMap::new(); + let mut relocations = PrimaryMap::new(); + for (i, input) in function_body_inputs.into_iter() { + let func_index = module.func_index(i); + let mut context = Context::new(); + context.func.name = get_func_name(func_index); + context.func.signature = module.signatures[module.functions[func_index]].clone(); + + let mut trans = FuncTranslator::new(); + trans + .translate( + input, + &mut context.func, + &mut FuncEnvironment::new(isa.frontend_config(), module), + ) + .map_err(CompileError::Wasm)?; + + 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(CompileError::Codegen)?; + functions.push(code_buf); + relocations.push(reloc_sink.func_relocs); + } + + // TODO: Reorganize where we create the Vec for the resolved imports. + Ok((Compilation::new(functions), relocations)) +} diff --git a/lib/environ/src/func_environ.rs b/lib/environ/src/func_environ.rs index 508e77d635..7b16138929 100644 --- a/lib/environ/src/func_environ.rs +++ b/lib/environ/src/func_environ.rs @@ -7,7 +7,7 @@ use cranelift_codegen::ir::types::*; use cranelift_codegen::ir::{ AbiParam, ArgumentPurpose, ExtFuncData, FuncRef, Function, InstBuilder, Signature, }; -use cranelift_codegen::isa; +use cranelift_codegen::isa::TargetFrontendConfig; use cranelift_entity::EntityRef; use cranelift_wasm::{ self, DefinedGlobalIndex, DefinedMemoryIndex, DefinedTableIndex, FuncIndex, GlobalIndex, @@ -50,8 +50,8 @@ pub fn get_imported_memory32_size_name() -> ir::ExternalName { /// The FuncEnvironment implementation for use by the `ModuleEnvironment`. pub struct FuncEnvironment<'module_environment> { - /// Compilation setting flags. - isa: &'module_environment isa::TargetIsa, + /// Target-specified configuration. + target_config: TargetFrontendConfig, /// The module-level environment which this function-level environment belongs to. module: &'module_environment Module, @@ -104,12 +104,9 @@ pub struct FuncEnvironment<'module_environment> { } impl<'module_environment> FuncEnvironment<'module_environment> { - pub fn new( - isa: &'module_environment isa::TargetIsa, - module: &'module_environment Module, - ) -> Self { + pub fn new(target_config: TargetFrontendConfig, module: &'module_environment Module) -> Self { Self { - isa, + target_config, module, vmctx: None, imported_functions_base: None, @@ -124,12 +121,12 @@ impl<'module_environment> FuncEnvironment<'module_environment> { imported_memory32_size_extfunc: None, memory_grow_extfunc: None, imported_memory_grow_extfunc: None, - offsets: VMOffsets::new(isa.pointer_bytes()), + offsets: VMOffsets::new(target_config.pointer_bytes()), } } fn pointer_type(&self) -> ir::Type { - self.isa.frontend_config().pointer_type() + self.target_config.pointer_type() } fn vmctx(&mut self, func: &mut Function) -> ir::GlobalValue { @@ -253,7 +250,7 @@ impl<'module_environment> FuncEnvironment<'module_environment> { AbiParam::special(self.pointer_type(), ArgumentPurpose::VMContext), ], returns: vec![AbiParam::new(I32)], - call_conv: self.isa.frontend_config().default_call_conv, + call_conv: self.target_config.default_call_conv, }) } @@ -303,7 +300,7 @@ impl<'module_environment> FuncEnvironment<'module_environment> { AbiParam::special(self.pointer_type(), ArgumentPurpose::VMContext), ], returns: vec![AbiParam::new(I32)], - call_conv: self.isa.frontend_config().default_call_conv, + call_conv: self.target_config.default_call_conv, }) } @@ -348,8 +345,8 @@ impl<'module_environment> FuncEnvironment<'module_environment> { } impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'module_environment> { - fn target_config(&self) -> isa::TargetFrontendConfig { - self.isa.frontend_config() + fn target_config(&self) -> TargetFrontendConfig { + self.target_config } fn make_table(&mut self, func: &mut ir::Function, index: TableIndex) -> ir::Table { diff --git a/lib/environ/src/lib.rs b/lib/environ/src/lib.rs index 3ea2f91720..f2fd72f656 100644 --- a/lib/environ/src/lib.rs +++ b/lib/environ/src/lib.rs @@ -45,12 +45,13 @@ mod module_environ; mod tunables; mod vmoffsets; -pub use compilation::{ - compile_module, Compilation, CompileError, RelocSink, Relocation, RelocationTarget, Relocations, -}; +pub mod cranelift; + +pub use compilation::{Compilation, CompileError, Relocation, RelocationTarget, Relocations}; pub use module::{Export, MemoryPlan, MemoryStyle, Module, TableElements, TablePlan, TableStyle}; pub use module_environ::{ - translate_signature, DataInitializer, ModuleEnvironment, ModuleTranslation, + translate_signature, DataInitializer, DataInitializerLocation, 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 b4f205009d..d60006afbd 100644 --- a/lib/environ/src/module.rs +++ b/lib/environ/src/module.rs @@ -6,7 +6,6 @@ use cranelift_wasm::{ DefinedFuncIndex, DefinedGlobalIndex, DefinedMemoryIndex, DefinedTableIndex, FuncIndex, Global, GlobalIndex, Memory, MemoryIndex, SignatureIndex, Table, TableIndex, }; -use std::cmp; use std::collections::HashMap; use std::string::String; use std::vec::Vec; diff --git a/lib/environ/src/module_environ.rs b/lib/environ/src/module_environ.rs index 4c85f18618..6e178f338c 100644 --- a/lib/environ/src/module_environ.rs +++ b/lib/environ/src/module_environ.rs @@ -1,6 +1,6 @@ use cranelift_codegen::ir; use cranelift_codegen::ir::{AbiParam, ArgumentPurpose}; -use cranelift_codegen::isa; +use cranelift_codegen::isa::TargetFrontendConfig; use cranelift_entity::PrimaryMap; use cranelift_wasm::{ self, translate_module, DefinedFuncIndex, FuncIndex, Global, GlobalIndex, Memory, MemoryIndex, @@ -13,139 +13,149 @@ use std::string::String; use std::vec::Vec; use tunables::Tunables; -/// Object containing the standalone environment information. To be passed after creation as -/// argument to `compile_module`. -pub struct ModuleEnvironment<'data, 'module> { +/// The result of translating via `ModuleEnvironment`. Function bodies are not +/// yet translated, and data initializers have not yet been copied out of the +/// original buffer. +pub struct ModuleTranslation<'data> { /// Compilation setting flags. - isa: &'module isa::TargetIsa, + pub target_config: TargetFrontendConfig, /// Module information. - module: &'module mut Module, + pub module: Module, - /// References to information to be decoded later. - lazy: LazyContents<'data>, + /// References to the function bodies. + pub function_body_inputs: PrimaryMap, + + /// References to the data initializers. + pub data_initializers: Vec>, /// Tunable parameters. - tunables: Tunables, + pub tunables: Tunables, } -impl<'data, 'module> ModuleEnvironment<'data, 'module> { - /// Allocates the enironment data structures with the given isa. - pub fn new( - isa: &'module isa::TargetIsa, - module: &'module mut Module, - tunables: Tunables, - ) -> Self { +impl<'data> ModuleTranslation<'data> { + /// Return a new `FuncEnvironment` for translating a function. + pub fn func_env(&self) -> FuncEnvironment { + FuncEnvironment::new(self.target_config, &self.module) + } +} + +/// Object containing the standalone environment information. +pub struct ModuleEnvironment<'data> { + /// The result to be filled in. + result: ModuleTranslation<'data>, +} + +impl<'data> ModuleEnvironment<'data> { + /// Allocates the enironment data structures. + pub fn new(target_config: TargetFrontendConfig, tunables: Tunables) -> Self { Self { - isa, - module, - lazy: LazyContents::new(), - tunables, + result: ModuleTranslation { + target_config, + module: Module::new(), + function_body_inputs: PrimaryMap::new(), + data_initializers: Vec::new(), + tunables, + }, } } fn pointer_type(&self) -> ir::Type { - self.isa.frontend_config().pointer_type() + self.result.target_config.pointer_type() } - /// Translate the given wasm module data using this environment. This consumes the - /// `ModuleEnvironment` with its mutable reference to the `Module` and produces a - /// `ModuleTranslation` with an immutable reference to the `Module` (which has - /// become fully populated). - pub fn translate(mut self, data: &'data [u8]) -> WasmResult> { + /// Translate a wasm module using this environment. This consumes the + /// `ModuleEnvironment` and produces a `ModuleTranslation`. + pub fn translate(mut self, data: &'data [u8]) -> WasmResult> { translate_module(data, &mut self)?; - Ok(ModuleTranslation { - isa: self.isa, - module: self.module, - lazy: self.lazy, - tunables: self.tunables, - }) + Ok(self.result) } } /// This trait is useful for `translate_module` because it tells how to translate /// enironment-dependent wasm instructions. These functions should not be called by the user. -impl<'data, 'module> cranelift_wasm::ModuleEnvironment<'data> - for ModuleEnvironment<'data, 'module> -{ - fn target_config(&self) -> isa::TargetFrontendConfig { - self.isa.frontend_config() +impl<'data> cranelift_wasm::ModuleEnvironment<'data> for ModuleEnvironment<'data> { + fn target_config(&self) -> TargetFrontendConfig { + self.result.target_config } fn declare_signature(&mut self, sig: &ir::Signature) { let sig = translate_signature(sig.clone(), self.pointer_type()); // TODO: Deduplicate signatures. - self.module.signatures.push(sig); + self.result.module.signatures.push(sig); } fn get_signature(&self, sig_index: SignatureIndex) -> &ir::Signature { - &self.module.signatures[sig_index] + &self.result.module.signatures[sig_index] } fn declare_func_import(&mut self, sig_index: SignatureIndex, module: &str, field: &str) { debug_assert_eq!( - self.module.functions.len(), - self.module.imported_funcs.len(), + self.result.module.functions.len(), + self.result.module.imported_funcs.len(), "Imported functions must be declared first" ); - self.module.functions.push(sig_index); + self.result.module.functions.push(sig_index); - self.module + self.result + .module .imported_funcs .push((String::from(module), String::from(field))); } fn get_num_func_imports(&self) -> usize { - self.module.imported_funcs.len() + self.result.module.imported_funcs.len() } fn declare_func_type(&mut self, sig_index: SignatureIndex) { - self.module.functions.push(sig_index); + self.result.module.functions.push(sig_index); } fn get_func_type(&self, func_index: FuncIndex) -> SignatureIndex { - self.module.functions[func_index] + self.result.module.functions[func_index] } fn declare_global_import(&mut self, global: Global, module: &str, field: &str) { debug_assert_eq!( - self.module.globals.len(), - self.module.imported_globals.len(), + self.result.module.globals.len(), + self.result.module.imported_globals.len(), "Imported globals must be declared first" ); - self.module.globals.push(global); + self.result.module.globals.push(global); - self.module + self.result + .module .imported_globals .push((String::from(module), String::from(field))); } fn declare_global(&mut self, global: Global) { - self.module.globals.push(global); + self.result.module.globals.push(global); } fn get_global(&self, global_index: GlobalIndex) -> &Global { - &self.module.globals[global_index] + &self.result.module.globals[global_index] } fn declare_table_import(&mut self, table: Table, module: &str, field: &str) { debug_assert_eq!( - self.module.table_plans.len(), - self.module.imported_tables.len(), + self.result.module.table_plans.len(), + self.result.module.imported_tables.len(), "Imported tables must be declared first" ); - let plan = TablePlan::for_table(table, &self.tunables); - self.module.table_plans.push(plan); + let plan = TablePlan::for_table(table, &self.result.tunables); + self.result.module.table_plans.push(plan); - self.module + self.result + .module .imported_tables .push((String::from(module), String::from(field))); } fn declare_table(&mut self, table: Table) { - let plan = TablePlan::for_table(table, &self.tunables); - self.module.table_plans.push(plan); + let plan = TablePlan::for_table(table, &self.result.tunables); + self.result.module.table_plans.push(plan); } fn declare_table_elements( @@ -155,7 +165,7 @@ impl<'data, 'module> cranelift_wasm::ModuleEnvironment<'data> offset: usize, elements: Vec, ) { - self.module.table_elements.push(TableElements { + self.result.module.table_elements.push(TableElements { table_index, base, offset, @@ -165,21 +175,22 @@ impl<'data, 'module> cranelift_wasm::ModuleEnvironment<'data> fn declare_memory_import(&mut self, memory: Memory, module: &str, field: &str) { debug_assert_eq!( - self.module.memory_plans.len(), - self.module.imported_memories.len(), + self.result.module.memory_plans.len(), + self.result.module.imported_memories.len(), "Imported memories must be declared first" ); - let plan = MemoryPlan::for_memory(memory, &self.tunables); - self.module.memory_plans.push(plan); + let plan = MemoryPlan::for_memory(memory, &self.result.tunables); + self.result.module.memory_plans.push(plan); - self.module + self.result + .module .imported_memories .push((String::from(module), String::from(field))); } fn declare_memory(&mut self, memory: Memory) { - let plan = MemoryPlan::for_memory(memory, &self.tunables); - self.module.memory_plans.push(plan); + let plan = MemoryPlan::for_memory(memory, &self.result.tunables); + self.result.module.memory_plans.push(plan); } fn declare_data_initialization( @@ -189,71 +200,55 @@ impl<'data, 'module> cranelift_wasm::ModuleEnvironment<'data> offset: usize, data: &'data [u8], ) { - self.lazy.data_initializers.push(DataInitializer { - memory_index, - base, - offset, + self.result.data_initializers.push(DataInitializer { + location: DataInitializerLocation { + memory_index, + base, + offset, + }, data, }); } fn declare_func_export(&mut self, func_index: FuncIndex, name: &str) { - self.module + self.result + .module .exports .insert(String::from(name), Export::Function(func_index)); } fn declare_table_export(&mut self, table_index: TableIndex, name: &str) { - self.module + self.result + .module .exports .insert(String::from(name), Export::Table(table_index)); } fn declare_memory_export(&mut self, memory_index: MemoryIndex, name: &str) { - self.module + self.result + .module .exports .insert(String::from(name), Export::Memory(memory_index)); } fn declare_global_export(&mut self, global_index: GlobalIndex, name: &str) { - self.module + self.result + .module .exports .insert(String::from(name), Export::Global(global_index)); } fn declare_start_func(&mut self, func_index: FuncIndex) { - debug_assert!(self.module.start_func.is_none()); - self.module.start_func = Some(func_index); + debug_assert!(self.result.module.start_func.is_none()); + self.result.module.start_func = Some(func_index); } fn define_function_body(&mut self, body_bytes: &'data [u8]) -> WasmResult<()> { - self.lazy.function_body_inputs.push(body_bytes); + self.result.function_body_inputs.push(body_bytes); Ok(()) } } -/// The result of translating via `ModuleEnvironment`. -pub struct ModuleTranslation<'data, 'module> { - /// Compilation setting flags. - pub isa: &'module isa::TargetIsa, - - /// Module information. - pub module: &'module Module, - - /// Pointers into the raw data buffer. - pub lazy: LazyContents<'data>, - - /// Tunable parameters. - pub tunables: Tunables, -} - -impl<'data, 'module> ModuleTranslation<'data, 'module> { - /// Return a new `FuncEnvironment` for translating a function. - pub fn func_env(&self) -> FuncEnvironment { - FuncEnvironment::new(self.isa, &self.module) - } -} - /// Add environment-specific function parameters. pub fn translate_signature(mut sig: ir::Signature, pointer_type: ir::Type) -> ir::Signature { sig.params @@ -261,33 +256,25 @@ pub fn translate_signature(mut sig: ir::Signature, pointer_type: ir::Type) -> ir sig } -/// A data initializer for linear memory. -pub struct DataInitializer<'data> { +/// A memory index and offset within that memory where a data initialization +/// should is to be performed. +#[derive(Clone)] +pub struct DataInitializerLocation { /// 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, +} + +/// A data initializer for linear memory. +pub struct DataInitializer<'data> { + /// The location where the initialization is to be performed. + pub location: DataInitializerLocation, + /// 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 33934cc72a..e0fc8abbde 100644 --- a/lib/environ/src/vmoffsets.rs +++ b/lib/environ/src/vmoffsets.rs @@ -1,4 +1,4 @@ -//! Offsets and sizes of various structs in wasmtime-execute's vmcontext +//! Offsets and sizes of various structs in wasmtime-runtime's vmcontext //! module. use cranelift_codegen::ir; diff --git a/lib/execute/README.md b/lib/execute/README.md deleted file mode 100644 index 13fb3d6a1d..0000000000 --- a/lib/execute/README.md +++ /dev/null @@ -1,4 +0,0 @@ -This is the `wasmtime-execute` crate, which contains wasm runtime support, -supporting the wasm ABI defined by [`wasmtime-environ`]. - -[`wasmtime-environ`]: https://crates.io/crates/wasmtime-environ diff --git a/lib/execute/src/action.rs b/lib/execute/src/action.rs deleted file mode 100644 index 5afbd08929..0000000000 --- a/lib/execute/src/action.rs +++ /dev/null @@ -1,142 +0,0 @@ -//! Support for performing actions with a wasm module from the outside. - -use cranelift_codegen::ir; -use link::LinkError; -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)] -pub enum RuntimeValue { - /// A runtime value with type i32. - I32(i32), - /// A runtime value with type i64. - I64(i64), - /// A runtime value with type f32. - F32(u32), - /// A runtime value with type f64. - F64(u64), -} - -impl RuntimeValue { - /// Return the type of this `RuntimeValue`. - pub fn value_type(self) -> ir::Type { - match self { - RuntimeValue::I32(_) => ir::types::I32, - RuntimeValue::I64(_) => ir::types::I64, - RuntimeValue::F32(_) => ir::types::F32, - RuntimeValue::F64(_) => ir::types::F64, - } - } - - /// Assuming this `RuntimeValue` holds an `i32`, return that value. - pub fn unwrap_i32(self) -> i32 { - match self { - RuntimeValue::I32(x) => x, - _ => panic!("unwrapping value of type {} as i32", self.value_type()), - } - } - - /// Assuming this `RuntimeValue` holds an `i64`, return that value. - pub fn unwrap_i64(self) -> i64 { - match self { - RuntimeValue::I64(x) => x, - _ => panic!("unwrapping value of type {} as i64", self.value_type()), - } - } - - /// Assuming this `RuntimeValue` holds an `f32`, return that value. - pub fn unwrap_f32(self) -> f32 { - f32::from_bits(self.unwrap_f32_bits()) - } - - /// Assuming this `RuntimeValue` holds an `f32`, return the bits of that value as a `u32`. - pub fn unwrap_f32_bits(self) -> u32 { - match self { - RuntimeValue::F32(x) => x, - _ => panic!("unwrapping value of type {} as f32", self.value_type()), - } - } - - /// Assuming this `RuntimeValue` holds an `f64`, return that value. - pub fn unwrap_f64(self) -> f64 { - f64::from_bits(self.unwrap_f64_bits()) - } - - /// Assuming this `RuntimeValue` holds an `f64`, return the bits of that value as a `u64`. - pub fn unwrap_f64_bits(self) -> u64 { - match self { - RuntimeValue::F64(x) => x, - _ => panic!("unwrapping value of type {} as f64", self.value_type()), - } - } -} - -impl fmt::Display for RuntimeValue { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - RuntimeValue::I32(x) => write!(f, "{}: i32", x), - RuntimeValue::I64(x) => write!(f, "{}: i64", x), - RuntimeValue::F32(x) => write!(f, "{}: f32", x), - RuntimeValue::F64(x) => write!(f, "{}: f64", x), - } - } -} - -/// The result of invoking a wasm function or reading a wasm global. -#[derive(Debug)] -pub enum ActionOutcome { - /// The action returned normally. Its return values are provided. - Returned { - /// The return values. - values: Vec, - }, - - /// A trap occurred while the action was executing. - Trapped { - /// The trap message. - message: String, - }, -} - -/// An error detected while invoking a wasm function or reading a wasm global. -/// Note that at this level, traps are not reported errors, but are rather -/// returned through `ActionOutcome`. -#[derive(Fail, Debug)] -pub enum ActionError { - /// No field with the specified name was present. - #[fail(display = "Unknown field: {}", _0)] - Field(String), - - /// The field was present but was the wrong kind (eg. function, table, global, or memory). - #[fail(display = "Kind error: {}", _0)] - Kind(String), - - /// The field was present but was the wrong type (eg. i32, i64, f32, or f64). - #[fail(display = "Type error: {}", _0)] - Type(String), - - /// The module did not pass validation. - #[fail(display = "Validation error: {}", _0)] - Validate(String), - - /// A wasm translation error occured. - #[fail(display = "WebAssembly compilation error: {}", _0)] - Compile(CompileError), - - /// 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)] - Link(LinkError), - - /// Start function trapped. - #[fail(display = "Start function trapped: {}", _0)] - Start(String), -} diff --git a/lib/execute/src/instance_plus.rs b/lib/execute/src/instance_plus.rs deleted file mode 100644 index 6e5925d921..0000000000 --- a/lib/execute/src/instance_plus.rs +++ /dev/null @@ -1,288 +0,0 @@ -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::boxed::Box; -use std::cmp::max; -use std::rc::Rc; -use std::slice; -use std::string::String; -use std::vec::Vec; -use std::{mem, ptr}; -use target_tunables::target_tunables; -use trampoline_park::TrampolinePark; -use wasmtime_environ::{ - compile_module, Compilation, CompileError, DataInitializer, Module, ModuleEnvironment, -}; -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(); - let tunables = target_tunables(isa.triple()); - - 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. And pack the values into memory - // instead of just using fixed-sized slots. - 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 definition = match unsafe { self.instance.lookup_immutable(memory_name) } { - Some(Export::Memory { - definition, - memory: _memory, - vmctx: _vmctx, - }) => definition, - 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 = &*definition; - &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 (definition, global) = match unsafe { self.instance.lookup_immutable(global_name) } { - Some(Export::Global { definition, global }) => (definition, 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 = &*definition; - 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/trampoline_park.rs b/lib/execute/src/trampoline_park.rs deleted file mode 100644 index 4f09d80d76..0000000000 --- a/lib/execute/src/trampoline_park.rs +++ /dev/null @@ -1,153 +0,0 @@ -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 std::vec::Vec; -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/Cargo.toml b/lib/jit/Cargo.toml similarity index 97% rename from lib/execute/Cargo.toml rename to lib/jit/Cargo.toml index 1e6e14042b..e045717295 100644 --- a/lib/execute/Cargo.toml +++ b/lib/jit/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "wasmtime-execute" +name = "wasmtime-jit" version = "0.1.0" authors = ["The Cranelift Project Developers"] publish = false diff --git a/lib/execute/LICENSE b/lib/jit/LICENSE similarity index 100% rename from lib/execute/LICENSE rename to lib/jit/LICENSE diff --git a/lib/jit/README.md b/lib/jit/README.md new file mode 100644 index 0000000000..cbf255d893 --- /dev/null +++ b/lib/jit/README.md @@ -0,0 +1,6 @@ +This is the `wasmtime-jit` crate, which contains JIT-based execution +for wasm, using the wasm ABI defined by [`wasmtime-environ`] and the +runtime support provided by [`wasmtime-runtime`]. + +[`wasmtime-environ`]: https://crates.io/crates/wasmtime-environ +[`wasmtime-runtime`]: https://crates.io/crates/wasmtime-runtime diff --git a/lib/jit/src/action.rs b/lib/jit/src/action.rs new file mode 100644 index 0000000000..7d909b0ed7 --- /dev/null +++ b/lib/jit/src/action.rs @@ -0,0 +1,285 @@ +//! Support for performing actions with a wasm module from the outside. + +use compiler::Compiler; +use cranelift_codegen::ir; +use instantiate::SetupError; +use std::cmp::max; +use std::string::String; +use std::vec::Vec; +use std::{fmt, mem, ptr, slice}; +use wasmtime_runtime::{wasmtime_call_trampoline, Export, Instance}; + +/// A runtime value. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum RuntimeValue { + /// A runtime value with type i32. + I32(i32), + /// A runtime value with type i64. + I64(i64), + /// A runtime value with type f32. + F32(u32), + /// A runtime value with type f64. + F64(u64), +} + +impl RuntimeValue { + /// Return the type of this `RuntimeValue`. + pub fn value_type(self) -> ir::Type { + match self { + RuntimeValue::I32(_) => ir::types::I32, + RuntimeValue::I64(_) => ir::types::I64, + RuntimeValue::F32(_) => ir::types::F32, + RuntimeValue::F64(_) => ir::types::F64, + } + } + + /// Assuming this `RuntimeValue` holds an `i32`, return that value. + pub fn unwrap_i32(self) -> i32 { + match self { + RuntimeValue::I32(x) => x, + _ => panic!("unwrapping value of type {} as i32", self.value_type()), + } + } + + /// Assuming this `RuntimeValue` holds an `i64`, return that value. + pub fn unwrap_i64(self) -> i64 { + match self { + RuntimeValue::I64(x) => x, + _ => panic!("unwrapping value of type {} as i64", self.value_type()), + } + } + + /// Assuming this `RuntimeValue` holds an `f32`, return that value. + pub fn unwrap_f32(self) -> f32 { + f32::from_bits(self.unwrap_f32_bits()) + } + + /// Assuming this `RuntimeValue` holds an `f32`, return the bits of that value as a `u32`. + pub fn unwrap_f32_bits(self) -> u32 { + match self { + RuntimeValue::F32(x) => x, + _ => panic!("unwrapping value of type {} as f32", self.value_type()), + } + } + + /// Assuming this `RuntimeValue` holds an `f64`, return that value. + pub fn unwrap_f64(self) -> f64 { + f64::from_bits(self.unwrap_f64_bits()) + } + + /// Assuming this `RuntimeValue` holds an `f64`, return the bits of that value as a `u64`. + pub fn unwrap_f64_bits(self) -> u64 { + match self { + RuntimeValue::F64(x) => x, + _ => panic!("unwrapping value of type {} as f64", self.value_type()), + } + } +} + +impl fmt::Display for RuntimeValue { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + RuntimeValue::I32(x) => write!(f, "{}: i32", x), + RuntimeValue::I64(x) => write!(f, "{}: i64", x), + RuntimeValue::F32(x) => write!(f, "{}: f32", x), + RuntimeValue::F64(x) => write!(f, "{}: f64", x), + } + } +} + +/// The result of invoking a wasm function or reading a wasm global. +#[derive(Debug)] +pub enum ActionOutcome { + /// The action returned normally. Its return values are provided. + Returned { + /// The return values. + values: Vec, + }, + + /// A trap occurred while the action was executing. + Trapped { + /// The trap message. + message: String, + }, +} + +/// An error detected while invoking a wasm function or reading a wasm global. +/// Note that at this level, traps are not reported errors, but are rather +/// returned through `ActionOutcome`. +#[derive(Fail, Debug)] +pub enum ActionError { + /// An internal implementation error occurred. + #[fail(display = "{}", _0)] + Setup(SetupError), + + /// No field with the specified name was present. + #[fail(display = "Unknown field: {}", _0)] + Field(String), + + /// The field was present but was the wrong kind (eg. function, table, global, or memory). + #[fail(display = "Kind error: {}", _0)] + Kind(String), + + /// The field was present but was the wrong type (eg. i32, i64, f32, or f64). + #[fail(display = "Type error: {}", _0)] + Type(String), +} + +/// Invoke a function in an `Instance` identified by an export name. +pub fn invoke( + compiler: &mut Compiler, + instance: &mut Instance, + function_name: &str, + args: &[RuntimeValue], +) -> Result { + let (address, signature, callee_vmctx) = match 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. And pack the values into memory + // instead of just using fixed-sized slots. + 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 = compiler + .get_trampoline(address, &signature, value_size) + .map_err(ActionError::Setup)?; + + // Make all JIT code produced thus far executable. + compiler.publish_compiled_code(); + + // 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<'instance>( + instance: &'instance Instance, + memory_name: &str, + start: usize, + len: usize, +) -> Result<&'instance [u8], ActionError> { + let definition = match unsafe { instance.lookup_immutable(memory_name) } { + Some(Export::Memory { + definition, + memory: _memory, + vmctx: _vmctx, + }) => definition, + 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 = &*definition; + &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(instance: &Instance, global_name: &str) -> Result { + let (definition, global) = match unsafe { instance.lookup_immutable(global_name) } { + Some(Export::Global { definition, global }) => (definition, 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 = &*definition; + 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 + ))) + } + }) + } +} diff --git a/lib/execute/src/jit_code.rs b/lib/jit/src/code_memory.rs similarity index 96% rename from lib/execute/src/jit_code.rs rename to lib/jit/src/code_memory.rs index 3598e27c12..b9bf13f636 100644 --- a/lib/execute/src/jit_code.rs +++ b/lib/jit/src/code_memory.rs @@ -7,15 +7,15 @@ use std::{cmp, mem}; use wasmtime_runtime::{Mmap, VMFunctionBody}; /// Memory manager for executable code. -pub struct JITCode { +pub(crate) struct CodeMemory { current: Mmap, mmaps: Vec, position: usize, published: usize, } -impl JITCode { - /// Create a new `JITCode` instance. +impl CodeMemory { + /// Create a new `CodeMemory` instance. pub fn new() -> Self { Self { current: Mmap::new(), diff --git a/lib/jit/src/compiler.rs b/lib/jit/src/compiler.rs new file mode 100644 index 0000000000..3ec3642c16 --- /dev/null +++ b/lib/jit/src/compiler.rs @@ -0,0 +1,252 @@ +//! JIT compilation. + +use code_memory::CodeMemory; +use cranelift_codegen::ir::InstBuilder; +use cranelift_codegen::isa::{TargetFrontendConfig, TargetIsa}; +use cranelift_codegen::Context; +use cranelift_codegen::{binemit, ir}; +use cranelift_entity::PrimaryMap; +use cranelift_frontend::{FunctionBuilder, FunctionBuilderContext}; +use cranelift_wasm::DefinedFuncIndex; +use instantiate::SetupError; +use std::boxed::Box; +use std::collections::HashMap; +use std::string::String; +use std::vec::Vec; +use target_tunables::target_tunables; +use wasmtime_environ::cranelift; +use wasmtime_environ::{Compilation, CompileError, Module, Relocations, Tunables}; +use wasmtime_runtime::{InstantiationError, VMFunctionBody}; + +/// A WebAssembly code JIT compiler. +/// +/// A `Compiler` instance owns the executable memory that it allocates. +/// +/// TODO: Evolve this to support streaming rather than requiring a `&[u8]` +/// containing a whole wasm module at once. +/// +/// TODO: Consider using cranelift-module. +pub struct Compiler { + isa: Box, + + code_memory: CodeMemory, + trampoline_park: HashMap<*const VMFunctionBody, *const VMFunctionBody>, + + /// The `FunctionBuilderContext`, shared between trampline function compilations. + fn_builder_ctx: FunctionBuilderContext, +} + +impl Compiler { + /// Construct a new `Compiler`. + pub fn new(isa: Box) -> Self { + Self { + isa, + code_memory: CodeMemory::new(), + trampoline_park: HashMap::new(), + fn_builder_ctx: FunctionBuilderContext::new(), + } + } +} + +impl Compiler { + /// Return the target's frontend configuration settings. + pub fn frontend_config(&self) -> TargetFrontendConfig { + self.isa.frontend_config() + } + + /// Return the tunables in use by this engine. + pub fn tunables(&self) -> Tunables { + target_tunables(self.isa.triple()) + } + + /// Compile the given function bodies. + pub(crate) fn compile<'data>( + &mut self, + module: &Module, + function_body_inputs: PrimaryMap, + ) -> Result< + ( + PrimaryMap, + Relocations, + ), + SetupError, + > { + let (compilation, relocations) = + cranelift::compile_module(&module, function_body_inputs, &*self.isa) + .map_err(SetupError::Compile)?; + + let allocated_functions = + allocate_functions(&mut self.code_memory, compilation).map_err(|message| { + SetupError::Instantiate(InstantiationError::Resource(format!( + "failed to allocate memory for functions: {}", + message + ))) + })?; + + Ok((allocated_functions, relocations)) + } + + /// Create a trampoline for invoking a function. + pub(crate) fn get_trampoline( + &mut self, + callee_address: *const VMFunctionBody, + signature: &ir::Signature, + value_size: usize, + ) -> Result<*const VMFunctionBody, SetupError> { + use std::collections::hash_map::Entry::{Occupied, Vacant}; + Ok(match self.trampoline_park.entry(callee_address) { + Occupied(entry) => *entry.get(), + Vacant(entry) => { + let body = make_trampoline( + &*self.isa, + &mut self.code_memory, + &mut self.fn_builder_ctx, + callee_address, + signature, + value_size, + )?; + entry.insert(body); + body + } + }) + } + + /// Make memory containing compiled code executable. + pub(crate) fn publish_compiled_code(&mut self) { + self.code_memory.publish(); + } +} + +/// Create a trampoline for invoking a function. +fn make_trampoline( + isa: &TargetIsa, + code_memory: &mut CodeMemory, + fn_builder_ctx: &mut FunctionBuilderContext, + callee_address: *const VMFunctionBody, + signature: &ir::Signature, + value_size: usize, +) -> Result<*const VMFunctionBody, SetupError> { + 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 (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 {}; + let mut trap_sink = binemit::NullTrapSink {}; + context + .compile_and_emit(isa, &mut code_buf, &mut reloc_sink, &mut trap_sink) + .map_err(|error| SetupError::Compile(CompileError::Codegen(error)))?; + + Ok(code_memory + .allocate_copy_of_byte_slice(&code_buf) + .map_err(|message| SetupError::Instantiate(InstantiationError::Resource(message)))? + .as_ptr()) +} + +fn allocate_functions( + code_memory: &mut CodeMemory, + 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_memory.allocate_copy_of_byte_slice(body)?; + result.push(fatptr); + } + Ok(result) +} + +/// We don't expect trampoline compilation to produce any relocations, so +/// this `RelocSink` just asserts that it doesn't recieve any. +struct RelocSink {} + +impl binemit::RelocSink for RelocSink { + fn reloc_ebb( + &mut self, + _offset: binemit::CodeOffset, + _reloc: binemit::Reloc, + _ebb_offset: binemit::CodeOffset, + ) { + panic!("trampoline compilation should not produce ebb relocs"); + } + fn reloc_external( + &mut self, + _offset: binemit::CodeOffset, + _reloc: binemit::Reloc, + _name: &ir::ExternalName, + _addend: binemit::Addend, + ) { + panic!("trampoline compilation should not produce external symbol relocs"); + } + fn reloc_jt( + &mut self, + _offset: binemit::CodeOffset, + _reloc: binemit::Reloc, + _jt: ir::JumpTable, + ) { + panic!("trampoline compilation should not produce jump table relocs"); + } +} diff --git a/lib/jit/src/instantiate.rs b/lib/jit/src/instantiate.rs new file mode 100644 index 0000000000..c7772531ca --- /dev/null +++ b/lib/jit/src/instantiate.rs @@ -0,0 +1,199 @@ +//! Define the `instantiate` function, which takes a byte array containing an +//! encoded wasm module and returns a live wasm instance. Also, define +//! `CompiledModule` to allow compiling and instantiating to be done as separate +//! steps. + +use compiler::Compiler; +use cranelift_entity::{BoxedSlice, PrimaryMap}; +use cranelift_wasm::DefinedFuncIndex; +use link::link_module; +use resolver::Resolver; +use std::boxed::Box; +use std::rc::Rc; +use std::string::String; +use std::vec::Vec; +use wasmtime_environ::{ + CompileError, DataInitializer, DataInitializerLocation, Module, ModuleEnvironment, +}; +use wasmtime_runtime::{Imports, Instance, InstantiationError, VMFunctionBody}; + +/// An error condition while setting up a wasm instance, be it validation, +/// compilation, or instantiation. +#[derive(Fail, Debug)] +pub enum SetupError { + /// The module did not pass validation. + #[fail(display = "Validation error: {}", _0)] + Validate(String), + + /// A wasm translation error occured. + #[fail(display = "WebAssembly compilation error: {}", _0)] + Compile(CompileError), + + /// Some runtime resource was unavailable or insufficient, or the start function + /// trapped. + #[fail(display = "Instantiation error: {}", _0)] + Instantiate(InstantiationError), +} + +/// This is similar to `CompiledModule`, but references the data initializers +/// from the wasm buffer rather than holding its own copy. +struct RawCompiledModule<'data> { + module: Module, + finished_functions: BoxedSlice, + imports: Imports, + data_initializers: Box<[DataInitializer<'data>]>, +} + +impl<'data> RawCompiledModule<'data> { + /// Create a new `RawCompiledModule` by compiling the wasm module in `data` and instatiating it. + fn new( + compiler: &mut Compiler, + data: &'data [u8], + resolver: &mut Resolver, + ) -> Result { + let environ = ModuleEnvironment::new(compiler.frontend_config(), compiler.tunables()); + + let translation = environ + .translate(data) + .map_err(|error| SetupError::Compile(CompileError::Wasm(error)))?; + + let (allocated_functions, relocations) = + compiler.compile(&translation.module, translation.function_body_inputs)?; + + let imports = link_module( + &translation.module, + &allocated_functions, + relocations, + resolver, + ) + .map_err(|err| SetupError::Instantiate(InstantiationError::Link(err)))?; + + // 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. + compiler.publish_compiled_code(); + + Ok(Self { + module: translation.module, + finished_functions, + imports, + data_initializers: translation.data_initializers.into_boxed_slice(), + }) + } +} + +/// A compiled wasm module, ready to be instantiated. +pub struct CompiledModule { + module: Rc, + finished_functions: BoxedSlice, + imports: Imports, + data_initializers: Box<[OwnedDataInitializer]>, +} + +impl CompiledModule { + /// Compile a data buffer into a `CompiledModule`, which may then be instantiated. + pub fn new<'data>( + compiler: &mut Compiler, + data: &'data [u8], + resolver: &mut Resolver, + ) -> Result { + let raw = RawCompiledModule::<'data>::new(compiler, data, resolver)?; + + Ok(Self { + module: Rc::new(raw.module), + finished_functions: raw.finished_functions, + imports: raw.imports, + data_initializers: raw + .data_initializers + .iter() + .map(OwnedDataInitializer::new) + .collect::>() + .into_boxed_slice(), + }) + } + + /// Construct a `CompiledModule` from component parts. + pub fn from_parts( + module: Module, + finished_functions: BoxedSlice, + imports: Imports, + data_initializers: Box<[OwnedDataInitializer]>, + ) -> Self { + Self { + module: Rc::new(module), + finished_functions, + imports, + data_initializers, + } + } + + /// Crate an `Instance` from this `CompiledModule`. + /// + /// Note that if only one instance of this module is needed, it may be more + /// efficient to call the top-level `instantiate`, since that avoids copying + /// the data initializers. + pub fn instantiate(&mut self) -> Result, InstantiationError> { + let data_initializers = self + .data_initializers + .iter() + .map(|init| DataInitializer { + location: init.location.clone(), + data: &*init.data, + }) + .collect::>(); + Instance::new( + Rc::clone(&self.module), + self.finished_functions.clone(), + self.imports.clone(), + &data_initializers, + ) + } +} + +/// Similar to `DataInitializer`, but owns its own copy of the data rather +/// than holding a slice of the original module. +pub struct OwnedDataInitializer { + /// The location where the initialization is to be performed. + location: DataInitializerLocation, + + /// The initialization data. + data: Box<[u8]>, +} + +impl OwnedDataInitializer { + fn new(borrowed: &DataInitializer) -> Self { + Self { + location: borrowed.location.clone(), + data: borrowed.data.to_vec().into_boxed_slice(), + } + } +} + +/// Create a new `Instance` by compiling the wasm module in `data` and instatiating it. +/// +/// This is equivalent to createing a `CompiledModule` and calling `instantiate()` on it, +/// but avoids creating an intermediate copy of the data initializers. +pub fn instantiate( + compiler: &mut Compiler, + data: &[u8], + resolver: &mut Resolver, +) -> Result, SetupError> { + let raw = RawCompiledModule::new(compiler, data, resolver)?; + + Instance::new( + Rc::new(raw.module), + raw.finished_functions, + raw.imports, + &*raw.data_initializers, + ) + .map_err(SetupError::Instantiate) +} diff --git a/lib/execute/src/lib.rs b/lib/jit/src/lib.rs similarity index 81% rename from lib/execute/src/lib.rs rename to lib/jit/src/lib.rs index 39b061c2da..195502a7d6 100644 --- a/lib/execute/src/lib.rs +++ b/lib/jit/src/lib.rs @@ -36,27 +36,31 @@ extern crate wasmtime_runtime; #[macro_use] extern crate alloc; extern crate failure; +extern crate target_lexicon; #[macro_use] extern crate failure_derive; -extern crate target_lexicon; mod action; -mod instance_plus; -mod jit_code; +mod code_memory; +mod compiler; +mod instantiate; mod link; mod namespace; mod resolver; mod target_tunables; -mod trampoline_park; pub use action::{ActionError, ActionOutcome, RuntimeValue}; -pub use instance_plus::InstancePlus; -pub use jit_code::JITCode; +pub use compiler::Compiler; +pub use instantiate::{instantiate, CompiledModule, SetupError}; pub use link::link_module; -pub use namespace::{InstancePlusIndex, Namespace}; +pub use namespace::{InstanceIndex, Namespace}; pub use resolver::{NullResolver, Resolver}; pub use target_tunables::target_tunables; +// Re-export `Instance` so that users won't need to separately depend on +// wasmtime-runtime in common cases. +pub use wasmtime_runtime::{Instance, InstantiationError}; + #[cfg(not(feature = "std"))] mod std { pub use alloc::{boxed, rc, string, vec}; diff --git a/lib/execute/src/link.rs b/lib/jit/src/link.rs similarity index 98% rename from lib/execute/src/link.rs rename to lib/jit/src/link.rs index 2f34d39f6d..596c27846c 100644 --- a/lib/execute/src/link.rs +++ b/lib/jit/src/link.rs @@ -1,24 +1,20 @@ +//! Linking for JIT-compiled code. + use cranelift_codegen::binemit::Reloc; use cranelift_entity::PrimaryMap; use cranelift_wasm::{DefinedFuncIndex, Global, GlobalInit, Memory, Table, TableElementType}; 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, }; use wasmtime_runtime::libcalls; use wasmtime_runtime::{ - Export, Imports, VMFunctionBody, VMFunctionImport, VMGlobalImport, VMMemoryImport, + Export, Imports, LinkError, VMFunctionBody, VMFunctionImport, VMGlobalImport, VMMemoryImport, VMTableImport, }; -/// A link error, such as incompatible or unmatched imports/exports. -#[derive(Fail, Debug)] -#[fail(display = "Link error: {}", _0)] -pub struct LinkError(String); - /// Links a module that has been compiled with `compiled_module` in `wasmtime-environ`. pub fn link_module( module: &Module, diff --git a/lib/execute/src/namespace.rs b/lib/jit/src/namespace.rs similarity index 62% rename from lib/execute/src/namespace.rs rename to lib/jit/src/namespace.rs index a30eda4c9e..bd357c59ac 100644 --- a/lib/execute/src/namespace.rs +++ b/lib/jit/src/namespace.rs @@ -2,19 +2,20 @@ //! to exports. This file provides one possible way to manage multiple instances //! and resolve imports to exports among them. +use action::{get, inspect_memory, invoke}; use action::{ActionError, ActionOutcome, RuntimeValue}; -use cranelift_codegen::isa; +use compiler::Compiler; use cranelift_entity::PrimaryMap; -use instance_plus::InstancePlus; -use jit_code::JITCode; use resolver::Resolver; +use std::boxed::Box; use std::collections::HashMap; -use wasmtime_runtime::Export; +use std::string::String; +use wasmtime_runtime::{Export, Instance}; -/// An opaque reference to an `InstancePlus`. +/// An opaque reference to an `Instance`. #[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct InstancePlusIndex(u32); -entity_impl!(InstancePlusIndex, "instance"); +pub struct InstanceIndex(u32); +entity_impl!(InstanceIndex, "instance"); /// A namespace containing instances keyed by name. /// @@ -22,10 +23,10 @@ entity_impl!(InstancePlusIndex, "instance"); /// imports using defined exports. pub struct Namespace { /// Mapping from identifiers to indices in `self.instances`. - names: HashMap, + names: HashMap, /// The instances, available by index. - instances: PrimaryMap, + instances: PrimaryMap>, } impl Namespace { @@ -37,13 +38,13 @@ impl Namespace { } } - /// Install a new `InstancePlus` in this `Namespace`, optionally with the + /// Install a new `Instance` in this `Namespace`, optionally with the /// given name, and return its index. pub fn instance( &mut self, instance_name: Option<&str>, - instance: InstancePlus, - ) -> InstancePlusIndex { + instance: Box, + ) -> InstanceIndex { let index = self.instances.push(instance); if let Some(instance_name) = instance_name { self.names.insert(instance_name.into(), index); @@ -52,41 +53,47 @@ impl Namespace { } /// Get the instance index registered with the given `instance_name`. - pub fn get_instance_index(&mut self, instance_name: &str) -> Option<&mut InstancePlusIndex> { + pub fn get_instance_index(&mut self, instance_name: &str) -> Option<&mut InstanceIndex> { self.names.get_mut(instance_name) } /// Register an instance with a given name. - pub fn register(&mut self, name: String, index: InstancePlusIndex) { + pub fn register(&mut self, name: String, index: InstanceIndex) { self.names.insert(name, index); } /// Invoke an exported function from an instance. pub fn invoke( &mut self, - jit_code: &mut JITCode, - isa: &isa::TargetIsa, - index: InstancePlusIndex, + compiler: &mut Compiler, + index: InstanceIndex, field_name: &str, args: &[RuntimeValue], ) -> Result { - self.instances[index].invoke(jit_code, isa, &field_name, &args) + invoke(compiler, &mut self.instances[index], &field_name, &args) + } + + /// Get a slice of memory from an instance. + pub fn inspect_memory( + &self, + index: InstanceIndex, + field_name: &str, + start: usize, + len: usize, + ) -> Result<&[u8], ActionError> { + inspect_memory(&self.instances[index], &field_name, start, len) } /// Get the value of an exported global from an instance. - pub fn get( - &mut self, - index: InstancePlusIndex, - field_name: &str, - ) -> Result { - self.instances[index].get(&field_name) + pub fn get(&self, index: InstanceIndex, field_name: &str) -> Result { + get(&self.instances[index], &field_name) } } impl Resolver for Namespace { fn resolve(&mut self, instance: &str, field: &str) -> Option { if let Some(index) = self.names.get(instance) { - self.instances[*index].instance.lookup(field) + self.instances[*index].lookup(field) } else { None } diff --git a/lib/execute/src/resolver.rs b/lib/jit/src/resolver.rs similarity index 83% rename from lib/execute/src/resolver.rs rename to lib/jit/src/resolver.rs index 452cd148a9..9b8ba197cb 100644 --- a/lib/execute/src/resolver.rs +++ b/lib/jit/src/resolver.rs @@ -1,3 +1,6 @@ +//! Define the `Resolver` trait, allowing custom resolution for external +//! references. + use wasmtime_runtime::Export; /// Import resolver connects imports with available exported values. diff --git a/lib/execute/src/target_tunables.rs b/lib/jit/src/target_tunables.rs similarity index 100% rename from lib/execute/src/target_tunables.rs rename to lib/jit/src/target_tunables.rs diff --git a/lib/runtime/README.md b/lib/runtime/README.md index 560835bb0e..97fe8c372a 100644 --- a/lib/runtime/README.md +++ b/lib/runtime/README.md @@ -1,7 +1,7 @@ This is the `wasmtime-runtime` crate, which contains wasm runtime library support, supporting the wasm ABI used by [`wasmtime-environ`], -[`wasmtime-execute`], and [`wasmtime-obj`]. +[`wasmtime-jit`], and [`wasmtime-obj`]. [`wasmtime-environ`]: https://crates.io/crates/wasmtime-environ -[`wasmtime-execute`]: https://crates.io/crates/wasmtime-execute +[`wasmtime-jit`]: https://crates.io/crates/wasmtime-jit [`wasmtime-obj`]: https://crates.io/crates/wasmtime-obj diff --git a/lib/runtime/signalhandlers/SignalHandlers.cpp b/lib/runtime/signalhandlers/SignalHandlers.cpp index 31db53b467..91d15a8d1d 100644 --- a/lib/runtime/signalhandlers/SignalHandlers.cpp +++ b/lib/runtime/signalhandlers/SignalHandlers.cpp @@ -640,9 +640,9 @@ WasmTrapHandler(int signum, siginfo_t* info, void* context) } assert(previousSignal); - // This signal is not for any JIT code we expect, so we need to forward - // the signal to the next handler. If there is no next handler (SIG_IGN or - // SIG_DFL), then it's time to crash. To do this, we set the signal back to + // This signal is not for any compiled wasm code we expect, so we need to + // forward the signal to the next handler. If there is no next handler (SIG_IGN + // or SIG_DFL), then it's time to crash. To do this, we set the signal back to // its original disposition and return. This will cause the faulting op to // be re-executed which will crash in the normal way. The advantage of // doing this to calling _exit() is that we remove ourselves from the crash diff --git a/lib/runtime/src/imports.rs b/lib/runtime/src/imports.rs index c42d7e9e44..585e33e65d 100644 --- a/lib/runtime/src/imports.rs +++ b/lib/runtime/src/imports.rs @@ -3,7 +3,7 @@ use cranelift_wasm::{FuncIndex, GlobalIndex, MemoryIndex, TableIndex}; use vmcontext::{VMFunctionImport, VMGlobalImport, VMMemoryImport, VMTableImport}; /// Resolved import pointers. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Imports { /// Resolved addresses for imported functions. pub functions: BoxedSlice, diff --git a/lib/runtime/src/instance.rs b/lib/runtime/src/instance.rs index 8c7557b853..3158a027c5 100644 --- a/lib/runtime/src/instance.rs +++ b/lib/runtime/src/instance.rs @@ -23,6 +23,9 @@ use vmcontext::{ use wasmtime_environ::{DataInitializer, Module}; /// An Instance of a WebAssemby module. +/// +/// Note that compiled wasm code passes around raw pointers to `Instance`, so +/// this shouldn't be moved. #[derive(Debug)] pub struct Instance { /// The `Module` this `Instance` was instantiated from. @@ -53,7 +56,7 @@ pub struct Instance { /// WebAssembly global variable data. vmctx_globals: BoxedSlice, - /// Context pointer used by JIT code. + /// Context pointer used by compiled wasm code. vmctx: VMContext, } @@ -63,7 +66,7 @@ impl Instance { module: Rc, finished_functions: BoxedSlice, mut vmctx_imports: Imports, - data_initializers: Vec, + data_initializers: &[DataInitializer], ) -> Result, InstantiationError> { let mut sig_registry = create_and_initialize_signatures(&module); let mut tables = create_tables(&module); @@ -125,7 +128,7 @@ impl Instance { // Check initializer bounds before initializing anything. check_table_init_bounds(&mut *result)?; - check_memory_init_bounds(&mut *result, &data_initializers)?; + check_memory_init_bounds(&mut *result, data_initializers)?; // Apply the initializers. initialize_tables(&mut *result)?; @@ -148,22 +151,22 @@ impl Instance { Ok(result) } - /// Return a reference to the vmctx used by JIT code. + /// Return a reference to the vmctx used by compiled wasm code. pub fn vmctx(&self) -> &VMContext { &self.vmctx } - /// Return a raw pointer to the vmctx used by JIT code. + /// Return a raw pointer to the vmctx used by compiled wasm code. pub fn vmctx_ptr(&self) -> *const VMContext { self.vmctx() } - /// Return a mutable reference to the vmctx used by JIT code. + /// Return a mutable reference to the vmctx used by compiled wasm code. pub fn vmctx_mut(&mut self) -> &mut VMContext { &mut self.vmctx } - /// Return a mutable raw pointer to the vmctx used by JIT code. + /// Return a mutable raw pointer to the vmctx used by compiled wasm code. pub fn vmctx_mut_ptr(&mut self) -> *mut VMContext { self.vmctx_mut() } @@ -184,7 +187,7 @@ impl Instance { .unwrap_or_else(|| panic!("no memory for index {}", memory_index.index())) .grow(delta); - // Keep current the VMContext pointers used by JIT code. + // Keep current the VMContext pointers used by compiled wasm code. self.vmctx_memories[memory_index] = self.memories[memory_index].vmmemory(); result @@ -314,7 +317,7 @@ impl Instance { /// 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); + let temporary_mut = &mut *(self as *const Self as *mut Self); temporary_mut.lookup(field) } } @@ -346,9 +349,9 @@ fn check_table_init_bounds(instance: &mut Instance) -> Result<(), InstantiationE }; if slice.get_mut(start..start + init.elements.len()).is_none() { - return Err(InstantiationError::Link( + return Err(InstantiationError::Link(LinkError( "elements segment does not fit".to_owned(), - )); + ))); } } @@ -361,8 +364,8 @@ fn check_memory_init_bounds( ) -> Result<(), InstantiationError> { for init in data_initializers { // TODO: Refactor this. - let mut start = init.offset; - if let Some(base) = init.base { + let mut start = init.location.offset; + if let Some(base) = init.location.base { let global = if let Some(def_index) = instance.module.defined_global_index(base) { unsafe { instance.vmctx.global_mut(def_index) } } else { @@ -372,12 +375,13 @@ fn check_memory_init_bounds( } // TODO: Refactor this. - let memory = if let Some(defined_memory_index) = - instance.module.defined_memory_index(init.memory_index) + let memory = if let Some(defined_memory_index) = instance + .module + .defined_memory_index(init.location.memory_index) { unsafe { instance.vmctx.memory(defined_memory_index) } } else { - let import = &instance.vmctx_imports.memories[init.memory_index]; + let import = &instance.vmctx_imports.memories[init.location.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); @@ -386,9 +390,9 @@ fn check_memory_init_bounds( 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( + return Err(InstantiationError::Link(LinkError( "data segment does not fit".to_owned(), - )); + ))); } } @@ -457,9 +461,9 @@ fn initialize_tables(instance: &mut Instance) -> Result<(), InstantiationError> }; } } else { - return Err(InstantiationError::Link( + return Err(InstantiationError::Link(LinkError( "elements segment does not fit".to_owned(), - )); + ))); } } @@ -482,11 +486,11 @@ fn create_memories( /// Initialize the table memory from the provided initializers. fn initialize_memories( instance: &mut Instance, - data_initializers: Vec, + data_initializers: &[DataInitializer], ) -> Result<(), InstantiationError> { for init in data_initializers { - let mut start = init.offset; - if let Some(base) = init.base { + let mut start = init.location.offset; + if let Some(base) = init.location.base { let global = if let Some(def_index) = instance.module.defined_global_index(base) { unsafe { instance.vmctx.global_mut(def_index) } } else { @@ -495,12 +499,13 @@ fn initialize_memories( 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) + let memory = if let Some(defined_memory_index) = instance + .module + .defined_memory_index(init.location.memory_index) { unsafe { instance.vmctx.memory(defined_memory_index) } } else { - let import = &instance.vmctx_imports.memories[init.memory_index]; + let import = &instance.vmctx_imports.memories[init.location.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); @@ -510,9 +515,9 @@ fn initialize_memories( 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( + return Err(InstantiationError::Link(LinkError( "data segment does not fit".to_owned(), - )); + ))); } } @@ -555,6 +560,11 @@ fn initialize_globals(instance: &mut Instance) { } } +/// An link error while instantiating a module. +#[derive(Fail, Debug)] +#[fail(display = "Link error: {}", _0)] +pub struct LinkError(pub String); + /// An error while instantiating a module. #[derive(Fail, Debug)] pub enum InstantiationError { @@ -562,9 +572,9 @@ pub enum InstantiationError { #[fail(display = "Insufficient resources: {}", _0)] Resource(String), - /// A wasm translation error occured. - #[fail(display = "Link error: {}", _0)] - Link(String), + /// A wasm link error occured. + #[fail(display = "{}", _0)] + Link(LinkError), /// A compilation error occured. #[fail(display = "Trap occurred while invoking start function: {}", _0)] diff --git a/lib/runtime/src/lib.rs b/lib/runtime/src/lib.rs index 304b106c67..f8a67aec2e 100644 --- a/lib/runtime/src/lib.rs +++ b/lib/runtime/src/lib.rs @@ -60,7 +60,7 @@ pub mod libcalls; pub use export::Export; pub use imports::Imports; -pub use instance::{Instance, InstantiationError}; +pub use instance::{Instance, InstantiationError, LinkError}; pub use mmap::Mmap; pub use signalhandlers::{wasmtime_init_eager, wasmtime_init_finish}; pub use traphandlers::{wasmtime_call, wasmtime_call_trampoline}; diff --git a/lib/runtime/src/libcalls.rs b/lib/runtime/src/libcalls.rs index 8a0fb0de1d..c71e799be4 100644 --- a/lib/runtime/src/libcalls.rs +++ b/lib/runtime/src/libcalls.rs @@ -1,6 +1,6 @@ -//! Runtime library calls. Note that the JIT may sometimes perform these inline -//! rather than calling them, particularly when CPUs have special instructions -//! which compute them directly. +//! Runtime library calls. Note that wasm compilers may sometimes perform these +//! inline rather than calling them, particularly when CPUs have special +//! instructions which compute them directly. use cranelift_wasm::{DefinedMemoryIndex, MemoryIndex}; use vmcontext::VMContext; diff --git a/lib/runtime/src/memory.rs b/lib/runtime/src/memory.rs index 5413dc6204..a5a6ea38ae 100644 --- a/lib/runtime/src/memory.rs +++ b/lib/runtime/src/memory.rs @@ -148,7 +148,7 @@ impl LinearMemory { Some(prev_pages) } - /// Return a `VMMemoryDefinition` for exposing the memory to JIT code. + /// Return a `VMMemoryDefinition` for exposing the memory to compiled wasm code. pub fn vmmemory(&mut self) -> VMMemoryDefinition { VMMemoryDefinition { base: self.mmap.as_mut_ptr(), diff --git a/lib/runtime/src/mmap.rs b/lib/runtime/src/mmap.rs index 749140c52b..3384318371 100644 --- a/lib/runtime/src/mmap.rs +++ b/lib/runtime/src/mmap.rs @@ -110,7 +110,7 @@ impl Mmap { self.ptr } - /// Return the lengthof the allocated memory. + /// Return the length of the allocated memory. pub fn len(&self) -> usize { self.len } diff --git a/lib/runtime/src/table.rs b/lib/runtime/src/table.rs index e25aa035f2..cb4aae11a3 100644 --- a/lib/runtime/src/table.rs +++ b/lib/runtime/src/table.rs @@ -39,7 +39,7 @@ impl Table { } } - /// Return a `VMTableDefinition` for exposing the table to JIT code. + /// Return a `VMTableDefinition` for exposing the table to compiled wasm code. pub fn vmtable(&mut self) -> VMTableDefinition { VMTableDefinition { base: self.vec.as_mut_ptr() as *mut u8, diff --git a/lib/runtime/src/vmcontext.rs b/lib/runtime/src/vmcontext.rs index 29da5e1891..e34eee1fa2 100644 --- a/lib/runtime/src/vmcontext.rs +++ b/lib/runtime/src/vmcontext.rs @@ -1,5 +1,5 @@ //! This file declares `VMContext` and several related structs which contain -//! fields that JIT code accesses directly. +//! fields that compiled wasm code accesses directly. use cranelift_entity::EntityRef; use cranelift_wasm::{ @@ -62,7 +62,7 @@ mod test_vmfunction_body { } } -/// The fields a JIT needs to access to utilize a WebAssembly table +/// The fields compiled code needs to access to utilize a WebAssembly table /// imported from another instance. #[derive(Debug, Copy, Clone)] #[repr(C)] @@ -98,7 +98,7 @@ mod test_vmtable_import { } } -/// The fields a JIT needs to access to utilize a WebAssembly linear +/// The fields compiled code needs to access to utilize a WebAssembly linear /// memory imported from another instance. #[derive(Debug, Copy, Clone)] #[repr(C)] @@ -134,7 +134,7 @@ mod test_vmmemory_import { } } -/// The fields a JIT needs to access to utilize a WebAssembly global +/// The fields compiled code needs to access to utilize a WebAssembly global /// variable imported from another instance. #[derive(Debug, Copy, Clone)] #[repr(C)] @@ -163,7 +163,7 @@ mod test_vmglobal_import { } } -/// The fields a JIT needs to access to utilize a WebAssembly linear +/// The fields compiled code needs to access to utilize a WebAssembly linear /// memory defined within the instance, namely the start address and the /// size in bytes. #[derive(Debug, Copy, Clone)] @@ -206,7 +206,7 @@ mod test_vmmemory_definition { } } -/// The fields a JIT needs to access to utilize a WebAssembly table +/// The fields compiled code needs to access to utilize a WebAssembly table /// defined within the instance. #[derive(Debug, Copy, Clone)] #[repr(C)] diff --git a/lib/wast/Cargo.toml b/lib/wast/Cargo.toml index 9c23c201a1..14c117f8c3 100644 --- a/lib/wast/Cargo.toml +++ b/lib/wast/Cargo.toml @@ -14,7 +14,7 @@ cranelift-codegen = "0.26.0" cranelift-native = "0.26.0" cranelift-wasm = "0.26.0" cranelift-entity = "0.26.0" -wasmtime-execute = { path = "../execute" } +wasmtime-jit = { path = "../jit" } wasmtime-runtime = { path = "../runtime" } wasmtime-environ = { path = "../environ" } wabt = "0.7" diff --git a/lib/wast/src/lib.rs b/lib/wast/src/lib.rs index 50f68500c9..3cdeb5c8e7 100644 --- a/lib/wast/src/lib.rs +++ b/lib/wast/src/lib.rs @@ -32,7 +32,7 @@ extern crate target_lexicon; extern crate wabt; extern crate wasmparser; extern crate wasmtime_environ; -extern crate wasmtime_execute; +extern crate wasmtime_jit; extern crate wasmtime_runtime; mod spectest; diff --git a/lib/wast/src/spectest.rs b/lib/wast/src/spectest.rs index 4ea623b55a..19afc595af 100644 --- a/lib/wast/src/spectest.rs +++ b/lib/wast/src/spectest.rs @@ -2,13 +2,12 @@ use cranelift_codegen::ir::types; use cranelift_codegen::{ir, isa}; use cranelift_entity::PrimaryMap; use cranelift_wasm::{DefinedFuncIndex, Global, GlobalInit, Memory, Table, TableElementType}; -use std::rc::Rc; use target_lexicon::HOST; use wasmtime_environ::{ translate_signature, Export, MemoryPlan, MemoryStyle, Module, TablePlan, TableStyle, }; -use wasmtime_execute::{target_tunables, ActionError, InstancePlus}; -use wasmtime_runtime::{Imports, VMFunctionBody}; +use wasmtime_jit::{target_tunables, CompiledModule}; +use wasmtime_runtime::{Imports, Instance, InstantiationError, VMFunctionBody}; extern "C" fn spectest_print() {} @@ -46,7 +45,7 @@ extern "C" fn spectest_print_f64_f64(x: f64, y: f64) { /// Return an instance implementing the "spectest" interface used in the /// spec testsuite. -pub fn instantiate_spectest() -> Result { +pub fn instantiate_spectest() -> Result, InstantiationError> { let call_conv = isa::CallConv::triple_default(&HOST); let pointer_type = types::Type::triple_pointer_type(&HOST); let mut module = Module::new(); @@ -218,10 +217,11 @@ pub fn instantiate_spectest() -> Result { let imports = Imports::none(); let data_initializers = Vec::new(); - InstancePlus::with_parts( - Rc::new(module), + CompiledModule::from_parts( + module, finished_functions.into_boxed_slice(), imports, - data_initializers, + data_initializers.into_boxed_slice(), ) + .instantiate() } diff --git a/lib/wast/src/wast.rs b/lib/wast/src/wast.rs index 35ce76d505..4e00c2deaf 100644 --- a/lib/wast/src/wast.rs +++ b/lib/wast/src/wast.rs @@ -1,12 +1,12 @@ -use cranelift_codegen::isa; use spectest::instantiate_spectest; use std::io::Read; use std::path::Path; use std::{fmt, fs, io, str}; use wabt::script::{Action, Command, CommandKind, ModuleBinary, ScriptParser, Value}; use wasmparser::{validate, OperatorValidatorConfig, ValidatingParserConfig}; -use wasmtime_execute::{ - ActionError, ActionOutcome, InstancePlus, InstancePlusIndex, JITCode, Namespace, RuntimeValue, +use wasmtime_jit::{ + instantiate, ActionError, ActionOutcome, Compiler, Instance, InstanceIndex, InstantiationError, + Namespace, RuntimeValue, SetupError, }; /// Translate from a script::Value to a RuntimeValue. @@ -77,22 +77,22 @@ pub struct WastFileError { /// to be performed on them. pub struct WastContext { /// A namespace of wasm modules, keyed by an optional name. - current: Option, + current: Option, namespace: Namespace, - jit_code: JITCode, + compiler: Box, } impl WastContext { /// Construct a new instance of `WastContext`. - pub fn new() -> Self { + pub fn new(compiler: Box) -> Self { Self { current: None, namespace: Namespace::new(), - jit_code: JITCode::new(), + compiler, } } - fn validate(&mut self, data: &[u8]) -> Result<(), ActionError> { + fn validate(&mut self, data: &[u8]) -> Result<(), String> { let config = ValidatingParserConfig { operator_config: OperatorValidatorConfig { enable_threads: false, @@ -107,26 +107,19 @@ impl WastContext { Ok(()) } else { // TODO: Work with wasmparser to get better error messages. - Err(ActionError::Validate("module did not validate".to_owned())) + Err("module did not validate".to_owned()) } } - fn instantiate( - &mut self, - isa: &isa::TargetIsa, - module: ModuleBinary, - ) -> Result { + fn instantiate(&mut self, module: ModuleBinary) -> Result, SetupError> { let data = module.into_vec(); - self.validate(&data)?; + self.validate(&data).map_err(SetupError::Validate)?; - InstancePlus::new(&mut self.jit_code, isa, &data, &mut self.namespace) + instantiate(&mut *self.compiler, &data, &mut self.namespace) } - fn get_index( - &mut self, - instance_name: &Option, - ) -> Result { + fn get_index(&mut self, instance_name: &Option) -> Result { let index = *if let Some(instance_name) = instance_name { self.namespace .get_instance_index(instance_name) @@ -145,24 +138,20 @@ impl WastContext { } /// Register "spectest" which is used by the spec testsuite. - pub fn register_spectest(&mut self) -> Result<(), ActionError> { + pub fn register_spectest(&mut self) -> Result<(), InstantiationError> { let instance = instantiate_spectest()?; self.namespace.instance(Some("spectest"), instance); Ok(()) } /// Perform the action portion of a command. - fn perform_action( - &mut self, - isa: &isa::TargetIsa, - action: Action, - ) -> Result { + fn perform_action(&mut self, action: Action) -> Result { match action { Action::Invoke { module: instance_name, field, args, - } => self.invoke(isa, instance_name, &field, &args), + } => self.invoke(instance_name, &field, &args), Action::Get { module: instance_name, field, @@ -173,11 +162,10 @@ impl WastContext { /// Define a module and register it. fn module( &mut self, - isa: &isa::TargetIsa, instance_name: Option, module: ModuleBinary, ) -> Result<(), ActionError> { - let instance = self.instantiate(isa, module)?; + let instance = self.instantiate(module).map_err(ActionError::Setup)?; let index = self .namespace .instance(instance_name.as_ref().map(String::as_str), instance); @@ -195,7 +183,6 @@ impl WastContext { /// Invoke an exported function from an instance. fn invoke( &mut self, - isa: &isa::TargetIsa, instance_name: Option, field: &str, args: &[Value], @@ -206,7 +193,7 @@ impl WastContext { .collect::>(); let index = self.get_index(&instance_name)?; self.namespace - .invoke(&mut self.jit_code, isa, index, &field, &value_args) + .invoke(&mut *self.compiler, index, &field, &value_args) .map_err(WastError::Action) } @@ -227,24 +214,15 @@ impl WastContext { } /// Perform the action of a `PerformAction`. - fn perform_action_command( - &mut self, - isa: &isa::TargetIsa, - action: Action, - ) -> Result<(), WastError> { - match self.perform_action(isa, action)? { + fn perform_action_command(&mut self, action: Action) -> Result<(), WastError> { + match self.perform_action(action)? { ActionOutcome::Returned { .. } => Ok(()), ActionOutcome::Trapped { message } => Err(WastError::Trap(message)), } } /// Run a wast script from a byte buffer. - pub fn run_buffer( - &mut self, - isa: &isa::TargetIsa, - filename: &str, - wast: &[u8], - ) -> Result<(), WastFileError> { + pub fn run_buffer(&mut self, filename: &str, wast: &[u8]) -> Result<(), WastFileError> { let mut parser = ScriptParser::from_str(str::from_utf8(wast).unwrap()).unwrap(); while let Some(Command { kind, line }) = parser.next().expect("parser") { @@ -253,7 +231,7 @@ impl WastContext { module: instance_name, name, } => { - self.module(isa, name, instance_name) + self.module(name, instance_name) .map_err(|error| WastFileError { filename: filename.to_string(), line, @@ -269,7 +247,7 @@ impl WastContext { })?; } CommandKind::PerformAction(action) => { - self.perform_action_command(isa, action) + self.perform_action_command(action) .map_err(|error| WastFileError { filename: filename.to_string(), line, @@ -277,13 +255,11 @@ impl WastContext { })?; } CommandKind::AssertReturn { action, expected } => { - match self - .perform_action(isa, action) - .map_err(|error| WastFileError { - filename: filename.to_string(), - line, - error, - })? { + match self.perform_action(action).map_err(|error| WastFileError { + filename: filename.to_string(), + line, + error, + })? { ActionOutcome::Returned { values } => { for (v, e) in values .iter() @@ -312,13 +288,11 @@ impl WastContext { } } CommandKind::AssertTrap { action, message } => { - match self - .perform_action(isa, action) - .map_err(|error| WastFileError { - filename: filename.to_string(), - line, - error, - })? { + match self.perform_action(action).map_err(|error| WastFileError { + filename: filename.to_string(), + line, + error, + })? { ActionOutcome::Returned { values } => { return Err(WastFileError { filename: filename.to_string(), @@ -340,13 +314,11 @@ impl WastContext { } } CommandKind::AssertExhaustion { action } => { - match self - .perform_action(isa, action) - .map_err(|error| WastFileError { - filename: filename.to_string(), - line, - error, - })? { + match self.perform_action(action).map_err(|error| WastFileError { + filename: filename.to_string(), + line, + error, + })? { ActionOutcome::Returned { values } => { return Err(WastFileError { filename: filename.to_string(), @@ -366,13 +338,11 @@ impl WastContext { } } CommandKind::AssertReturnCanonicalNan { action } => { - match self - .perform_action(isa, action) - .map_err(|error| WastFileError { - filename: filename.to_string(), - line, - error, - })? { + match self.perform_action(action).map_err(|error| WastFileError { + filename: filename.to_string(), + line, + error, + })? { ActionOutcome::Returned { values } => { for v in values.iter() { match v { @@ -420,13 +390,11 @@ impl WastContext { } } CommandKind::AssertReturnArithmeticNan { action } => { - match self - .perform_action(isa, action) - .map_err(|error| WastFileError { - filename: filename.to_string(), - line, - error, - })? { + match self.perform_action(action).map_err(|error| WastFileError { + filename: filename.to_string(), + line, + error, + })? { ActionOutcome::Returned { values } => { for v in values.iter() { match v { @@ -474,7 +442,7 @@ impl WastContext { } } CommandKind::AssertInvalid { module, message } => { - self.module(isa, None, module).expect_err(&format!( + self.module(None, module).expect_err(&format!( "{}:{}: invalid module was successfully instantiated", filename, line )); @@ -484,7 +452,7 @@ impl WastContext { ); } CommandKind::AssertMalformed { module, message } => { - self.module(isa, None, module).expect_err(&format!( + self.module(None, module).expect_err(&format!( "{}:{}: malformed module was successfully instantiated", filename, line )); @@ -494,7 +462,7 @@ impl WastContext { ); } CommandKind::AssertUninstantiable { module, message } => { - let _err = self.module(isa, None, module).expect_err(&format!( + let _err = self.module(None, module).expect_err(&format!( "{}:{}: uninstantiable module was successfully instantiated", filename, line )); @@ -504,7 +472,7 @@ impl WastContext { ); } CommandKind::AssertUnlinkable { module, message } => { - let _err = self.module(isa, None, module).expect_err(&format!( + let _err = self.module(None, module).expect_err(&format!( "{}:{}: unlinkable module was successfully linked", filename, line )); @@ -520,14 +488,14 @@ impl WastContext { } /// Run a wast script from a file. - pub fn run_file(&mut self, isa: &isa::TargetIsa, path: &Path) -> Result<(), WastFileError> { + pub fn run_file(&mut self, path: &Path) -> Result<(), WastFileError> { let filename = path.display().to_string(); let buffer = read_to_end(path).map_err(|e| WastFileError { filename, line: 0, error: WastError::IO(e), })?; - self.run_buffer(isa, &path.display().to_string(), &buffer) + self.run_buffer(&path.display().to_string(), &buffer) } } diff --git a/src/wasm2obj.rs b/src/wasm2obj.rs index 08be4b605a..ac6cf61623 100644 --- a/src/wasm2obj.rs +++ b/src/wasm2obj.rs @@ -53,7 +53,7 @@ use std::path::PathBuf; use std::process; use std::str::FromStr; use target_lexicon::Triple; -use wasmtime_environ::{compile_module, Module, ModuleEnvironment, Tunables}; +use wasmtime_environ::{cranelift, ModuleEnvironment, Tunables}; use wasmtime_obj::emit_module; const USAGE: &str = " @@ -133,30 +133,38 @@ fn handle_module(path: PathBuf, target: &Option, output: &str) -> Result let mut obj = Artifact::new(isa.triple().clone(), String::from(output)); - let mut module = Module::new(); // TODO: Expose the tunables as command-line flags. let tunables = Tunables::default(); - let environ = ModuleEnvironment::new(&*isa, &mut module, tunables); - let translation = environ.translate(&data).map_err(|e| e.to_string())?; + + let (module, lazy_function_body_inputs, lazy_data_initializers) = { + let environ = ModuleEnvironment::new(isa.frontend_config(), tunables); + + let translation = environ + .translate(&data) + .map_err(|error| error.to_string())?; + + ( + translation.module, + translation.function_body_inputs, + translation.data_initializers, + ) + }; // FIXME: We need to initialize memory in a way that supports alternate // memory spaces, imported base addresses, and offsets. - for init in &translation.lazy.data_initializers { + for init in lazy_data_initializers.into_iter() { obj.define("memory", Vec::from(init.data)) .map_err(|err| format!("{}", err))?; } - let (compilation, relocations) = compile_module( - &translation.module, - &translation.lazy.function_body_inputs, - &*isa, - ) - .map_err(|e| e.to_string())?; + let (compilation, relocations) = + cranelift::compile_module(&module, lazy_function_body_inputs, &*isa) + .map_err(|e| e.to_string())?; - emit_module(&mut obj, &translation.module, &compilation, &relocations)?; + emit_module(&mut obj, &module, &compilation, &relocations)?; - if !translation.module.table_plans.is_empty() { - if translation.module.table_plans.len() > 1 { + if !module.table_plans.is_empty() { + if module.table_plans.len() > 1 { return Err(String::from("multiple tables not supported yet")); } return Err(String::from("FIXME: implement tables")); diff --git a/src/wasmtime.rs b/src/wasmtime.rs index 2dbae33cb5..7a4b9fc017 100644 --- a/src/wasmtime.rs +++ b/src/wasmtime.rs @@ -33,7 +33,7 @@ extern crate cranelift_codegen; extern crate cranelift_native; extern crate docopt; -extern crate wasmtime_execute; +extern crate wasmtime_jit; extern crate wasmtime_wast; #[macro_use] extern crate serde_derive; @@ -41,7 +41,6 @@ extern crate file_per_thread_logger; extern crate pretty_env_logger; extern crate wabt; -use cranelift_codegen::isa::TargetIsa; use cranelift_codegen::settings; use cranelift_codegen::settings::Configurable; use docopt::Docopt; @@ -52,7 +51,7 @@ use std::io::prelude::*; use std::path::Path; use std::path::PathBuf; use std::process::exit; -use wasmtime_execute::{ActionOutcome, InstancePlus, JITCode, Namespace}; +use wasmtime_jit::{instantiate, ActionOutcome, Compiler, Namespace}; use wasmtime_wast::instantiate_spectest; static LOG_FILENAME_PREFIX: &str = "wasmtime.dbg."; @@ -123,6 +122,7 @@ fn main() { } let isa = isa_builder.finish(settings::Flags::new(flag_builder)); + let mut compiler = Compiler::new(isa); let mut namespace = Namespace::new(); @@ -132,11 +132,9 @@ fn main() { instantiate_spectest().expect("instantiating spectest"), ); - let mut jit_code = JITCode::new(); - for filename in &args.arg_file { let path = Path::new(&filename); - match handle_module(&mut jit_code, &mut namespace, &args, path, &*isa) { + match handle_module(&mut compiler, &mut namespace, &args, path) { Ok(()) => {} Err(message) => { let name = path.as_os_str().to_string_lossy(); @@ -148,11 +146,10 @@ fn main() { } fn handle_module( - jit_code: &mut JITCode, + compiler: &mut Compiler, namespace: &mut Namespace, args: &Args, path: &Path, - isa: &TargetIsa, ) -> Result<(), String> { let mut data = read_to_end(path.to_path_buf()).map_err(|err| String::from(err.description()))?; @@ -162,17 +159,16 @@ fn handle_module( data = wabt::wat2wasm(data).map_err(|err| String::from(err.description()))?; } - // Create a new `InstancePlus` by compiling and instantiating a wasm module. - let instance_plus = - InstancePlus::new(jit_code, isa, &data, namespace).map_err(|e| e.to_string())?; + // Create a new `Instance` by compiling and instantiating a wasm module. + let instance = instantiate(compiler, &data, namespace).map_err(|e| e.to_string())?; // Register it in the namespace. - let index = namespace.instance(None, instance_plus); + let index = namespace.instance(None, instance); // If a function to invoke was given, invoke it. if let Some(ref f) = args.flag_invoke { match namespace - .invoke(jit_code, isa, index, &f, &[]) + .invoke(compiler, index, &f, &[]) .map_err(|e| e.to_string())? { ActionOutcome::Returned { .. } => {} @@ -191,7 +187,7 @@ mod tests { use cranelift_codegen::settings::Configurable; use std::path::PathBuf; use wabt; - use wasmtime_execute::{InstancePlus, JITCode, NullResolver}; + use wasmtime_jit::{instantiate, Compiler, NullResolver}; const PATH_MODULE_RS2WASM_ADD_FUNC: &str = r"filetests/rs2wasm-add-func.wat"; @@ -214,8 +210,8 @@ mod tests { let isa = isa_builder.finish(settings::Flags::new(flag_builder)); let mut resolver = NullResolver {}; - let mut code = JITCode::new(); - let instance = InstancePlus::new(&mut code, &*isa, &data, &mut resolver); + let mut compiler = Compiler::new(isa); + let instance = instantiate(&mut compiler, &data, &mut resolver); assert!(instance.is_ok()); } } diff --git a/src/wast.rs b/src/wast.rs index c9568a154f..82fc3b66dd 100644 --- a/src/wast.rs +++ b/src/wast.rs @@ -28,6 +28,7 @@ extern crate cranelift_codegen; extern crate cranelift_native; extern crate docopt; +extern crate wasmtime_jit; extern crate wasmtime_wast; #[macro_use] extern crate serde_derive; @@ -38,6 +39,7 @@ use cranelift_codegen::settings; use cranelift_codegen::settings::Configurable; use docopt::Docopt; use std::path::Path; +use wasmtime_jit::Compiler; use wasmtime_wast::WastContext; static LOG_FILENAME_PREFIX: &str = "cranelift.dbg."; @@ -94,7 +96,8 @@ fn main() { } let isa = isa_builder.finish(settings::Flags::new(flag_builder)); - let mut wast_context = WastContext::new(); + let engine = Compiler::new(isa); + let mut wast_context = WastContext::new(Box::new(engine)); wast_context .register_spectest() @@ -102,7 +105,7 @@ fn main() { for filename in &args.arg_file { wast_context - .run_file(&*isa, Path::new(&filename)) + .run_file(Path::new(&filename)) .unwrap_or_else(|e| panic!("{}", e)); } } diff --git a/tests/wast_testsuites.rs b/tests/wast_testsuites.rs index 7ee178bcf9..62a62523d4 100644 --- a/tests/wast_testsuites.rs +++ b/tests/wast_testsuites.rs @@ -1,11 +1,13 @@ extern crate cranelift_codegen; extern crate cranelift_native; +extern crate wasmtime_jit; extern crate wasmtime_wast; use cranelift_codegen::isa; use cranelift_codegen::settings; use cranelift_codegen::settings::Configurable; use std::path::Path; +use wasmtime_jit::Compiler; use wasmtime_wast::WastContext; include!(concat!(env!("OUT_DIR"), "/wast_testsuite_tests.rs"));