From 9ae4475b17f4262eb501c12933dce957a7c1f57d Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Thu, 29 Nov 2018 10:11:11 -0800 Subject: [PATCH 01/20] Rewrite linear memory handling in terms of simple mmap/VirtualAlloc. The memmap crate doesn't make it straightforward to have part of the region be writeable and part readonly. Since this is a fairly boutique use case, and we don't need all that much code, just use the low-level APIs directly. Also, introduce a concept of "tunables" for adjusting the parameters of the runtime. --- Cargo.toml | 8 +- fuzz/Cargo.toml | 6 +- lib/environ/Cargo.toml | 6 +- lib/environ/src/environ.rs | 75 +++++++---- lib/environ/src/lib.rs | 10 +- lib/environ/src/module.rs | 61 ++++++++- lib/execute/Cargo.toml | 8 +- lib/execute/src/instance.rs | 25 ++-- lib/execute/src/lib.rs | 2 +- lib/execute/src/memory.rs | 246 ++++++++++++++++++++++++++++-------- lib/obj/Cargo.toml | 4 +- src/main.rs | 15 ++- src/wasm2obj.rs | 6 +- 13 files changed, 360 insertions(+), 112 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c15d39019ace..785e80997431 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,10 +18,10 @@ 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" } 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..33acdbf194d2 100644 --- a/lib/environ/Cargo.toml +++ b/lib/environ/Cargo.toml @@ -10,9 +10,9 @@ 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" } memoffset = "0.2.1" [features] diff --git a/lib/environ/src/environ.rs b/lib/environ/src/environ.rs index 9b4bcce80559..7335eedd2486 100644 --- a/lib/environ/src/environ.rs +++ b/lib/environ/src/environ.rs @@ -1,6 +1,6 @@ use cranelift_codegen::cursor::FuncCursor; use cranelift_codegen::ir; -use cranelift_codegen::ir::immediates::{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, @@ -11,11 +11,16 @@ use cranelift_wasm::{ self, translate_module, FuncIndex, Global, GlobalIndex, GlobalVariable, Memory, MemoryIndex, SignatureIndex, Table, TableIndex, WasmResult, }; -use module::{DataInitializer, Export, LazyContents, Module, TableElements}; +use module::{ + DataInitializer, Export, LazyContents, MemoryPlan, MemoryStyle, Module, TableElements, +}; +use std::clone::Clone; use std::mem; use std::string::String; use std::vec::Vec; +use tunables::Tunables; use vmcontext; +use WASM_PAGE_SIZE; /// Compute a `ir::ExternalName` for a given wasm function index. pub fn get_func_name(func_index: FuncIndex) -> ir::ExternalName { @@ -34,20 +39,28 @@ pub struct ModuleEnvironment<'data, 'module> { /// References to information to be decoded later. pub lazy: LazyContents<'data>, + + /// Tunable parameters. + pub tunables: Tunables, } impl<'data, 'module> ModuleEnvironment<'data, 'module> { /// Allocates the enironment data structures with the given isa. - pub fn new(isa: &'module isa::TargetIsa, module: &'module mut Module) -> 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) + FuncEnvironment::new(self.isa, &self.module, self.tunables.clone()) } fn pointer_type(&self) -> ir::Type { @@ -66,6 +79,7 @@ impl<'data, 'module> ModuleEnvironment<'data, 'module> { isa: self.isa, module: self.module, lazy: self.lazy, + tunables: self.tunables, }) } } @@ -95,12 +109,16 @@ pub struct FuncEnvironment<'module_environment> { /// The external function declaration for implementing wasm's `grow_memory`. pub grow_memory_extfunc: Option, + + /// Tunable parameters. + pub tunables: Tunables, } impl<'module_environment> FuncEnvironment<'module_environment> { pub fn new( isa: &'module_environment isa::TargetIsa, module: &'module_environment Module, + tunables: Tunables, ) -> Self { Self { isa, @@ -111,6 +129,7 @@ impl<'module_environment> FuncEnvironment<'module_environment> { globals_base: None, current_memory_extfunc: None, grow_memory_extfunc: None, + tunables, } } @@ -228,7 +247,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( @@ -330,16 +350,12 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m 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( @@ -349,13 +365,26 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m 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), @@ -365,7 +394,7 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m func.create_heap(ir::HeapData { base: heap_base, min_size: 0.into(), - guard_size, + offset_guard_size, style: heap_style, index_type: I32, }) @@ -403,9 +432,9 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m 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, }) } @@ -548,12 +577,14 @@ 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. pub fn func_env(&self) -> FuncEnvironment { - FuncEnvironment::new(self.isa, &self.module) + FuncEnvironment::new(self.isa, &self.module, self.tunables.clone()) } } diff --git a/lib/environ/src/lib.rs b/lib/environ/src/lib.rs index edbc4cb3e6c6..fb3b98c159ce 100644 --- a/lib/environ/src/lib.rs +++ b/lib/environ/src/lib.rs @@ -42,11 +42,19 @@ extern crate alloc; mod compilation; mod environ; mod module; +mod tunables; mod vmcontext; pub use compilation::{compile_module, Compilation, 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; + +/// 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..a12e528aca89 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,59 @@ 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 { + 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), + } + } 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 + } + } +} + +/// 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 `Memory`. + pub fn for_memory(memory: Memory, tunables: &Tunables) -> Self { + Self { + memory, + style: MemoryStyle::for_memory(memory, tunables), + // fixme: saturate this + offset_guard_size: tunables.offset_guard_size, + } + } +} + /// A translated WebAssembly module, excluding the function bodies and /// memory initializers. #[derive(Debug)] @@ -52,8 +107,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, @@ -76,7 +131,7 @@ impl Module { imported_funcs: Vec::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/execute/Cargo.toml b/lib/execute/Cargo.toml index 9c587b1a64e2..306b8ad7f13a 100644 --- a/lib/execute/Cargo.toml +++ b/lib/execute/Cargo.toml @@ -10,14 +10,14 @@ 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" } wasmtime-environ = { path = "../environ" } region = "1.0.0" -memmap = "0.7.0" lazy_static = "1.2.0" libc = "0.2.44" +errno = "0.2.4" [build-dependencies] cmake = "0.1.35" diff --git a/lib/execute/src/instance.rs b/lib/execute/src/instance.rs index 36ade3476755..ddb95611fe6f 100644 --- a/lib/execute/src/instance.rs +++ b/lib/execute/src/instance.rs @@ -28,16 +28,16 @@ impl Instance { module: &Module, compilation: &Compilation, data_initializers: &[DataInitializer], - ) -> Self { + ) -> Result { 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_memories(module, data_initializers)?; result.instantiate_globals(module); - result + Ok(result) } /// Allocate memory in `self` for just the tables of the current module. @@ -48,10 +48,9 @@ impl Instance { table_initializers: &[TableElements], ) { debug_assert!(self.tables.is_empty()); - // TODO: Enable this once PrimaryMap supports this. - //self.tables.reserve_exact(module.tables.len()); + self.tables.reserve_exact(module.tables.len()); for table in module.tables.values() { - let len = table.size; + let len = table.minimum as usize; let mut v = Vec::with_capacity(len); v.resize(len, 0); self.tables.push(v); @@ -70,13 +69,16 @@ impl Instance { } /// Allocate memory in `instance` for just the memories of the current module. - fn instantiate_memories(&mut self, module: &Module, data_initializers: &[DataInitializer]) { + fn instantiate_memories( + &mut self, + module: &Module, + data_initializers: &[DataInitializer], + ) -> Result<(), String> { 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.reserve_exact(module.memory_plans.len()); + for plan in module.memory_plans.values() { + let v = LinearMemory::new(&plan)?; self.memories.push(v); } for init in data_initializers { @@ -85,6 +87,7 @@ impl Instance { let to_init = &mut mem_mut[init.offset..init.offset + init.data.len()]; to_init.copy_from_slice(init.data); } + Ok(()) } /// Allocate memory in `instance` for just the globals of the current module, diff --git a/lib/execute/src/lib.rs b/lib/execute/src/lib.rs index b03046ef7510..ae73241637a0 100644 --- a/lib/execute/src/lib.rs +++ b/lib/execute/src/lib.rs @@ -30,7 +30,7 @@ extern crate cranelift_codegen; extern crate cranelift_entity; extern crate cranelift_wasm; -extern crate memmap; +extern crate errno; extern crate region; extern crate wasmtime_environ; #[cfg(not(feature = "std"))] diff --git a/lib/execute/src/memory.rs b/lib/execute/src/memory.rs index 4830b938615a..916d7af9a9b7 100644 --- a/lib/execute/src/memory.rs +++ b/lib/execute/src/memory.rs @@ -1,92 +1,225 @@ -use memmap; +use errno; +use libc; +use region; use std::fmt; +use std::mem; +use std::ptr; +use std::slice; +use wasmtime_environ::{MemoryPlan, MemoryStyle, WASM_MAX_PAGES, WASM_PAGE_SIZE}; -const PAGE_SIZE: u32 = 65536; -const MAX_PAGES: u32 = 65536; +/// 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. +struct PtrLen { + ptr: *mut u8, + len: usize, +} + +impl PtrLen { + /// Create a new `PtrLen` pointing to at least `size` bytes of memory, + /// suitably sized and aligned for memory protection. + #[cfg(not(target_os = "windows"))] + 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_ANONYMOUS, + -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")] + 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()) + } + } + + fn as_slice(&self) -> &[u8] { + unsafe { slice::from_raw_parts(self.ptr, self.len) } + } + + fn as_mut_slice(&mut self) -> &mut [u8] { + unsafe { slice::from_raw_parts_mut(self.ptr, self.len) } + } +} + +impl Drop for PtrLen { + #[cfg(not(target_os = "windows"))] + fn drop(&mut self) { + let r = unsafe { libc::munmap(self.ptr as *mut libc::c_void, self.len) }; + assert_eq!(r, 0); + } + + #[cfg(target_os = "windows")] + fn drop(&mut self) { + 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); + } +} /// A linear memory instance. /// /// This linear memory has a stable base address and at the same time allows /// for dynamical growing. pub struct LinearMemory { - mmap: memmap::MmapMut, + ptrlen: PtrLen, 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 ptrlen = PtrLen::with_size(request_bytes)?; + + // Make the unmapped and offset-guard pages inaccessible. + unsafe { + region::protect( + ptrlen.ptr.add(mapped_bytes), + inaccessible_bytes, + region::Protection::Read, + ).expect("unable to make memory readonly"); } + + Ok(Self { + ptrlen, + current: plan.memory.minimum, + maximum: plan.memory.maximum, + offset_guard_size: offset_guard_bytes, + }) } /// Returns an base address of this linear memory. pub fn base_addr(&mut self) -> *mut u8 { - self.mmap.as_mut_ptr() + self.ptrlen.ptr } /// Returns a number of allocated wasm pages. pub fn current_size(&self) -> u32 { - self.current + assert_eq!(self.ptrlen.len % WASM_PAGE_SIZE as usize, 0); + let num_pages = self.ptrlen.len / WASM_PAGE_SIZE as usize; + assert_eq!(num_pages as u32 as usize, num_pages); + num_pages as u32 } /// Grow memory by the specified amount of pages. /// /// 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.ptrlen.len { + // 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); - self.mmap = new_mmap; - } + let mapped_pages = self.current as usize; + let mapped_bytes = mapped_pages * WASM_PAGE_SIZE as usize; + let guard_bytes = self.offset_guard_size; - self.current = new_pages; + let mut new_ptrlen = PtrLen::with_size(new_bytes).ok()?; + + // Make the offset-guard pages inaccessible. + unsafe { + region::protect( + new_ptrlen.ptr.add(mapped_bytes), + guard_bytes, + region::Protection::Read, + ).expect("unable to make memory readonly"); + } + + new_ptrlen + .as_mut_slice() + .copy_from_slice(self.ptrlen.as_slice()); - // 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); + self.ptrlen = new_ptrlen; } + self.current = new_pages; + Some(prev_pages) } } @@ -102,12 +235,25 @@ impl fmt::Debug for LinearMemory { impl AsRef<[u8]> for LinearMemory { fn as_ref(&self) -> &[u8] { - &self.mmap + self.ptrlen.as_slice() } } impl AsMut<[u8]> for LinearMemory { fn as_mut(&mut self) -> &mut [u8] { - &mut self.mmap + self.ptrlen.as_mut_slice() + } +} + +#[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/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/src/main.rs b/src/main.rs index 63bb9bdaa9be..4936e9bf7fa6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -59,8 +59,8 @@ use std::io::prelude::*; use std::io::stdout; use std::path::Path; use std::path::PathBuf; -use std::process::exit; -use wasmtime_environ::{Module, ModuleEnvironment}; +use std::process::{exit, Command}; +use wasmtime_environ::{Module, ModuleEnvironment, Tunables}; use wasmtime_execute::{compile_and_link_module, execute, finish_instantiation, Instance}; static LOG_FILENAME_PREFIX: &str = "cranelift.dbg."; @@ -149,7 +149,9 @@ 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; @@ -161,7 +163,7 @@ fn handle_module(args: &Args, path: PathBuf, isa: &TargetIsa) -> Result<(), Stri translation.module, &compilation, &translation.lazy.data_initializers, - ); + )?; let mut context = finish_instantiation(&translation.module, &compilation, &mut instance)?; @@ -219,7 +221,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 +244,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/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 From feb75a96f85cbbe628b89f98221c3cb88c188ab2 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Thu, 29 Nov 2018 13:44:30 -0800 Subject: [PATCH 02/20] Add tunables.rs. --- lib/environ/src/tunables.rs | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 lib/environ/src/tunables.rs diff --git a/lib/environ/src/tunables.rs b/lib/environ/src/tunables.rs new file mode 100644 index 000000000000..7fd9b291bcc7 --- /dev/null +++ b/lib/environ/src/tunables.rs @@ -0,0 +1,27 @@ +/// 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. + pub 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. + /// + /// Allocating 2 GiB of address space lets us translate wasm + /// offsets into x86 offsets as aggressively as we can. + offset_guard_size: 0x8000_0000, + } + } +} From 05cc574bc1a4a2b6b8119f083d6f7151db017b04 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Thu, 29 Nov 2018 14:20:34 -0800 Subject: [PATCH 03/20] Use MAP_ANON instead of MAP_ANONYMOUS. --- lib/execute/src/memory.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/execute/src/memory.rs b/lib/execute/src/memory.rs index 916d7af9a9b7..ff515a28e6ae 100644 --- a/lib/execute/src/memory.rs +++ b/lib/execute/src/memory.rs @@ -31,7 +31,7 @@ impl PtrLen { ptr::null_mut(), alloc_size, libc::PROT_READ | libc::PROT_WRITE, - libc::MAP_PRIVATE | libc::MAP_ANONYMOUS, + libc::MAP_PRIVATE | libc::MAP_ANON, -1, 0, ); From 6720f43211f152aebc69581a18311077710cba9f Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Thu, 29 Nov 2018 14:35:59 -0800 Subject: [PATCH 04/20] Switch Darwin to use PC-redirection for calling the Unwind function. --- lib/execute/signalhandlers/SignalHandlers.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/execute/signalhandlers/SignalHandlers.cpp b/lib/execute/signalhandlers/SignalHandlers.cpp index f47d7ec7f905..84327e9ea06a 100644 --- a/lib/execute/signalhandlers/SignalHandlers.cpp +++ b/lib/execute/signalhandlers/SignalHandlers.cpp @@ -400,12 +400,17 @@ HandleTrap(CONTEXT* context) RecordTrap(pc, codeSegment); +#if defined(__APPLE__) + // 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; } From a68fa84903a3b0ed5c6afcd510ffe3339bc7ac6a Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Thu, 29 Nov 2018 13:40:39 -0800 Subject: [PATCH 05/20] Start a wast testing harness and add some tests. This implements a minimal wast testing harness in tests/wast.rs, which runs the wast tests under tests/wast. It also adds tests for trapping in a variety of ways, and fixes several bugs exposed by those tests. --- lib/environ/src/compilation.rs | 3 +- lib/environ/src/lib.rs | 4 +- lib/environ/src/module.rs | 3 +- lib/execute/Cargo.toml | 3 +- lib/execute/signalhandlers/SignalHandlers.cpp | 5 + lib/execute/src/code.rs | 72 +++++ lib/execute/src/execute.rs | 82 ++--- lib/execute/src/instance.rs | 5 + lib/execute/src/invoke.rs | 271 ++++++++++++++++ lib/execute/src/lib.rs | 8 +- lib/execute/src/memory.rs | 146 ++------- lib/execute/src/mmap.rs | 136 ++++++++ lib/execute/src/traphandlers.rs | 1 + src/main.rs | 25 +- tests/wast.rs | 297 ++++++++++++++++++ tests/wast/misc_traps.wast | 67 ++++ tests/wast/stack_overflow.wast | 26 ++ 17 files changed, 958 insertions(+), 196 deletions(-) create mode 100644 lib/execute/src/code.rs create mode 100644 lib/execute/src/invoke.rs create mode 100644 lib/execute/src/mmap.rs create mode 100644 tests/wast.rs create mode 100644 tests/wast/misc_traps.wast create mode 100644 tests/wast/stack_overflow.wast diff --git a/lib/environ/src/compilation.rs b/lib/environ/src/compilation.rs index d6d82d664447..87b63d0a9cbc 100644 --- a/lib/environ/src/compilation.rs +++ b/lib/environ/src/compilation.rs @@ -77,7 +77,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(), } diff --git a/lib/environ/src/lib.rs b/lib/environ/src/lib.rs index fb3b98c159ce..6e550c3c8f58 100644 --- a/lib/environ/src/lib.rs +++ b/lib/environ/src/lib.rs @@ -45,7 +45,9 @@ mod module; mod tunables; mod vmcontext; -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, MemoryPlan, MemoryStyle, Module, TableElements}; pub use tunables::Tunables; diff --git a/lib/environ/src/module.rs b/lib/environ/src/module.rs index a12e528aca89..1e9903804b34 100644 --- a/lib/environ/src/module.rs +++ b/lib/environ/src/module.rs @@ -80,12 +80,11 @@ pub struct MemoryPlan { } impl MemoryPlan { - /// Draw up a plan for implementing `Memory`. + /// Draw up a plan for implementing a `Memory`. pub fn for_memory(memory: Memory, tunables: &Tunables) -> Self { Self { memory, style: MemoryStyle::for_memory(memory, tunables), - // fixme: saturate this offset_guard_size: tunables.offset_guard_size, } } diff --git a/lib/execute/Cargo.toml b/lib/execute/Cargo.toml index 306b8ad7f13a..aad42d259035 100644 --- a/lib/execute/Cargo.toml +++ b/lib/execute/Cargo.toml @@ -13,10 +13,11 @@ readme = "README.md" 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" lazy_static = "1.2.0" -libc = "0.2.44" +libc = { version = "0.2.44", default-features = false } errno = "0.2.4" [build-dependencies] diff --git a/lib/execute/signalhandlers/SignalHandlers.cpp b/lib/execute/signalhandlers/SignalHandlers.cpp index 84327e9ea06a..060645685bd5 100644 --- a/lib/execute/signalhandlers/SignalHandlers.cpp +++ b/lib/execute/signalhandlers/SignalHandlers.cpp @@ -400,6 +400,11 @@ 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(__APPLE__) // 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 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..11cc2284fd11 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, TableIndex}; use instance::Instance; +use invoke::{invoke_by_index, InvokeOutcome}; use memory::LinearMemory; 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::string::String; use std::vec::Vec; -use traphandlers::call_wasm; 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 @@ -112,7 +114,7 @@ extern "C" fn current_memory(memory_index: u32, vmctx: *mut *mut u8) -> u32 { /// Create the VmCtx data structure for the JIT'd code to use. This must /// match the VmCtx layout in the environment. -fn make_vmctx(instance: &mut Instance, mem_base_addrs: &mut [*mut u8]) -> Vec<*mut u8> { +fn make_vmctx(instance: &mut Instance) -> Vec<*mut u8> { debug_assert!( instance.tables.len() <= 1, "non-default tables is not supported" @@ -128,7 +130,7 @@ fn make_vmctx(instance: &mut Instance, mem_base_addrs: &mut [*mut u8]) -> Vec<*m 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); + vmctx.push(instance.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); @@ -139,6 +141,8 @@ fn make_vmctx(instance: &mut Instance, mem_base_addrs: &mut [*mut u8]) -> Vec<*m /// prepares the execution context pub fn finish_instantiation( + code: &mut Code, + isa: &TargetIsa, module: &Module, compilation: &Compilation, instance: &mut Instance, @@ -164,67 +168,25 @@ pub fn finish_instantiation( } // Collect all memory base addresses and Vec. - let mut mem_base_addrs = instance + instance.mem_base_addrs = instance .memories .values_mut() .map(LinearMemory::base_addr) .collect::>(); - let mut vmctx = make_vmctx(instance, &mut mem_base_addrs); + let mut vmctx = make_vmctx(instance); if let Some(start_index) = module.start_func { - execute_by_index(module, compilation, &mut vmctx, start_index)?; + let result = invoke_by_index(code, isa, module, compilation, &mut vmctx, start_index, &[])?; + match result { + InvokeOutcome::Returned { values } => { + assert!(values.is_empty()); + } + InvokeOutcome::Trapped { message } => { + return Err(format!("start function trapped: {}", message)); + } + } } 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 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 ddb95611fe6f..8cab4cd2fee7 100644 --- a/lib/execute/src/instance.rs +++ b/lib/execute/src/instance.rs @@ -6,6 +6,7 @@ use cranelift_entity::EntityRef; use cranelift_entity::PrimaryMap; use cranelift_wasm::{GlobalIndex, MemoryIndex, TableIndex}; use memory::LinearMemory; +use std::string::String; use std::vec::Vec; use wasmtime_environ::{Compilation, DataInitializer, Module, TableElements}; @@ -20,6 +21,9 @@ pub struct Instance { /// WebAssembly global variable data. pub globals: Vec, + + /// Memory base address vector pointed to by vmctx. + pub mem_base_addrs: Vec<*mut u8>, } impl Instance { @@ -33,6 +37,7 @@ impl Instance { tables: PrimaryMap::new(), memories: PrimaryMap::new(), globals: Vec::new(), + mem_base_addrs: Vec::new(), }; result.instantiate_tables(module, compilation, &module.table_elements); result.instantiate_memories(module, data_initializers)?; diff --git a/lib/execute/src/invoke.rs b/lib/execute/src/invoke.rs new file mode 100644 index 000000000000..1519e90f04b1 --- /dev/null +++ b/lib/execute/src/invoke.rs @@ -0,0 +1,271 @@ +//! 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 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 Vec<*mut u8>, + 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 Vec<*mut u8>, + 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.as_ptr() as usize, + args, + &sig, + ) +} + +fn call_through_wrapper( + code: &mut Code, + isa: &isa::TargetIsa, + callee: usize, + vmctx: usize, + 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 ae73241637a0..0abbc0c06131 100644 --- a/lib/execute/src/lib.rs +++ b/lib/execute/src/lib.rs @@ -29,6 +29,7 @@ extern crate cranelift_codegen; extern crate cranelift_entity; +extern crate cranelift_frontend; extern crate cranelift_wasm; extern crate errno; extern crate region; @@ -40,14 +41,19 @@ extern crate alloc; extern crate lazy_static; extern crate libc; +mod code; mod execute; mod instance; +mod invoke; mod memory; +mod mmap; mod signalhandlers; mod traphandlers; -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}; #[cfg(not(feature = "std"))] diff --git a/lib/execute/src/memory.rs b/lib/execute/src/memory.rs index ff515a28e6ae..51758183daa1 100644 --- a/lib/execute/src/memory.rs +++ b/lib/execute/src/memory.rs @@ -1,108 +1,17 @@ -use errno; -use libc; +//! Memory management for linear memory. + +use mmap::Mmap; use region; use std::fmt; -use std::mem; -use std::ptr; -use std::slice; +use std::string::String; use wasmtime_environ::{MemoryPlan, MemoryStyle, WASM_MAX_PAGES, WASM_PAGE_SIZE}; -/// 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. -struct PtrLen { - ptr: *mut u8, - len: usize, -} - -impl PtrLen { - /// Create a new `PtrLen` pointing to at least `size` bytes of memory, - /// suitably sized and aligned for memory protection. - #[cfg(not(target_os = "windows"))] - 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")] - 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()) - } - } - - fn as_slice(&self) -> &[u8] { - unsafe { slice::from_raw_parts(self.ptr, self.len) } - } - - fn as_mut_slice(&mut self) -> &mut [u8] { - unsafe { slice::from_raw_parts_mut(self.ptr, self.len) } - } -} - -impl Drop for PtrLen { - #[cfg(not(target_os = "windows"))] - fn drop(&mut self) { - let r = unsafe { libc::munmap(self.ptr as *mut libc::c_void, self.len) }; - assert_eq!(r, 0); - } - - #[cfg(target_os = "windows")] - fn drop(&mut self) { - 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); - } -} - /// A linear memory instance. /// /// This linear memory has a stable base address and at the same time allows /// for dynamical growing. pub struct LinearMemory { - ptrlen: PtrLen, + mmap: Mmap, current: u32, maximum: Option, offset_guard_size: usize, @@ -132,19 +41,19 @@ impl LinearMemory { let unmapped_bytes = unmapped_pages * WASM_PAGE_SIZE as usize; let inaccessible_bytes = unmapped_bytes + offset_guard_bytes; - let ptrlen = PtrLen::with_size(request_bytes)?; + let mmap = Mmap::with_size(request_bytes)?; // Make the unmapped and offset-guard pages inaccessible. unsafe { region::protect( - ptrlen.ptr.add(mapped_bytes), + mmap.as_ptr().add(mapped_bytes), inaccessible_bytes, - region::Protection::Read, - ).expect("unable to make memory readonly"); + region::Protection::None, + ).expect("unable to make memory inaccessible"); } Ok(Self { - ptrlen, + mmap, current: plan.memory.minimum, maximum: plan.memory.maximum, offset_guard_size: offset_guard_bytes, @@ -153,13 +62,13 @@ impl LinearMemory { /// Returns an base address of this linear memory. pub fn base_addr(&mut self) -> *mut u8 { - self.ptrlen.ptr + self.mmap.as_mut_ptr() } /// Returns a number of allocated wasm pages. pub fn current_size(&self) -> u32 { - assert_eq!(self.ptrlen.len % WASM_PAGE_SIZE as usize, 0); - let num_pages = self.ptrlen.len / WASM_PAGE_SIZE as usize; + assert_eq!(self.mmap.len() % WASM_PAGE_SIZE as usize, 0); + let num_pages = self.mmap.len() / WASM_PAGE_SIZE as usize; assert_eq!(num_pages as u32 as usize, num_pages); num_pages as u32 } @@ -193,29 +102,29 @@ impl LinearMemory { let new_bytes = new_pages as usize * WASM_PAGE_SIZE as usize; - if new_bytes > self.ptrlen.len { + if new_bytes > self.mmap.len() { // If we have no maximum, this is a "dynamic" heap, and it's allowed to move. assert!(self.maximum.is_none()); let mapped_pages = self.current as usize; let mapped_bytes = mapped_pages * WASM_PAGE_SIZE as usize; let guard_bytes = self.offset_guard_size; - let mut new_ptrlen = PtrLen::with_size(new_bytes).ok()?; + let mut new_mmap = Mmap::with_size(new_bytes).ok()?; // Make the offset-guard pages inaccessible. unsafe { region::protect( - new_ptrlen.ptr.add(mapped_bytes), + new_mmap.as_ptr().add(mapped_bytes), guard_bytes, region::Protection::Read, ).expect("unable to make memory readonly"); } - new_ptrlen + new_mmap .as_mut_slice() - .copy_from_slice(self.ptrlen.as_slice()); + .copy_from_slice(self.mmap.as_slice()); - self.ptrlen = new_ptrlen; + self.mmap = new_mmap; } self.current = new_pages; @@ -235,25 +144,12 @@ impl fmt::Debug for LinearMemory { impl AsRef<[u8]> for LinearMemory { fn as_ref(&self) -> &[u8] { - self.ptrlen.as_slice() + self.mmap.as_slice() } } impl AsMut<[u8]> for LinearMemory { fn as_mut(&mut self) -> &mut [u8] { - self.ptrlen.as_mut_slice() - } -} - -#[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); + 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..996945246915 --- /dev/null +++ b/lib/execute/src/mmap.rs @@ -0,0 +1,136 @@ +//! 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. +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/traphandlers.rs b/lib/execute/src/traphandlers.rs index bc84c2309eaa..28f3861e17b3 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`). diff --git a/src/main.rs b/src/main.rs index 4936e9bf7fa6..e7162a1cbb92 100644 --- a/src/main.rs +++ b/src/main.rs @@ -59,9 +59,9 @@ use std::io::prelude::*; use std::io::stdout; use std::path::Path; use std::path::PathBuf; -use std::process::{exit, Command}; +use std::process::exit; use wasmtime_environ::{Module, ModuleEnvironment, Tunables}; -use wasmtime_execute::{compile_and_link_module, execute, finish_instantiation, Instance}; +use wasmtime_execute::{compile_and_link_module, finish_instantiation, invoke, Code, Instance}; static LOG_FILENAME_PREFIX: &str = "cranelift.dbg."; @@ -157,6 +157,8 @@ fn handle_module(args: &Args, path: PathBuf, isa: &TargetIsa) -> Result<(), Stri 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( @@ -165,11 +167,24 @@ fn handle_module(args: &Args, path: PathBuf, isa: &TargetIsa) -> Result<(), Stri &translation.lazy.data_initializers, )?; - let mut context = - finish_instantiation(&translation.module, &compilation, &mut instance)?; + let mut context = 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, + &mut context, + &f, + &[], + )?; } instance diff --git a/tests/wast.rs b/tests/wast.rs new file mode 100644 index 000000000000..e1d6d89240f6 --- /dev/null +++ b/tests/wast.rs @@ -0,0 +1,297 @@ +extern crate cranelift_codegen; +extern crate wabt; +extern crate wasmtime_environ; +extern crate wasmtime_execute; + +use cranelift_codegen::settings::Configurable; +use cranelift_codegen::{isa, settings}; +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, ScriptParser}; +use wasmtime_environ::{Compilation, Module, ModuleEnvironment, Tunables}; +use wasmtime_execute::{ + compile_and_link_module, finish_instantiation, invoke, Code, Instance, InvokeOutcome, Value, +}; + +struct InstanceWorld { + module: Module, + context: Vec<*mut u8>, + // FIXME + #[allow(dead_code)] + instance: Instance, + compilation: Compilation, +} + +impl InstanceWorld { + fn new(code: &mut Code, isa: &isa::TargetIsa, data: &[u8]) -> Result { + let mut module = Module::new(); + let tunables = Tunables::default(); + let (context, 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, + context, + instance, + compilation, + }) + } + + fn invoke( + &mut self, + code: &mut Code, + isa: &isa::TargetIsa, + f: &str, + args: &[Value], + ) -> Result { + invoke( + code, + isa, + &self.module, + &self.compilation, + &mut self.context, + &f, + args, + ).map_err(|e| e.to_string()) + } +} + +fn translate(code: &mut Code, isa: &isa::TargetIsa, data: &[u8]) -> Result { + InstanceWorld::new(code, isa, data) +} + +struct Instances { + current: Option, + namespace: HashMap, +} + +impl Instances { + fn new() -> Self { + Self { + current: None, + namespace: HashMap::new(), + } + } + + fn unnamed(&mut self, instance: InstanceWorld) { + self.current = Some(instance); + } + + fn named(&mut self, name: String, instance: InstanceWorld) { + self.namespace.insert(name, instance); + } + + fn perform_action( + &mut self, + code: &mut Code, + isa: &isa::TargetIsa, + action: Action, + ) -> InvokeOutcome { + match action { + Action::Invoke { + module, + field, + args, + } => { + let mut value_args = Vec::new(); + 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(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(code, isa, &field, &value_args) + .expect(&format!("error invoking {} in module {}", field, name)), + } + } + _ => panic!("unsupported action {:?}", action), + } + } +} + +#[test] +fn spec_core() { + 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"); + }); + let isa = isa_builder.finish(settings::Flags::new(flag_builder)); + + let mut paths: Vec<_> = fs::read_dir("tests/wast") + .unwrap() + .map(|r| r.unwrap()) + .filter(|p| { + // Ignore files starting with `.`, which could be editor temporary files + if let Some(stem) = p.path().file_stem() { + if let Some(stemstr) = stem.to_str() { + return !stemstr.starts_with('.'); + } + } + false + }).collect(); + paths.sort_by_key(|dir| dir.path()); + for path in paths { + let path = path.path(); + let source = read_to_end(&path).unwrap(); + test_wast(&path, &*isa, &source); + } +} + +#[cfg(test)] +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) +} + +#[cfg(test)] +fn test_wast(path: &Path, isa: &isa::TargetIsa, wast: &[u8]) { + println!("Testing {}", path.display()); + + let mut parser = ScriptParser::from_str(str::from_utf8(wast).unwrap()).unwrap(); + let mut instances = Instances::new(); + let mut code = Code::new(); + + while let Some(Command { kind, line }) = parser.next().unwrap() { + match kind { + CommandKind::Module { module, name } => { + if let Some(name) = name { + instances.named( + name, + translate(&mut code, &*isa, &module.clone().into_vec()).unwrap(), + ); + } + + instances.unnamed(translate(&mut code, &*isa, &module.clone().into_vec()).unwrap()); + } + CommandKind::PerformAction(action) => { + match instances.perform_action(&mut code, &*isa, action) { + InvokeOutcome::Returned { .. } => {} + InvokeOutcome::Trapped { message } => { + panic!("{}:{}: a trap occurred: {}", path.display(), line, message); + } + } + } + CommandKind::AssertReturn { action, expected } => { + match instances.perform_action(&mut code, &*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 {}:{}", path.display(), line) + } + script::Value::I64(x) => { + assert_eq!(x, v.unwrap_i64(), "at {}:{}", path.display(), line) + } + script::Value::F32(x) => assert_eq!( + x.to_bits(), + v.unwrap_f32(), + "at {}:{}", + path.display(), + line + ), + script::Value::F64(x) => assert_eq!( + x.to_bits(), + v.unwrap_f64(), + "at {}:{}", + path.display(), + line + ), + }; + } + } + InvokeOutcome::Trapped { message } => { + panic!( + "{}:{}: expected normal return, but a trap occurred: {}", + path.display(), + line, + message + ); + } + } + } + CommandKind::AssertTrap { action, message } => { + match instances.perform_action(&mut code, &*isa, action) { + InvokeOutcome::Returned { values } => panic!( + "{}:{}: expected trap, but invoke returned with {:?}", + path.display(), + line, + values + ), + InvokeOutcome::Trapped { + message: trap_message, + } => { + println!( + "{}:{}: TODO: Check the trap message: expected {}, got {}", + path.display(), + line, + message, + trap_message + ); + } + } + } + CommandKind::AssertExhaustion { action } => { + match instances.perform_action(&mut code, &*isa, action) { + InvokeOutcome::Returned { values } => panic!( + "{}:{}: expected exhaustion, but invoke returned with {:?}", + path.display(), + line, + values + ), + InvokeOutcome::Trapped { message } => { + println!( + "{}:{}: TODO: Check the exhaustion message: {}", + path.display(), + line, + message + ); + } + } + } + command => { + println!("{}:{}: TODO: implement {:?}", path.display(), line, command); + } + } + } +} diff --git a/tests/wast/misc_traps.wast b/tests/wast/misc_traps.wast new file mode 100644 index 000000000000..96acf58bfe5e --- /dev/null +++ b/tests/wast/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/tests/wast/stack_overflow.wast b/tests/wast/stack_overflow.wast new file mode 100644 index 000000000000..baf4c98a7a43 --- /dev/null +++ b/tests/wast/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") From 6de1a470561c88d02a1a07af184f300271de4009 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Fri, 30 Nov 2018 15:35:34 -0800 Subject: [PATCH 06/20] Tidy. --- lib/environ/src/environ.rs | 38 ++++++++++++++------------------------ 1 file changed, 14 insertions(+), 24 deletions(-) diff --git a/lib/environ/src/environ.rs b/lib/environ/src/environ.rs index 7335eedd2486..6cd3c151fee7 100644 --- a/lib/environ/src/environ.rs +++ b/lib/environ/src/environ.rs @@ -32,16 +32,16 @@ pub fn get_func_name(func_index: FuncIndex) -> ir::ExternalName { /// 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. - pub tunables: Tunables, + tunables: Tunables, } impl<'data, 'module> ModuleEnvironment<'data, 'module> { @@ -59,13 +59,8 @@ impl<'data, 'module> ModuleEnvironment<'data, 'module> { } } - fn func_env(&self) -> FuncEnvironment { - FuncEnvironment::new(self.isa, &self.module, self.tunables.clone()) - } - 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 @@ -90,35 +85,31 @@ 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 `current_memory`. - pub current_memory_extfunc: Option, + current_memory_extfunc: Option, /// The external function declaration for implementing wasm's `grow_memory`. - pub grow_memory_extfunc: Option, - - /// Tunable parameters. - pub tunables: Tunables, + grow_memory_extfunc: Option, } impl<'module_environment> FuncEnvironment<'module_environment> { pub fn new( isa: &'module_environment isa::TargetIsa, module: &'module_environment Module, - tunables: Tunables, ) -> Self { Self { isa, @@ -129,7 +120,6 @@ impl<'module_environment> FuncEnvironment<'module_environment> { globals_base: None, current_memory_extfunc: None, grow_memory_extfunc: None, - tunables, } } @@ -583,8 +573,8 @@ pub struct ModuleTranslation<'data, 'module> { } 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, self.tunables.clone()) + FuncEnvironment::new(self.isa, &self.module) } } From ed73a72887a9a4794f01d350c3f63cdfd3f8ce16 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Wed, 28 Nov 2018 18:38:38 -0800 Subject: [PATCH 07/20] Rename main.rs to wasmtime.rs. --- Cargo.toml | 2 +- src/{main.rs => wasmtime.rs} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename src/{main.rs => wasmtime.rs} (100%) diff --git a/Cargo.toml b/Cargo.toml index 785e80997431..188ea582f202 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ publish = false [[bin]] name = "wasmtime" -path = "src/main.rs" +path = "src/wasmtime.rs" [[bin]] name = "wasm2obj" diff --git a/src/main.rs b/src/wasmtime.rs similarity index 100% rename from src/main.rs rename to src/wasmtime.rs From a98978566999b6fe2b380b043376dabe7393f8a4 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Fri, 30 Nov 2018 16:50:05 -0800 Subject: [PATCH 08/20] Remove use of offset_of! from wasmtime-environ. wasmtime-environ is meant to support cross compilation, so it shouldn't have dependencies on target layout of structs. This moves the layout back into wasmtime-execute, and adds a system of asserts for checking that wasmtime-environ's offsets stay in sync. --- lib/environ/Cargo.toml | 2 +- lib/environ/src/environ.rs | 41 +++----- lib/environ/src/lib.rs | 4 +- lib/environ/src/vmcontext.rs | 33 ------- lib/environ/src/vmoffsets.rs | 145 ++++++++++++++++++++++++++++ lib/execute/Cargo.toml | 4 + lib/execute/src/execute.rs | 2 + lib/execute/src/lib.rs | 4 + lib/execute/src/memory.rs | 4 +- lib/execute/src/vmcontext.rs | 182 +++++++++++++++++++++++++++++++++++ 10 files changed, 355 insertions(+), 66 deletions(-) delete mode 100644 lib/environ/src/vmcontext.rs create mode 100644 lib/environ/src/vmoffsets.rs create mode 100644 lib/execute/src/vmcontext.rs diff --git a/lib/environ/Cargo.toml b/lib/environ/Cargo.toml index 33acdbf194d2..0ab1a41fb813 100644 --- a/lib/environ/Cargo.toml +++ b/lib/environ/Cargo.toml @@ -13,7 +13,7 @@ readme = "README.md" 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" } -memoffset = "0.2.1" +cast = { version = "0.2.2", default-features = false } [features] default = ["std"] diff --git a/lib/environ/src/environ.rs b/lib/environ/src/environ.rs index 6cd3c151fee7..9b8071bb62de 100644 --- a/lib/environ/src/environ.rs +++ b/lib/environ/src/environ.rs @@ -15,17 +15,15 @@ use module::{ DataInitializer, Export, LazyContents, MemoryPlan, MemoryStyle, Module, TableElements, }; use std::clone::Clone; -use std::mem; use std::string::String; use std::vec::Vec; use tunables::Tunables; -use vmcontext; +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()) } /// Object containing the standalone environment information. To be passed after creation as @@ -104,6 +102,9 @@ pub struct FuncEnvironment<'module_environment> { /// The external function declaration for implementing wasm's `grow_memory`. grow_memory_extfunc: Option, + + /// Offsets to struct fields accessed by JIT code. + offsets: VMOffsets, } impl<'module_environment> FuncEnvironment<'module_environment> { @@ -120,6 +121,7 @@ impl<'module_environment> FuncEnvironment<'module_environment> { globals_base: None, current_memory_extfunc: None, grow_memory_extfunc: None, + offsets: VMOffsets::new(isa.frontend_config().pointer_bytes()), } } @@ -149,10 +151,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() } @@ -302,19 +300,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 { @@ -328,16 +323,13 @@ 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 (offset_guard_size, heap_style, readonly_base) = match self.module.memory_plans[index] { @@ -349,7 +341,7 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m 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, @@ -377,7 +369,7 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m 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, }); @@ -395,27 +387,22 @@ 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, }); diff --git a/lib/environ/src/lib.rs b/lib/environ/src/lib.rs index 6e550c3c8f58..7bd0a8cd3bca 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; @@ -43,7 +41,7 @@ mod compilation; mod environ; mod module; mod tunables; -mod vmcontext; +mod vmoffsets; pub use compilation::{ compile_module, Compilation, RelocSink, Relocation, RelocationTarget, Relocations, 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..7dfa25125191 --- /dev/null +++ b/lib/environ/src/vmoffsets.rs @@ -0,0 +1,145 @@ +/// 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 + } + + /// The offset of the `instance` field. + #[allow(dead_code)] + pub fn vmctx_instance(&self) -> u8 { + 3 * self.pointer_size + } + + /// Return the size of `VMContext`. + #[allow(dead_code)] + pub fn size_of_vmctx(&self) -> u8 { + 4 * 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 aad42d259035..25e3d687c880 100644 --- a/lib/execute/Cargo.toml +++ b/lib/execute/Cargo.toml @@ -19,12 +19,16 @@ region = "1.0.0" lazy_static = "1.2.0" libc = { version = "0.2.44", default-features = false } errno = "0.2.4" +cast = { version = "0.2.2", default-features = false } [build-dependencies] cmake = "0.1.35" bindgen = "0.44.0" regex = "1.0.6" +[dev-dependencies] +memoffset = "0.2.1" + [features] default = ["std"] std = ["cranelift-codegen/std", "cranelift-wasm/std"] diff --git a/lib/execute/src/execute.rs b/lib/execute/src/execute.rs index 11cc2284fd11..91f5ab28f50a 100644 --- a/lib/execute/src/execute.rs +++ b/lib/execute/src/execute.rs @@ -68,6 +68,7 @@ fn relocate( 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; @@ -76,6 +77,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; diff --git a/lib/execute/src/lib.rs b/lib/execute/src/lib.rs index 0abbc0c06131..55b690c50d96 100644 --- a/lib/execute/src/lib.rs +++ b/lib/execute/src/lib.rs @@ -40,6 +40,10 @@ extern crate alloc; #[macro_use] extern crate lazy_static; extern crate libc; +#[cfg(test)] +#[macro_use] +extern crate memoffset; +extern crate cast; mod code; mod execute; diff --git a/lib/execute/src/memory.rs b/lib/execute/src/memory.rs index 51758183daa1..4ee93b609e37 100644 --- a/lib/execute/src/memory.rs +++ b/lib/execute/src/memory.rs @@ -1,5 +1,6 @@ //! Memory management for linear memory. +use cast; use mmap::Mmap; use region; use std::fmt; @@ -69,8 +70,7 @@ impl LinearMemory { pub fn current_size(&self) -> u32 { assert_eq!(self.mmap.len() % WASM_PAGE_SIZE as usize, 0); let num_pages = self.mmap.len() / WASM_PAGE_SIZE as usize; - assert_eq!(num_pages as u32 as usize, num_pages); - num_pages as u32 + cast::u32(num_pages).unwrap() } /// Grow memory by the specified amount of pages. diff --git a/lib/execute/src/vmcontext.rs b/lib/execute/src/vmcontext.rs new file mode 100644 index 000000000000..c50a5cec2628 --- /dev/null +++ b/lib/execute/src/vmcontext.rs @@ -0,0 +1,182 @@ +//! This file declares `VMContext` and several related structs which contain +//! fields that JIT code accesses directly. + +use std::ptr::{size_of, align_of}; + +/// 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, + // If more elements are added here, remember to add offset_of tests below! +} + +#[cfg(test)] +mod test { + use wasmtime_environ::VMOffsets; + + #[test] + fn check_vmmemory_offsets() { + let offsets = VMOffsets::new(size_of<*mut u8>()); + assert_eq!(size_of(), offsets.size_of_vmmemory()); + assert_eq!(offset_of!(VMMemory, base), offsets.vmmemory_base()); + assert_eq!(offset_of!(VMMemory, current_length), offsets.vmmemory_current_length()); + } +} + +impl VMMemory { + 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 + } +} + +#[repr(C, packed, align(8))] +pub struct VMGlobal { + pub storage: [u8; 8], + // If more elements are added here, remember to add offset_of tests below! +} + +/// The storage for a WebAssembly global. +#[cfg(test)] +mod test { + 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>()); + assert_eq!(size_of(), offsets.size_of_vmglobal()); + } +} + +/// 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 VMTableStorage { + pub base: *mut u8, + pub current_elements: usize, + // If more elements are added here, remember to add offset_of tests below! +} + +#[cfg(test)] +mod test { + use wasmtime_environ::VMOffsets; + + #[test] + fn check_vmtable_offsets() { + let offsets = VMOffsets::new(size_of<*mut u8>()); + assert_eq!(size_of(), offsets.size_of_vmtable()); + assert_eq!(offset_of!(VMTableStorage, base), offsets.vmtable_base()); + assert_eq!(offset_of!(VMTableStorage, current_elements), offsets.vmtable_current_elements()); + } +} + +impl VMTableStorage { + 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 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 `VMMemory` instances, indexed by + /// WebAssembly memory index. + pub memories: *mut VMMemory, + /// A pointer to an array of globals. + pub globals: *mut u8, + /// A pointer to an array of `VMTableStorage` instances, indexed by + /// WebAssembly table index. + pub tables: *mut VMTableStorage, + /// A pointer to extra runtime state that isn't directly accessed + /// from JIT code. + pub instance: *mut u8, + // If more elements are added here, remember to add offset_of tests below! +} + +#[cfg(test)] +mod test { + use wasmtime_environ::VMOffsets; + + #[test] + fn check_vmctx_offsets() { + let offsets = VMOffsets::new(size_of<*mut u8>()); + assert_eq!(size_of(), offsets.size_of_vmctx()); + assert_eq!(offset_of!(VMContext, globals), offsets.vmctx_globals()); + assert_eq!(offset_of!(VMContext, memories), offsets.vmctx_memories()); + assert_eq!(offset_of!(VMContext, tables), offsets.vmctx_tables()); + assert_eq!(offset_of!(VMContext, instance), offsets.vmctx_instance()); + } +} + +impl VMContext { + unsafe pub fn global_storage(&mut self, index: usize) -> *mut u8 { + globals.add(index * global_size) + } + + unsafe pub fn global_i32(&mut self, index: usize) -> &mut i32 { + self.global_storage(index) as &mut i32 + } + + unsafe pub fn global_i64(&mut self, index: usize) -> &mut i64 { + self.global_storage(index) as &mut i64 + } + + unsafe pub fn global_f32(&mut self, index: usize) -> &mut f32 { + self.global_storage(index) as &mut f32 + } + + unsafe pub fn global_f64(&mut self, index: usize) -> &mut f64 { + self.global_storage(index) as &mut f64 + } + + unsafe pub fn memory(&mut self, index: usize) -> &mut VMMemory { + memories.add(index) as &mut VMMemory + } + + unsafe pub fn table(&mut self, index: usize) -> &mut VMTableStorage { + tables.add(index) as &mut VMTableStorage + } +} From 235e3cef250761cd52eb0d4e6cc02a2591a9aa31 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Mon, 3 Dec 2018 03:20:15 -0800 Subject: [PATCH 09/20] Improve infrastructure. Do more encapsulation of raw pointers, use more PrimaryMaps instead of Vecs, add a table.rs for managing table storage. --- lib/environ/src/compilation.rs | 12 +- lib/environ/src/module.rs | 4 +- lib/execute/Cargo.toml | 4 +- lib/execute/src/execute.rs | 80 ++++--------- lib/execute/src/instance.rs | 205 ++++++++++++++++++++------------ lib/execute/src/invoke.rs | 16 +-- lib/execute/src/lib.rs | 4 +- lib/execute/src/memory.rs | 28 ++--- lib/execute/src/mmap.rs | 1 + lib/execute/src/table.rs | 65 ++++++++++ lib/execute/src/traphandlers.rs | 3 +- lib/execute/src/vmcontext.rs | 163 ++++++++++++++++--------- src/wasmtime.rs | 4 +- tests/wast.rs | 16 +-- 14 files changed, 356 insertions(+), 249 deletions(-) create mode 100644 lib/execute/src/table.rs diff --git a/lib/environ/src/compilation.rs b/lib/environ/src/compilation.rs index 87b63d0a9cbc..427bee39c064 100644 --- a/lib/environ/src/compilation.rs +++ b/lib/environ/src/compilation.rs @@ -52,10 +52,10 @@ impl binemit::RelocSink for RelocSink { let reloc_target = 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 *name == ExternalName::testcase("wasmtime_memory_grow") { + RelocationTarget::MemoryGrow + } else if *name == ExternalName::testcase("wasmtime_memory_size") { + RelocationTarget::MemorySize } else { panic!("unrecognized external name") }; @@ -104,9 +104,9 @@ pub enum RelocationTarget { /// The user function index. UserFunc(FuncIndex), /// 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/module.rs b/lib/environ/src/module.rs index 1e9903804b34..9a52b5706136 100644 --- a/lib/environ/src/module.rs +++ b/lib/environ/src/module.rs @@ -98,7 +98,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, @@ -127,7 +127,7 @@ impl Module { pub fn new() -> Self { Self { signatures: PrimaryMap::new(), - imported_funcs: Vec::new(), + imported_funcs: PrimaryMap::new(), functions: PrimaryMap::new(), tables: PrimaryMap::new(), memory_plans: PrimaryMap::new(), diff --git a/lib/execute/Cargo.toml b/lib/execute/Cargo.toml index 25e3d687c880..84907f6856bb 100644 --- a/lib/execute/Cargo.toml +++ b/lib/execute/Cargo.toml @@ -20,15 +20,13 @@ lazy_static = "1.2.0" libc = { version = "0.2.44", default-features = false } errno = "0.2.4" cast = { version = "0.2.2", default-features = false } +memoffset = "0.2.1" [build-dependencies] cmake = "0.1.35" bindgen = "0.44.0" regex = "1.0.6" -[dev-dependencies] -memoffset = "0.2.1" - [features] default = ["std"] std = ["cranelift-codegen/std", "cranelift-wasm/std"] diff --git a/lib/execute/src/execute.rs b/lib/execute/src/execute.rs index 91f5ab28f50a..c4310c5b33fd 100644 --- a/lib/execute/src/execute.rs +++ b/lib/execute/src/execute.rs @@ -5,15 +5,15 @@ use code::Code; use cranelift_codegen::binemit::Reloc; use cranelift_codegen::isa::TargetIsa; use cranelift_entity::{EntityRef, PrimaryMap}; -use cranelift_wasm::{DefinedFuncIndex, MemoryIndex, TableIndex}; +use cranelift_wasm::{DefinedFuncIndex, MemoryIndex}; use instance::Instance; use invoke::{invoke_by_index, InvokeOutcome}; -use memory::LinearMemory; use region::protect; use region::Protection; -use std::ptr::{self, write_unaligned}; +use std::ptr::write_unaligned; use std::string::String; use std::vec::Vec; +use vmcontext::VMContext; use wasmtime_environ::{ compile_module, Compilation, Module, ModuleTranslation, Relocation, RelocationTarget, }; @@ -53,7 +53,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 => { @@ -62,8 +62,8 @@ 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, }; let body = &mut compilation.functions[i]; @@ -93,52 +93,20 @@ 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) -> 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(instance.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 @@ -148,7 +116,7 @@ pub fn finish_instantiation( 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() { @@ -169,17 +137,9 @@ pub fn finish_instantiation( } } - // Collect all memory base addresses and Vec. - instance.mem_base_addrs = instance - .memories - .values_mut() - .map(LinearMemory::base_addr) - .collect::>(); - - let mut vmctx = make_vmctx(instance); - if let Some(start_index) = module.start_func { - let result = invoke_by_index(code, isa, module, compilation, &mut vmctx, start_index, &[])?; + 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()); @@ -190,5 +150,5 @@ pub fn finish_instantiation( } } - Ok(vmctx) + Ok(()) } diff --git a/lib/execute/src/instance.rs b/lib/execute/src/instance.rs index 8cab4cd2fee7..32c22d4dc148 100644 --- a/lib/execute/src/instance.rs +++ b/lib/execute/src/instance.rs @@ -1,29 +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::string::String; -use std::vec::Vec; -use wasmtime_environ::{Compilation, DataInitializer, Module, TableElements}; +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, - /// Memory base address vector pointed to by vmctx. - pub mem_base_addrs: Vec<*mut u8>, + /// Table storage base address vector pointed to by vmctx. + vmctx_tables: PrimaryMap, + + /// Context pointer used by JIT code. + vmctx: VMContext, } impl Instance { @@ -33,82 +39,68 @@ impl Instance { compilation: &Compilation, data_initializers: &[DataInitializer], ) -> Result { - let mut result = Self { - tables: PrimaryMap::new(), - memories: PrimaryMap::new(), - globals: Vec::new(), - mem_base_addrs: Vec::new(), - }; - result.instantiate_tables(module, compilation, &module.table_elements); - result.instantiate_memories(module, data_initializers)?; - result.instantiate_globals(module); - Ok(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()); - self.tables.reserve_exact(module.tables.len()); - for table in module.tables.values() { - let len = table.minimum as usize; - 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], - ) -> Result<(), String> { - debug_assert!(self.memories.is_empty()); - // Allocate the underlying memory and initialize it to all zeros. - self.memories.reserve_exact(module.memory_plans.len()); - for plan in module.memory_plans.values() { - let v = LinearMemory::new(&plan)?; - 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); - } - Ok(()) + /// 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. @@ -121,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 index 1519e90f04b1..b34b49aeffad 100644 --- a/lib/execute/src/invoke.rs +++ b/lib/execute/src/invoke.rs @@ -11,6 +11,7 @@ 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. @@ -91,7 +92,7 @@ pub fn invoke( isa: &isa::TargetIsa, module: &Module, compilation: &Compilation, - vmctx: &mut Vec<*mut u8>, + vmctx: *mut VMContext, function: &str, args: &[Value], ) -> Result { @@ -109,7 +110,7 @@ pub fn invoke_by_index( isa: &isa::TargetIsa, module: &Module, compilation: &Compilation, - vmctx: &mut Vec<*mut u8>, + vmctx: *mut VMContext, fn_index: FuncIndex, args: &[Value], ) -> Result { @@ -138,21 +139,14 @@ pub fn invoke_by_index( return Err("failed to install signal handlers".to_string()); } - call_through_wrapper( - code, - isa, - exec_code_buf as usize, - vmctx.as_ptr() as usize, - args, - &sig, - ) + 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: usize, + vmctx: *mut VMContext, args: &[Value], sig: &ir::Signature, ) -> Result { diff --git a/lib/execute/src/lib.rs b/lib/execute/src/lib.rs index 55b690c50d96..537de949b445 100644 --- a/lib/execute/src/lib.rs +++ b/lib/execute/src/lib.rs @@ -40,7 +40,6 @@ extern crate alloc; #[macro_use] extern crate lazy_static; extern crate libc; -#[cfg(test)] #[macro_use] extern crate memoffset; extern crate cast; @@ -52,13 +51,16 @@ mod invoke; mod memory; mod mmap; mod signalhandlers; +mod table; mod traphandlers; +mod vmcontext; 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; #[cfg(not(feature = "std"))] mod std { diff --git a/lib/execute/src/memory.rs b/lib/execute/src/memory.rs index 4ee93b609e37..b55e28a2784b 100644 --- a/lib/execute/src/memory.rs +++ b/lib/execute/src/memory.rs @@ -1,16 +1,16 @@ -//! Memory management for linear memory. +//! Memory management for linear memories. +//! +//! `LinearMemory` is to WebAssembly linear memories what `Table` is to WebAssembly tables. use cast; use mmap::Mmap; use region; -use std::fmt; 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: Mmap, current: u32, @@ -61,13 +61,8 @@ impl LinearMemory { }) } - /// Returns an base address of this linear memory. - pub fn base_addr(&mut self) -> *mut u8 { - self.mmap.as_mut_ptr() - } - - /// 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 { assert_eq!(self.mmap.len() % WASM_PAGE_SIZE as usize, 0); let num_pages = self.mmap.len() / WASM_PAGE_SIZE as usize; cast::u32(num_pages).unwrap() @@ -131,14 +126,9 @@ impl LinearMemory { 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()) } } diff --git a/lib/execute/src/mmap.rs b/lib/execute/src/mmap.rs index 996945246915..6b9fdab071fa 100644 --- a/lib/execute/src/mmap.rs +++ b/lib/execute/src/mmap.rs @@ -16,6 +16,7 @@ fn round_up_to_page_size(size: usize, page_size: usize) -> usize { /// 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, 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 28f3861e17b3..405ea6b78005 100644 --- a/lib/execute/src/traphandlers.rs +++ b/lib/execute/src/traphandlers.rs @@ -74,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 index c50a5cec2628..f7889c3db9e9 100644 --- a/lib/execute/src/vmcontext.rs +++ b/lib/execute/src/vmcontext.rs @@ -1,14 +1,19 @@ //! This file declares `VMContext` and several related structs which contain //! fields that JIT code accesses directly. -use std::ptr::{size_of, align_of}; +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. -#[repr(C, packed)] +#[derive(Debug)] +#[repr(C)] pub struct VMMemory { - pub base: *mut u8, - pub current_length: usize, + base: *mut u8, + current_length: usize, // If more elements are added here, remember to add offset_of tests below! } @@ -18,14 +23,24 @@ mod test { #[test] fn check_vmmemory_offsets() { - let offsets = VMOffsets::new(size_of<*mut u8>()); - assert_eq!(size_of(), offsets.size_of_vmmemory()); + let offsets = VMOffsets::new(size_of::<*mut u8>()); + assert_eq!(size_of::(), offsets.size_of_vmmemory()); assert_eq!(offset_of!(VMMemory, base), offsets.vmmemory_base()); - assert_eq!(offset_of!(VMMemory, current_length), offsets.vmmemory_current_length()); + assert_eq!( + offset_of!(VMMemory, current_length), + 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) } } @@ -47,38 +62,50 @@ impl VMMemory { } } -#[repr(C, packed, align(8))] +/// 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 { - pub storage: [u8; 8], + storage: [u8; 8], // If more elements are added here, remember to add offset_of tests below! } -/// The storage for a WebAssembly global. #[cfg(test)] mod test { + use std::mem::align_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()); + 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>()); - assert_eq!(size_of(), offsets.size_of_vmglobal()); + let offsets = VMOffsets::new(size_of::<*mut u8>()); + assert_eq!(size_of::(), 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, packed)] -pub struct VMTableStorage { - pub base: *mut u8, - pub current_elements: usize, +#[repr(C)] +pub struct VMTable { + base: *mut u8, + current_elements: usize, // If more elements are added here, remember to add offset_of tests below! } @@ -88,20 +115,30 @@ mod test { #[test] fn check_vmtable_offsets() { - let offsets = VMOffsets::new(size_of<*mut u8>()); - assert_eq!(size_of(), offsets.size_of_vmtable()); - assert_eq!(offset_of!(VMTableStorage, base), offsets.vmtable_base()); - assert_eq!(offset_of!(VMTableStorage, current_elements), offsets.vmtable_current_elements()); + let offsets = VMOffsets::new(size_of::<*mut u8>()); + assert_eq!(size_of::(), offsets.size_of_vmtable()); + assert_eq!(offset_of!(VMTable, base), offsets.vmtable_base()); + assert_eq!( + offset_of!(VMTable, current_elements), + offsets.vmtable_current_elements() + ); } } -impl VMTableStorage { +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_length) } + 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_length) } + unsafe { slice::from_raw_parts_mut(self.base, self.current_elements) } } pub fn as_ptr(&self) -> *const u8 { @@ -113,26 +150,24 @@ impl VMTableStorage { } pub fn len(&self) -> usize { - self.current_length + 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. -#[repr(C, packed)] +#[derive(Debug)] +#[repr(C)] pub struct VMContext { /// A pointer to an array of `VMMemory` instances, indexed by /// WebAssembly memory index. - pub memories: *mut VMMemory, + memories: *mut VMMemory, /// A pointer to an array of globals. - pub globals: *mut u8, - /// A pointer to an array of `VMTableStorage` instances, indexed by + globals: *mut VMGlobal, + /// A pointer to an array of `VMTable` instances, indexed by /// WebAssembly table index. - pub tables: *mut VMTableStorage, - /// A pointer to extra runtime state that isn't directly accessed - /// from JIT code. - pub instance: *mut u8, + tables: *mut VMTable, // If more elements are added here, remember to add offset_of tests below! } @@ -142,41 +177,63 @@ mod test { #[test] fn check_vmctx_offsets() { - let offsets = VMOffsets::new(size_of<*mut u8>()); - assert_eq!(size_of(), offsets.size_of_vmctx()); - assert_eq!(offset_of!(VMContext, globals), offsets.vmctx_globals()); + let offsets = VMOffsets::new(size_of::<*mut u8>()); + assert_eq!(size_of::(), offsets.size_of_vmctx()); assert_eq!(offset_of!(VMContext, memories), offsets.vmctx_memories()); + assert_eq!(offset_of!(VMContext, globals), offsets.vmctx_globals()); assert_eq!(offset_of!(VMContext, tables), offsets.vmctx_tables()); assert_eq!(offset_of!(VMContext, instance), offsets.vmctx_instance()); } } impl VMContext { - unsafe pub fn global_storage(&mut self, index: usize) -> *mut u8 { - globals.add(index * global_size) + /// 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) } - unsafe pub fn global_i32(&mut self, index: usize) -> &mut i32 { - 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) } - unsafe pub fn global_i64(&mut self, index: usize) -> &mut i64 { - 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) } - unsafe pub fn global_f32(&mut self, index: usize) -> &mut f32 { - 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) } - unsafe pub fn global_f64(&mut self, index: usize) -> &mut f64 { - 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()) } - unsafe pub fn memory(&mut self, index: usize) -> &mut VMMemory { - memories.add(index) as &mut VMMemory + /// Return a mutable reference to table `index`. + pub unsafe fn table(&mut self, index: TableIndex) -> &mut VMTable { + &mut *self.tables.add(index.index()) } - unsafe pub fn table(&mut self, index: usize) -> &mut VMTableStorage { - tables.add(index) as &mut VMTableStorage + /// 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/src/wasmtime.rs b/src/wasmtime.rs index e7162a1cbb92..e3e66445bb95 100644 --- a/src/wasmtime.rs +++ b/src/wasmtime.rs @@ -167,7 +167,7 @@ fn handle_module(args: &Args, path: PathBuf, isa: &TargetIsa) -> Result<(), Stri &translation.lazy.data_initializers, )?; - let mut context = finish_instantiation( + finish_instantiation( &mut code, isa, &translation.module, @@ -181,7 +181,7 @@ fn handle_module(args: &Args, path: PathBuf, isa: &TargetIsa) -> Result<(), Stri isa, &translation.module, &compilation, - &mut context, + instance.vmctx(), &f, &[], )?; diff --git a/tests/wast.rs b/tests/wast.rs index e1d6d89240f6..b050b0fa81bd 100644 --- a/tests/wast.rs +++ b/tests/wast.rs @@ -19,9 +19,6 @@ use wasmtime_execute::{ struct InstanceWorld { module: Module, - context: Vec<*mut u8>, - // FIXME - #[allow(dead_code)] instance: Instance, compilation: Compilation, } @@ -30,7 +27,7 @@ impl InstanceWorld { fn new(code: &mut Code, isa: &isa::TargetIsa, data: &[u8]) -> Result { let mut module = Module::new(); let tunables = Tunables::default(); - let (context, instance, compilation) = { + let (instance, compilation) = { let translation = { let environ = ModuleEnvironment::new(isa, &mut module, tunables); @@ -46,16 +43,13 @@ impl InstanceWorld { &translation.lazy.data_initializers, )?; - ( - finish_instantiation(code, isa, &translation.module, &compilation, &mut instance)?, - instance, - compilation, - ) + finish_instantiation(code, isa, &translation.module, &compilation, &mut instance)?; + + (instance, compilation) }; Ok(Self { module, - context, instance, compilation, }) @@ -73,7 +67,7 @@ impl InstanceWorld { isa, &self.module, &self.compilation, - &mut self.context, + self.instance.vmctx(), &f, args, ).map_err(|e| e.to_string()) From 3cfbed1e36966a1571b6c04818161422ce7fa8f1 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Mon, 3 Dec 2018 04:01:48 -0800 Subject: [PATCH 10/20] Add support for math libcalls. --- lib/environ/src/compilation.rs | 4 ++ lib/execute/src/execute.rs | 15 +++++++ lib/execute/src/lib.rs | 1 + lib/execute/src/libcalls.rs | 73 ++++++++++++++++++++++++++++++++++ 4 files changed, 93 insertions(+) create mode 100644 lib/execute/src/libcalls.rs diff --git a/lib/environ/src/compilation.rs b/lib/environ/src/compilation.rs index 427bee39c064..ed4c5798a613 100644 --- a/lib/environ/src/compilation.rs +++ b/lib/environ/src/compilation.rs @@ -56,6 +56,8 @@ impl binemit::RelocSink for RelocSink { RelocationTarget::MemoryGrow } else if *name == ExternalName::testcase("wasmtime_memory_size") { RelocationTarget::MemorySize + } else if let ExternalName::LibCall(libcall) = *name { + RelocationTarget::LibCall(libcall) } else { panic!("unrecognized external name") }; @@ -103,6 +105,8 @@ 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. MemoryGrow, /// Function for query current size of the default linear memory. diff --git a/lib/execute/src/execute.rs b/lib/execute/src/execute.rs index c4310c5b33fd..8dd1bef775fc 100644 --- a/lib/execute/src/execute.rs +++ b/lib/execute/src/execute.rs @@ -64,6 +64,21 @@ fn relocate( }, 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, + other => panic!("unexpected libcall: {}", other), + } + } }; let body = &mut compilation.functions[i]; diff --git a/lib/execute/src/lib.rs b/lib/execute/src/lib.rs index 537de949b445..0d26a990401e 100644 --- a/lib/execute/src/lib.rs +++ b/lib/execute/src/lib.rs @@ -48,6 +48,7 @@ mod code; mod execute; mod instance; mod invoke; +mod libcalls; mod memory; mod mmap; mod signalhandlers; 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 + } + } +} From a2b4fe6f97a0394d50ed0ccd5d1eea517601a8df Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Mon, 3 Dec 2018 04:19:20 -0800 Subject: [PATCH 11/20] Convert the wast test harness into a crate. This uses a build.rs file to collect all the wast tests and create individual `#[test]` lines for them, so that `cargo test` can run them in parallel. --- Cargo.toml | 1 + lib/execute/src/lib.rs | 2 + lib/execute/src/world.rs | 66 ++++ lib/wast/Cargo.toml | 20 ++ lib/wast/LICENSE | 220 +++++++++++++ lib/wast/README.md | 4 + lib/wast/build.rs | 46 +++ .../wast/misc_testsuite}/misc_traps.wast | 0 .../wast/misc_testsuite}/stack_overflow.wast | 0 lib/wast/src/lib.rs | 34 ++ lib/wast/src/wast.rs | 177 +++++++++++ lib/wast/tests/wast_files.rs | 22 ++ tests/wast.rs | 291 ------------------ 13 files changed, 592 insertions(+), 291 deletions(-) create mode 100644 lib/execute/src/world.rs create mode 100644 lib/wast/Cargo.toml create mode 100644 lib/wast/LICENSE create mode 100644 lib/wast/README.md create mode 100644 lib/wast/build.rs rename {tests/wast => lib/wast/misc_testsuite}/misc_traps.wast (100%) rename {tests/wast => lib/wast/misc_testsuite}/stack_overflow.wast (100%) create mode 100644 lib/wast/src/lib.rs create mode 100644 lib/wast/src/wast.rs create mode 100644 lib/wast/tests/wast_files.rs delete mode 100644 tests/wast.rs diff --git a/Cargo.toml b/Cargo.toml index 188ea582f202..9b7d45ac4dd6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,7 @@ cranelift-wasm = { git = "https://github.com/sunfishcode/cranelift.git", branch 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/lib/execute/src/lib.rs b/lib/execute/src/lib.rs index 0d26a990401e..adc3c3882f42 100644 --- a/lib/execute/src/lib.rs +++ b/lib/execute/src/lib.rs @@ -55,6 +55,7 @@ mod signalhandlers; mod table; mod traphandlers; mod vmcontext; +mod world; pub use code::Code; pub use execute::{compile_and_link_module, finish_instantiation}; @@ -62,6 +63,7 @@ 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/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/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..f9001eaa85bd --- /dev/null +++ b/lib/wast/README.md @@ -0,0 +1,4 @@ +This is the `wasmtime-wast` crate, which contains support for running +"wast" tests, such as the spec testsuite. + +[`wasmtime-wast`]: https://crates.io/crates/wasmtime-wast diff --git a/lib/wast/build.rs b/lib/wast/build.rs new file mode 100644 index 000000000000..6241c3f62cbf --- /dev/null +++ b/lib/wast/build.rs @@ -0,0 +1,46 @@ +use std::env; +use std::fs::{read_dir, File}; +use std::io::Write; +use std::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"); + + let mut paths: Vec<_> = read_dir("spec_testsuite") + .unwrap() + .map(|r| r.unwrap()) + .filter(|p| { + // Ignore files starting with `.`, which could be editor temporary files + if let Some(stem) = p.path().file_stem() { + if let Some(stemstr) = stem.to_str() { + return !stemstr.starts_with('.'); + } + } + false + }).collect(); + + paths.sort_by_key(|dir| dir.path()); + for path in paths { + let path = path.path(); + writeln!(out, "#[test]"); + writeln!( + out, + "fn {}() {{", + path.file_stem() + .expect("file_stem") + .to_str() + .expect("to_str") + ); + writeln!( + out, + " wast_file(Path::new(\"{}\"), &*native_isa()).expect(\"error loading wast file {}\");", + path.display(), + path.display() + ); + writeln!(out, "}}"); + writeln!(out); + } +} diff --git a/tests/wast/misc_traps.wast b/lib/wast/misc_testsuite/misc_traps.wast similarity index 100% rename from tests/wast/misc_traps.wast rename to lib/wast/misc_testsuite/misc_traps.wast diff --git a/tests/wast/stack_overflow.wast b/lib/wast/misc_testsuite/stack_overflow.wast similarity index 100% rename from tests/wast/stack_overflow.wast rename to lib/wast/misc_testsuite/stack_overflow.wast 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..529baf5d9d47 --- /dev/null +++ b/lib/wast/src/wast.rs @@ -0,0 +1,177 @@ +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 + ); + } + } + } + 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/tests/wast.rs b/tests/wast.rs deleted file mode 100644 index b050b0fa81bd..000000000000 --- a/tests/wast.rs +++ /dev/null @@ -1,291 +0,0 @@ -extern crate cranelift_codegen; -extern crate wabt; -extern crate wasmtime_environ; -extern crate wasmtime_execute; - -use cranelift_codegen::settings::Configurable; -use cranelift_codegen::{isa, settings}; -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, ScriptParser}; -use wasmtime_environ::{Compilation, Module, ModuleEnvironment, Tunables}; -use wasmtime_execute::{ - compile_and_link_module, finish_instantiation, invoke, Code, Instance, InvokeOutcome, Value, -}; - -struct InstanceWorld { - module: Module, - instance: Instance, - compilation: Compilation, -} - -impl InstanceWorld { - 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, - }) - } - - fn invoke( - &mut self, - code: &mut Code, - isa: &isa::TargetIsa, - f: &str, - args: &[Value], - ) -> Result { - invoke( - code, - isa, - &self.module, - &self.compilation, - self.instance.vmctx(), - &f, - args, - ).map_err(|e| e.to_string()) - } -} - -fn translate(code: &mut Code, isa: &isa::TargetIsa, data: &[u8]) -> Result { - InstanceWorld::new(code, isa, data) -} - -struct Instances { - current: Option, - namespace: HashMap, -} - -impl Instances { - fn new() -> Self { - Self { - current: None, - namespace: HashMap::new(), - } - } - - fn unnamed(&mut self, instance: InstanceWorld) { - self.current = Some(instance); - } - - fn named(&mut self, name: String, instance: InstanceWorld) { - self.namespace.insert(name, instance); - } - - fn perform_action( - &mut self, - code: &mut Code, - isa: &isa::TargetIsa, - action: Action, - ) -> InvokeOutcome { - match action { - Action::Invoke { - module, - field, - args, - } => { - let mut value_args = Vec::new(); - 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(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(code, isa, &field, &value_args) - .expect(&format!("error invoking {} in module {}", field, name)), - } - } - _ => panic!("unsupported action {:?}", action), - } - } -} - -#[test] -fn spec_core() { - 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"); - }); - let isa = isa_builder.finish(settings::Flags::new(flag_builder)); - - let mut paths: Vec<_> = fs::read_dir("tests/wast") - .unwrap() - .map(|r| r.unwrap()) - .filter(|p| { - // Ignore files starting with `.`, which could be editor temporary files - if let Some(stem) = p.path().file_stem() { - if let Some(stemstr) = stem.to_str() { - return !stemstr.starts_with('.'); - } - } - false - }).collect(); - paths.sort_by_key(|dir| dir.path()); - for path in paths { - let path = path.path(); - let source = read_to_end(&path).unwrap(); - test_wast(&path, &*isa, &source); - } -} - -#[cfg(test)] -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) -} - -#[cfg(test)] -fn test_wast(path: &Path, isa: &isa::TargetIsa, wast: &[u8]) { - println!("Testing {}", path.display()); - - let mut parser = ScriptParser::from_str(str::from_utf8(wast).unwrap()).unwrap(); - let mut instances = Instances::new(); - let mut code = Code::new(); - - while let Some(Command { kind, line }) = parser.next().unwrap() { - match kind { - CommandKind::Module { module, name } => { - if let Some(name) = name { - instances.named( - name, - translate(&mut code, &*isa, &module.clone().into_vec()).unwrap(), - ); - } - - instances.unnamed(translate(&mut code, &*isa, &module.clone().into_vec()).unwrap()); - } - CommandKind::PerformAction(action) => { - match instances.perform_action(&mut code, &*isa, action) { - InvokeOutcome::Returned { .. } => {} - InvokeOutcome::Trapped { message } => { - panic!("{}:{}: a trap occurred: {}", path.display(), line, message); - } - } - } - CommandKind::AssertReturn { action, expected } => { - match instances.perform_action(&mut code, &*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 {}:{}", path.display(), line) - } - script::Value::I64(x) => { - assert_eq!(x, v.unwrap_i64(), "at {}:{}", path.display(), line) - } - script::Value::F32(x) => assert_eq!( - x.to_bits(), - v.unwrap_f32(), - "at {}:{}", - path.display(), - line - ), - script::Value::F64(x) => assert_eq!( - x.to_bits(), - v.unwrap_f64(), - "at {}:{}", - path.display(), - line - ), - }; - } - } - InvokeOutcome::Trapped { message } => { - panic!( - "{}:{}: expected normal return, but a trap occurred: {}", - path.display(), - line, - message - ); - } - } - } - CommandKind::AssertTrap { action, message } => { - match instances.perform_action(&mut code, &*isa, action) { - InvokeOutcome::Returned { values } => panic!( - "{}:{}: expected trap, but invoke returned with {:?}", - path.display(), - line, - values - ), - InvokeOutcome::Trapped { - message: trap_message, - } => { - println!( - "{}:{}: TODO: Check the trap message: expected {}, got {}", - path.display(), - line, - message, - trap_message - ); - } - } - } - CommandKind::AssertExhaustion { action } => { - match instances.perform_action(&mut code, &*isa, action) { - InvokeOutcome::Returned { values } => panic!( - "{}:{}: expected exhaustion, but invoke returned with {:?}", - path.display(), - line, - values - ), - InvokeOutcome::Trapped { message } => { - println!( - "{}:{}: TODO: Check the exhaustion message: {}", - path.display(), - line, - message - ); - } - } - } - command => { - println!("{}:{}: TODO: implement {:?}", path.display(), line, command); - } - } - } -} From a7a314e7b1528f52a035f591448e32d413aed102 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Mon, 3 Dec 2018 04:59:40 -0800 Subject: [PATCH 12/20] More infrastructure. Improve handling of memory.grow/size, add a standalone wast runner, test harness improvements. --- Cargo.toml | 4 ++ lib/environ/src/compilation.rs | 12 ++-- lib/environ/src/environ.rs | 44 ++++++++------ lib/environ/src/lib.rs | 1 + lib/environ/src/module.rs | 21 ++++--- lib/environ/src/tunables.rs | 17 ++++-- lib/environ/src/vmoffsets.rs | 8 +-- lib/execute/Cargo.toml | 1 - lib/execute/src/execute.rs | 5 ++ lib/execute/src/lib.rs | 1 - lib/execute/src/memory.rs | 23 +++----- lib/execute/src/vmcontext.rs | 69 +++++++++++++++------- lib/wast/build.rs | 23 ++++++-- lib/wast/src/wast.rs | 66 +++++++++++++++++++++ src/run_wast.rs | 103 +++++++++++++++++++++++++++++++++ 15 files changed, 316 insertions(+), 82 deletions(-) create mode 100644 src/run_wast.rs diff --git a/Cargo.toml b/Cargo.toml index 9b7d45ac4dd6..36995d4209d3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,10 @@ publish = false name = "wasmtime" path = "src/wasmtime.rs" +[[bin]] +name = "run_wast" +path = "src/run_wast.rs" + [[bin]] name = "wasm2obj" path = "src/wasm2obj.rs" diff --git a/lib/environ/src/compilation.rs b/lib/environ/src/compilation.rs index ed4c5798a613..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,13 @@ impl binemit::RelocSink for RelocSink { name: &ExternalName, addend: binemit::Addend, ) { - let reloc_target = if let ExternalName::User { namespace, index } = *name { - debug_assert!(namespace == 0); - RelocationTarget::UserFunc(FuncIndex::new(index as usize)) - } else if *name == ExternalName::testcase("wasmtime_memory_grow") { + let reloc_target = if *name == get_memory_grow_name() { RelocationTarget::MemoryGrow - } else if *name == ExternalName::testcase("wasmtime_memory_size") { + } 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 let ExternalName::LibCall(libcall) = *name { RelocationTarget::LibCall(libcall) } else { diff --git a/lib/environ/src/environ.rs b/lib/environ/src/environ.rs index 9b8071bb62de..50f4f76f759e 100644 --- a/lib/environ/src/environ.rs +++ b/lib/environ/src/environ.rs @@ -3,7 +3,7 @@ use cranelift_codegen::ir; 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; @@ -26,6 +26,16 @@ pub fn get_func_name(func_index: FuncIndex) -> ir::ExternalName { 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> { @@ -97,11 +107,11 @@ pub struct FuncEnvironment<'module_environment> { /// The Cranelift global holding the base address of the globals vector. globals_base: Option, - /// The external function declaration for implementing wasm's `current_memory`. - current_memory_extfunc: Option, + /// The external function declaration for implementing wasm's `memory.size`. + memory_size_extfunc: Option, - /// The external function declaration for implementing wasm's `grow_memory`. - grow_memory_extfunc: Option, + /// The external function declaration for implementing wasm's `memory.grow`. + memory_grow_extfunc: Option, /// Offsets to struct fields accessed by JIT code. offsets: VMOffsets, @@ -119,8 +129,8 @@ 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()), } } @@ -484,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![ @@ -497,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()) } @@ -517,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![ @@ -529,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()) } } diff --git a/lib/environ/src/lib.rs b/lib/environ/src/lib.rs index 7bd0a8cd3bca..9b93d89ac80a 100644 --- a/lib/environ/src/lib.rs +++ b/lib/environ/src/lib.rs @@ -49,6 +49,7 @@ pub use compilation::{ pub use environ::{ModuleEnvironment, ModuleTranslation}; 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; diff --git a/lib/environ/src/module.rs b/lib/environ/src/module.rs index 9a52b5706136..1a6d017769e9 100644 --- a/lib/environ/src/module.rs +++ b/lib/environ/src/module.rs @@ -52,17 +52,23 @@ pub enum MemoryStyle { impl MemoryStyle { /// Decide on an implementation style for the given `Memory`. - pub fn for_memory(memory: Memory, tunables: &Tunables) -> Self { + 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), - } + ( + 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 + ( + MemoryStyle::Dynamic, + tunables.dynamic_memory_offset_guard_size, + ) } } } @@ -82,10 +88,11 @@ pub struct MemoryPlan { 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: MemoryStyle::for_memory(memory, tunables), - offset_guard_size: tunables.offset_guard_size, + style, + offset_guard_size, } } } diff --git a/lib/environ/src/tunables.rs b/lib/environ/src/tunables.rs index 7fd9b291bcc7..c2769bf2aade 100644 --- a/lib/environ/src/tunables.rs +++ b/lib/environ/src/tunables.rs @@ -4,8 +4,11 @@ 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. - pub offset_guard_size: u64, + /// 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 { @@ -17,11 +20,17 @@ impl Default for Tunables { /// need for explicit bounds checks. static_memory_bound: 0x1_0000, - /// Size in bytes of the offset guard. + /// 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. - offset_guard_size: 0x8000_0000, + 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/vmoffsets.rs b/lib/environ/src/vmoffsets.rs index 7dfa25125191..f1d66888f559 100644 --- a/lib/environ/src/vmoffsets.rs +++ b/lib/environ/src/vmoffsets.rs @@ -72,16 +72,10 @@ impl VMOffsets { 2 * self.pointer_size } - /// The offset of the `instance` field. - #[allow(dead_code)] - pub fn vmctx_instance(&self) -> u8 { - 3 * self.pointer_size - } - /// Return the size of `VMContext`. #[allow(dead_code)] pub fn size_of_vmctx(&self) -> u8 { - 4 * self.pointer_size + 3 * self.pointer_size } /// Return the offset from the `memories` pointer to `VMMemory` index `index`. diff --git a/lib/execute/Cargo.toml b/lib/execute/Cargo.toml index 84907f6856bb..f13206202a72 100644 --- a/lib/execute/Cargo.toml +++ b/lib/execute/Cargo.toml @@ -19,7 +19,6 @@ region = "1.0.0" lazy_static = "1.2.0" libc = { version = "0.2.44", default-features = false } errno = "0.2.4" -cast = { version = "0.2.2", default-features = false } memoffset = "0.2.1" [build-dependencies] diff --git a/lib/execute/src/execute.rs b/lib/execute/src/execute.rs index 8dd1bef775fc..1ac5e88732bc 100644 --- a/lib/execute/src/execute.rs +++ b/lib/execute/src/execute.rs @@ -36,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, @@ -76,6 +80,7 @@ fn relocate( 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), } } diff --git a/lib/execute/src/lib.rs b/lib/execute/src/lib.rs index adc3c3882f42..f78c9d7adc20 100644 --- a/lib/execute/src/lib.rs +++ b/lib/execute/src/lib.rs @@ -42,7 +42,6 @@ extern crate lazy_static; extern crate libc; #[macro_use] extern crate memoffset; -extern crate cast; mod code; mod execute; diff --git a/lib/execute/src/memory.rs b/lib/execute/src/memory.rs index b55e28a2784b..6dcc4a074c69 100644 --- a/lib/execute/src/memory.rs +++ b/lib/execute/src/memory.rs @@ -2,7 +2,6 @@ //! //! `LinearMemory` is to WebAssembly linear memories what `Table` is to WebAssembly tables. -use cast; use mmap::Mmap; use region; use std::string::String; @@ -63,9 +62,7 @@ impl LinearMemory { /// Returns the number of allocated wasm pages. pub fn size(&self) -> u32 { - assert_eq!(self.mmap.len() % WASM_PAGE_SIZE as usize, 0); - let num_pages = self.mmap.len() / WASM_PAGE_SIZE as usize; - cast::u32(num_pages).unwrap() + self.current } /// Grow memory by the specified amount of pages. @@ -97,27 +94,25 @@ impl LinearMemory { let new_bytes = new_pages as usize * WASM_PAGE_SIZE as usize; - if new_bytes > self.mmap.len() { + 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 mapped_pages = self.current as usize; - let mapped_bytes = mapped_pages * WASM_PAGE_SIZE as usize; let guard_bytes = self.offset_guard_size; + let request_bytes = new_bytes.checked_add(guard_bytes)?; - let mut new_mmap = Mmap::with_size(new_bytes).ok()?; + let mut new_mmap = Mmap::with_size(request_bytes).ok()?; // Make the offset-guard pages inaccessible. unsafe { region::protect( - new_mmap.as_ptr().add(mapped_bytes), + new_mmap.as_ptr().add(new_bytes), guard_bytes, - region::Protection::Read, - ).expect("unable to make memory readonly"); + region::Protection::None, + ).expect("unable to make memory inaccessible"); } - new_mmap - .as_mut_slice() - .copy_from_slice(self.mmap.as_slice()); + 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; } diff --git a/lib/execute/src/vmcontext.rs b/lib/execute/src/vmcontext.rs index f7889c3db9e9..dc3446fa16a1 100644 --- a/lib/execute/src/vmcontext.rs +++ b/lib/execute/src/vmcontext.rs @@ -18,17 +18,25 @@ pub struct VMMemory { } #[cfg(test)] -mod 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>()); - assert_eq!(size_of::(), offsets.size_of_vmmemory()); - assert_eq!(offset_of!(VMMemory, base), offsets.vmmemory_base()); + 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), - offsets.vmmemory_current_length() + usize::from(offsets.vmmemory_current_length()) ); } } @@ -74,13 +82,14 @@ pub struct VMGlobal { } #[cfg(test)] -mod test { - use std::mem::align_of; +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::()); assert!(align_of::() >= align_of::()); @@ -88,8 +97,11 @@ mod test { #[test] fn check_vmglobal_offsets() { - let offsets = VMOffsets::new(size_of::<*mut u8>()); - assert_eq!(size_of::(), offsets.size_of_vmglobal()); + let offsets = VMOffsets::new(size_of::<*mut u8>() as u8); + assert_eq!( + size_of::(), + usize::from(offsets.size_of_vmglobal()) + ); } } @@ -110,17 +122,22 @@ pub struct VMTable { } #[cfg(test)] -mod 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>()); - assert_eq!(size_of::(), offsets.size_of_vmtable()); - assert_eq!(offset_of!(VMTable, base), offsets.vmtable_base()); + 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), - offsets.vmtable_current_elements() + usize::from(offsets.vmtable_current_elements()) ); } } @@ -173,16 +190,26 @@ pub struct VMContext { #[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>()); - assert_eq!(size_of::(), offsets.size_of_vmctx()); - assert_eq!(offset_of!(VMContext, memories), offsets.vmctx_memories()); - assert_eq!(offset_of!(VMContext, globals), offsets.vmctx_globals()); - assert_eq!(offset_of!(VMContext, tables), offsets.vmctx_tables()); - assert_eq!(offset_of!(VMContext, instance), offsets.vmctx_instance()); + 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()) + ); } } diff --git a/lib/wast/build.rs b/lib/wast/build.rs index 6241c3f62cbf..b3116440afdd 100644 --- a/lib/wast/build.rs +++ b/lib/wast/build.rs @@ -29,10 +29,14 @@ fn main() { writeln!( out, "fn {}() {{", - path.file_stem() - .expect("file_stem") - .to_str() - .expect("to_str") + avoid_keywords( + &path + .file_stem() + .expect("file_stem") + .to_str() + .expect("to_str") + .replace("-", "_") + ) ); writeln!( out, @@ -44,3 +48,14 @@ fn main() { writeln!(out); } } + +fn avoid_keywords(name: &str) -> &str { + match name { + "if" => "if_", + "loop" => "loop_", + "type" => "type_", + "const" => "const_", + "return" => "return_", + other => other, + } +} diff --git a/lib/wast/src/wast.rs b/lib/wast/src/wast.rs index 529baf5d9d47..c6811e0278f2 100644 --- a/lib/wast/src/wast.rs +++ b/lib/wast/src/wast.rs @@ -155,6 +155,72 @@ pub fn wast_buffer(name: &str, isa: &isa::TargetIsa, wast: &[u8]) { } } } + 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); } 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())); + } +} From 9ed697184e928e5e707f6d9ca2e12c4135bd0c10 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Mon, 3 Dec 2018 05:23:13 -0800 Subject: [PATCH 13/20] Fix the description of the wasmtime program. --- src/wasmtime.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/wasmtime.rs b/src/wasmtime.rs index e3e66445bb95..cc09f968d7c4 100644 --- a/src/wasmtime.rs +++ b/src/wasmtime.rs @@ -66,9 +66,10 @@ use wasmtime_execute::{compile_and_link_module, finish_instantiation, invoke, Co 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] ... From 1a08ec0b3079d0fdda25d57bd603b837eee9e48d Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Mon, 3 Dec 2018 05:53:07 -0800 Subject: [PATCH 14/20] Add a spec testsuite submodule. --- .gitmodules | 3 ++ lib/wast/build.rs | 73 +++++++++++++++++++++++++++++------------ lib/wast/spec_testsuite | 1 + 3 files changed, 56 insertions(+), 21 deletions(-) create mode 100644 .gitmodules create mode 160000 lib/wast/spec_testsuite 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/lib/wast/build.rs b/lib/wast/build.rs index b3116440afdd..ba2a39e18fa3 100644 --- a/lib/wast/build.rs +++ b/lib/wast/build.rs @@ -9,44 +9,63 @@ fn main() { let mut out = File::create(out_dir.join("run_wast_files.rs")).expect("error creating run_wast_files.rs"); - let mut paths: Vec<_> = read_dir("spec_testsuite") + test_directory(&mut out, "misc_testsuite"); + test_directory(&mut out, "spec_testsuite"); +} + +fn test_directory(out: &mut File, testsuite: &str) { + let mut dir_entries: Vec<_> = read_dir(testsuite) .unwrap() .map(|r| r.unwrap()) - .filter(|p| { - // Ignore files starting with `.`, which could be editor temporary files - if let Some(stem) = p.path().file_stem() { - if let Some(stemstr) = stem.to_str() { - return !stemstr.starts_with('.'); + .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(); - paths.sort_by_key(|dir| dir.path()); - for path in paths { - let path = path.path(); - writeln!(out, "#[test]"); + dir_entries.sort_by_key(|dir| dir.path()); + + writeln!(out, "mod {} {{", testsuite); + 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( - &path - .file_stem() - .expect("file_stem") - .to_str() - .expect("to_str") - .replace("-", "_") - ) + " fn {}() {{", + avoid_keywords(&stemstr.replace("-", "_")) ); writeln!( out, - " wast_file(Path::new(\"{}\"), &*native_isa()).expect(\"error loading wast file {}\");", + " wast_file(Path::new(\"{}\"), &*native_isa()).expect(\"error loading wast file {}\");", path.display(), path.display() ); - writeln!(out, "}}"); + writeln!(out, " }}"); writeln!(out); } + writeln!(out, "}}"); } fn avoid_keywords(name: &str) -> &str { @@ -59,3 +78,15 @@ fn avoid_keywords(name: &str) -> &str { 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/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 From 5cb1a7c7e24e53f4d1753a32ca03bd2a8d7b7e51 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Mon, 3 Dec 2018 08:27:44 -0800 Subject: [PATCH 15/20] Enable RUST_BACKTRACE=1 when running "cargo test". --- test-all.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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" From b7f851d1bf190f5c194e4bf607b4c8dfcdbabe75 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Mon, 3 Dec 2018 15:08:18 -0800 Subject: [PATCH 16/20] Handle write errors in the build script. --- lib/wast/build.rs | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/lib/wast/build.rs b/lib/wast/build.rs index ba2a39e18fa3..ed3b12f72a51 100644 --- a/lib/wast/build.rs +++ b/lib/wast/build.rs @@ -1,6 +1,6 @@ use std::env; use std::fs::{read_dir, File}; -use std::io::Write; +use std::io::{self, Write}; use std::path::PathBuf; fn main() { @@ -9,11 +9,11 @@ fn main() { 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"); - test_directory(&mut out, "spec_testsuite"); + test_directory(&mut out, "misc_testsuite").unwrap(); + test_directory(&mut out, "spec_testsuite").unwrap(); } -fn test_directory(out: &mut File, testsuite: &str) { +fn test_directory(out: &mut File, testsuite: &str) -> io::Result<()> { let mut dir_entries: Vec<_> = read_dir(testsuite) .unwrap() .map(|r| r.unwrap()) @@ -37,8 +37,8 @@ fn test_directory(out: &mut File, testsuite: &str) { dir_entries.sort_by_key(|dir| dir.path()); - writeln!(out, "mod {} {{", testsuite); - writeln!(out, " use super::{{native_isa, wast_file, Path}};"); + writeln!(out, "mod {} {{", testsuite)?; + writeln!(out, " use super::{{native_isa, wast_file, Path}};")?; for dir_entry in dir_entries { let path = dir_entry.path(); let stemstr = path @@ -47,25 +47,26 @@ fn test_directory(out: &mut File, testsuite: &str) { .to_str() .expect("to_str"); - writeln!(out, " #[test]"); + writeln!(out, " #[test]")?; if ignore(testsuite, stemstr) { - writeln!(out, " #[ignore]"); + 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, " }}")?; + writeln!(out)?; } - writeln!(out, "}}"); + writeln!(out, "}}")?; + Ok(()) } fn avoid_keywords(name: &str) -> &str { From e54139a65d44ff0c365b0339bfaa7f86d790f6da Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Mon, 3 Dec 2018 15:18:23 -0800 Subject: [PATCH 17/20] Generalize to better support the spec proposals tests. --- lib/wast/build.rs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/lib/wast/build.rs b/lib/wast/build.rs index ed3b12f72a51..964fe4ab7e6e 100644 --- a/lib/wast/build.rs +++ b/lib/wast/build.rs @@ -1,7 +1,7 @@ use std::env; use std::fs::{read_dir, File}; use std::io::{self, Write}; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; fn main() { let out_dir = @@ -37,7 +37,16 @@ fn test_directory(out: &mut File, testsuite: &str) -> io::Result<()> { dir_entries.sort_by_key(|dir| dir.path()); - writeln!(out, "mod {} {{", testsuite)?; + 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(); From d04869dad71a05aceb6be356de90ace9354600b1 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Mon, 3 Dec 2018 16:49:13 -0800 Subject: [PATCH 18/20] Reword the lib/wast README.md and add a link to the spec testsuite. --- lib/wast/README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/wast/README.md b/lib/wast/README.md index f9001eaa85bd..ff671b026f19 100644 --- a/lib/wast/README.md +++ b/lib/wast/README.md @@ -1,4 +1,5 @@ -This is the `wasmtime-wast` crate, which contains support for running -"wast" tests, such as the spec testsuite. +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. -[`wasmtime-wast`]: https://crates.io/crates/wasmtime-wast +[WebAssembly spec testsuite]: https://github.com/WebAssembly/testsuite From d42639a0f7cf1c6ed02ac3efdbf77f8e66c060eb Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Tue, 4 Dec 2018 19:57:12 -0500 Subject: [PATCH 19/20] Temporarily disable use of Mach ports for trap handling on Darwin. --- lib/execute/signalhandlers/SignalHandlers.cpp | 30 ++++++++++++++----- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/lib/execute/signalhandlers/SignalHandlers.cpp b/lib/execute/signalhandlers/SignalHandlers.cpp index 060645685bd5..bbe06cef0545 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; @@ -405,7 +419,7 @@ HandleTrap(CONTEXT* context) // a longjmp. sAlreadyHandlingTrap = false; -#if defined(__APPLE__) +#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. @@ -457,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. @@ -687,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 @@ -751,10 +765,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) { @@ -803,6 +817,6 @@ EnsureDarwinMachPorts() return false; } +#endif return true; } -#endif From 9f000ae31e544d4b102a08aefb6c4523e3c2c7ac Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Tue, 4 Dec 2018 21:57:48 -0500 Subject: [PATCH 20/20] On Darwin, guard page accesses are raised as SIGBUS. --- lib/execute/signalhandlers/SignalHandlers.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/execute/signalhandlers/SignalHandlers.cpp b/lib/execute/signalhandlers/SignalHandlers.cpp index bbe06cef0545..eb3608d5f62e 100644 --- a/lib/execute/signalhandlers/SignalHandlers.cpp +++ b/lib/execute/signalhandlers/SignalHandlers.cpp @@ -723,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;