diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000000..f9cf57cd9762 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "lib/wast/spec_testsuite"] + path = lib/wast/spec_testsuite + url = https://github.com/WebAssembly/testsuite diff --git a/Cargo.toml b/Cargo.toml index c15d39019ace..36995d4209d3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,20 +11,25 @@ publish = false [[bin]] name = "wasmtime" -path = "src/main.rs" +path = "src/wasmtime.rs" + +[[bin]] +name = "run_wast" +path = "src/run_wast.rs" [[bin]] name = "wasm2obj" path = "src/wasm2obj.rs" [dependencies] -cranelift-codegen = "0.25.0" -cranelift-native = "0.25.0" -cranelift-entity = "0.25.0" -cranelift-wasm = "0.25.0" +cranelift-codegen = { git = "https://github.com/sunfishcode/cranelift.git", branch = "guard-offset" } +cranelift-native = { git = "https://github.com/sunfishcode/cranelift.git", branch = "guard-offset" } +cranelift-entity = { git = "https://github.com/sunfishcode/cranelift.git", branch = "guard-offset" } +cranelift-wasm = { git = "https://github.com/sunfishcode/cranelift.git", branch = "guard-offset" } wasmtime-environ = { path = "lib/environ" } wasmtime-execute = { path = "lib/execute" } wasmtime-obj = { path = "lib/obj" } +wasmtime-wast = { path = "lib/wast" } docopt = "1.0.1" serde = "1.0.75" serde_derive = "1.0.75" diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 238b9b8a3aa1..3e83ceabc1b2 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -10,9 +10,9 @@ cargo-fuzz = true [dependencies] wasmtime-environ = { path = "../lib/environ" } wasmtime-execute = { path = "../lib/execute" } -cranelift-codegen = "0.25.0" -cranelift-wasm = "0.25.0" -cranelift-native = "0.25.0" +cranelift-codegen = { git = "https://github.com/sunfishcode/cranelift.git", branch = "guard-offset" } +cranelift-wasm = { git = "https://github.com/sunfishcode/cranelift.git", branch = "guard-offset" } +cranelift-native = { git = "https://github.com/sunfishcode/cranelift.git", branch = "guard-offset" } libfuzzer-sys = { git = "https://github.com/rust-fuzz/libfuzzer-sys.git" } wasmparser = { version = "0.22.0", default-features = false } diff --git a/lib/environ/Cargo.toml b/lib/environ/Cargo.toml index 4debd42e60e5..0ab1a41fb813 100644 --- a/lib/environ/Cargo.toml +++ b/lib/environ/Cargo.toml @@ -10,10 +10,10 @@ license = "Apache-2.0 WITH LLVM-exception" readme = "README.md" [dependencies] -cranelift-codegen = "0.25.0" -cranelift-entity = "0.25.0" -cranelift-wasm = "0.25.0" -memoffset = "0.2.1" +cranelift-codegen = { git = "https://github.com/sunfishcode/cranelift.git", branch = "guard-offset" } +cranelift-entity = { git = "https://github.com/sunfishcode/cranelift.git", branch = "guard-offset" } +cranelift-wasm = { git = "https://github.com/sunfishcode/cranelift.git", branch = "guard-offset" } +cast = { version = "0.2.2", default-features = false } [features] default = ["std"] diff --git a/lib/environ/src/compilation.rs b/lib/environ/src/compilation.rs index d6d82d664447..6540bc011893 100644 --- a/lib/environ/src/compilation.rs +++ b/lib/environ/src/compilation.rs @@ -8,7 +8,7 @@ use cranelift_codegen::isa; use cranelift_codegen::Context; use cranelift_entity::{EntityRef, PrimaryMap}; use cranelift_wasm::{DefinedFuncIndex, FuncIndex, FuncTranslator}; -use environ::{get_func_name, ModuleTranslation}; +use environ::{get_func_name, get_memory_grow_name, get_memory_size_name, ModuleTranslation}; use std::string::{String, ToString}; use std::vec::Vec; @@ -49,13 +49,15 @@ impl binemit::RelocSink for RelocSink { name: &ExternalName, addend: binemit::Addend, ) { - let reloc_target = if let ExternalName::User { namespace, index } = *name { + let reloc_target = if *name == get_memory_grow_name() { + RelocationTarget::MemoryGrow + } else if *name == get_memory_size_name() { + RelocationTarget::MemorySize + } else if let ExternalName::User { namespace, index } = *name { debug_assert!(namespace == 0); RelocationTarget::UserFunc(FuncIndex::new(index as usize)) - } else if *name == ExternalName::testcase("grow_memory") { - RelocationTarget::GrowMemory - } else if *name == ExternalName::testcase("current_memory") { - RelocationTarget::CurrentMemory + } else if let ExternalName::LibCall(libcall) = *name { + RelocationTarget::LibCall(libcall) } else { panic!("unrecognized external name") }; @@ -77,7 +79,8 @@ impl binemit::RelocSink for RelocSink { } impl RelocSink { - fn new() -> Self { + /// Return a new `RelocSink` instance. + pub fn new() -> Self { Self { func_relocs: Vec::new(), } @@ -102,10 +105,12 @@ pub struct Relocation { pub enum RelocationTarget { /// The user function index. UserFunc(FuncIndex), + /// A compiler-generated libcall. + LibCall(ir::LibCall), /// Function for growing the default memory by the specified amount of pages. - GrowMemory, + MemoryGrow, /// Function for query current size of the default linear memory. - CurrentMemory, + MemorySize, } /// Relocations to apply to function bodies. diff --git a/lib/environ/src/environ.rs b/lib/environ/src/environ.rs index 9b4bcce80559..50f4f76f759e 100644 --- a/lib/environ/src/environ.rs +++ b/lib/environ/src/environ.rs @@ -1,9 +1,9 @@ use cranelift_codegen::cursor::FuncCursor; use cranelift_codegen::ir; -use cranelift_codegen::ir::immediates::{Imm64, Offset32}; +use cranelift_codegen::ir::immediates::{Imm64, Offset32, Uimm64}; use cranelift_codegen::ir::types::*; use cranelift_codegen::ir::{ - AbiParam, ArgumentPurpose, ExtFuncData, ExternalName, FuncRef, Function, InstBuilder, Signature, + AbiParam, ArgumentPurpose, ExtFuncData, FuncRef, Function, InstBuilder, Signature, }; use cranelift_codegen::isa; use cranelift_entity::EntityRef; @@ -11,48 +11,64 @@ use cranelift_wasm::{ self, translate_module, FuncIndex, Global, GlobalIndex, GlobalVariable, Memory, MemoryIndex, SignatureIndex, Table, TableIndex, WasmResult, }; -use module::{DataInitializer, Export, LazyContents, Module, TableElements}; -use std::mem; +use module::{ + DataInitializer, Export, LazyContents, MemoryPlan, MemoryStyle, Module, TableElements, +}; +use std::clone::Clone; use std::string::String; use std::vec::Vec; -use vmcontext; +use tunables::Tunables; +use vmoffsets::VMOffsets; +use WASM_PAGE_SIZE; /// Compute a `ir::ExternalName` for a given wasm function index. pub fn get_func_name(func_index: FuncIndex) -> ir::ExternalName { - debug_assert!(FuncIndex::new(func_index.index() as u32 as usize) == func_index); - ir::ExternalName::user(0, func_index.index() as u32) + ir::ExternalName::user(0, func_index.as_u32()) +} + +/// Compute a `ir::ExternalName` for the `memory.grow` libcall. +pub fn get_memory_grow_name() -> ir::ExternalName { + ir::ExternalName::user(1, 0) +} + +/// Compute a `ir::ExternalName` for the `memory.size` libcall. +pub fn get_memory_size_name() -> ir::ExternalName { + ir::ExternalName::user(1, 1) } /// Object containing the standalone environment information. To be passed after creation as /// argument to `compile_module`. pub struct ModuleEnvironment<'data, 'module> { /// Compilation setting flags. - pub isa: &'module isa::TargetIsa, + isa: &'module isa::TargetIsa, /// Module information. - pub module: &'module mut Module, + module: &'module mut Module, /// References to information to be decoded later. - pub lazy: LazyContents<'data>, + lazy: LazyContents<'data>, + + /// Tunable parameters. + 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) -> Self { + pub fn new( + isa: &'module isa::TargetIsa, + module: &'module mut Module, + tunables: Tunables, + ) -> Self { Self { isa, module, lazy: LazyContents::new(), + tunables, } } - fn func_env(&self) -> FuncEnvironment { - FuncEnvironment::new(self.isa, &self.module) - } - fn pointer_type(&self) -> ir::Type { - use cranelift_wasm::FuncEnvironment; - self.func_env().pointer_type() + self.isa.frontend_config().pointer_type() } /// Translate the given wasm module data using this environment. This consumes the @@ -66,6 +82,7 @@ impl<'data, 'module> ModuleEnvironment<'data, 'module> { isa: self.isa, module: self.module, lazy: self.lazy, + tunables: self.tunables, }) } } @@ -76,25 +93,28 @@ pub struct FuncEnvironment<'module_environment> { isa: &'module_environment isa::TargetIsa, /// The module-level environment which this function-level environment belongs to. - pub module: &'module_environment Module, + module: &'module_environment Module, /// The Cranelift global holding the vmctx address. - pub vmctx: Option, + vmctx: Option, /// The Cranelift global holding the base address of the memories vector. - pub memories_base: Option, + memories_base: Option, /// The Cranelift global holding the base address of the tables vector. - pub tables_base: Option, + tables_base: Option, /// The Cranelift global holding the base address of the globals vector. - pub globals_base: Option, + globals_base: Option, + + /// The external function declaration for implementing wasm's `memory.size`. + memory_size_extfunc: Option, - /// The external function declaration for implementing wasm's `current_memory`. - pub current_memory_extfunc: Option, + /// The external function declaration for implementing wasm's `memory.grow`. + memory_grow_extfunc: Option, - /// The external function declaration for implementing wasm's `grow_memory`. - pub grow_memory_extfunc: Option, + /// Offsets to struct fields accessed by JIT code. + offsets: VMOffsets, } impl<'module_environment> FuncEnvironment<'module_environment> { @@ -109,8 +129,9 @@ impl<'module_environment> FuncEnvironment<'module_environment> { memories_base: None, tables_base: None, globals_base: None, - current_memory_extfunc: None, - grow_memory_extfunc: None, + memory_size_extfunc: None, + memory_grow_extfunc: None, + offsets: VMOffsets::new(isa.frontend_config().pointer_bytes()), } } @@ -140,10 +161,6 @@ impl<'module_environment> FuncEnvironment<'module_environment> { impl<'data, 'module> cranelift_wasm::ModuleEnvironment<'data> for ModuleEnvironment<'data, 'module> { - fn get_func_name(&self, func_index: FuncIndex) -> ir::ExternalName { - get_func_name(func_index) - } - fn target_config(&self) -> isa::TargetFrontendConfig { self.isa.frontend_config() } @@ -228,7 +245,8 @@ impl<'data, 'module> cranelift_wasm::ModuleEnvironment<'data> } fn declare_memory(&mut self, memory: Memory) { - self.module.memories.push(memory); + let plan = MemoryPlan::for_memory(memory, &self.tunables); + self.module.memory_plans.push(plan); } fn declare_data_initialization( @@ -292,19 +310,16 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m let globals_base = self.globals_base.unwrap_or_else(|| { let new_base = func.create_global_value(ir::GlobalValueData::Load { base: vmctx, - offset: Offset32::new(offset_of!(vmcontext::VMContext, globals) as i32), + offset: Offset32::new(i32::from(self.offsets.vmctx_globals())), global_type: self.pointer_type(), readonly: true, }); self.globals_base = Some(new_base); new_base }); - // For now, give each global gets a pointer-sized region of - // storage, regardless of its type. - let offset = index.index() * mem::size_of::<*mut u8>(); let gv = func.create_global_value(ir::GlobalValueData::IAddImm { base: globals_base, - offset: Imm64::new(offset as i64), + offset: Imm64::new(i64::from(self.offsets.index_vmglobal(index.as_u32()))), global_type: self.pointer_type(), }); GlobalVariable::Memory { @@ -318,54 +333,60 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m let memories_base = self.memories_base.unwrap_or_else(|| { let new_base = func.create_global_value(ir::GlobalValueData::Load { base: vmctx, - offset: Offset32::new(offset_of!(vmcontext::VMContext, memories) as i32), + offset: Offset32::new(i32::from(self.offsets.vmctx_memories())), global_type: self.pointer_type(), readonly: true, }); self.memories_base = Some(new_base); new_base }); - let offset = index.index() * mem::size_of::(); - let offset32 = offset as i32; - debug_assert_eq!(offset32 as usize, offset); // If we have a declared maximum, we can make this a "static" heap, which is // allocated up front and never moved. - let (guard_size, heap_style, readonly_base) = - if self.module.memories[index].maximum.is_some() { - ( - 0x8000_0000.into(), - ir::HeapStyle::Static { - bound: 0x1_0000_0000.into(), - }, - true, - ) - } else { + let (offset_guard_size, heap_style, readonly_base) = match self.module.memory_plans[index] { + MemoryPlan { + memory: _, + style: MemoryStyle::Dynamic, + offset_guard_size, + } => { let heap_bound = func.create_global_value(ir::GlobalValueData::Load { base: memories_base, offset: Offset32::new( - offset32 + offset_of!(vmcontext::VMMemory, current_length) as i32, + self.offsets.index_vmmemory_current_length(index.as_u32()), ), global_type: I32, readonly: false, }); ( - 0.into(), + Uimm64::new(offset_guard_size), ir::HeapStyle::Dynamic { bound_gv: heap_bound, }, false, ) - }; + } + MemoryPlan { + memory: _, + style: MemoryStyle::Static { bound }, + offset_guard_size, + } => ( + Uimm64::new(offset_guard_size), + ir::HeapStyle::Static { + bound: Uimm64::new(u64::from(bound) * u64::from(WASM_PAGE_SIZE)), + }, + true, + ), + }; + let heap_base = func.create_global_value(ir::GlobalValueData::Load { base: memories_base, - offset: Offset32::new(offset32 + offset_of!(vmcontext::VMMemory, base) as i32), + offset: Offset32::new(self.offsets.index_vmmemory_base(index.as_u32())), global_type: self.pointer_type(), readonly: readonly_base, }); func.create_heap(ir::HeapData { base: heap_base, min_size: 0.into(), - guard_size, + offset_guard_size, style: heap_style, index_type: I32, }) @@ -376,36 +397,31 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m let tables_base = self.tables_base.unwrap_or_else(|| { let new_base = func.create_global_value(ir::GlobalValueData::Load { base: vmctx, - offset: Offset32::new(offset_of!(vmcontext::VMContext, tables) as i32), + offset: Offset32::new(i32::from(self.offsets.vmctx_tables())), global_type: self.pointer_type(), readonly: true, }); self.tables_base = Some(new_base); new_base }); - let offset = index.index() * mem::size_of::(); - let offset32 = offset as i32; - debug_assert_eq!(offset32 as usize, offset); let base_gv = func.create_global_value(ir::GlobalValueData::Load { base: tables_base, - offset: Offset32::new(offset32 + offset_of!(vmcontext::VMTable, base) as i32), + offset: Offset32::new(self.offsets.index_vmtable_base(index.as_u32())), global_type: self.pointer_type(), readonly: false, }); let bound_gv = func.create_global_value(ir::GlobalValueData::Load { base: tables_base, - offset: Offset32::new( - offset32 + offset_of!(vmcontext::VMTable, current_num_elements) as i32, - ), + offset: Offset32::new(self.offsets.index_vmtable_current_elements(index.as_u32())), global_type: I32, readonly: false, }); func.create_table(ir::TableData { base_gv, - min_size: Imm64::new(0), + min_size: Uimm64::new(0), bound_gv, - element_size: Imm64::new(i64::from(self.pointer_bytes())), + element_size: Uimm64::new(u64::from(self.pointer_bytes())), index_type: I32, }) } @@ -478,7 +494,7 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m _heap: ir::Heap, val: ir::Value, ) -> WasmResult { - let grow_mem_func = self.grow_memory_extfunc.unwrap_or_else(|| { + let memory_grow_func = self.memory_grow_extfunc.unwrap_or_else(|| { let sig_ref = pos.func.import_signature(Signature { call_conv: self.isa.frontend_config().default_call_conv, params: vec![ @@ -491,17 +507,18 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m // We currently allocate all code segments independently, so nothing // is colocated. let colocated = false; - // FIXME: Use a real ExternalName system. pos.func.import_function(ExtFuncData { - name: ExternalName::testcase("grow_memory"), + name: get_memory_grow_name(), signature: sig_ref, colocated, }) }); - self.grow_memory_extfunc = Some(grow_mem_func); + self.memory_grow_extfunc = Some(memory_grow_func); let memory_index = pos.ins().iconst(I32, index.index() as i64); let vmctx = pos.func.special_param(ArgumentPurpose::VMContext).unwrap(); - let call_inst = pos.ins().call(grow_mem_func, &[val, memory_index, vmctx]); + let call_inst = pos + .ins() + .call(memory_grow_func, &[val, memory_index, vmctx]); Ok(*pos.func.dfg.inst_results(call_inst).first().unwrap()) } @@ -511,7 +528,7 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m index: MemoryIndex, _heap: ir::Heap, ) -> WasmResult { - let cur_mem_func = self.current_memory_extfunc.unwrap_or_else(|| { + let memory_size_func = self.memory_size_extfunc.unwrap_or_else(|| { let sig_ref = pos.func.import_signature(Signature { call_conv: self.isa.frontend_config().default_call_conv, params: vec![ @@ -523,17 +540,16 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m // We currently allocate all code segments independently, so nothing // is colocated. let colocated = false; - // FIXME: Use a real ExternalName system. pos.func.import_function(ExtFuncData { - name: ExternalName::testcase("current_memory"), + name: get_memory_size_name(), signature: sig_ref, colocated, }) }); - self.current_memory_extfunc = Some(cur_mem_func); + self.memory_size_extfunc = Some(memory_size_func); let memory_index = pos.ins().iconst(I32, index.index() as i64); let vmctx = pos.func.special_param(ArgumentPurpose::VMContext).unwrap(); - let call_inst = pos.ins().call(cur_mem_func, &[memory_index, vmctx]); + let call_inst = pos.ins().call(memory_size_func, &[memory_index, vmctx]); Ok(*pos.func.dfg.inst_results(call_inst).first().unwrap()) } } @@ -548,11 +564,13 @@ pub struct ModuleTranslation<'data, 'module> { /// Pointers into the raw data buffer. pub lazy: LazyContents<'data>, + + /// Tunable parameters. + pub tunables: Tunables, } -/// Convenience functions for the user to be called after execution for debug purposes. impl<'data, 'module> ModuleTranslation<'data, 'module> { - /// Return a new `FuncEnvironment` for translation a function. + /// Return a new `FuncEnvironment` for translating a function. pub fn func_env(&self) -> FuncEnvironment { FuncEnvironment::new(self.isa, &self.module) } diff --git a/lib/environ/src/lib.rs b/lib/environ/src/lib.rs index edbc4cb3e6c6..9b93d89ac80a 100644 --- a/lib/environ/src/lib.rs +++ b/lib/environ/src/lib.rs @@ -33,8 +33,6 @@ extern crate cranelift_codegen; extern crate cranelift_entity; extern crate cranelift_wasm; -#[macro_use] -extern crate memoffset; #[cfg(not(feature = "std"))] #[macro_use] extern crate alloc; @@ -42,11 +40,22 @@ extern crate alloc; mod compilation; mod environ; mod module; -mod vmcontext; +mod tunables; +mod vmoffsets; -pub use compilation::{compile_module, Compilation, Relocation, RelocationTarget, Relocations}; +pub use compilation::{ + compile_module, Compilation, RelocSink, Relocation, RelocationTarget, Relocations, +}; pub use environ::{ModuleEnvironment, ModuleTranslation}; -pub use module::{DataInitializer, Export, Module, TableElements}; +pub use module::{DataInitializer, Export, MemoryPlan, MemoryStyle, Module, TableElements}; +pub use tunables::Tunables; +pub use vmoffsets::VMOffsets; + +/// WebAssembly page sizes are defined to be 64KiB. +pub const WASM_PAGE_SIZE: u32 = 0x10000; + +/// The number of pages we can have before we run out of byte index space. +pub const WASM_MAX_PAGES: u32 = 0x10000; #[cfg(not(feature = "std"))] mod std { diff --git a/lib/environ/src/module.rs b/lib/environ/src/module.rs index c9bed3266c3d..1a6d017769e9 100644 --- a/lib/environ/src/module.rs +++ b/lib/environ/src/module.rs @@ -6,9 +6,11 @@ use cranelift_wasm::{ DefinedFuncIndex, FuncIndex, Global, GlobalIndex, Memory, MemoryIndex, SignatureIndex, Table, TableIndex, }; +use std::cmp; use std::collections::HashMap; use std::string::String; use std::vec::Vec; +use tunables::Tunables; /// A WebAssembly table initializer. #[derive(Clone, Debug)] @@ -36,6 +38,65 @@ pub enum Export { Global(GlobalIndex), } +/// Implemenation styles for WebAssembly linear memory. +#[derive(Debug, Clone)] +pub enum MemoryStyle { + /// The actual memory can be resized and moved. + Dynamic, + /// Addresss space is allocated up front. + Static { + /// The number of mapped and unmapped pages. + bound: u32, + }, +} + +impl MemoryStyle { + /// Decide on an implementation style for the given `Memory`. + pub fn for_memory(memory: Memory, tunables: &Tunables) -> (Self, u64) { + if let Some(maximum) = memory.maximum { + // A heap with a declared maximum is prepared to be used with + // threads and therefore be immovable, so make it static. + ( + MemoryStyle::Static { + bound: cmp::max(tunables.static_memory_bound, maximum), + }, + tunables.static_memory_offset_guard_size, + ) + } else { + // A heap without a declared maximum is likely to want to be small + // at least some of the time, so make it dynamic. + ( + MemoryStyle::Dynamic, + tunables.dynamic_memory_offset_guard_size, + ) + } + } +} + +/// A WebAssembly linear memory description along with our chosen style for +/// implementing it. +#[derive(Debug)] +pub struct MemoryPlan { + /// The WebAssembly linear memory description. + pub memory: Memory, + /// Our chosen implementation style. + pub style: MemoryStyle, + /// Our chosen offset-guard size. + pub offset_guard_size: u64, +} + +impl MemoryPlan { + /// Draw up a plan for implementing a `Memory`. + pub fn for_memory(memory: Memory, tunables: &Tunables) -> Self { + let (style, offset_guard_size) = MemoryStyle::for_memory(memory, tunables); + Self { + memory, + style, + offset_guard_size, + } + } +} + /// A translated WebAssembly module, excluding the function bodies and /// memory initializers. #[derive(Debug)] @@ -44,7 +105,7 @@ pub struct Module { pub signatures: PrimaryMap, /// Names of imported functions. - pub imported_funcs: Vec<(String, String)>, + pub imported_funcs: PrimaryMap, /// Types of functions, imported and local. pub functions: PrimaryMap, @@ -52,8 +113,8 @@ pub struct Module { /// WebAssembly tables. pub tables: PrimaryMap, - /// WebAssembly linear memories. - pub memories: PrimaryMap, + /// WebAssembly linear memory plans. + pub memory_plans: PrimaryMap, /// WebAssembly global variables. pub globals: PrimaryMap, @@ -73,10 +134,10 @@ impl Module { pub fn new() -> Self { Self { signatures: PrimaryMap::new(), - imported_funcs: Vec::new(), + imported_funcs: PrimaryMap::new(), functions: PrimaryMap::new(), tables: PrimaryMap::new(), - memories: PrimaryMap::new(), + memory_plans: PrimaryMap::new(), globals: PrimaryMap::new(), exports: HashMap::new(), start_func: None, diff --git a/lib/environ/src/tunables.rs b/lib/environ/src/tunables.rs new file mode 100644 index 000000000000..c2769bf2aade --- /dev/null +++ b/lib/environ/src/tunables.rs @@ -0,0 +1,36 @@ +/// Tunable parameters for WebAssembly compilation. +#[derive(Clone)] +pub struct Tunables { + /// For static heaps, the size of the heap protected by bounds checking. + pub static_memory_bound: u32, + + /// The size of the offset guard for static heaps. + pub static_memory_offset_guard_size: u64, + + /// The size of the offset guard for dynamic heaps. + pub dynamic_memory_offset_guard_size: u64, +} + +impl Default for Tunables { + fn default() -> Self { + Self { + /// Size in wasm pages of the bound for static memories. + /// + /// When we allocate 4 GiB of address space, we can avoid the + /// need for explicit bounds checks. + static_memory_bound: 0x1_0000, + + /// Size in bytes of the offset guard for static memories. + /// + /// Allocating 2 GiB of address space lets us translate wasm + /// offsets into x86 offsets as aggressively as we can. + static_memory_offset_guard_size: 0x8000_0000, + + /// Size in bytes of the offset guard for dynamic memories. + /// + /// Allocate a small guard to optimize common cases but without + /// wasting too much memor. + dynamic_memory_offset_guard_size: 0x1_0000, + } + } +} diff --git a/lib/environ/src/vmcontext.rs b/lib/environ/src/vmcontext.rs deleted file mode 100644 index 098b22c454cd..000000000000 --- a/lib/environ/src/vmcontext.rs +++ /dev/null @@ -1,33 +0,0 @@ -/// The main fields a JIT needs to access to utilize a WebAssembly linear, -/// memory, namely the start address and the size in bytes. -#[repr(C, packed)] -pub struct VMMemory { - pub base: *mut u8, - pub current_length: usize, -} - -/// The main fields a JIT needs to access to utilize a WebAssembly table, -/// namely the start address and the number of elements. -#[repr(C, packed)] -pub struct VMTable { - pub base: *mut u8, - pub current_num_elements: usize, -} - -/// The VM "context", which is pointed to by the `vmctx` arg in Cranelift. -/// This has pointers to the globals, memories, tables, and other runtime -/// state associated with the current instance. -#[repr(C, packed)] -pub struct VMContext { - /// A pointer to an array of globals. - pub globals: *mut u8, - /// A pointer to an array of `VMMemory` instances, indexed by - /// WebAssembly memory index. - pub memories: *mut VMMemory, - /// A pointer to an array of `VMTable` instances, indexed by - /// WebAssembly table index. - pub tables: *mut VMTable, - /// A pointer to extra runtime state that isn't directly accessed - /// from JIT code. - pub instance: *mut u8, -} diff --git a/lib/environ/src/vmoffsets.rs b/lib/environ/src/vmoffsets.rs new file mode 100644 index 000000000000..f1d66888f559 --- /dev/null +++ b/lib/environ/src/vmoffsets.rs @@ -0,0 +1,139 @@ +/// This class computes offsets to fields within `VMContext` and other +/// related structs that JIT code accesses directly. +pub struct VMOffsets { + pointer_size: u8, +} + +impl VMOffsets { + /// Return a new `VMOffsets` instance, for a given pointer size. + pub fn new(pointer_size: u8) -> Self { + Self { pointer_size } + } +} + +/// Offsets for `wasmtime_execute::VMMemory`. +impl VMOffsets { + /// The offset of the `base` field. + pub fn vmmemory_base(&self) -> u8 { + 0 * self.pointer_size + } + + /// The offset of the `current_length` field. + pub fn vmmemory_current_length(&self) -> u8 { + 1 * self.pointer_size + } + + /// Return the size of `VMMemory`. + pub fn size_of_vmmemory(&self) -> u8 { + 2 * self.pointer_size + } +} + +/// Offsets for `wasmtime_execute::VMGlobal`. +impl VMOffsets { + /// Return the size of `VMGlobal`. + pub fn size_of_vmglobal(&self) -> u8 { + 8 + } +} + +/// Offsets for `wasmtime_execute::VMTable`. +impl VMOffsets { + /// The offset of the `base` field. + pub fn vmtable_base(&self) -> u8 { + 0 * self.pointer_size + } + + /// The offset of the `current_elements` field. + pub fn vmtable_current_elements(&self) -> u8 { + 1 * self.pointer_size + } + + /// Return the size of `VMTable`. + pub fn size_of_vmtable(&self) -> u8 { + 2 * self.pointer_size + } +} + +/// Offsets for `wasmtime_execute::VMContext`. +impl VMOffsets { + /// The offset of the `memories` field. + pub fn vmctx_memories(&self) -> u8 { + 0 * self.pointer_size + } + + /// The offset of the `globals` field. + pub fn vmctx_globals(&self) -> u8 { + 1 * self.pointer_size + } + + /// The offset of the `tables` field. + pub fn vmctx_tables(&self) -> u8 { + 2 * self.pointer_size + } + + /// Return the size of `VMContext`. + #[allow(dead_code)] + pub fn size_of_vmctx(&self) -> u8 { + 3 * self.pointer_size + } + + /// Return the offset from the `memories` pointer to `VMMemory` index `index`. + pub fn index_vmmemory(&self, index: u32) -> i32 { + cast::i32( + index + .checked_mul(u32::from(self.size_of_vmmemory())) + .unwrap(), + ).unwrap() + } + + /// Return the offset from the `globals` pointer to `VMGlobal` index `index`. + pub fn index_vmglobal(&self, index: u32) -> i32 { + cast::i32( + index + .checked_mul(u32::from(self.size_of_vmglobal())) + .unwrap(), + ).unwrap() + } + + /// Return the offset from the `tables` pointer to `VMTable` index `index`. + pub fn index_vmtable(&self, index: u32) -> i32 { + cast::i32( + index + .checked_mul(u32::from(self.size_of_vmtable())) + .unwrap(), + ).unwrap() + } + + /// Return the offset from the `memories` pointer to the `base` field in + /// `VMMemory` index `index`. + pub fn index_vmmemory_base(&self, index: u32) -> i32 { + self.index_vmmemory(index) + .checked_add(i32::from(self.vmmemory_base())) + .unwrap() + } + + /// Return the offset from the `memories` pointer to the `current_length` field in + /// `VMMemory` index `index`. + pub fn index_vmmemory_current_length(&self, index: u32) -> i32 { + self.index_vmmemory(index) + .checked_add(i32::from(self.vmmemory_current_length())) + .unwrap() + } + + /// Return the offset from the `tables` pointer to the `base` field in + /// `VMTable` index `index`. + pub fn index_vmtable_base(&self, index: u32) -> i32 { + self.index_vmtable(index) + .checked_add(i32::from(self.vmtable_base())) + .unwrap() + } + + /// Return the offset from the `tables` pointer to the `current_elements` field in + /// `VMTable` index `index`. + pub fn index_vmtable_current_elements(&self, index: u32) -> i32 { + self.index_vmtable(index) + .checked_add(i32::from(self.vmtable_current_elements())) + .unwrap() + } +} diff --git a/lib/execute/Cargo.toml b/lib/execute/Cargo.toml index 9c587b1a64e2..f13206202a72 100644 --- a/lib/execute/Cargo.toml +++ b/lib/execute/Cargo.toml @@ -10,14 +10,16 @@ license = "Apache-2.0 WITH LLVM-exception" readme = "README.md" [dependencies] -cranelift-codegen = "0.25.0" -cranelift-entity = "0.25.0" -cranelift-wasm = "0.25.0" +cranelift-codegen = { git = "https://github.com/sunfishcode/cranelift.git", branch = "guard-offset" } +cranelift-entity = { git = "https://github.com/sunfishcode/cranelift.git", branch = "guard-offset" } +cranelift-wasm = { git = "https://github.com/sunfishcode/cranelift.git", branch = "guard-offset" } +cranelift-frontend = { git = "https://github.com/sunfishcode/cranelift.git", branch = "guard-offset" } wasmtime-environ = { path = "../environ" } region = "1.0.0" -memmap = "0.7.0" lazy_static = "1.2.0" -libc = "0.2.44" +libc = { version = "0.2.44", default-features = false } +errno = "0.2.4" +memoffset = "0.2.1" [build-dependencies] cmake = "0.1.35" diff --git a/lib/execute/signalhandlers/SignalHandlers.cpp b/lib/execute/signalhandlers/SignalHandlers.cpp index f47d7ec7f905..eb3608d5f62e 100644 --- a/lib/execute/signalhandlers/SignalHandlers.cpp +++ b/lib/execute/signalhandlers/SignalHandlers.cpp @@ -1,6 +1,9 @@ //! This file is largely derived from the code in WasmSignalHandlers.cpp in SpiderMonkey: //! //! https://dxr.mozilla.org/mozilla-central/source/js/src/wasm/WasmSignalHandlers.cpp +//! +//! Use of Mach ports on Darwin platforms (the USE_APPLE_MACH_PORTS code below) is +//! currently disabled. #include "SignalHandlers.h" @@ -12,7 +15,7 @@ #if defined(_WIN32) # include // must include before util/Windows.h's `#undef`s # include "util/Windows.h" -#elif defined(__APPLE__) +#elif defined(USE_APPLE_MACH_PORTS) # include # include # include @@ -176,7 +179,7 @@ # define EPC_sig(p) ((p)->uc_mcontext.mc_pc) # define RFP_sig(p) ((p)->uc_mcontext.mc_regs[30]) # endif -#elif defined(__APPLE__) +#elif defined(USE_APPLE_MACH_PORTS) # define EIP_sig(p) ((p)->thread.uts.ts32.__eip) # define EBP_sig(p) ((p)->thread.uts.ts32.__ebp) # define ESP_sig(p) ((p)->thread.uts.ts32.__esp) @@ -187,6 +190,17 @@ # define R13_sig(p) ((p)->thread.__sp) # define R14_sig(p) ((p)->thread.__lr) # define R15_sig(p) ((p)->thread.__pc) +#elif defined(__APPLE__) +# define EIP_sig(p) ((p)->uc_mcontext->__ss.__eip) +# define EBP_sig(p) ((p)->uc_mcontext->__ss.__ebp) +# define ESP_sig(p) ((p)->uc_mcontext->__ss.__esp) +# define RIP_sig(p) ((p)->uc_mcontext->__ss.__rip) +# define RBP_sig(p) ((p)->uc_mcontext->__ss.__rbp) +# define RSP_sig(p) ((p)->uc_mcontext->__ss.__rsp) +# define R11_sig(p) ((p)->uc_mcontext->__ss.__r11) +# define R13_sig(p) ((p)->uc_mcontext->__ss.__sp) +# define R14_sig(p) ((p)->uc_mcontext->__ss.__lr) +# define R15_sig(p) ((p)->uc_mcontext->__ss.__pc) #else # error "Don't know how to read/write to the thread state via the mcontext_t." #endif @@ -271,7 +285,7 @@ enum { REG_EIP = 14 }; # endif // !defined(__BIONIC_HAVE_UCONTEXT_T) #endif // defined(ANDROID) -#if defined(__APPLE__) +#if defined(USE_APPLE_MACH_PORTS) # if defined(__x86_64__) struct macos_x64_context { x86_thread_state64_t thread; @@ -400,12 +414,22 @@ HandleTrap(CONTEXT* context) RecordTrap(pc, codeSegment); + // Unwind calls longjmp, so it doesn't run the automatic + // sAlreadhHanldingTrap cleanups, so reset it manually before doing + // a longjmp. + sAlreadyHandlingTrap = false; + +#if defined(USE_APPLE_MACH_PORTS) + // Reroute the PC to run the Unwind function on the main stack after the + // handler exits. This doesn't yet work for stack overflow traps, because + // in that case the main thread doesn't have any space left to run. + SetContextPC(context, reinterpret_cast(&Unwind)); +#else // For now, just call Unwind directly, rather than redirecting the PC there, // so that it runs on the alternate signal handler stack. To run on the main // stack, reroute the context PC like this: - // SetContextPC(context, reinterpret_cast(&Unwind)); - Unwind(); +#endif return true; } @@ -447,7 +471,7 @@ WasmTrapHandler(LPEXCEPTION_POINTERS exception) return EXCEPTION_CONTINUE_EXECUTION; } -#elif defined(__APPLE__) +#elif defined(USE_APPLE_MACH_PORTS) // On OSX we are forced to use the lower-level Mach exception mechanism instead // of Unix signals because breakpad uses Mach exceptions and would otherwise // report a crash before wasm gets a chance to handle the exception. @@ -677,7 +701,7 @@ EnsureEagerSignalHandlers() return false; } -#elif defined(__APPLE__) +#elif defined(USE_APPLE_MACH_PORTS) // All the Mach setup in EnsureLazyProcessSignalHandlers. #else // SA_ONSTACK allows us to handle signals on an alternate stack, so that @@ -699,8 +723,9 @@ EnsureEagerSignalHandlers() abort(); } -# if defined(__arm__) - // On Arm Handle Unaligned Accesses +# if defined(__arm__) || defined(__APPLE__) + // On ARM, handle Unaligned Accesses. + // On Darwin, guard page accesses are raised as SIGBUS. struct sigaction busHandler; busHandler.sa_flags = SA_SIGINFO | SA_NODEFER | SA_ONSTACK; busHandler.sa_sigaction = WasmTrapHandler; @@ -741,10 +766,10 @@ EnsureEagerSignalHandlers() return true; } -#ifdef __APPLE__ bool EnsureDarwinMachPorts() { +#ifdef USE_APPLE_MACH_PORTS pthread_attr_t handlerThreadAttr; int r = pthread_attr_init(&handlerThreadAttr); if (r != 0) { @@ -793,6 +818,6 @@ EnsureDarwinMachPorts() return false; } +#endif return true; } -#endif diff --git a/lib/execute/src/code.rs b/lib/execute/src/code.rs new file mode 100644 index 000000000000..0a0b9d8a0b97 --- /dev/null +++ b/lib/execute/src/code.rs @@ -0,0 +1,72 @@ +//! Memory management for executable code. + +use mmap::Mmap; +use region; +use std::cmp; +use std::mem; +use std::slice; +use std::string::String; +use std::vec::Vec; + +/// Memory manager for executable code. +pub struct Code { + current: Mmap, + mmaps: Vec, + position: usize, + published: usize, +} + +impl Code { + /// Create a new `Code` instance. + pub fn new() -> Self { + Self { + current: Mmap::new(), + mmaps: Vec::new(), + position: 0, + published: 0, + } + } + + /// Allocate `size` bytes of memory which can be made executable later by + /// calling `publish()`. + /// TODO: alignment + pub fn allocate(&mut self, size: usize) -> Result<*mut u8, String> { + if self.current.len() - self.position < size { + self.mmaps.push(mem::replace( + &mut self.current, + Mmap::with_size(cmp::max(0x10000, size.next_power_of_two()))?, + )); + self.position = 0; + } + let old_position = self.position; + self.position += size; + Ok(self.current.as_mut_slice()[old_position..self.position].as_mut_ptr()) + } + + /// Allocate enough memory to hold a copy of `slice` and copy the data into it. + /// TODO: Reorganize the code that calls this to emit code directly into the + /// mmap region rather than into a Vec that we need to copy in. + pub fn allocate_copy_of_slice(&mut self, slice: &[u8]) -> Result<&mut [u8], String> { + let ptr = self.allocate(slice.len())?; + let new = unsafe { slice::from_raw_parts_mut(ptr, slice.len()) }; + new.copy_from_slice(slice); + Ok(new) + } + + /// Make all allocated memory executable. + pub fn publish(&mut self) { + self.mmaps + .push(mem::replace(&mut self.current, Mmap::new())); + self.position = 0; + + for m in &mut self.mmaps[self.published..] { + if !m.as_ptr().is_null() { + unsafe { + region::protect(m.as_mut_ptr(), m.len(), region::Protection::ReadExecute) + .expect("unable to make memory readonly"); + } + } + } + self.published = self.mmaps.len(); + } +} diff --git a/lib/execute/src/execute.rs b/lib/execute/src/execute.rs index 319a8529c8dd..1ac5e88732bc 100644 --- a/lib/execute/src/execute.rs +++ b/lib/execute/src/execute.rs @@ -1,19 +1,21 @@ +//! TODO: Move the contents of this file to other files, as "execute.rs" is +//! no longer a descriptive filename. + +use code::Code; use cranelift_codegen::binemit::Reloc; use cranelift_codegen::isa::TargetIsa; use cranelift_entity::{EntityRef, PrimaryMap}; -use cranelift_wasm::{DefinedFuncIndex, FuncIndex, MemoryIndex, TableIndex}; +use cranelift_wasm::{DefinedFuncIndex, MemoryIndex}; use instance::Instance; -use memory::LinearMemory; +use invoke::{invoke_by_index, InvokeOutcome}; use region::protect; use region::Protection; -use signalhandlers::{ensure_eager_signal_handlers, ensure_full_signal_handlers, TrapContext}; -use std::mem::transmute; -use std::ptr::{self, write_unaligned}; +use std::ptr::write_unaligned; use std::string::String; use std::vec::Vec; -use traphandlers::call_wasm; +use vmcontext::VMContext; use wasmtime_environ::{ - compile_module, Compilation, Export, Module, ModuleTranslation, Relocation, RelocationTarget, + compile_module, Compilation, Module, ModuleTranslation, Relocation, RelocationTarget, }; /// Executes a module that has been translated with the `wasmtime-environ` environment @@ -34,6 +36,10 @@ where Ok(compilation) } +extern "C" { + pub fn __rust_probestack(); +} + /// Performs the relocations inside the function bytecode, provided the necessary metadata fn relocate( compilation: &mut Compilation, @@ -51,7 +57,7 @@ fn relocate( RelocationTarget::UserFunc(index) => match module.defined_func_index(index) { Some(f) => compilation.functions[f].as_ptr() as usize, None => { - let func = &module.imported_funcs[index.index()]; + let func = &module.imported_funcs[index]; match imports(&func.0, &func.1) { Some(ptr) => ptr, None => { @@ -60,12 +66,29 @@ fn relocate( } } }, - RelocationTarget::GrowMemory => grow_memory as usize, - RelocationTarget::CurrentMemory => current_memory as usize, + RelocationTarget::MemoryGrow => wasmtime_memory_grow as usize, + RelocationTarget::MemorySize => wasmtime_memory_size as usize, + RelocationTarget::LibCall(libcall) => { + use cranelift_codegen::ir::LibCall::*; + use libcalls::*; + match libcall { + CeilF32 => wasmtime_f32_ceil as usize, + FloorF32 => wasmtime_f32_floor as usize, + TruncF32 => wasmtime_f32_trunc as usize, + NearestF32 => wasmtime_f32_nearest as usize, + CeilF64 => wasmtime_f64_ceil as usize, + FloorF64 => wasmtime_f64_floor as usize, + TruncF64 => wasmtime_f64_trunc as usize, + NearestF64 => wasmtime_f64_nearest as usize, + Probestack => __rust_probestack as usize, + other => panic!("unexpected libcall: {}", other), + } + } }; let body = &mut compilation.functions[i]; match r.reloc { + #[cfg(target_pointer_width = "64")] Reloc::Abs8 => unsafe { let reloc_address = body.as_mut_ptr().add(r.offset as usize) as usize; let reloc_addend = r.addend as isize; @@ -74,6 +97,7 @@ fn relocate( .unwrap(); write_unaligned(reloc_address as *mut u64, reloc_abs); }, + #[cfg(target_pointer_width = "32")] Reloc::X86PCRel4 => unsafe { let reloc_address = body.as_mut_ptr().add(r.offset as usize) as usize; let reloc_addend = r.addend as isize; @@ -89,60 +113,30 @@ fn relocate( } } -extern "C" fn grow_memory(size: u32, memory_index: u32, vmctx: *mut *mut u8) -> u32 { - unsafe { - // FIXME: update the VMMemory's size - let instance = (*vmctx.offset(4)) as *mut Instance; - (*instance) - .memory_mut(MemoryIndex::new(memory_index as usize)) - .grow(size) - .unwrap_or(u32::max_value()) - } -} +extern "C" fn wasmtime_memory_grow(size: u32, memory_index: u32, vmctx: *mut VMContext) -> u32 { + let instance = unsafe { (&mut *vmctx).instance() }; + let memory_index = MemoryIndex::new(memory_index as usize); -extern "C" fn current_memory(memory_index: u32, vmctx: *mut *mut u8) -> u32 { - unsafe { - // FIXME: read the VMMemory's size instead - let instance = (*vmctx.offset(4)) as *mut Instance; - (*instance) - .memory_mut(MemoryIndex::new(memory_index as usize)) - .current_size() - } + instance + .memory_grow(memory_index, size) + .unwrap_or(u32::max_value()) } -/// Create the VmCtx data structure for the JIT'd code to use. This must -/// match the VmCtx layout in the environment. -fn make_vmctx(instance: &mut Instance, mem_base_addrs: &mut [*mut u8]) -> Vec<*mut u8> { - debug_assert!( - instance.tables.len() <= 1, - "non-default tables is not supported" - ); - - let (default_table_ptr, default_table_len) = instance - .tables - .get_mut(TableIndex::new(0)) - .map(|table| (table.as_mut_ptr() as *mut u8, table.len())) - .unwrap_or((ptr::null_mut(), 0)); +extern "C" fn wasmtime_memory_size(memory_index: u32, vmctx: *mut VMContext) -> u32 { + let instance = unsafe { (&mut *vmctx).instance() }; + let memory_index = MemoryIndex::new(memory_index as usize); - // FIXME: Actually use environ's VMContext struct - let mut vmctx = Vec::new(); - vmctx.push(instance.globals.as_mut_ptr()); - // FIXME: These need to be VMMemory now - vmctx.push(mem_base_addrs.as_mut_ptr() as *mut u8); - // FIXME: These need to be VMTable now - vmctx.push(default_table_ptr); - vmctx.push(default_table_len as *mut u8); - vmctx.push(instance as *mut Instance as *mut u8); - - vmctx + instance.memory_size(memory_index) } /// prepares the execution context pub fn finish_instantiation( + code: &mut Code, + isa: &TargetIsa, module: &Module, compilation: &Compilation, instance: &mut Instance, -) -> Result, String> { +) -> Result<(), String> { // TODO: Put all the function bodies into a page-aligned memory region, and // then make them ReadExecute rather than ReadWriteExecute. for code_buf in compilation.functions.values() { @@ -163,68 +157,18 @@ pub fn finish_instantiation( } } - // Collect all memory base addresses and Vec. - let mut mem_base_addrs = instance - .memories - .values_mut() - .map(LinearMemory::base_addr) - .collect::>(); - - let mut vmctx = make_vmctx(instance, &mut mem_base_addrs); - if let Some(start_index) = module.start_func { - execute_by_index(module, compilation, &mut vmctx, start_index)?; - } - - Ok(vmctx) -} - -/// Jumps to the code region of memory and execute the exported function -pub fn execute( - module: &Module, - compilation: &Compilation, - vmctx: &mut Vec<*mut u8>, - function: &str, -) -> Result<(), String> { - let fn_index = match module.exports.get(function) { - Some(Export::Function(index)) => *index, - Some(_) => return Err(format!("exported item \"{}\" is not a function", function)), - None => return Err(format!("no export named \"{}\"", function)), - }; - - execute_by_index(module, compilation, vmctx, fn_index) -} - -fn execute_by_index( - module: &Module, - compilation: &Compilation, - vmctx: &mut Vec<*mut u8>, - fn_index: FuncIndex, -) -> Result<(), String> { - let code_buf = - &compilation.functions[module - .defined_func_index(fn_index) - .expect("imported start functions not supported yet")]; - - let mut traps = TrapContext { - triedToInstallSignalHandlers: false, - haveSignalHandlers: false, - }; - - // Rather than writing inline assembly to jump to the code region, we use the fact that - // the Rust ABI for calling a function with no arguments and no return values matches the one - // of the generated code. Thanks to this, we can transmute the code region into a first-class - // Rust function and call it. - unsafe { - // Ensure that our signal handlers are ready for action. - ensure_eager_signal_handlers(); - ensure_full_signal_handlers(&mut traps); - if !traps.haveSignalHandlers { - return Err("failed to install signal handlers".to_string()); + let vmctx = instance.vmctx(); + let result = invoke_by_index(code, isa, module, compilation, vmctx, start_index, &[])?; + match result { + InvokeOutcome::Returned { values } => { + assert!(values.is_empty()); + } + InvokeOutcome::Trapped { message } => { + return Err(format!("start function trapped: {}", message)); + } } - - let func = transmute::<_, fn(*const *mut u8)>(code_buf.as_ptr()); - call_wasm(|| func(vmctx.as_mut_ptr()))?; } + Ok(()) } diff --git a/lib/execute/src/instance.rs b/lib/execute/src/instance.rs index 36ade3476755..32c22d4dc148 100644 --- a/lib/execute/src/instance.rs +++ b/lib/execute/src/instance.rs @@ -1,25 +1,35 @@ //! An `Instance` contains all the runtime state used by execution of a wasm //! module. -use cranelift_codegen::ir; use cranelift_entity::EntityRef; use cranelift_entity::PrimaryMap; use cranelift_wasm::{GlobalIndex, MemoryIndex, TableIndex}; use memory::LinearMemory; -use std::vec::Vec; -use wasmtime_environ::{Compilation, DataInitializer, Module, TableElements}; +use std::string::String; +use table::{AnyFunc, Table}; +use vmcontext::{VMContext, VMGlobal, VMMemory, VMTable}; +use wasmtime_environ::{Compilation, DataInitializer, Module}; /// An Instance of a WebAssemby module. #[derive(Debug)] pub struct Instance { + /// WebAssembly linear memory data. + memories: PrimaryMap, + /// WebAssembly table data. - pub tables: PrimaryMap>, + tables: PrimaryMap, - /// WebAssembly linear memory data. - pub memories: PrimaryMap, + /// Memory base address vector pointed to by vmctx. + vmctx_memories: PrimaryMap, /// WebAssembly global variable data. - pub globals: Vec, + vmctx_globals: PrimaryMap, + + /// Table storage base address vector pointed to by vmctx. + vmctx_tables: PrimaryMap, + + /// Context pointer used by JIT code. + vmctx: VMContext, } impl Instance { @@ -28,79 +38,69 @@ impl Instance { module: &Module, compilation: &Compilation, data_initializers: &[DataInitializer], - ) -> Self { - let mut result = Self { - tables: PrimaryMap::new(), - memories: PrimaryMap::new(), - globals: Vec::new(), - }; - result.instantiate_tables(module, compilation, &module.table_elements); - result.instantiate_memories(module, data_initializers); - result.instantiate_globals(module); - result + ) -> Result { + let mut memories = instantiate_memories(module, data_initializers)?; + let mut tables = instantiate_tables(module, compilation); + + let mut vmctx_memories = memories + .values_mut() + .map(LinearMemory::vmmemory) + .collect::>(); + + let mut vmctx_globals = instantiate_globals(module); + + let mut vmctx_tables = tables + .values_mut() + .map(Table::vmtable) + .collect::>(); + + let vmctx_memories_ptr = vmctx_memories.values_mut().into_slice().as_mut_ptr(); + let vmctx_globals_ptr = vmctx_globals.values_mut().into_slice().as_mut_ptr(); + let vmctx_tables_ptr = vmctx_tables.values_mut().into_slice().as_mut_ptr(); + + Ok(Self { + memories, + tables, + vmctx_memories, + vmctx_globals, + vmctx_tables, + vmctx: VMContext::new(vmctx_memories_ptr, vmctx_globals_ptr, vmctx_tables_ptr), + }) } - /// Allocate memory in `self` for just the tables of the current module. - fn instantiate_tables( - &mut self, - module: &Module, - compilation: &Compilation, - table_initializers: &[TableElements], - ) { - debug_assert!(self.tables.is_empty()); - // TODO: Enable this once PrimaryMap supports this. - //self.tables.reserve_exact(module.tables.len()); - for table in module.tables.values() { - let len = table.size; - let mut v = Vec::with_capacity(len); - v.resize(len, 0); - self.tables.push(v); - } - for init in table_initializers { - debug_assert!(init.base.is_none(), "globalvar base not supported yet"); - let to_init = - &mut self.tables[init.table_index][init.offset..init.offset + init.elements.len()]; - for (i, func_idx) in init.elements.iter().enumerate() { - let code_buf = &compilation.functions[module.defined_func_index(*func_idx).expect( - "table element initializer with imported function not supported yet", - )]; - to_init[i] = code_buf.as_ptr() as usize; - } - } + /// Return the vmctx pointer to be passed into JIT code. + pub fn vmctx(&mut self) -> *mut VMContext { + &mut self.vmctx as *mut VMContext } - /// Allocate memory in `instance` for just the memories of the current module. - fn instantiate_memories(&mut self, module: &Module, data_initializers: &[DataInitializer]) { - debug_assert!(self.memories.is_empty()); - // Allocate the underlying memory and initialize it to all zeros. - // TODO: Enable this once PrimaryMap supports it. - //self.memories.reserve_exact(module.memories.len()); - for memory in module.memories.values() { - let v = LinearMemory::new(memory.pages_count as u32, memory.maximum.map(|m| m as u32)); - self.memories.push(v); - } - for init in data_initializers { - debug_assert!(init.base.is_none(), "globalvar base not supported yet"); - let mem_mut = self.memories[init.memory_index].as_mut(); - let to_init = &mut mem_mut[init.offset..init.offset + init.data.len()]; - to_init.copy_from_slice(init.data); - } + /// Return the offset from the vmctx pointer to its containing Instance. + pub fn vmctx_offset() -> isize { + offset_of!(Instance, vmctx) as isize } - /// Allocate memory in `instance` for just the globals of the current module, - /// without any initializers applied yet. - fn instantiate_globals(&mut self, module: &Module) { - debug_assert!(self.globals.is_empty()); - // Allocate the underlying memory and initialize it to all zeros. - let globals_data_size = module.globals.len() * 8; - self.globals.resize(globals_data_size, 0); + /// Grow memory by the specified amount of pages. + /// + /// Returns `None` if memory can't be grown by the specified amount + /// of pages. + pub fn memory_grow(&mut self, memory_index: MemoryIndex, delta: u32) -> Option { + let result = self + .memories + .get_mut(memory_index) + .unwrap_or_else(|| panic!("no memory for index {}", memory_index.index())) + .grow(delta); + + // Keep current the VMContext pointers used by JIT code. + self.vmctx_memories[memory_index] = self.memories[memory_index].vmmemory(); + + result } - /// Returns a mutable reference to a linear memory under the specified index. - pub fn memory_mut(&mut self, memory_index: MemoryIndex) -> &mut LinearMemory { + /// Returns the number of allocated wasm pages. + pub fn memory_size(&mut self, memory_index: MemoryIndex) -> u32 { self.memories - .get_mut(memory_index) + .get(memory_index) .unwrap_or_else(|| panic!("no memory for index {}", memory_index.index())) + .size() } /// Returns a slice of the contents of allocated linear memory. @@ -113,9 +113,64 @@ impl Instance { } /// Shows the value of a global variable. - pub fn inspect_global(&self, global_index: GlobalIndex, ty: ir::Type) -> &[u8] { - let offset = global_index.index() * 8; - let len = ty.bytes() as usize; - &self.globals[offset..offset + len] + pub fn inspect_global(&self, global_index: GlobalIndex) -> &VMGlobal { + &self.vmctx_globals[global_index] + } +} + +/// Allocate memory for just the memories of the current module. +fn instantiate_memories( + module: &Module, + data_initializers: &[DataInitializer], +) -> Result, String> { + let mut memories = PrimaryMap::with_capacity(module.memory_plans.len()); + for plan in module.memory_plans.values() { + memories.push(LinearMemory::new(&plan)?); } + + for init in data_initializers { + debug_assert!(init.base.is_none(), "globalvar base not supported yet"); + let mem_mut = memories[init.memory_index].as_mut(); + let to_init = &mut mem_mut[init.offset..init.offset + init.data.len()]; + to_init.copy_from_slice(init.data); + } + + Ok(memories) +} + +/// Allocate memory for just the tables of the current module. +fn instantiate_tables(module: &Module, compilation: &Compilation) -> PrimaryMap { + let mut tables = PrimaryMap::with_capacity(module.tables.len()); + for table in module.tables.values() { + tables.push(Table::new(table)); + } + + for init in &module.table_elements { + debug_assert!(init.base.is_none(), "globalvar base not supported yet"); + let slice = &mut tables[init.table_index].as_mut(); + let subslice = &mut slice[init.offset..init.offset + init.elements.len()]; + for (i, func_idx) in init.elements.iter().enumerate() { + let code_buf = &compilation.functions[module.defined_func_index(*func_idx).expect( + "table element initializer with imported function not supported yet", + )]; + subslice[i] = AnyFunc { + func_ptr: code_buf.as_ptr(), + type_id: 0, // TODO: Implement signature checking. + }; + } + } + + tables +} + +/// Allocate memory for just the globals of the current module, +/// without any initializers applied yet. +fn instantiate_globals(module: &Module) -> PrimaryMap { + let mut vmctx_globals = PrimaryMap::with_capacity(module.globals.len()); + + for _ in 0..module.globals.len() { + vmctx_globals.push(VMGlobal::default()); + } + + vmctx_globals } diff --git a/lib/execute/src/invoke.rs b/lib/execute/src/invoke.rs new file mode 100644 index 000000000000..b34b49aeffad --- /dev/null +++ b/lib/execute/src/invoke.rs @@ -0,0 +1,265 @@ +//! Support for invoking wasm functions from outside a wasm module. + +use code::Code; +use cranelift_codegen::ir::InstBuilder; +use cranelift_codegen::{binemit, ir, isa, Context}; +use cranelift_frontend::{FunctionBuilder, FunctionBuilderContext}; +use cranelift_wasm::FuncIndex; +use signalhandlers::{ensure_eager_signal_handlers, ensure_full_signal_handlers, TrapContext}; +use std::mem; +use std::ptr; +use std::string::String; +use std::vec::Vec; +use traphandlers::call_wasm; +use vmcontext::VMContext; +use wasmtime_environ::{Compilation, Export, Module, RelocSink}; + +/// A runtime value. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum Value { + /// 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 Value { + /// Return the type of this `Value`. + pub fn value_type(self) -> ir::Type { + match self { + Value::I32(_) => ir::types::I32, + Value::I64(_) => ir::types::I64, + Value::F32(_) => ir::types::F32, + Value::F64(_) => ir::types::F64, + } + } + + /// Assuming this `Value` holds an `i32`, return that value. + pub fn unwrap_i32(self) -> i32 { + match self { + Value::I32(x) => x, + _ => panic!("unwrapping value of type {} as i32", self.value_type()), + } + } + + /// Assuming this `Value` holds an `i64`, return that value. + pub fn unwrap_i64(self) -> i64 { + match self { + Value::I64(x) => x, + _ => panic!("unwrapping value of type {} as i64", self.value_type()), + } + } + + /// Assuming this `Value` holds an `f32`, return that value. + pub fn unwrap_f32(self) -> u32 { + match self { + Value::F32(x) => x, + _ => panic!("unwrapping value of type {} as f32", self.value_type()), + } + } + + /// Assuming this `Value` holds an `f64`, return that value. + pub fn unwrap_f64(self) -> u64 { + match self { + Value::F64(x) => x, + _ => panic!("unwrapping value of type {} as f64", self.value_type()), + } + } +} + +/// The result of invoking a wasm function. +#[derive(Debug)] +pub enum InvokeOutcome { + /// The function returned normally. Its return values are provided. + Returned { + /// The return values. + values: Vec, + }, + /// A trap occurred while the function was executing. + Trapped { + /// The trap message. + message: String, + }, +} + +/// Jumps to the code region of memory and invoke the exported function +pub fn invoke( + code: &mut Code, + isa: &isa::TargetIsa, + module: &Module, + compilation: &Compilation, + vmctx: *mut VMContext, + function: &str, + args: &[Value], +) -> Result { + let fn_index = match module.exports.get(function) { + Some(Export::Function(index)) => *index, + Some(_) => return Err(format!("exported item \"{}\" is not a function", function)), + None => return Err(format!("no export named \"{}\"", function)), + }; + + invoke_by_index(code, isa, module, compilation, vmctx, fn_index, args) +} + +pub fn invoke_by_index( + code: &mut Code, + isa: &isa::TargetIsa, + module: &Module, + compilation: &Compilation, + vmctx: *mut VMContext, + fn_index: FuncIndex, + args: &[Value], +) -> Result { + let code_buf = + &compilation.functions[module + .defined_func_index(fn_index) + .expect("imported start functions not supported yet")]; + let sig = &module.signatures[module.functions[fn_index]]; + + let exec_code_buf = code.allocate_copy_of_slice(&code_buf)?.as_ptr(); + + // TODO: Move this out to be done once per thread rather than per call. + let mut traps = TrapContext { + triedToInstallSignalHandlers: false, + haveSignalHandlers: false, + }; + + // Rather than writing inline assembly to jump to the code region, we use the fact that + // the Rust ABI for calling a function with no arguments and no return values matches the one + // of the generated code. Thanks to this, we can transmute the code region into a first-class + // Rust function and call it. + // Ensure that our signal handlers are ready for action. + ensure_eager_signal_handlers(); + ensure_full_signal_handlers(&mut traps); + if !traps.haveSignalHandlers { + return Err("failed to install signal handlers".to_string()); + } + + call_through_wrapper(code, isa, exec_code_buf as usize, vmctx, args, &sig) +} + +fn call_through_wrapper( + code: &mut Code, + isa: &isa::TargetIsa, + callee: usize, + vmctx: *mut VMContext, + args: &[Value], + sig: &ir::Signature, +) -> Result { + for (index, value) in args.iter().enumerate() { + assert_eq!(value.value_type(), sig.params[index].value_type); + } + + let wrapper_sig = ir::Signature::new(isa.frontend_config().default_call_conv); + let mut context = Context::new(); + context.func = ir::Function::with_name_signature(ir::ExternalName::user(0, 0), wrapper_sig); + + let value_size = 8; + let mut results_vec = Vec::new(); + results_vec.resize(sig.returns.len(), 0i64); + + let mut fn_builder_ctx = FunctionBuilderContext::new(); + { + let mut builder = FunctionBuilder::new(&mut context.func, &mut 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 callee_value = builder.ins().iconst(pointer_type, callee as i64); + + for value in args { + match value { + Value::I32(i) => { + callee_args.push(builder.ins().iconst(ir::types::I32, i64::from(*i))) + } + Value::I64(i) => callee_args.push(builder.ins().iconst(ir::types::I64, *i)), + Value::F32(i) => callee_args.push( + builder + .ins() + .f32const(ir::immediates::Ieee32::with_bits(*i)), + ), + Value::F64(i) => callee_args.push( + builder + .ins() + .f64const(ir::immediates::Ieee64::with_bits(*i)), + ), + } + } + + let vmctx_value = builder.ins().iconst(pointer_type, vmctx as i64); + callee_args.push(vmctx_value); + + let new_sig = builder.import_signature(sig.clone()); + + // TODO: It's possible to make this a direct call. We just need Cranelift + // to support functions declared with an immediate integer address. + let call = builder + .ins() + .call_indirect(new_sig, callee_value, &callee_args); + + let results = builder.func.dfg.inst_results(call).to_vec(); + + let results_vec_value = builder + .ins() + .iconst(pointer_type, results_vec.as_ptr() as i64); + + let mut mflags = ir::MemFlags::new(); + mflags.set_notrap(); + mflags.set_aligned(); + for (i, r) in results.iter().enumerate() { + builder + .ins() + .store(mflags, *r, results_vec_value, (i * value_size) as i32); + } + + builder.ins().return_(&[]); + } + + 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(|e| e.to_string())?; + assert!(reloc_sink.func_relocs.is_empty()); + + let exec_code_buf = code.allocate_copy_of_slice(&code_buf)?.as_ptr(); + code.publish(); + + let func = unsafe { mem::transmute::<_, fn()>(exec_code_buf) }; + + Ok(match call_wasm(func) { + Ok(()) => { + let mut values = Vec::with_capacity(sig.returns.len()); + + for (index, abi_param) in sig.returns.iter().enumerate() { + let v = unsafe { + let ptr = results_vec.as_ptr().add(index * value_size); + + match abi_param.value_type { + ir::types::I32 => Value::I32(ptr::read(ptr as *const i32)), + ir::types::I64 => Value::I64(ptr::read(ptr as *const i64)), + ir::types::F32 => Value::F32(ptr::read(ptr as *const u32)), + ir::types::F64 => Value::F64(ptr::read(ptr as *const u64)), + other => panic!("unsupported value type {:?}", other), + } + }; + + values.push(v); + } + + InvokeOutcome::Returned { values } + } + Err(message) => InvokeOutcome::Trapped { message }, + }) +} diff --git a/lib/execute/src/lib.rs b/lib/execute/src/lib.rs index b03046ef7510..f78c9d7adc20 100644 --- a/lib/execute/src/lib.rs +++ b/lib/execute/src/lib.rs @@ -29,8 +29,9 @@ extern crate cranelift_codegen; extern crate cranelift_entity; +extern crate cranelift_frontend; extern crate cranelift_wasm; -extern crate memmap; +extern crate errno; extern crate region; extern crate wasmtime_environ; #[cfg(not(feature = "std"))] @@ -39,16 +40,29 @@ extern crate alloc; #[macro_use] extern crate lazy_static; extern crate libc; +#[macro_use] +extern crate memoffset; +mod code; mod execute; mod instance; +mod invoke; +mod libcalls; mod memory; +mod mmap; mod signalhandlers; +mod table; mod traphandlers; +mod vmcontext; +mod world; -pub use execute::{compile_and_link_module, execute, finish_instantiation}; +pub use code::Code; +pub use execute::{compile_and_link_module, finish_instantiation}; pub use instance::Instance; +pub use invoke::{invoke, InvokeOutcome, Value}; pub use traphandlers::{call_wasm, LookupCodeSegment, RecordTrap, Unwind}; +pub use vmcontext::VMContext; +pub use world::InstanceWorld; #[cfg(not(feature = "std"))] mod std { diff --git a/lib/execute/src/libcalls.rs b/lib/execute/src/libcalls.rs new file mode 100644 index 000000000000..f4166c2a646b --- /dev/null +++ b/lib/execute/src/libcalls.rs @@ -0,0 +1,73 @@ +//! 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. + +pub extern "C" fn wasmtime_f32_ceil(x: f32) -> f32 { + x.ceil() +} + +pub extern "C" fn wasmtime_f32_floor(x: f32) -> f32 { + x.floor() +} + +pub extern "C" fn wasmtime_f32_trunc(x: f32) -> f32 { + x.trunc() +} + +pub extern "C" fn wasmtime_f32_nearest(x: f32) -> f32 { + // Rust doesn't have a nearest function, so do it manually. + if x == 0.0 { + // Preserve the sign of zero. + x + } else { + // Nearest is either ceil or floor depending on which is nearest or even. + let u = x.ceil(); + let d = x.floor(); + let um = (x - u).abs(); + let dm = (x - d).abs(); + if um < dm + || (um == dm && { + let h = u / 2.; + h.floor() == h + }) { + u + } else { + d + } + } +} + +pub extern "C" fn wasmtime_f64_ceil(x: f64) -> f64 { + x.ceil() +} + +pub extern "C" fn wasmtime_f64_floor(x: f64) -> f64 { + x.floor() +} + +pub extern "C" fn wasmtime_f64_trunc(x: f64) -> f64 { + x.trunc() +} + +pub extern "C" fn wasmtime_f64_nearest(x: f64) -> f64 { + // Rust doesn't have a nearest function, so do it manually. + if x == 0.0 { + // Preserve the sign of zero. + x + } else { + // Nearest is either ceil or floor depending on which is nearest or even. + let u = x.ceil(); + let d = x.floor(); + let um = (x - u).abs(); + let dm = (x - d).abs(); + if um < dm + || (um == dm && { + let h = u / 2.; + h.floor() == h + }) { + u + } else { + d + } + } +} diff --git a/lib/execute/src/memory.rs b/lib/execute/src/memory.rs index 4830b938615a..6dcc4a074c69 100644 --- a/lib/execute/src/memory.rs +++ b/lib/execute/src/memory.rs @@ -1,46 +1,67 @@ -use memmap; -use std::fmt; +//! Memory management for linear memories. +//! +//! `LinearMemory` is to WebAssembly linear memories what `Table` is to WebAssembly tables. -const PAGE_SIZE: u32 = 65536; -const MAX_PAGES: u32 = 65536; +use mmap::Mmap; +use region; +use std::string::String; +use vmcontext::VMMemory; +use wasmtime_environ::{MemoryPlan, MemoryStyle, WASM_MAX_PAGES, WASM_PAGE_SIZE}; /// A linear memory instance. -/// -/// This linear memory has a stable base address and at the same time allows -/// for dynamical growing. +#[derive(Debug)] pub struct LinearMemory { - mmap: memmap::MmapMut, + mmap: Mmap, current: u32, maximum: Option, + offset_guard_size: usize, } impl LinearMemory { - /// Create a new linear memory instance with specified initial and maximum number of pages. - /// - /// `maximum` cannot be set to more than `65536` pages. - pub fn new(initial: u32, maximum: Option) -> Self { - assert!(initial <= MAX_PAGES); - assert!(maximum.is_none() || maximum.unwrap() <= MAX_PAGES); - - let len = PAGE_SIZE * match maximum { - Some(val) => val, - None => initial, - }; - let mmap = memmap::MmapMut::map_anon(len as usize).unwrap(); - Self { - mmap, - current: initial, - maximum, + /// Create a new linear memory instance with specified minimum and maximum number of pages. + pub fn new(plan: &MemoryPlan) -> Result { + // `maximum` cannot be set to more than `65536` pages. + assert!(plan.memory.minimum <= WASM_MAX_PAGES); + assert!(plan.memory.maximum.is_none() || plan.memory.maximum.unwrap() <= WASM_MAX_PAGES); + + let offset_guard_bytes = plan.offset_guard_size as usize; + + let minimum_pages = match plan.style { + MemoryStyle::Dynamic => plan.memory.minimum, + MemoryStyle::Static { bound } => { + assert!(bound >= plan.memory.minimum); + bound + } + } as usize; + let minimum_bytes = minimum_pages.checked_mul(WASM_PAGE_SIZE as usize).unwrap(); + let request_bytes = minimum_bytes.checked_add(offset_guard_bytes).unwrap(); + let mapped_pages = plan.memory.minimum as usize; + let mapped_bytes = mapped_pages * WASM_PAGE_SIZE as usize; + let unmapped_pages = minimum_pages - mapped_pages; + let unmapped_bytes = unmapped_pages * WASM_PAGE_SIZE as usize; + let inaccessible_bytes = unmapped_bytes + offset_guard_bytes; + + let mmap = Mmap::with_size(request_bytes)?; + + // Make the unmapped and offset-guard pages inaccessible. + unsafe { + region::protect( + mmap.as_ptr().add(mapped_bytes), + inaccessible_bytes, + region::Protection::None, + ).expect("unable to make memory inaccessible"); } - } - /// Returns an base address of this linear memory. - pub fn base_addr(&mut self) -> *mut u8 { - self.mmap.as_mut_ptr() + Ok(Self { + mmap, + current: plan.memory.minimum, + maximum: plan.memory.maximum, + offset_guard_size: offset_guard_bytes, + }) } - /// Returns a number of allocated wasm pages. - pub fn current_size(&self) -> u32 { + /// Returns the number of allocated wasm pages. + pub fn size(&self) -> u32 { self.current } @@ -48,66 +69,72 @@ impl LinearMemory { /// /// Returns `None` if memory can't be grown by the specified amount /// of pages. - pub fn grow(&mut self, add_pages: u32) -> Option { - let new_pages = match self.current.checked_add(add_pages) { + pub fn grow(&mut self, delta: u32) -> Option { + let new_pages = match self.current.checked_add(delta) { Some(new_pages) => new_pages, + // Linear memory size overflow. None => return None, }; - if let Some(val) = self.maximum { - if new_pages > val { - return None; - } - } else { - // Wasm linear memories are never allowed to grow beyond what is - // indexable. If the memory has no maximum, enforce the greatest - // limit here. - if new_pages >= 65536 { + let prev_pages = self.current; + + if let Some(maximum) = self.maximum { + if new_pages > maximum { + // Linear memory size would exceed the declared maximum. return None; } } - let prev_pages = self.current; - let new_bytes = (new_pages * PAGE_SIZE) as usize; + // Wasm linear memories are never allowed to grow beyond what is + // indexable. If the memory has no maximum, enforce the greatest + // limit here. + if new_pages >= WASM_MAX_PAGES { + // Linear memory size would exceed the index range. + return None; + } - if self.mmap.len() < new_bytes { - // If we have no maximum, this is a "dynamic" heap, and it's allowed - // to move. + let new_bytes = new_pages as usize * WASM_PAGE_SIZE as usize; + + if new_bytes > self.mmap.len() - self.offset_guard_size { + // If we have no maximum, this is a "dynamic" heap, and it's allowed to move. assert!(self.maximum.is_none()); - let mut new_mmap = memmap::MmapMut::map_anon(new_bytes).unwrap(); - new_mmap.copy_from_slice(&self.mmap); + let guard_bytes = self.offset_guard_size; + let request_bytes = new_bytes.checked_add(guard_bytes)?; + + let mut new_mmap = Mmap::with_size(request_bytes).ok()?; + + // Make the offset-guard pages inaccessible. + unsafe { + region::protect( + new_mmap.as_ptr().add(new_bytes), + guard_bytes, + region::Protection::None, + ).expect("unable to make memory inaccessible"); + } + + let copy_len = self.mmap.len() - self.offset_guard_size; + new_mmap.as_mut_slice()[..copy_len].copy_from_slice(&self.mmap.as_slice()[..copy_len]); + self.mmap = new_mmap; } self.current = new_pages; - // Ensure that newly allocated area is zeroed. - let new_start_offset = (prev_pages * PAGE_SIZE) as usize; - let new_end_offset = (new_pages * PAGE_SIZE) as usize; - for i in new_start_offset..new_end_offset { - assert!(self.mmap[i] == 0); - } - Some(prev_pages) } -} -impl fmt::Debug for LinearMemory { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("LinearMemory") - .field("current", &self.current) - .field("maximum", &self.maximum) - .finish() + pub fn vmmemory(&mut self) -> VMMemory { + VMMemory::new(self.mmap.as_mut_ptr(), self.mmap.len()) } } impl AsRef<[u8]> for LinearMemory { fn as_ref(&self) -> &[u8] { - &self.mmap + self.mmap.as_slice() } } impl AsMut<[u8]> for LinearMemory { fn as_mut(&mut self) -> &mut [u8] { - &mut self.mmap + self.mmap.as_mut_slice() } } diff --git a/lib/execute/src/mmap.rs b/lib/execute/src/mmap.rs new file mode 100644 index 000000000000..6b9fdab071fa --- /dev/null +++ b/lib/execute/src/mmap.rs @@ -0,0 +1,137 @@ +//! Low-level abstraction for allocating and managing zero-filled pages +//! of memory. + +use errno; +use libc; +use region; +use std::mem; +use std::ptr; +use std::slice; +use std::string::String; + +/// Round `size` up to the nearest multiple of `page_size`. +fn round_up_to_page_size(size: usize, page_size: usize) -> usize { + (size + (page_size - 1)) & !(page_size - 1) +} + +/// A simple struct consisting of a page-aligned pointer to page-aligned +/// and initially-zeroed memory and a length. +#[derive(Debug)] +pub struct Mmap { + ptr: *mut u8, + len: usize, +} + +impl Mmap { + pub fn new() -> Self { + Self { + ptr: ptr::null_mut(), + len: 0, + } + } + + /// Create a new `Mmap` pointing to at least `size` bytes of memory, + /// suitably sized and aligned for memory protection. + #[cfg(not(target_os = "windows"))] + pub fn with_size(size: usize) -> Result { + let page_size = region::page::size(); + let alloc_size = round_up_to_page_size(size, page_size); + unsafe { + let ptr = libc::mmap( + ptr::null_mut(), + alloc_size, + libc::PROT_READ | libc::PROT_WRITE, + libc::MAP_PRIVATE | libc::MAP_ANON, + -1, + 0, + ); + if mem::transmute::<_, isize>(ptr) != -1isize { + Ok(Self { + ptr: ptr as *mut u8, + len: alloc_size, + }) + } else { + Err(errno::errno().to_string()) + } + } + } + + #[cfg(target_os = "windows")] + pub fn with_size(size: usize) -> Result { + use winapi::um::memoryapi::VirtualAlloc; + use winapi::um::winnt::{MEM_COMMIT, MEM_RESERVE, PAGE_READWRITE}; + + let page_size = region::page::size(); + + // VirtualAlloc always rounds up to the next multiple of the page size + let ptr = unsafe { + VirtualAlloc( + ptr::null_mut(), + size, + MEM_COMMIT | MEM_RESERVE, + PAGE_READWRITE, + ) + }; + if !ptr.is_null() { + Ok(Self { + ptr: ptr as *mut u8, + len: round_up_to_page_size(size, page_size), + }) + } else { + Err(errno::errno().to_string()) + } + } + + pub fn as_slice(&self) -> &[u8] { + unsafe { slice::from_raw_parts(self.ptr, self.len) } + } + + pub fn as_mut_slice(&mut self) -> &mut [u8] { + unsafe { slice::from_raw_parts_mut(self.ptr, self.len) } + } + + pub fn as_ptr(&self) -> *const u8 { + self.ptr + } + + pub fn as_mut_ptr(&mut self) -> *mut u8 { + self.ptr + } + + pub fn len(&self) -> usize { + self.len + } +} + +impl Drop for Mmap { + #[cfg(not(target_os = "windows"))] + fn drop(&mut self) { + if !self.ptr.is_null() { + let r = unsafe { libc::munmap(self.ptr as *mut libc::c_void, self.len) }; + assert_eq!(r, 0, "munmap failed: {}", errno::errno()); + } + } + + #[cfg(target_os = "windows")] + fn drop(&mut self) { + if !self.ptr.is_null() { + use winapi::um::memoryapi::VirtualFree; + use winapi::um::winnt::MEM_RELEASE; + let r = unsafe { VirtualFree(self.ptr, self.len, MEM_RELEASE) }; + assert_eq!(r, 0); + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_round_up_to_page_size() { + assert_eq!(round_up_to_page_size(0, 4096), 0); + assert_eq!(round_up_to_page_size(1, 4096), 4096); + assert_eq!(round_up_to_page_size(4096, 4096), 4096); + assert_eq!(round_up_to_page_size(4097, 4096), 8192); + } +} diff --git a/lib/execute/src/table.rs b/lib/execute/src/table.rs new file mode 100644 index 000000000000..0aef5a4c4c7a --- /dev/null +++ b/lib/execute/src/table.rs @@ -0,0 +1,65 @@ +//! Memory management for tables. +//! +//! `Table` is to WebAssembly tables what `LinearMemory` is to WebAssembly linear memories. + +use cranelift_wasm::{self, TableElementType}; +use std::ptr; +use vmcontext::VMTable; + +#[derive(Debug, Clone)] +pub struct AnyFunc { + pub func_ptr: *const u8, + pub type_id: usize, +} + +impl Default for AnyFunc { + fn default() -> Self { + Self { + func_ptr: ptr::null(), + type_id: 0, + } + } +} + +/// A table instance. +#[derive(Debug)] +pub struct Table { + vec: Vec, + maximum: Option, +} + +impl Table { + /// Create a new table instance with specified minimum and maximum number of pages. + pub fn new(table: &cranelift_wasm::Table) -> Self { + match table.ty { + TableElementType::Func => (), + TableElementType::Val(ty) => { + unimplemented!("tables of types other than anyfunc ({})", ty) + } + }; + + let mut vec = Vec::new(); + vec.resize(table.minimum as usize, AnyFunc::default()); + + Self { + vec, + maximum: table.maximum, + } + } + + pub fn vmtable(&mut self) -> VMTable { + VMTable::new(self.vec.as_mut_ptr() as *mut u8, self.vec.len()) + } +} + +impl AsRef<[AnyFunc]> for Table { + fn as_ref(&self) -> &[AnyFunc] { + self.vec.as_slice() + } +} + +impl AsMut<[AnyFunc]> for Table { + fn as_mut(&mut self) -> &mut [AnyFunc] { + self.vec.as_mut_slice() + } +} diff --git a/lib/execute/src/traphandlers.rs b/lib/execute/src/traphandlers.rs index bc84c2309eaa..405ea6b78005 100644 --- a/lib/execute/src/traphandlers.rs +++ b/lib/execute/src/traphandlers.rs @@ -6,6 +6,7 @@ use signalhandlers::{jmp_buf, CodeSegment}; use std::cell::{Cell, RefCell}; use std::mem; use std::ptr; +use std::string::String; // Currently we uset setjmp/longjmp to unwind out of a signal handler // and back to the point where WebAssembly was called (via `call_wasm`). @@ -73,10 +74,9 @@ impl ScopeGuard { impl Drop for ScopeGuard { fn drop(&mut self) { let orig_num_bufs = self.orig_num_bufs; - // TODO: Use `shrink_to` once it stablizes. JMP_BUFS.with(|bufs| { bufs.borrow_mut() - .resize(orig_num_bufs, unsafe { mem::uninitialized() }) + .resize(orig_num_bufs, unsafe { mem::zeroed() }) }); } } diff --git a/lib/execute/src/vmcontext.rs b/lib/execute/src/vmcontext.rs new file mode 100644 index 000000000000..dc3446fa16a1 --- /dev/null +++ b/lib/execute/src/vmcontext.rs @@ -0,0 +1,266 @@ +//! This file declares `VMContext` and several related structs which contain +//! fields that JIT code accesses directly. + +use cranelift_entity::EntityRef; +use cranelift_wasm::{GlobalIndex, MemoryIndex, TableIndex}; +use instance::Instance; +use std::mem::size_of; +use std::slice; + +/// The main fields a JIT needs to access to utilize a WebAssembly linear, +/// memory, namely the start address and the size in bytes. +#[derive(Debug)] +#[repr(C)] +pub struct VMMemory { + base: *mut u8, + current_length: usize, + // If more elements are added here, remember to add offset_of tests below! +} + +#[cfg(test)] +mod test_vmmemory { + use super::VMMemory; + use std::mem::size_of; + use wasmtime_environ::VMOffsets; + + #[test] + fn check_vmmemory_offsets() { + let offsets = VMOffsets::new(size_of::<*mut u8>() as u8); + assert_eq!( + size_of::(), + usize::from(offsets.size_of_vmmemory()) + ); + assert_eq!( + offset_of!(VMMemory, base), + usize::from(offsets.vmmemory_base()) + ); + assert_eq!( + offset_of!(VMMemory, current_length), + usize::from(offsets.vmmemory_current_length()) + ); + } +} + +impl VMMemory { + pub fn new(base: *mut u8, current_length: usize) -> Self { + Self { + base, + current_length, + } + } + + pub fn as_slice(&self) -> &[u8] { + unsafe { slice::from_raw_parts(self.base, self.current_length) } + } + + pub fn as_mut_slice(&mut self) -> &mut [u8] { + unsafe { slice::from_raw_parts_mut(self.base, self.current_length) } + } + + pub fn as_ptr(&self) -> *const u8 { + self.base + } + + pub fn as_mut_ptr(&mut self) -> *mut u8 { + self.base + } + + pub fn len(&self) -> usize { + self.current_length + } +} + +/// The storage for a WebAssembly global. +/// +/// TODO: Pack the globals more densely, rather than using the same size +/// for every type. +#[derive(Debug, Clone)] +#[repr(C, align(8))] +pub struct VMGlobal { + storage: [u8; 8], + // If more elements are added here, remember to add offset_of tests below! +} + +#[cfg(test)] +mod test_vmglobal { + use super::VMGlobal; + use std::mem::{align_of, size_of}; + use wasmtime_environ::VMOffsets; + + #[test] + fn check_vmglobal_alignment() { + assert!(align_of::() >= align_of::()); + assert!(align_of::() >= align_of::()); + assert!(align_of::() >= align_of::()); + assert!(align_of::() >= align_of::()); + } + + #[test] + fn check_vmglobal_offsets() { + let offsets = VMOffsets::new(size_of::<*mut u8>() as u8); + assert_eq!( + size_of::(), + usize::from(offsets.size_of_vmglobal()) + ); + } +} + +impl Default for VMGlobal { + fn default() -> Self { + VMGlobal { storage: [0; 8] } + } +} + +#[derive(Debug)] +/// The main fields a JIT needs to access to utilize a WebAssembly table, +/// namely the start address and the number of elements. +#[repr(C)] +pub struct VMTable { + base: *mut u8, + current_elements: usize, + // If more elements are added here, remember to add offset_of tests below! +} + +#[cfg(test)] +mod test_vmtable { + use super::VMTable; + use std::mem::size_of; + use wasmtime_environ::VMOffsets; + + #[test] + fn check_vmtable_offsets() { + let offsets = VMOffsets::new(size_of::<*mut u8>() as u8); + assert_eq!(size_of::(), usize::from(offsets.size_of_vmtable())); + assert_eq!( + offset_of!(VMTable, base), + usize::from(offsets.vmtable_base()) + ); + assert_eq!( + offset_of!(VMTable, current_elements), + usize::from(offsets.vmtable_current_elements()) + ); + } +} + +impl VMTable { + pub fn new(base: *mut u8, current_elements: usize) -> Self { + Self { + base, + current_elements, + } + } + + pub fn as_slice(&self) -> &[u8] { + unsafe { slice::from_raw_parts(self.base, self.current_elements) } + } + + pub fn as_mut_slice(&mut self) -> &mut [u8] { + unsafe { slice::from_raw_parts_mut(self.base, self.current_elements) } + } + + pub fn as_ptr(&self) -> *const u8 { + self.base + } + + pub fn as_mut_ptr(&mut self) -> *mut u8 { + self.base + } + + pub fn len(&self) -> usize { + self.current_elements + } +} + +/// The VM "context", which is pointed to by the `vmctx` arg in Cranelift. +/// This has pointers to the globals, memories, tables, and other runtime +/// state associated with the current instance. +#[derive(Debug)] +#[repr(C)] +pub struct VMContext { + /// A pointer to an array of `VMMemory` instances, indexed by + /// WebAssembly memory index. + memories: *mut VMMemory, + /// A pointer to an array of globals. + globals: *mut VMGlobal, + /// A pointer to an array of `VMTable` instances, indexed by + /// WebAssembly table index. + tables: *mut VMTable, + // If more elements are added here, remember to add offset_of tests below! +} + +#[cfg(test)] +mod test { + use super::VMContext; + use std::mem::size_of; + use wasmtime_environ::VMOffsets; + + #[test] + fn check_vmctx_offsets() { + let offsets = VMOffsets::new(size_of::<*mut u8>() as u8); + assert_eq!(size_of::(), usize::from(offsets.size_of_vmctx())); + assert_eq!( + offset_of!(VMContext, memories), + usize::from(offsets.vmctx_memories()) + ); + assert_eq!( + offset_of!(VMContext, globals), + usize::from(offsets.vmctx_globals()) + ); + assert_eq!( + offset_of!(VMContext, tables), + usize::from(offsets.vmctx_tables()) + ); + } +} + +impl VMContext { + /// Create a new `VMContext` instance. + pub fn new(memories: *mut VMMemory, globals: *mut VMGlobal, tables: *mut VMTable) -> Self { + Self { + memories, + globals, + tables, + } + } + + /// Return the base pointer of the globals array. + pub unsafe fn global_storage(&mut self, index: GlobalIndex) -> *mut VMGlobal { + self.globals.add(index.index() * size_of::()) + } + + /// Return a mutable reference to global `index` which has type i32. + pub unsafe fn global_i32(&mut self, index: GlobalIndex) -> &mut i32 { + &mut *(self.global_storage(index) as *mut i32) + } + + /// Return a mutable reference to global `index` which has type i64. + pub unsafe fn global_i64(&mut self, index: GlobalIndex) -> &mut i64 { + &mut *(self.global_storage(index) as *mut i64) + } + + /// Return a mutable reference to global `index` which has type f32. + pub unsafe fn global_f32(&mut self, index: GlobalIndex) -> &mut f32 { + &mut *(self.global_storage(index) as *mut f32) + } + + /// Return a mutable reference to global `index` which has type f64. + pub unsafe fn global_f64(&mut self, index: GlobalIndex) -> &mut f64 { + &mut *(self.global_storage(index) as *mut f64) + } + + /// Return a mutable reference to linear memory `index`. + pub unsafe fn memory(&mut self, index: MemoryIndex) -> &mut VMMemory { + &mut *self.memories.add(index.index()) + } + + /// Return a mutable reference to table `index`. + pub unsafe fn table(&mut self, index: TableIndex) -> &mut VMTable { + &mut *self.tables.add(index.index()) + } + + /// Return a mutable reference to the associated `Instance`. + pub unsafe fn instance(&mut self) -> &mut Instance { + &mut *((self as *mut VMContext as *mut u8).offset(-Instance::vmctx_offset()) + as *mut Instance) + } +} diff --git a/lib/execute/src/world.rs b/lib/execute/src/world.rs new file mode 100644 index 000000000000..15f5a6b28c3f --- /dev/null +++ b/lib/execute/src/world.rs @@ -0,0 +1,66 @@ +use cranelift_codegen::isa; +use std::str; +use wasmtime_environ::{Compilation, Module, ModuleEnvironment, Tunables}; +use {compile_and_link_module, finish_instantiation, invoke, Code, Instance, InvokeOutcome, Value}; + +/// A module, an instance of that module, and accompanying compilation artifacts. +/// +/// TODO: Rename and reorganize this. +pub struct InstanceWorld { + module: Module, + instance: Instance, + compilation: Compilation, +} + +impl InstanceWorld { + /// Create a new `InstanceWorld` by compiling the wasm module in `data` and instatiating it. + pub fn new(code: &mut Code, isa: &isa::TargetIsa, data: &[u8]) -> Result { + let mut module = Module::new(); + let tunables = Tunables::default(); + let (instance, compilation) = { + let translation = { + let environ = ModuleEnvironment::new(isa, &mut module, tunables); + + environ.translate(&data).map_err(|e| e.to_string())? + }; + + let imports_resolver = |_env: &str, _function: &str| None; + + let compilation = compile_and_link_module(isa, &translation, &imports_resolver)?; + let mut instance = Instance::new( + translation.module, + &compilation, + &translation.lazy.data_initializers, + )?; + + finish_instantiation(code, isa, &translation.module, &compilation, &mut instance)?; + + (instance, compilation) + }; + + Ok(Self { + module, + instance, + compilation, + }) + } + + /// Invoke a function in this `InstanceWorld` by name. + pub fn invoke( + &mut self, + code: &mut Code, + isa: &isa::TargetIsa, + function_name: &str, + args: &[Value], + ) -> Result { + invoke( + code, + isa, + &self.module, + &self.compilation, + self.instance.vmctx(), + &function_name, + args, + ).map_err(|e| e.to_string()) + } +} diff --git a/lib/obj/Cargo.toml b/lib/obj/Cargo.toml index 53f8fec53d17..9e20ad2595d6 100644 --- a/lib/obj/Cargo.toml +++ b/lib/obj/Cargo.toml @@ -8,7 +8,7 @@ categories = ["wasm"] license = "Apache-2.0 WITH LLVM-exception" [dependencies] -cranelift-codegen = "0.25.0" -cranelift-entity = "0.25.0" +cranelift-codegen = { git = "https://github.com/sunfishcode/cranelift.git", branch = "guard-offset" } +cranelift-entity = { git = "https://github.com/sunfishcode/cranelift.git", branch = "guard-offset" } wasmtime-environ = { path = "../environ" } faerie = "0.6.0" diff --git a/lib/wast/Cargo.toml b/lib/wast/Cargo.toml new file mode 100644 index 000000000000..ab4ba1aaa9c8 --- /dev/null +++ b/lib/wast/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "wasmtime-wast" +version = "0.0.0" +authors = ["The Cranelift Project Developers"] +publish = false +description = "wast testing support for wasmtime" +categories = ["wasm"] +repository = "https://github.com/CraneStation/wasmtime" +license = "Apache-2.0 WITH LLVM-exception" +readme = "README.md" + +[dependencies] +cranelift-codegen = { git = "https://github.com/sunfishcode/cranelift.git", branch = "guard-offset" } +cranelift-native = { git = "https://github.com/sunfishcode/cranelift.git", branch = "guard-offset" } +wasmtime-execute = { path = "../execute" } +wabt = "0.7" + +[badges] +maintenance = { status = "experimental" } +travis-ci = { repository = "CraneStation/wasmtime" } diff --git a/lib/wast/LICENSE b/lib/wast/LICENSE new file mode 100644 index 000000000000..f9d81955f4bc --- /dev/null +++ b/lib/wast/LICENSE @@ -0,0 +1,220 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +--- LLVM Exceptions to the Apache 2.0 License ---- + +As an exception, if, as a result of your compiling your source code, portions +of this Software are embedded into an Object form of such source code, you +may redistribute such embedded portions in such Object form without complying +with the conditions of Sections 4(a), 4(b) and 4(d) of the License. + +In addition, if you combine or link compiled forms of this Software with +software that is licensed under the GPLv2 ("Combined Software") and if a +court of competent jurisdiction determines that the patent provision (Section +3), the indemnity provision (Section 9) or other Section of the License +conflicts with the conditions of the GPLv2, you may retroactively and +prospectively choose to deem waived or otherwise exclude such Section(s) of +the License, but only in their entirety and only with respect to the Combined +Software. + diff --git a/lib/wast/README.md b/lib/wast/README.md new file mode 100644 index 000000000000..ff671b026f19 --- /dev/null +++ b/lib/wast/README.md @@ -0,0 +1,5 @@ +This is the `wasmtime-wast` crate, which contains an implementation of WebAssembly's +"wast" test scripting language, which is used in the +[WebAssembly spec testsuite], using wasmtime for execution. + +[WebAssembly spec testsuite]: https://github.com/WebAssembly/testsuite diff --git a/lib/wast/build.rs b/lib/wast/build.rs new file mode 100644 index 000000000000..964fe4ab7e6e --- /dev/null +++ b/lib/wast/build.rs @@ -0,0 +1,102 @@ +use std::env; +use std::fs::{read_dir, File}; +use std::io::{self, Write}; +use std::path::{Path, PathBuf}; + +fn main() { + let out_dir = + PathBuf::from(env::var("OUT_DIR").expect("The OUT_DIR environment variable must be set")); + let mut out = + File::create(out_dir.join("run_wast_files.rs")).expect("error creating run_wast_files.rs"); + + test_directory(&mut out, "misc_testsuite").unwrap(); + test_directory(&mut out, "spec_testsuite").unwrap(); +} + +fn test_directory(out: &mut File, testsuite: &str) -> io::Result<()> { + let mut dir_entries: Vec<_> = read_dir(testsuite) + .unwrap() + .map(|r| r.unwrap()) + .filter(|dir_entry| { + let p = dir_entry.path(); + if let Some(ext) = p.extension() { + // Only look at wast files. + if ext == "wast" { + // Ignore files starting with `.`, which could be editor temporary files + if let Some(stem) = p.file_stem() { + if let Some(stemstr) = stem.to_str() { + if !stemstr.starts_with('.') { + return true; + } + } + } + } + } + false + }).collect(); + + dir_entries.sort_by_key(|dir| dir.path()); + + writeln!( + out, + "mod {} {{", + Path::new(testsuite) + .file_stem() + .unwrap() + .to_str() + .unwrap() + .replace("-", "_") + )?; + writeln!(out, " use super::{{native_isa, wast_file, Path}};")?; + for dir_entry in dir_entries { + let path = dir_entry.path(); + let stemstr = path + .file_stem() + .expect("file_stem") + .to_str() + .expect("to_str"); + + writeln!(out, " #[test]")?; + if ignore(testsuite, stemstr) { + writeln!(out, " #[ignore]")?; + } + writeln!( + out, + " fn {}() {{", + avoid_keywords(&stemstr.replace("-", "_")) + )?; + writeln!( + out, + " wast_file(Path::new(\"{}\"), &*native_isa()).expect(\"error loading wast file {}\");", + path.display(), + path.display() + )?; + writeln!(out, " }}")?; + writeln!(out)?; + } + writeln!(out, "}}")?; + Ok(()) +} + +fn avoid_keywords(name: &str) -> &str { + match name { + "if" => "if_", + "loop" => "loop_", + "type" => "type_", + "const" => "const_", + "return" => "return_", + other => other, + } +} + +fn ignore(testsuite: &str, name: &str) -> bool { + match testsuite { + "spec_testsuite" => match name { + // These are the remaining spec testsuite failures. + "call_indirect" | "data" | "elem" | "exports" | "func" | "func_ptrs" | "globals" + | "imports" | "linking" | "names" | "start" => true, + _ => false, + }, + _ => false, + } +} diff --git a/lib/wast/misc_testsuite/misc_traps.wast b/lib/wast/misc_testsuite/misc_traps.wast new file mode 100644 index 000000000000..96acf58bfe5e --- /dev/null +++ b/lib/wast/misc_testsuite/misc_traps.wast @@ -0,0 +1,67 @@ +(module + (memory 1 1) + (func (export "load_oob") + i32.const 65536 + i32.load + drop + ) +) + +(assert_trap (invoke "load_oob") "out of bounds memory access") +(assert_trap (invoke "load_oob") "out of bounds memory access") + +(module + (memory 1 1) + (func (export "store_oob") + i32.const 65536 + i32.const 65536 + i32.store + ) +) + +(assert_trap (invoke "store_oob") "out of bounds memory access") +(assert_trap (invoke "store_oob") "out of bounds memory access") + +(module + (memory 0 0) + (func (export "load_oob_0") + i32.const 0 + i32.load + drop + ) +) + +(assert_trap (invoke "load_oob_0") "out of bounds memory access") +(assert_trap (invoke "load_oob_0") "out of bounds memory access") + +(module + (memory 0 0) + (func (export "store_oob_0") + i32.const 0 + i32.const 0 + i32.store + ) +) + +(assert_trap (invoke "store_oob_0") "out of bounds memory access") +(assert_trap (invoke "store_oob_0") "out of bounds memory access") + +(module + (func (export "divbyzero") (result i32) + i32.const 1 + i32.const 0 + i32.div_s + ) +) + +(assert_trap (invoke "divbyzero") "integer divide by zero") +(assert_trap (invoke "divbyzero") "integer divide by zero") + +(module + (func (export "unreachable") + (unreachable) + ) +) + +(assert_trap (invoke "unreachable") "unreachable") +(assert_trap (invoke "unreachable") "unreachable") diff --git a/lib/wast/misc_testsuite/stack_overflow.wast b/lib/wast/misc_testsuite/stack_overflow.wast new file mode 100644 index 000000000000..baf4c98a7a43 --- /dev/null +++ b/lib/wast/misc_testsuite/stack_overflow.wast @@ -0,0 +1,26 @@ +(module + (func $foo + (call $foo) + ) + (func (export "stack_overflow") + (call $foo) + ) +) + +(assert_exhaustion (invoke "stack_overflow") "call stack exhausted") +(assert_exhaustion (invoke "stack_overflow") "call stack exhausted") + +(module + (func $foo + (call $bar) + ) + (func $bar + (call $foo) + ) + (func (export "stack_overflow") + (call $foo) + ) +) + +(assert_exhaustion (invoke "stack_overflow") "call stack exhausted") +(assert_exhaustion (invoke "stack_overflow") "call stack exhausted") diff --git a/lib/wast/spec_testsuite b/lib/wast/spec_testsuite new file mode 160000 index 000000000000..b2800641d6c6 --- /dev/null +++ b/lib/wast/spec_testsuite @@ -0,0 +1 @@ +Subproject commit b2800641d6c6b6a0c462f83e620843c414bea579 diff --git a/lib/wast/src/lib.rs b/lib/wast/src/lib.rs new file mode 100644 index 000000000000..2ec356061bae --- /dev/null +++ b/lib/wast/src/lib.rs @@ -0,0 +1,34 @@ +//! JIT-style runtime for WebAssembly using Cranelift. + +#![deny(missing_docs, trivial_numeric_casts, unused_extern_crates)] +#![warn(unused_import_braces)] +#![deny(unstable_features)] +#![cfg_attr( + feature = "clippy", + plugin(clippy(conf_file = "../../clippy.toml")) +)] +#![cfg_attr( + feature = "cargo-clippy", + allow(new_without_default, new_without_default_derive) +)] +#![cfg_attr( + feature = "cargo-clippy", + warn( + float_arithmetic, + mut_mut, + nonminimal_bool, + option_map_unwrap_or, + option_map_unwrap_or_else, + print_stdout, + unicode_not_nfc, + use_self + ) +)] + +extern crate cranelift_codegen; +extern crate wabt; +extern crate wasmtime_execute; + +mod wast; + +pub use wast::{wast_buffer, wast_file}; diff --git a/lib/wast/src/wast.rs b/lib/wast/src/wast.rs new file mode 100644 index 000000000000..c6811e0278f2 --- /dev/null +++ b/lib/wast/src/wast.rs @@ -0,0 +1,243 @@ +use cranelift_codegen::isa; +use std::collections::HashMap; +use std::fs; +use std::io; +use std::io::Read; +use std::path::Path; +use std::str; +use wabt::script::{self, Action, Command, CommandKind, ModuleBinary, ScriptParser}; +use wasmtime_execute::{Code, InstanceWorld, InvokeOutcome, Value}; + +struct Instances { + current: Option, + namespace: HashMap, + code: Code, +} + +impl Instances { + pub fn new() -> Self { + Self { + current: None, + namespace: HashMap::new(), + code: Code::new(), + } + } + + fn instantiate(&mut self, isa: &isa::TargetIsa, module: ModuleBinary) -> InstanceWorld { + InstanceWorld::new(&mut self.code, isa, &module.into_vec()).unwrap() + } + + pub fn define_unnamed_module(&mut self, isa: &isa::TargetIsa, module: ModuleBinary) { + self.current = Some(self.instantiate(isa, module)); + } + + pub fn define_named_module( + &mut self, + isa: &isa::TargetIsa, + name: String, + module: ModuleBinary, + ) { + let world = self.instantiate(isa, module); + self.namespace.insert(name, world); + } + + pub fn perform_action(&mut self, isa: &isa::TargetIsa, action: Action) -> InvokeOutcome { + match action { + Action::Invoke { + module, + field, + args, + } => { + let mut value_args = Vec::with_capacity(args.len()); + for a in args { + value_args.push(match a { + script::Value::I32(i) => Value::I32(i), + script::Value::I64(i) => Value::I64(i), + script::Value::F32(i) => Value::F32(i.to_bits()), + script::Value::F64(i) => Value::F64(i.to_bits()), + }); + } + match module { + None => match self.current { + None => panic!("invoke performed with no module present"), + Some(ref mut instance_world) => instance_world + .invoke(&mut self.code, isa, &field, &value_args) + .expect(&format!("error invoking {} in current module", field)), + }, + Some(name) => self + .namespace + .get_mut(&name) + .expect(&format!("module {} not declared", name)) + .invoke(&mut self.code, isa, &field, &value_args) + .expect(&format!("error invoking {} in module {}", field, name)), + } + } + _ => panic!("unsupported action {:?}", action), + } + } +} + +/// Run a wast script from a byte buffer. +pub fn wast_buffer(name: &str, isa: &isa::TargetIsa, wast: &[u8]) { + let mut parser = ScriptParser::from_str(str::from_utf8(wast).unwrap()).unwrap(); + let mut instances = Instances::new(); + + while let Some(Command { kind, line }) = parser.next().unwrap() { + match kind { + CommandKind::Module { module, name } => { + if let Some(name) = name { + instances.define_named_module(&*isa, name, module.clone()); + } + + instances.define_unnamed_module(&*isa, module) + } + CommandKind::PerformAction(action) => match instances.perform_action(&*isa, action) { + InvokeOutcome::Returned { .. } => {} + InvokeOutcome::Trapped { message } => { + panic!("{}:{}: a trap occurred: {}", name, line, message); + } + }, + CommandKind::AssertReturn { action, expected } => { + match instances.perform_action(&*isa, action) { + InvokeOutcome::Returned { values } => { + for (v, e) in values.iter().zip(expected.iter()) { + match *e { + script::Value::I32(x) => { + assert_eq!(x, v.unwrap_i32(), "at {}:{}", name, line) + } + script::Value::I64(x) => { + assert_eq!(x, v.unwrap_i64(), "at {}:{}", name, line) + } + script::Value::F32(x) => { + assert_eq!(x.to_bits(), v.unwrap_f32(), "at {}:{}", name, line) + } + script::Value::F64(x) => { + assert_eq!(x.to_bits(), v.unwrap_f64(), "at {}:{}", name, line) + } + }; + } + } + InvokeOutcome::Trapped { message } => { + panic!( + "{}:{}: expected normal return, but a trap occurred: {}", + name, line, message + ); + } + } + } + CommandKind::AssertTrap { action, message } => { + match instances.perform_action(&*isa, action) { + InvokeOutcome::Returned { values } => panic!( + "{}:{}: expected trap, but invoke returned with {:?}", + name, line, values + ), + InvokeOutcome::Trapped { + message: trap_message, + } => { + println!( + "{}:{}: TODO: Check the trap message: expected {}, got {}", + name, line, message, trap_message + ); + } + } + } + CommandKind::AssertExhaustion { action } => { + match instances.perform_action(&*isa, action) { + InvokeOutcome::Returned { values } => panic!( + "{}:{}: expected exhaustion, but invoke returned with {:?}", + name, line, values + ), + InvokeOutcome::Trapped { message } => { + println!( + "{}:{}: TODO: Check the exhaustion message: {}", + name, line, message + ); + } + } + } + CommandKind::AssertReturnCanonicalNan { action } => { + match instances.perform_action(&*isa, action) { + InvokeOutcome::Returned { values } => { + for v in values.iter() { + match v { + Value::I32(_) | Value::I64(_) => { + panic!("unexpected integer type in NaN test"); + } + Value::F32(x) => assert_eq!( + x & 0x7fffffff, + 0x7fc00000, + "expected canonical NaN at {}:{}", + name, + line + ), + Value::F64(x) => assert_eq!( + x & 0x7fffffffffffffff, + 0x7ff8000000000000, + "expected canonical NaN at {}:{}", + name, + line + ), + }; + } + } + InvokeOutcome::Trapped { message } => { + panic!( + "{}:{}: expected canonical NaN return, but a trap occurred: {}", + name, line, message + ); + } + } + } + CommandKind::AssertReturnArithmeticNan { action } => { + match instances.perform_action(&*isa, action) { + InvokeOutcome::Returned { values } => { + for v in values.iter() { + match v { + Value::I32(_) | Value::I64(_) => { + panic!("unexpected integer type in NaN test"); + } + Value::F32(x) => assert_eq!( + x & 0x00400000, + 0x00400000, + "expected arithmetic NaN at {}:{}", + name, + line + ), + Value::F64(x) => assert_eq!( + x & 0x0008000000000000, + 0x0008000000000000, + "expected arithmetic NaN at {}:{}", + name, + line + ), + }; + } + } + InvokeOutcome::Trapped { message } => { + panic!( + "{}:{}: expected canonical NaN return, but a trap occurred: {}", + name, line, message + ); + } + } + } + command => { + println!("{}:{}: TODO: implement {:?}", name, line, command); + } + } + } +} + +/// Run a wast script from a file. +pub fn wast_file(path: &Path, isa: &isa::TargetIsa) -> Result<(), String> { + let wast = read_to_end(path).map_err(|e| e.to_string())?; + wast_buffer(&path.display().to_string(), isa, &wast); + Ok(()) +} + +fn read_to_end(path: &Path) -> Result, io::Error> { + let mut buf: Vec = Vec::new(); + let mut file = fs::File::open(path)?; + file.read_to_end(&mut buf)?; + Ok(buf) +} diff --git a/lib/wast/tests/wast_files.rs b/lib/wast/tests/wast_files.rs new file mode 100644 index 000000000000..9cdf59ab066b --- /dev/null +++ b/lib/wast/tests/wast_files.rs @@ -0,0 +1,22 @@ +extern crate cranelift_codegen; +extern crate cranelift_native; +extern crate wasmtime_wast; + +use cranelift_codegen::isa; +use cranelift_codegen::settings; +use cranelift_codegen::settings::Configurable; +use std::path::Path; +use wasmtime_wast::wast_file; + +include!(concat!(env!("OUT_DIR"), "/run_wast_files.rs")); + +#[cfg(test)] +fn native_isa() -> Box { + let mut flag_builder = settings::builder(); + flag_builder.enable("enable_verifier").unwrap(); + + let isa_builder = cranelift_native::builder().unwrap_or_else(|_| { + panic!("host machine is not a supported target"); + }); + isa_builder.finish(settings::Flags::new(flag_builder)) +} diff --git a/src/run_wast.rs b/src/run_wast.rs new file mode 100644 index 000000000000..07934f8e7bb4 --- /dev/null +++ b/src/run_wast.rs @@ -0,0 +1,103 @@ +//! CLI tool to run wast tests using the wasmtime libraries. + +#![deny( + missing_docs, + trivial_numeric_casts, + unused_extern_crates, + unstable_features +)] +#![warn(unused_import_braces)] +#![cfg_attr( + feature = "clippy", + plugin(clippy(conf_file = "../../clippy.toml")) +)] +#![cfg_attr( + feature = "cargo-clippy", + allow(new_without_default, new_without_default_derive) +)] +#![cfg_attr( + feature = "cargo-clippy", + warn( + float_arithmetic, + mut_mut, + nonminimal_bool, + option_map_unwrap_or, + option_map_unwrap_or_else, + unicode_not_nfc, + use_self + ) +)] + +extern crate cranelift_codegen; +extern crate cranelift_native; +extern crate docopt; +extern crate wasmtime_wast; +#[macro_use] +extern crate serde_derive; +extern crate file_per_thread_logger; +extern crate pretty_env_logger; + +use cranelift_codegen::settings; +use cranelift_codegen::settings::Configurable; +use docopt::Docopt; +use std::path::Path; +use wasmtime_wast::wast_file; + +static LOG_FILENAME_PREFIX: &str = "cranelift.dbg."; + +const USAGE: &str = " +Wast test runner. + +Usage: + run_wast [-do] ... + run_wast --help | --version + +Options: + -h, --help print this help message + --version print the Cranelift version + -o, --optimize runs optimization passes on the translated functions + -d, --debug enable debug output on stderr/stdout +"; + +#[derive(Deserialize, Debug, Clone)] +struct Args { + arg_file: Vec, + flag_debug: bool, + flag_function: Option, + flag_optimize: bool, +} + +fn main() { + let args: Args = Docopt::new(USAGE) + .and_then(|d| { + d.help(true) + .version(Some(String::from("0.0.0"))) + .deserialize() + }).unwrap_or_else(|e| e.exit()); + let isa_builder = cranelift_native::builder().unwrap_or_else(|_| { + panic!("host machine is not a supported target"); + }); + let mut flag_builder = settings::builder(); + + // Enable verifier passes in debug mode. + if cfg!(debug_assertions) { + flag_builder.enable("enable_verifier").unwrap(); + } + + if args.flag_debug { + pretty_env_logger::init(); + } else { + file_per_thread_logger::initialize(LOG_FILENAME_PREFIX); + } + + // Enable optimization if requested. + if args.flag_optimize { + flag_builder.set("opt_level", "best").unwrap(); + } + + let isa = isa_builder.finish(settings::Flags::new(flag_builder)); + for filename in &args.arg_file { + let path = Path::new(&filename); + wast_file(path, &*isa).expect(&format!("error reading file {}", path.display())); + } +} diff --git a/src/wasm2obj.rs b/src/wasm2obj.rs index e7785dba629f..766e3f1560e9 100644 --- a/src/wasm2obj.rs +++ b/src/wasm2obj.rs @@ -56,7 +56,7 @@ use std::path::PathBuf; use std::process; use std::str::FromStr; use target_lexicon::Triple; -use wasmtime_environ::{compile_module, Module, ModuleEnvironment}; +use wasmtime_environ::{compile_module, Module, ModuleEnvironment, Tunables}; use wasmtime_obj::emit_module; const USAGE: &str = " @@ -136,7 +136,9 @@ 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(); - let environ = ModuleEnvironment::new(&*isa, &mut module); + // 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())?; // FIXME: We need to initialize memory in a way that supports alternate diff --git a/src/main.rs b/src/wasmtime.rs similarity index 86% rename from src/main.rs rename to src/wasmtime.rs index 63bb9bdaa9be..cc09f968d7c4 100644 --- a/src/main.rs +++ b/src/wasmtime.rs @@ -60,15 +60,16 @@ use std::io::stdout; use std::path::Path; use std::path::PathBuf; use std::process::exit; -use wasmtime_environ::{Module, ModuleEnvironment}; -use wasmtime_execute::{compile_and_link_module, execute, finish_instantiation, Instance}; +use wasmtime_environ::{Module, ModuleEnvironment, Tunables}; +use wasmtime_execute::{compile_and_link_module, finish_instantiation, invoke, Code, Instance}; static LOG_FILENAME_PREFIX: &str = "cranelift.dbg."; const USAGE: &str = " -Wasm to Cranelift IL translation utility. -Takes a binary WebAssembly module and returns its functions in Cranelift IL format. -The translation is dependent on the environment chosen. +Wasm runner. + +Takes a binary or text WebAssembly module and instantiates it, optionally +allowing selected functions in it to be invoked. Usage: wasmtime [-mopd] ... @@ -149,25 +150,42 @@ fn handle_module(args: &Args, path: PathBuf, isa: &TargetIsa) -> Result<(), Stri data = wabt::wat2wasm(data).map_err(|err| String::from(err.description()))?; } let mut module = Module::new(); - let environ = ModuleEnvironment::new(isa, &mut module); + // TODO: Expose the tunables as command-line flags. + let tunables = Tunables::default(); + let environ = ModuleEnvironment::new(isa, &mut module, tunables); let imports_resolver = |_env: &str, _function: &str| None; let translation = environ.translate(&data).map_err(|e| e.to_string())?; + let mut code = Code::new(); + let instance = match compile_and_link_module(isa, &translation, &imports_resolver) { Ok(compilation) => { let mut instance = Instance::new( translation.module, &compilation, &translation.lazy.data_initializers, - ); + )?; - let mut context = - finish_instantiation(&translation.module, &compilation, &mut instance)?; + finish_instantiation( + &mut code, + isa, + &translation.module, + &compilation, + &mut instance, + )?; if let Some(ref f) = args.flag_function { - execute(&translation.module, &compilation, &mut context, &f)?; + invoke( + &mut code, + isa, + &translation.module, + &compilation, + instance.vmctx(), + &f, + &[], + )?; } instance @@ -219,7 +237,7 @@ mod tests { use cranelift_codegen::settings::Configurable; use std::path::PathBuf; use wabt; - use wasmtime_environ::{Module, ModuleEnvironment}; + use wasmtime_environ::{Module, ModuleEnvironment, Tunables}; const PATH_MODULE_RS2WASM_ADD_FUNC: &str = r"filetests/rs2wasm-add-func.wat"; @@ -242,7 +260,8 @@ mod tests { let isa = isa_builder.finish(settings::Flags::new(flag_builder)); let mut module = Module::new(); - let environ = ModuleEnvironment::new(&*isa, &mut module); + let tunables = Tunables::default(); + let environ = ModuleEnvironment::new(&*isa, &mut module, tunables); let translation = environ.translate(&data); assert!(translation.is_ok()); diff --git a/test-all.sh b/test-all.sh index 0b0056691816..f8cf22c95c34 100755 --- a/test-all.sh +++ b/test-all.sh @@ -45,7 +45,7 @@ cargo build # Run the tests. We run these in debug mode so that assertions are enabled. banner "Rust unit tests" -cargo test --all +RUST_BACKTRACE=1 cargo test --all # Make sure the documentation builds. banner "Rust documentation: $topdir/target/doc/wasmtime/index.html"