From 920728d14d4bf3656cc788a22f30e0632f6d3bd6 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Sat, 19 Oct 2019 13:12:04 -0700 Subject: [PATCH 1/8] Implement registering JIT unwind information on Windows. This commit implements registering unwind information for JIT functions on Windows so that the operating system can both walk and unwind stacks containing JIT frames. Currently this only works with Cranelift as lightbeam does not emit unwind information yet. This commit also resets the stack guard page on Windows for stack overflow exceptions, allowing reliable stack overflow traps. With these changes, all previously disabled test suite tests (not including the multi-value tests) on Windows are now passing. Fixes #291. --- build.rs | 32 --- misc/wasmtime-py/src/import.rs | 13 +- wasmtime-api/src/trampoline/func.rs | 14 +- wasmtime-environ/src/cache/tests.rs | 5 +- wasmtime-environ/src/compilation.rs | 22 +- wasmtime-environ/src/cranelift.rs | 16 +- wasmtime-environ/src/lib.rs | 4 +- wasmtime-environ/src/lightbeam.rs | 3 +- wasmtime-jit/Cargo.toml | 3 + wasmtime-jit/src/code_memory.rs | 197 +++++++++++++----- wasmtime-jit/src/compiler.rs | 25 +-- wasmtime-jit/src/function_table.rs | 134 ++++++++++++ wasmtime-jit/src/lib.rs | 1 + .../signalhandlers/SignalHandlers.cpp | 11 +- .../signalhandlers/SignalHandlers.hpp | 2 +- wasmtime-runtime/src/traphandlers.rs | 31 ++- 16 files changed, 382 insertions(+), 131 deletions(-) create mode 100644 wasmtime-jit/src/function_table.rs diff --git a/build.rs b/build.rs index cb94a74af8..86408d216b 100644 --- a/build.rs +++ b/build.rs @@ -206,38 +206,6 @@ fn ignore(testsuite: &str, testname: &str, strategy: &str) -> bool { // ABI only has a single return register, so we need to wait on full // multi-value support in Cranelift. (_, _) if is_multi_value => true, - - // Until Windows unwind information is added we must disable SIMD spec tests that trap. - (_, _) if testname.starts_with("simd") => return true, - - ("spec_testsuite", "address") => true, - ("spec_testsuite", "align") => true, - ("spec_testsuite", "call") => true, - ("spec_testsuite", "call_indirect") => true, - ("spec_testsuite", "conversions") => true, - ("spec_testsuite", "elem") => true, - ("spec_testsuite", "fac") => true, - ("spec_testsuite", "func_ptrs") => true, - ("spec_testsuite", "globals") => true, - ("spec_testsuite", "i32") => true, - ("spec_testsuite", "i64") => true, - ("spec_testsuite", "f32") => true, - ("spec_testsuite", "f64") => true, - ("spec_testsuite", "if") => true, - ("spec_testsuite", "imports") => true, - ("spec_testsuite", "int_exprs") => true, - ("spec_testsuite", "linking") => true, - ("spec_testsuite", "memory_grow") => true, - ("spec_testsuite", "memory_trap") => true, - ("spec_testsuite", "resizing") => true, - ("spec_testsuite", "select") => true, - ("spec_testsuite", "skip_stack_guard_page") => true, - ("spec_testsuite", "start") => true, - ("spec_testsuite", "traps") => true, - ("spec_testsuite", "unreachable") => true, - ("spec_testsuite", "unwind") => true, - ("misc_testsuite", "misc_traps") => true, - ("misc_testsuite", "stack_overflow") => true, (_, _) => false, }; } diff --git a/misc/wasmtime-py/src/import.rs b/misc/wasmtime-py/src/import.rs index 9268bc5b07..41c903ec8a 100644 --- a/misc/wasmtime-py/src/import.rs +++ b/misc/wasmtime-py/src/import.rs @@ -16,7 +16,7 @@ use cranelift_entity::{EntityRef, PrimaryMap}; use cranelift_frontend::{FunctionBuilder, FunctionBuilderContext}; use cranelift_wasm::{DefinedFuncIndex, FuncIndex}; use target_lexicon::HOST; -use wasmtime_environ::{Export, Module}; +use wasmtime_environ::{CompiledFunction, Export, Module}; use wasmtime_jit::CodeMemory; use wasmtime_runtime::{Imports, InstanceHandle, VMContext, VMFunctionBody}; @@ -184,9 +184,16 @@ fn make_trampoline( ) .expect("compile_and_emit"); + let mut unwind_info = Vec::new(); + context.emit_unwind_info(isa, &mut unwind_info); + code_memory - .allocate_copy_of_byte_slice(&code_buf) - .expect("allocate_copy_of_byte_slice") + .allocate_for_function(&CompiledFunction { + body: code_buf, + jt_offsets: context.func.jt_offsets, + unwind_info, + }) + .expect("allocate_for_function") .as_ptr() } diff --git a/wasmtime-api/src/trampoline/func.rs b/wasmtime-api/src/trampoline/func.rs index 1ed333f936..95b17631ff 100644 --- a/wasmtime-api/src/trampoline/func.rs +++ b/wasmtime-api/src/trampoline/func.rs @@ -9,8 +9,7 @@ use cranelift_codegen::{binemit, ir, isa}; use cranelift_entity::{EntityRef, PrimaryMap}; use cranelift_frontend::{FunctionBuilder, FunctionBuilderContext}; use cranelift_wasm::{DefinedFuncIndex, FuncIndex}; -//use target_lexicon::HOST; -use wasmtime_environ::{Export, Module}; +use wasmtime_environ::{CompiledFunction, Export, Module}; use wasmtime_jit::CodeMemory; use wasmtime_runtime::{InstanceHandle, VMContext, VMFunctionBody}; @@ -185,9 +184,16 @@ fn make_trampoline( ) .expect("compile_and_emit"); + let mut unwind_info = Vec::new(); + context.emit_unwind_info(isa, &mut unwind_info); + code_memory - .allocate_copy_of_byte_slice(&code_buf) - .expect("allocate_copy_of_byte_slice") + .allocate_for_function(&CompiledFunction { + body: code_buf, + jt_offsets: context.func.jt_offsets, + unwind_info, + }) + .expect("allocate_for_function") .as_ptr() } diff --git a/wasmtime-environ/src/cache/tests.rs b/wasmtime-environ/src/cache/tests.rs index e1b006bf03..7e9632da89 100644 --- a/wasmtime-environ/src/cache/tests.rs +++ b/wasmtime-environ/src/cache/tests.rs @@ -1,7 +1,7 @@ use super::config::tests::test_prolog; use super::*; use crate::address_map::{FunctionAddressMap, InstructionAddressMap}; -use crate::compilation::{CodeAndJTOffsets, Relocation, RelocationTarget, TrapInformation}; +use crate::compilation::{CompiledFunction, Relocation, RelocationTarget, TrapInformation}; use crate::module::{MemoryPlan, MemoryStyle, Module}; use alloc::boxed::Box; use alloc::vec::Vec; @@ -258,9 +258,10 @@ fn new_module_cache_data(rng: &mut impl Rng) -> ModuleCacheData { *v = (j as u32) * 3 / 4 } }); - CodeAndJTOffsets { + CompiledFunction { body: (0..(i * 3 / 2)).collect(), jt_offsets: sm, + unwind_info: (0..(i * 3 / 2)).collect(), } }) .collect(); diff --git a/wasmtime-environ/src/compilation.rs b/wasmtime-environ/src/compilation.rs index 8be354ba79..797d7d82a7 100644 --- a/wasmtime-environ/src/compilation.rs +++ b/wasmtime-environ/src/compilation.rs @@ -12,17 +12,20 @@ use serde::{Deserialize, Serialize}; use std::ops::Range; use thiserror::Error; -/// Compiled machine code: body and jump table offsets. +/// Compiled function: machine code body, jump table offsets, and unwind information. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] -pub struct CodeAndJTOffsets { +pub struct CompiledFunction { /// The function body. pub body: Vec, /// The jump tables offsets (in the body). pub jt_offsets: ir::JumpTableOffsets, + + /// The unwind information. + pub unwind_info: Vec, } -type Functions = PrimaryMap; +type Functions = PrimaryMap; /// The result of compiling a WebAssembly module's functions. #[derive(Deserialize, Serialize, Debug, PartialEq, Eq)] @@ -40,21 +43,22 @@ impl Compilation { /// Allocates the compilation result with the given function bodies. pub fn from_buffer( buffer: Vec, - functions: impl IntoIterator, ir::JumpTableOffsets)>, + functions: impl IntoIterator, ir::JumpTableOffsets, Range)>, ) -> Self { Self::new( functions .into_iter() - .map(|(range, jt_offsets)| CodeAndJTOffsets { - body: buffer[range].to_vec(), + .map(|(body_range, jt_offsets, unwind_range)| CompiledFunction { + body: buffer[body_range].to_vec(), jt_offsets, + unwind_info: buffer[unwind_range].to_vec(), }) .collect(), ) } /// Gets the bytes of a single function - pub fn get(&self, func: DefinedFuncIndex) -> &CodeAndJTOffsets { + pub fn get(&self, func: DefinedFuncIndex) -> &CompiledFunction { &self.functions[func] } @@ -67,7 +71,7 @@ impl Compilation { pub fn get_jt_offsets(&self) -> PrimaryMap { self.functions .iter() - .map(|(_, code_and_jt)| code_and_jt.jt_offsets.clone()) + .map(|(_, func)| func.jt_offsets.clone()) .collect::>() } } @@ -88,7 +92,7 @@ pub struct Iter<'a> { } impl<'a> Iterator for Iter<'a> { - type Item = &'a CodeAndJTOffsets; + type Item = &'a CompiledFunction; fn next(&mut self) -> Option { self.iterator.next().map(|(_, b)| b) diff --git a/wasmtime-environ/src/cranelift.rs b/wasmtime-environ/src/cranelift.rs index 02002b0853..e96b6d1e3d 100644 --- a/wasmtime-environ/src/cranelift.rs +++ b/wasmtime-environ/src/cranelift.rs @@ -5,7 +5,7 @@ use crate::address_map::{ }; use crate::cache::{ModuleCacheData, ModuleCacheEntry}; use crate::compilation::{ - CodeAndJTOffsets, Compilation, CompileError, Relocation, RelocationTarget, Relocations, + Compilation, CompileError, CompiledFunction, Relocation, RelocationTarget, Relocations, TrapInformation, Traps, }; use crate::func_environ::{ @@ -235,6 +235,7 @@ impl crate::compilation::Compiler for Cranelift { )?; let mut code_buf: Vec = Vec::new(); + let mut unwind_info = Vec::new(); let mut reloc_sink = RelocSink::new(func_index); let mut trap_sink = TrapSink::new(); let mut stackmap_sink = binemit::NullStackmapSink {}; @@ -246,7 +247,7 @@ impl crate::compilation::Compiler for Cranelift { &mut stackmap_sink, )?; - let jt_offsets = context.func.jt_offsets.clone(); + context.emit_unwind_info(isa, &mut unwind_info); let address_transform = if generate_debug_info { let body_len = code_buf.len(); @@ -261,16 +262,15 @@ impl crate::compilation::Compiler for Cranelift { None }; - let stack_slots = context.func.stack_slots.clone(); - Ok(( code_buf, - jt_offsets, + context.func.jt_offsets, reloc_sink.func_relocs, address_transform, ranges, - stack_slots, + context.func.stack_slots, trap_sink.traps, + unwind_info, )) }, ) @@ -285,10 +285,12 @@ impl crate::compilation::Compiler for Cranelift { ranges, sss, function_traps, + unwind_info, )| { - functions.push(CodeAndJTOffsets { + functions.push(CompiledFunction { body: function, jt_offsets: func_jt_offsets, + unwind_info, }); relocations.push(relocs); if let Some(address_transform) = address_transform { diff --git a/wasmtime-environ/src/lib.rs b/wasmtime-environ/src/lib.rs index c23e97b357..8c27157173 100644 --- a/wasmtime-environ/src/lib.rs +++ b/wasmtime-environ/src/lib.rs @@ -46,8 +46,8 @@ pub use crate::address_map::{ }; pub use crate::cache::{create_new_config as cache_create_new_config, init as cache_init}; pub use crate::compilation::{ - Compilation, CompileError, Compiler, Relocation, RelocationTarget, Relocations, - TrapInformation, Traps, + Compilation, CompileError, CompiledFunction, Compiler, Relocation, RelocationTarget, + Relocations, TrapInformation, Traps, }; pub use crate::cranelift::Cranelift; pub use crate::func_environ::BuiltinFunctionIndex; diff --git a/wasmtime-environ/src/lightbeam.rs b/wasmtime-environ/src/lightbeam.rs index 4407d0b35f..23e1c013bb 100644 --- a/wasmtime-environ/src/lightbeam.rs +++ b/wasmtime-environ/src/lightbeam.rs @@ -65,10 +65,11 @@ impl crate::compilation::Compiler for Lightbeam { // TODO pass jump table offsets to Compilation::from_buffer() when they // are implemented in lightbeam -- using empty set of offsets for now. + // TODO: pass an empty range for the unwind information until lightbeam emits it let code_section_ranges_and_jt = code_section .funcs() .into_iter() - .map(|r| (r, SecondaryMap::new())); + .map(|r| (r, SecondaryMap::new(), 0..0)); Ok(( Compilation::from_buffer(code_section.buffer().to_vec(), code_section_ranges_and_jt), diff --git a/wasmtime-jit/Cargo.toml b/wasmtime-jit/Cargo.toml index 059fae0472..96d2e3c4ed 100644 --- a/wasmtime-jit/Cargo.toml +++ b/wasmtime-jit/Cargo.toml @@ -25,6 +25,9 @@ target-lexicon = { version = "0.9.0", default-features = false } hashbrown = { version = "0.6.0", optional = true } wasmparser = { version = "0.39.2", default-features = false } +[target.'cfg(target_os = "windows")'.dependencies] +winapi = { version = "0.3.7", features = ["winnt", "impl-default"] } + [features] default = ["std"] std = ["cranelift-codegen/std", "cranelift-wasm/std", "wasmtime-environ/std", "wasmtime-debug/std", "wasmtime-runtime/std", "wasmparser/std"] diff --git a/wasmtime-jit/src/code_memory.rs b/wasmtime-jit/src/code_memory.rs index 4a9b3a3142..9cd3f5a296 100644 --- a/wasmtime-jit/src/code_memory.rs +++ b/wasmtime-jit/src/code_memory.rs @@ -1,16 +1,18 @@ //! Memory management for executable code. +use crate::function_table::FunctionTable; use alloc::boxed::Box; use alloc::string::String; use alloc::vec::Vec; use core::{cmp, mem}; use region; +use wasmtime_environ::{Compilation, CompiledFunction}; use wasmtime_runtime::{Mmap, VMFunctionBody}; /// Memory manager for executable code. pub struct CodeMemory { - current: Mmap, - mmaps: Vec, + current: (Mmap, FunctionTable), + mmaps: Vec<(Mmap, FunctionTable)>, position: usize, published: usize, } @@ -19,30 +21,144 @@ impl CodeMemory { /// Create a new `CodeMemory` instance. pub fn new() -> Self { Self { - current: Mmap::new(), + current: (Mmap::new(), FunctionTable::new()), mmaps: Vec::new(), position: 0, published: 0, } } + /// Allocate a continuous memory block for a single compiled function. + /// 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_for_function( + &mut self, + func: &CompiledFunction, + ) -> Result<&mut [VMFunctionBody], String> { + let size = Self::function_allocation_size(func); + + let start = self.position as u32; + let (buf, table) = self.allocate(size)?; + + let (_, _, _, vmfunc) = Self::copy_function(func, start, buf, table); + + Ok(vmfunc) + } + + /// Allocate a continuous memory block for a compilation. + /// + /// Allocates memory for both the function bodies as well as function unwind data. + pub fn allocate_for_compilation( + &mut self, + compilation: &Compilation, + ) -> Result, String> { + let total_len = compilation + .into_iter() + .fold(0, |acc, func| acc + Self::function_allocation_size(func)); + + let mut start = self.position as u32; + let (mut buf, mut table) = self.allocate(total_len)?; + let mut result = Vec::with_capacity(compilation.len()); + + for func in compilation.into_iter() { + let (next_start, next_buf, next_table, vmfunc) = + Self::copy_function(func, start, buf, table); + + result.push(vmfunc); + + start = next_start; + buf = next_buf; + table = next_table; + } + + Ok(result.into_boxed_slice()) + } + + /// Make all allocated memory executable. + pub fn publish(&mut self) { + self.push_current(0) + .expect("failed to push current memory map"); + + for (m, t) in &mut self.mmaps[self.published..] { + if m.len() != 0 { + unsafe { + region::protect(m.as_mut_ptr(), m.len(), region::Protection::ReadExecute) + } + .expect("unable to make memory readonly and executable"); + } + + t.publish(m.as_ptr() as u64) + .expect("failed to publish function table"); + } + + self.published = self.mmaps.len(); + } + /// Allocate `size` bytes of memory which can be made executable later by /// calling `publish()`. Note that we allocate the memory as writeable so /// that it can be written to and patched, though we make it readonly before /// actually executing from it. /// /// TODO: Add an alignment flag. - 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_at_least(cmp::max(0x10000, size))?, - )); - self.position = 0; + fn allocate(&mut self, size: usize) -> Result<(&mut [u8], &mut FunctionTable), String> { + if self.current.0.len() - self.position < size { + self.push_current(cmp::max(0x10000, size))?; } + let old_position = self.position; self.position += size; - Ok(&mut self.current.as_mut_slice()[old_position..self.position]) + + Ok(( + &mut self.current.0.as_mut_slice()[old_position..self.position], + &mut self.current.1, + )) + } + + /// Calculates the allocation size of the given compiled function. + fn function_allocation_size(func: &CompiledFunction) -> usize { + if func.unwind_info.is_empty() { + func.body.len() + } else { + // Account for necessary unwind information alignment padding (32-bit) + ((func.body.len() + 3) & !3) + func.unwind_info.len() + } + } + + /// Copies the data of the compiled function to the given buffer. + /// + /// This will also add the function to the current function table. + fn copy_function<'a>( + func: &CompiledFunction, + func_start: u32, + buf: &'a mut [u8], + table: &'a mut FunctionTable, + ) -> ( + u32, + &'a mut [u8], + &'a mut FunctionTable, + &'a mut [VMFunctionBody], + ) { + let func_end = func_start + (func.body.len() as u32); + + let (body, remainder) = buf.split_at_mut(func.body.len()); + body.copy_from_slice(&func.body); + let vmfunc = Self::view_as_mut_vmfunc_slice(body); + + if func.unwind_info.is_empty() { + return (func_end, remainder, table, vmfunc); + } + + // Keep unwind information 32-bit aligned (round up to the nearest 4 byte boundary) + let padding = ((func.body.len() + 3) & !3) - func.body.len(); + let (unwind, remainder) = remainder.split_at_mut(padding + func.unwind_info.len()); + unwind[padding..].copy_from_slice(&func.unwind_info); + + let unwind_start = func_end + (padding as u32); + let unwind_end = unwind_start + (func.unwind_info.len() as u32); + + table.add_function(func_start, func_end, unwind_start); + + (unwind_end, remainder, table, vmfunc) } /// Convert mut a slice from u8 to VMFunctionBody. @@ -52,51 +168,28 @@ impl CodeMemory { unsafe { &mut *body_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_byte_slice( - &mut self, - slice: &[u8], - ) -> Result<&mut [VMFunctionBody], String> { - let new = self.allocate(slice.len())?; - new.copy_from_slice(slice); - Ok(Self::view_as_mut_vmfunc_slice(new)) - } + /// Pushes the current Mmap (and function table) and allocates a new Mmap of the given size. + fn push_current(&mut self, new_size: usize) -> Result<(), String> { + let previous = mem::replace( + &mut self.current, + ( + if new_size == 0 { + Mmap::new() + } else { + Mmap::with_at_least(cmp::max(0x10000, new_size))? + }, + FunctionTable::new(), + ), + ); - /// Allocate enough continuous memory block for multiple code blocks. See also - /// allocate_copy_of_byte_slice. - pub fn allocate_copy_of_byte_slices( - &mut self, - slices: &[&[u8]], - ) -> Result, String> { - let total_len = slices.into_iter().fold(0, |acc, slice| acc + slice.len()); - let new = self.allocate(total_len)?; - let mut tail = new; - let mut result = Vec::with_capacity(slices.len()); - for slice in slices { - let (block, next_tail) = tail.split_at_mut(slice.len()); - block.copy_from_slice(slice); - tail = next_tail; - result.push(Self::view_as_mut_vmfunc_slice(block)); + if previous.0.len() > 0 { + self.mmaps.push(previous); + } else { + assert!(previous.1.len() == 0); } - Ok(result.into_boxed_slice()) - } - /// 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.len() != 0 { - unsafe { - region::protect(m.as_mut_ptr(), m.len(), region::Protection::ReadExecute) - } - .expect("unable to make memory readonly and executable"); - } - } - self.published = self.mmaps.len(); + Ok(()) } } diff --git a/wasmtime-jit/src/compiler.rs b/wasmtime-jit/src/compiler.rs index a54b640d84..993ba050e6 100644 --- a/wasmtime-jit/src/compiler.rs +++ b/wasmtime-jit/src/compiler.rs @@ -17,8 +17,8 @@ use cranelift_frontend::{FunctionBuilder, FunctionBuilderContext}; use cranelift_wasm::{DefinedFuncIndex, DefinedMemoryIndex, ModuleTranslationState}; use wasmtime_debug::{emit_debugsections_image, DebugInfoData}; use wasmtime_environ::{ - Compilation, CompileError, Compiler as _C, FunctionBodyData, Module, ModuleVmctxInfo, - Relocations, Traps, Tunables, VMOffsets, + Compilation, CompileError, CompiledFunction, Compiler as _C, FunctionBodyData, Module, + ModuleVmctxInfo, Relocations, Traps, Tunables, VMOffsets, }; use wasmtime_runtime::{ get_mut_trap_registry, InstantiationError, SignatureRegistry, TrapRegistrationGuard, @@ -323,7 +323,8 @@ fn make_trampoline( builder.finalize() } - let mut code_buf: Vec = Vec::new(); + let mut code_buf = Vec::new(); + let mut unwind_info = Vec::new(); let mut reloc_sink = RelocSink {}; let mut trap_sink = binemit::NullTrapSink {}; let mut stackmap_sink = binemit::NullStackmapSink {}; @@ -337,8 +338,14 @@ fn make_trampoline( ) .map_err(|error| SetupError::Compile(CompileError::Codegen(error)))?; + context.emit_unwind_info(isa, &mut unwind_info); + Ok(code_memory - .allocate_copy_of_byte_slice(&code_buf) + .allocate_for_function(&CompiledFunction { + body: code_buf, + jt_offsets: context.func.jt_offsets, + unwind_info, + }) .map_err(|message| SetupError::Instantiate(InstantiationError::Resource(message)))? .as_ptr()) } @@ -347,14 +354,8 @@ fn allocate_functions( code_memory: &mut CodeMemory, compilation: &Compilation, ) -> Result, String> { - // Allocate code for all function in one continuous memory block. - // First, collect all function bodies into vector to pass to the - // allocate_copy_of_byte_slices. - let bodies = compilation - .into_iter() - .map(|code_and_jt| &code_and_jt.body[..]) - .collect::>(); - let fat_ptrs = code_memory.allocate_copy_of_byte_slices(&bodies)?; + let fat_ptrs = code_memory.allocate_for_compilation(compilation)?; + // Second, create a PrimaryMap from result vector of pointers. let mut result = PrimaryMap::with_capacity(compilation.len()); for i in 0..fat_ptrs.len() { diff --git a/wasmtime-jit/src/function_table.rs b/wasmtime-jit/src/function_table.rs new file mode 100644 index 0000000000..3ec8bcd0da --- /dev/null +++ b/wasmtime-jit/src/function_table.rs @@ -0,0 +1,134 @@ +//! Runtime function table. +//! +//! This module is primarily used to track JIT functions on Windows for stack walking and unwind. + +/// Represents a runtime function table. +/// +/// The runtime function table is not implemented for non-Windows target platforms. +#[cfg(not(target_os = "windows"))] +pub(crate) struct FunctionTable; + +#[cfg(not(target_os = "windows"))] +impl FunctionTable { + /// Creates a new function table. + pub fn new() -> Self { + Self + } + + /// Returns the number of functions in the table, also referred to as its 'length'. + /// + /// For non-Windows platforms, the table will always be empty. + pub fn len(&self) -> usize { + 0 + } + + /// Adds a function to the table based off of the start offset, end offset, and unwind offset. + /// + /// The offsets are from the "module base", which is provided when the table is published. + /// + /// For non-Windows platforms, this is a no-op. + pub fn add_function(&mut self, _start: u32, _end: u32, _unwind: u32) {} + + /// Publishes the function table using the given base address. + /// + /// A published function table will automatically be deleted when it is dropped. + /// + /// For non-Windows platforms, this is a no-op. + pub fn publish(&mut self, _base_address: u64) -> Result<(), String> { + Ok(()) + } +} + +/// Represents a runtime function table. +/// +/// This is used to register JIT code with the operating system to enable stack walking and unwinding. +#[cfg(all(target_os = "windows", target_arch = "x86_64"))] +pub(crate) struct FunctionTable { + functions: Vec, + published: bool, +} + +#[cfg(all(target_os = "windows", target_arch = "x86_64"))] +impl FunctionTable { + /// Creates a new function table. + pub fn new() -> Self { + Self { + functions: Vec::new(), + published: false, + } + } + + /// Returns the number of functions in the table, also referred to as its 'length'. + pub fn len(&self) -> usize { + self.functions.len() + } + + /// Adds a function to the table based off of the start offset, end offset, and unwind offset. + /// + /// The offsets are from the "module base", which is provided when the table is published. + pub fn add_function(&mut self, start: u32, end: u32, unwind: u32) { + use winapi::um::winnt; + + assert!(!self.published, "table has already been published"); + + let mut entry = winnt::RUNTIME_FUNCTION::default(); + + entry.BeginAddress = start; + entry.EndAddress = end; + + unsafe { + *entry.u.UnwindInfoAddress_mut() = unwind; + } + + self.functions.push(entry); + } + + /// Publishes the function table using the given base address. + /// + /// A published function table will automatically be deleted when it is dropped. + pub fn publish(&mut self, base_address: u64) -> Result<(), String> { + use winapi::um::winnt; + + if self.published { + return Err("function table was already published".into()); + } + + self.published = true; + + if self.functions.is_empty() { + return Ok(()); + } + + unsafe { + // Windows heap allocations are 32-bit aligned, but assert just in case + assert!( + (self.functions.as_mut_ptr() as u64) % 4 == 0, + "function table allocation was not aligned" + ); + + if winnt::RtlAddFunctionTable( + self.functions.as_mut_ptr(), + self.functions.len() as u32, + base_address, + ) == 0 + { + return Err("failed to add function table".into()); + } + } + + Ok(()) + } +} + +#[cfg(target_os = "windows")] +impl Drop for FunctionTable { + fn drop(&mut self) { + use winapi::um::winnt; + + if self.published { + unsafe { + winnt::RtlDeleteFunctionTable(self.functions.as_mut_ptr()); + } + } + } +} diff --git a/wasmtime-jit/src/lib.rs b/wasmtime-jit/src/lib.rs index 79aafb456f..7400aa627a 100644 --- a/wasmtime-jit/src/lib.rs +++ b/wasmtime-jit/src/lib.rs @@ -34,6 +34,7 @@ mod action; mod code_memory; mod compiler; mod context; +mod function_table; mod instantiate; mod link; mod namespace; diff --git a/wasmtime-runtime/signalhandlers/SignalHandlers.cpp b/wasmtime-runtime/signalhandlers/SignalHandlers.cpp index f82fba6b27..ef9200a440 100644 --- a/wasmtime-runtime/signalhandlers/SignalHandlers.cpp +++ b/wasmtime-runtime/signalhandlers/SignalHandlers.cpp @@ -404,7 +404,7 @@ static __attribute__ ((warn_unused_result)) #endif bool -HandleTrap(CONTEXT* context) +HandleTrap(CONTEXT* context, bool reset_guard_page) { assert(sAlreadyHandlingTrap); @@ -412,7 +412,7 @@ HandleTrap(CONTEXT* context) return false; } - RecordTrap(ContextToPC(context)); + RecordTrap(ContextToPC(context), reset_guard_page); // Unwind calls longjmp, so it doesn't run the automatic // sAlreadhHanldingTrap cleanups, so reset it manually before doing @@ -467,7 +467,8 @@ WasmTrapHandler(LPEXCEPTION_POINTERS exception) return EXCEPTION_CONTINUE_SEARCH; } - if (!HandleTrap(exception->ContextRecord)) { + if (!HandleTrap(exception->ContextRecord, + record->ExceptionCode == EXCEPTION_STACK_OVERFLOW)) { return EXCEPTION_CONTINUE_SEARCH; } @@ -549,7 +550,7 @@ HandleMachException(const ExceptionRequest& request) { AutoHandlingTrap aht; - if (!HandleTrap(&context)) { + if (!HandleTrap(&context, false)) { return false; } } @@ -632,7 +633,7 @@ WasmTrapHandler(int signum, siginfo_t* info, void* context) if (!sAlreadyHandlingTrap) { AutoHandlingTrap aht; assert(signum == SIGSEGV || signum == SIGBUS || signum == SIGFPE || signum == SIGILL); - if (HandleTrap(static_cast(context))) { + if (HandleTrap(static_cast(context), false)) { return; } } diff --git a/wasmtime-runtime/signalhandlers/SignalHandlers.hpp b/wasmtime-runtime/signalhandlers/SignalHandlers.hpp index 4c5c4d1e4c..cd0c9e4cdb 100644 --- a/wasmtime-runtime/signalhandlers/SignalHandlers.hpp +++ b/wasmtime-runtime/signalhandlers/SignalHandlers.hpp @@ -13,7 +13,7 @@ extern "C" { int8_t CheckIfTrapAtAddress(const uint8_t* pc); // Record the Trap code and wasm bytecode offset in TLS somewhere -void RecordTrap(const uint8_t* pc); +void RecordTrap(const uint8_t* pc, bool reset_guard_page); void* EnterScope(void*); void LeaveScope(void*); diff --git a/wasmtime-runtime/src/traphandlers.rs b/wasmtime-runtime/src/traphandlers.rs index 8c8b32b7e4..5ddb3563c5 100644 --- a/wasmtime-runtime/src/traphandlers.rs +++ b/wasmtime-runtime/src/traphandlers.rs @@ -21,6 +21,7 @@ extern "C" { thread_local! { static RECORDED_TRAP: Cell> = Cell::new(None); static JMP_BUF: Cell<*const u8> = Cell::new(ptr::null()); + static RESET_GUARD_PAGE: Cell = Cell::new(false); } /// Check if there is a trap at given PC @@ -40,7 +41,7 @@ pub extern "C" fn CheckIfTrapAtAddress(_pc: *const u8) -> i8 { #[doc(hidden)] #[allow(non_snake_case)] #[no_mangle] -pub extern "C" fn RecordTrap(pc: *const u8) { +pub extern "C" fn RecordTrap(pc: *const u8, reset_guard_page: bool) { // TODO: please see explanation in CheckIfTrapAtAddress. let registry = get_trap_registry(); let trap_desc = registry @@ -49,6 +50,11 @@ pub extern "C" fn RecordTrap(pc: *const u8) { source_loc: ir::SourceLoc::default(), trap_code: ir::TrapCode::StackOverflow, }); + + if reset_guard_page { + RESET_GUARD_PAGE.with(|v| v.set(true)); + } + RECORDED_TRAP.with(|data| { assert_eq!( data.get(), @@ -77,9 +83,32 @@ pub extern "C" fn GetScope() -> *const u8 { #[allow(non_snake_case)] #[no_mangle] pub extern "C" fn LeaveScope(ptr: *const u8) { + RESET_GUARD_PAGE.with(|v| { + if v.get() { + reset_guard_page(); + v.set(false); + } + }); + JMP_BUF.with(|buf| buf.set(ptr)) } +#[cfg(target_os = "windows")] +fn reset_guard_page() { + extern "C" { + fn _resetstkoflw() -> winapi::ctypes::c_int; + } + + // We need to restore guard page under stack to handle future stack overflows properly. + // https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/resetstkoflw?view=vs-2019 + if unsafe { _resetstkoflw() } == 0 { + panic!("failed to restore stack guard page"); + } +} + +#[cfg(not(target_os = "windows"))] +fn reset_guard_page() {} + fn trap_message() -> String { let trap_desc = RECORDED_TRAP .with(|data| data.replace(None)) From 55eb06ecc208813cf3305c268926f7c6c585dc08 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 7 Nov 2019 07:38:35 -0800 Subject: [PATCH 2/8] Reformat with new stable rustfmt Fixes CI! --- lightbeam/src/microwasm.rs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/lightbeam/src/microwasm.rs b/lightbeam/src/microwasm.rs index 6d485de51c..dd304e03ef 100644 --- a/lightbeam/src/microwasm.rs +++ b/lightbeam/src/microwasm.rs @@ -1179,8 +1179,12 @@ where sig!((ty) -> (ty)) } - WasmOperator::GetGlobal { global_index } => sig!(() -> (self.module.global_type(*global_index).to_microwasm_type())), - WasmOperator::SetGlobal { global_index } => sig!((self.module.global_type(*global_index).to_microwasm_type()) -> ()), + WasmOperator::GetGlobal { global_index } => { + sig!(() -> (self.module.global_type(*global_index).to_microwasm_type())) + } + WasmOperator::SetGlobal { global_index } => { + sig!((self.module.global_type(*global_index).to_microwasm_type()) -> ()) + } WasmOperator::F32Load { .. } => sig!((I32) -> (F32)), WasmOperator::F64Load { .. } => sig!((I32) -> (F64)), @@ -1259,8 +1263,12 @@ where | WasmOperator::F64Le | WasmOperator::F64Ge => sig!((F64, F64) -> (I32)), - WasmOperator::I32Clz | WasmOperator::I32Ctz | WasmOperator::I32Popcnt => sig!((I32) -> (I32)), - WasmOperator::I64Clz | WasmOperator::I64Ctz | WasmOperator::I64Popcnt => sig!((I64) -> (I64)), + WasmOperator::I32Clz | WasmOperator::I32Ctz | WasmOperator::I32Popcnt => { + sig!((I32) -> (I32)) + } + WasmOperator::I64Clz | WasmOperator::I64Ctz | WasmOperator::I64Popcnt => { + sig!((I64) -> (I64)) + } WasmOperator::I32Add | WasmOperator::I32Sub From 6632a7da374711725018029972902ca130b1b23b Mon Sep 17 00:00:00 2001 From: Yury Delendik Date: Thu, 7 Nov 2019 11:39:23 -0600 Subject: [PATCH 3/8] [wasmtime-api] reduce examples complexity: hostref for store/engine (#489) * reduce amount of store.clone() * use HostRef Engine as ref / use Engine::default() --- src/bin/wasmtime.rs | 24 ++++++------- wasmtime-api/examples/gcd.rs | 6 ++-- wasmtime-api/examples/hello.rs | 11 +++--- wasmtime-api/examples/memory.rs | 14 ++++---- wasmtime-api/examples/multi.rs | 11 +++--- wasmtime-api/src/callable.rs | 4 +-- wasmtime-api/src/externals.rs | 38 ++++++++++----------- wasmtime-api/src/instance.rs | 10 +++--- wasmtime-api/src/module.rs | 4 +-- wasmtime-api/src/runtime.rs | 4 +-- wasmtime-api/src/values.rs | 2 +- wasmtime-api/src/wasm.rs | 27 ++++++--------- wasmtime-api/tests/import_calling_export.rs | 9 +++-- 13 files changed, 76 insertions(+), 88 deletions(-) diff --git a/src/bin/wasmtime.rs b/src/bin/wasmtime.rs index 0b26977712..3bf755a55f 100644 --- a/src/bin/wasmtime.rs +++ b/src/bin/wasmtime.rs @@ -270,14 +270,14 @@ fn main() -> Result<()> { strategy, ); let engine = HostRef::new(Engine::new(config)); - let store = HostRef::new(Store::new(engine)); + let store = HostRef::new(Store::new(&engine)); let mut module_registry = HashMap::new(); // Make spectest available by default. module_registry.insert( "spectest".to_owned(), - Instance::from_handle(store.clone(), instantiate_spectest()?)?, + Instance::from_handle(&store, instantiate_spectest()?)?, ); // Make wasi available by default. @@ -301,36 +301,36 @@ fn main() -> Result<()> { module_registry.insert( "wasi_unstable".to_owned(), - Instance::from_handle(store.clone(), wasi.clone())?, + Instance::from_handle(&store, wasi.clone())?, ); module_registry.insert( "wasi_unstable_preview0".to_owned(), - Instance::from_handle(store.clone(), wasi)?, + Instance::from_handle(&store, wasi)?, ); // Load the preload wasm modules. for filename in &args.flag_preload { let path = Path::new(&filename); - instantiate_module(store.clone(), &module_registry, path) + instantiate_module(&store, &module_registry, path) .with_context(|| format!("failed to process preload at `{}`", path.display()))?; } // Load the main wasm module. let path = Path::new(&args.arg_file); - handle_module(store, &module_registry, &args, path) + handle_module(&store, &module_registry, &args, path) .with_context(|| format!("failed to process main module `{}`", path.display()))?; Ok(()) } fn instantiate_module( - store: HostRef, + store: &HostRef, module_registry: &HashMap)>, path: &Path, ) -> Result<(HostRef, HostRef, Vec)> { // Read the wasm module binary either as `*.wat` or a raw binary let data = wat::parse_file(path.to_path_buf())?; - let module = HostRef::new(Module::new(store.clone(), &data)?); + let module = HostRef::new(Module::new(store, &data)?); // Resolve import using module_registry. let imports = module @@ -356,18 +356,18 @@ fn instantiate_module( }) .collect::, _>>()?; - let instance = HostRef::new(Instance::new(store.clone(), module.clone(), &imports)?); + let instance = HostRef::new(Instance::new(store, &module, &imports)?); Ok((instance, module, data)) } fn handle_module( - store: HostRef, + store: &HostRef, module_registry: &HashMap)>, args: &Args, path: &Path, ) -> Result<()> { - let (instance, _module, data) = instantiate_module(store.clone(), module_registry, path)?; + let (instance, _module, data) = instantiate_module(store, module_registry, path)?; // If a function to invoke was given, invoke it. if let Some(f) = &args.flag_invoke { @@ -379,7 +379,7 @@ fn handle_module( } fn invoke_export( - store: HostRef, + store: &HostRef, instance: HostRef, data: &ModuleData, name: &str, diff --git a/wasmtime-api/examples/gcd.rs b/wasmtime-api/examples/gcd.rs index 0e71279496..c15df5a53f 100644 --- a/wasmtime-api/examples/gcd.rs +++ b/wasmtime-api/examples/gcd.rs @@ -10,10 +10,10 @@ fn main() -> Result<()> { // Instantiate engine and store. let engine = HostRef::new(Engine::default()); - let store = HostRef::new(Store::new(engine)); + let store = HostRef::new(Store::new(&engine)); // Load a module. - let module = HostRef::new(Module::new(store.clone(), &wasm)?); + let module = HostRef::new(Module::new(&store, &wasm)?); // Find index of the `gcd` export. let gcd_index = module @@ -26,7 +26,7 @@ fn main() -> Result<()> { .0; // Instantiate the module. - let instance = HostRef::new(Instance::new(store.clone(), module, &[])?); + let instance = HostRef::new(Instance::new(&store, &module, &[])?); // Invoke `gcd` export let gcd = instance.borrow().exports()[gcd_index] diff --git a/wasmtime-api/examples/hello.rs b/wasmtime-api/examples/hello.rs index 1c1bc8ba8a..4f367cdd70 100644 --- a/wasmtime-api/examples/hello.rs +++ b/wasmtime-api/examples/hello.rs @@ -21,8 +21,8 @@ impl Callable for HelloCallback { fn main() -> Result<()> { // Initialize. println!("Initializing..."); - let engine = HostRef::new(Engine::new(Config::default())); - let store = HostRef::new(Store::new(engine)); + let engine = HostRef::new(Engine::default()); + let store = HostRef::new(Store::new(&engine)); // Load binary. println!("Loading binary..."); @@ -30,19 +30,18 @@ fn main() -> Result<()> { // Compile. println!("Compiling module..."); - let module = - HostRef::new(Module::new(store.clone(), &binary).context("> Error compiling module!")?); + let module = HostRef::new(Module::new(&store, &binary).context("> Error compiling module!")?); // Create external print functions. println!("Creating callback..."); let hello_type = FuncType::new(Box::new([]), Box::new([])); - let hello_func = HostRef::new(Func::new(store.clone(), hello_type, Rc::new(HelloCallback))); + let hello_func = HostRef::new(Func::new(&store, hello_type, Rc::new(HelloCallback))); // Instantiate. println!("Instantiating module..."); let imports = vec![hello_func.into()]; let instance = HostRef::new( - Instance::new(store.clone(), module, imports.as_slice()) + Instance::new(&store, &module, imports.as_slice()) .context("> Error instantiating module!")?, ); diff --git a/wasmtime-api/examples/memory.rs b/wasmtime-api/examples/memory.rs index 7d9be3099c..6ca8f9c125 100644 --- a/wasmtime-api/examples/memory.rs +++ b/wasmtime-api/examples/memory.rs @@ -64,8 +64,8 @@ macro_rules! call { fn main() -> Result<(), Error> { // Initialize. println!("Initializing..."); - let engine = HostRef::new(Engine::new(Config::default())); - let store = HostRef::new(Store::new(engine)); + let engine = HostRef::new(Engine::default()); + let store = HostRef::new(Store::new(&engine)); // Load binary. println!("Loading binary..."); @@ -73,14 +73,12 @@ fn main() -> Result<(), Error> { // Compile. println!("Compiling module..."); - let module = - HostRef::new(Module::new(store.clone(), &binary).context("> Error compiling module!")?); + let module = HostRef::new(Module::new(&store, &binary).context("> Error compiling module!")?); // Instantiate. println!("Instantiating module..."); - let instance = HostRef::new( - Instance::new(store.clone(), module, &[]).context("> Error instantiating module!")?, - ); + let instance = + HostRef::new(Instance::new(&store, &module, &[]).context("> Error instantiating module!")?); // Extract export. println!("Extracting export..."); @@ -141,7 +139,7 @@ fn main() -> Result<(), Error> { // TODO(wasm+): Once Wasm allows multiple memories, turn this into import. println!("Creating stand-alone memory..."); let memorytype = MemoryType::new(Limits::new(5, 5)); - let mut memory2 = Memory::new(store.clone(), memorytype); + let mut memory2 = Memory::new(&store, memorytype); check!(memory2.size(), 5u32); check!(memory2.grow(1), false); check!(memory2.grow(0), true); diff --git a/wasmtime-api/examples/multi.rs b/wasmtime-api/examples/multi.rs index 4bda0b48c3..98a80f5087 100644 --- a/wasmtime-api/examples/multi.rs +++ b/wasmtime-api/examples/multi.rs @@ -24,8 +24,8 @@ impl Callable for Callback { fn main() -> Result<()> { // Initialize. println!("Initializing..."); - let engine = HostRef::new(Engine::new(Config::default())); - let store = HostRef::new(Store::new(engine)); + let engine = HostRef::new(Engine::default()); + let store = HostRef::new(Store::new(&engine)); // Load binary. println!("Loading binary..."); @@ -33,8 +33,7 @@ fn main() -> Result<()> { // Compile. println!("Compiling module..."); - let module = - HostRef::new(Module::new(store.clone(), &binary).context("Error compiling module!")?); + let module = HostRef::new(Module::new(&store, &binary).context("Error compiling module!")?); // Create external print functions. println!("Creating callback..."); @@ -42,13 +41,13 @@ fn main() -> Result<()> { Box::new([ValType::I32, ValType::I64]), Box::new([ValType::I64, ValType::I32]), ); - let callback_func = HostRef::new(Func::new(store.clone(), callback_type, Rc::new(Callback))); + let callback_func = HostRef::new(Func::new(&store, callback_type, Rc::new(Callback))); // Instantiate. println!("Instantiating module..."); let imports = vec![callback_func.into()]; let instance = HostRef::new( - Instance::new(store.clone(), module, imports.as_slice()) + Instance::new(&store, &module, imports.as_slice()) .context("Error instantiating module!")?, ); diff --git a/wasmtime-api/src/callable.rs b/wasmtime-api/src/callable.rs index 0de7726fb5..a5838f6b2b 100644 --- a/wasmtime-api/src/callable.rs +++ b/wasmtime-api/src/callable.rs @@ -34,9 +34,9 @@ pub(crate) struct WasmtimeFn { } impl WasmtimeFn { - pub fn new(store: HostRef, instance: InstanceHandle, export: Export) -> WasmtimeFn { + pub fn new(store: &HostRef, instance: InstanceHandle, export: Export) -> WasmtimeFn { WasmtimeFn { - store, + store: store.clone(), instance, export, } diff --git a/wasmtime-api/src/externals.rs b/wasmtime-api/src/externals.rs index fbdab1cfb6..56aca24ad5 100644 --- a/wasmtime-api/src/externals.rs +++ b/wasmtime-api/src/externals.rs @@ -66,7 +66,7 @@ impl Extern { } pub(crate) fn from_wasmtime_export( - store: HostRef, + store: &HostRef, instance_handle: InstanceHandle, export: wasmtime_runtime::Export, ) -> Extern { @@ -118,18 +118,18 @@ pub struct Func { } impl Func { - pub fn new(store: HostRef, ty: FuncType, callable: Rc) -> Self { + pub fn new(store: &HostRef, ty: FuncType, callable: Rc) -> Self { let callable = Rc::new(NativeCallable::new(callable, &ty, &store)); Func::from_wrapped(store, ty, callable) } fn from_wrapped( - store: HostRef, + store: &HostRef, r#type: FuncType, callable: Rc, ) -> Func { Func { - _store: store, + _store: store.clone(), callable, r#type, } @@ -159,7 +159,7 @@ impl Func { pub(crate) fn from_wasmtime_function( export: wasmtime_runtime::Export, - store: HostRef, + store: &HostRef, instance_handle: InstanceHandle, ) -> Self { let ty = if let wasmtime_runtime::Export::Function { signature, .. } = &export { @@ -167,7 +167,7 @@ impl Func { } else { panic!("expected function export") }; - let callable = WasmtimeFn::new(store.clone(), instance_handle, export.clone()); + let callable = WasmtimeFn::new(store, instance_handle, export.clone()); Func::from_wrapped(store, ty, Rc::new(callable)) } } @@ -187,11 +187,11 @@ pub struct Global { } impl Global { - pub fn new(store: HostRef, r#type: GlobalType, val: Val) -> Global { + pub fn new(store: &HostRef, r#type: GlobalType, val: Val) -> Global { let (wasmtime_export, wasmtime_state) = generate_global_export(&r#type, val).expect("generated global"); Global { - _store: store, + _store: store.clone(), r#type, wasmtime_export, wasmtime_state: Some(wasmtime_state), @@ -248,7 +248,7 @@ impl Global { pub(crate) fn from_wasmtime_global( export: wasmtime_runtime::Export, - store: HostRef, + store: &HostRef, ) -> Global { let global = if let wasmtime_runtime::Export::Global { ref global, .. } = export { global @@ -257,7 +257,7 @@ impl Global { }; let ty = GlobalType::from_cranelift_global(global.clone()); Global { - _store: store, + _store: store.clone(), r#type: ty, wasmtime_export: export, wasmtime_state: None, @@ -302,7 +302,7 @@ fn set_table_item( } impl Table { - pub fn new(store: HostRef, r#type: TableType, init: Val) -> Table { + pub fn new(store: &HostRef, r#type: TableType, init: Val) -> Table { match r#type.element() { ValType::FuncRef => (), _ => panic!("table is not for funcref"), @@ -317,7 +317,7 @@ impl Table { let len = unsafe { (*definition).current_elements }; for i in 0..len { let _success = - set_table_item(&mut wasmtime_handle, &store, index, i, init.clone()); + set_table_item(&mut wasmtime_handle, store, index, i, init.clone()); assert!(_success); } } @@ -325,7 +325,7 @@ impl Table { } Table { - store, + store: store.clone(), r#type, wasmtime_handle, wasmtime_export, @@ -387,7 +387,7 @@ impl Table { pub(crate) fn from_wasmtime_table( export: wasmtime_runtime::Export, - store: HostRef, + store: &HostRef, instance_handle: wasmtime_runtime::InstanceHandle, ) -> Table { let table = if let wasmtime_runtime::Export::Table { ref table, .. } = export { @@ -397,7 +397,7 @@ impl Table { }; let ty = TableType::from_cranelift_table(table.table.clone()); Table { - store, + store: store.clone(), r#type: ty, wasmtime_handle: instance_handle, wasmtime_export: export, @@ -413,11 +413,11 @@ pub struct Memory { } impl Memory { - pub fn new(store: HostRef, r#type: MemoryType) -> Memory { + pub fn new(store: &HostRef, r#type: MemoryType) -> Memory { let (wasmtime_handle, wasmtime_export) = generate_memory_export(&r#type).expect("generated memory"); Memory { - _store: store, + _store: store.clone(), r#type, wasmtime_handle, wasmtime_export, @@ -471,7 +471,7 @@ impl Memory { pub(crate) fn from_wasmtime_memory( export: wasmtime_runtime::Export, - store: HostRef, + store: &HostRef, instance_handle: wasmtime_runtime::InstanceHandle, ) -> Memory { let memory = if let wasmtime_runtime::Export::Memory { ref memory, .. } = export { @@ -481,7 +481,7 @@ impl Memory { }; let ty = MemoryType::from_cranelift_memory(memory.memory.clone()); Memory { - _store: store, + _store: store.clone(), r#type: ty, wasmtime_handle: instance_handle, wasmtime_export: export, diff --git a/wasmtime-api/src/instance.rs b/wasmtime-api/src/instance.rs index d8df3f8b89..0dcb420f15 100644 --- a/wasmtime-api/src/instance.rs +++ b/wasmtime-api/src/instance.rs @@ -61,8 +61,8 @@ pub struct Instance { impl Instance { pub fn new( - store: HostRef, - module: HostRef, + store: &HostRef, + module: &HostRef, externs: &[Extern], ) -> Result { let context = store.borrow_mut().context().clone(); @@ -84,7 +84,7 @@ impl Instance { let name = export.name().to_string(); let export = instance_handle.lookup(&name).expect("export"); exports.push(Extern::from_wasmtime_export( - store.clone(), + store, instance_handle.clone(), export, )); @@ -103,7 +103,7 @@ impl Instance { } pub fn from_handle( - store: HostRef, + store: &HostRef, instance_handle: InstanceHandle, ) -> Result<(Instance, HashMap)> { let contexts = HashSet::new(); @@ -121,7 +121,7 @@ impl Instance { } export_names_map.insert(name.to_owned(), exports.len()); exports.push(Extern::from_wasmtime_export( - store.clone(), + store, instance_handle.clone(), export.clone(), )); diff --git a/wasmtime-api/src/module.rs b/wasmtime-api/src/module.rs index b70183c00b..c7683a74f0 100644 --- a/wasmtime-api/src/module.rs +++ b/wasmtime-api/src/module.rs @@ -182,10 +182,10 @@ pub struct Module { } impl Module { - pub fn new(store: HostRef, binary: &[u8]) -> Result { + pub fn new(store: &HostRef, binary: &[u8]) -> Result { let (imports, exports) = read_imports_and_exports(binary)?; Ok(Module { - store, + store: store.clone(), binary: binary.into(), imports, exports, diff --git a/wasmtime-api/src/runtime.rs b/wasmtime-api/src/runtime.rs index e3e0f4359a..a5c35cc0f8 100644 --- a/wasmtime-api/src/runtime.rs +++ b/wasmtime-api/src/runtime.rs @@ -102,13 +102,13 @@ pub struct Store { } impl Store { - pub fn new(engine: HostRef) -> Store { + pub fn new(engine: &HostRef) -> Store { let flags = engine.borrow().config().flags().clone(); let features = engine.borrow().config().features().clone(); let debug_info = engine.borrow().config().debug_info(); let strategy = engine.borrow().config().strategy(); Store { - engine, + engine: engine.clone(), context: Context::create(flags, features, debug_info, strategy), global_exports: Rc::new(RefCell::new(HashMap::new())), signature_cache: HashMap::new(), diff --git a/wasmtime-api/src/values.rs b/wasmtime-api/src/values.rs index 4dc762767a..2329d980ca 100644 --- a/wasmtime-api/src/values.rs +++ b/wasmtime-api/src/values.rs @@ -230,6 +230,6 @@ pub(crate) fn from_checked_anyfunc( signature, vmctx: item.vmctx, }; - let f = Func::from_wasmtime_function(export, store.clone(), instance_handle); + let f = Func::from_wasmtime_function(export, store, instance_handle); Val::FuncRef(HostRef::new(f)) } diff --git a/wasmtime-api/src/wasm.rs b/wasmtime-api/src/wasm.rs index e9205b121f..ad4563372f 100644 --- a/wasmtime-api/src/wasm.rs +++ b/wasmtime-api/src/wasm.rs @@ -609,7 +609,7 @@ pub unsafe extern "C" fn wasm_func_new( ty: *const wasm_functype_t, callback: wasm_func_callback_t, ) -> *mut wasm_func_t { - let store = (*store).store.clone(); + let store = &(*store).store; let ty = (*ty).functype.clone(); let callback = Rc::new(callback); let func = Box::new(wasm_func_t { @@ -663,13 +663,13 @@ pub unsafe extern "C" fn wasm_instance_new( imports: *const *const wasm_extern_t, result: *mut *mut wasm_trap_t, ) -> *mut wasm_instance_t { - let store = (*store).store.clone(); + let store = &(*store).store; let mut externs: Vec = Vec::with_capacity((*module).imports.len()); for i in 0..(*module).imports.len() { let import = *imports.offset(i as isize); externs.push((*import).ext.clone()); } - let module = (*module).module.clone(); + let module = &(*module).module; match Instance::new(store, module, &externs) { Ok(instance) => { let instance = Box::new(wasm_instance_t { @@ -731,7 +731,7 @@ pub unsafe extern "C" fn wasm_module_new( binary: *const wasm_byte_vec_t, ) -> *mut wasm_module_t { let binary = (*binary).as_slice(); - let store = (*store).store.clone(); + let store = &(*store).store; let module = Module::new(store, binary).expect("module"); let imports = module .imports() @@ -766,9 +766,9 @@ pub unsafe extern "C" fn wasm_store_delete(store: *mut wasm_store_t) { #[no_mangle] pub unsafe extern "C" fn wasm_store_new(engine: *mut wasm_engine_t) -> *mut wasm_store_t { - let engine = (*engine).engine.clone(); + let engine = &(*engine).engine; let store = Box::new(wasm_store_t { - store: HostRef::new(Store::new(engine)), + store: HostRef::new(Store::new(&engine)), }); Box::into_raw(store) } @@ -804,7 +804,7 @@ pub unsafe extern "C" fn wasm_func_new_with_env( env: *mut ::core::ffi::c_void, finalizer: ::core::option::Option, ) -> *mut wasm_func_t { - let store = (*store).store.clone(); + let store = &(*store).store; let ty = (*ty).functype.clone(); let callback = Rc::new(CallbackWithEnv { callback, @@ -1327,7 +1327,7 @@ pub unsafe extern "C" fn wasm_global_new( val: *const wasm_val_t, ) -> *mut wasm_global_t { let global = HostRef::new(Global::new( - (*store).store.clone(), + &(*store).store, (*gt).globaltype.clone(), (*val).val(), )); @@ -1446,10 +1446,7 @@ pub unsafe extern "C" fn wasm_memory_new( store: *mut wasm_store_t, mt: *const wasm_memorytype_t, ) -> *mut wasm_memory_t { - let memory = HostRef::new(Memory::new( - (*store).store.clone(), - (*mt).memorytype.clone(), - )); + let memory = HostRef::new(Memory::new(&(*store).store, (*mt).memorytype.clone())); let m = Box::new(wasm_memory_t { memory, ext: None }); Box::into_raw(m) } @@ -1537,11 +1534,7 @@ pub unsafe extern "C" fn wasm_table_new( Val::AnyRef(AnyRef::Null) }; let t = Box::new(wasm_table_t { - table: HostRef::new(Table::new( - (*store).store.clone(), - (*tt).tabletype.clone(), - init, - )), + table: HostRef::new(Table::new(&(*store).store, (*tt).tabletype.clone(), init)), ext: None, }); Box::into_raw(t) diff --git a/wasmtime-api/tests/import_calling_export.rs b/wasmtime-api/tests/import_calling_export.rs index deee58ce4a..8e49387d85 100644 --- a/wasmtime-api/tests/import_calling_export.rs +++ b/wasmtime-api/tests/import_calling_export.rs @@ -25,10 +25,10 @@ fn test_import_calling_export() { } let engine = HostRef::new(Engine::new(Config::default())); - let store = HostRef::new(Store::new(engine)); + let store = HostRef::new(Store::new(&engine)); let module = HostRef::new( Module::new( - store.clone(), + &store, &read("tests/import_calling_export.wasm").expect("failed to read wasm file"), ) .expect("failed to create module"), @@ -39,15 +39,14 @@ fn test_import_calling_export() { }); let callback_func = HostRef::new(Func::new( - store.clone(), + &store, FuncType::new(Box::new([]), Box::new([])), callback.clone(), )); let imports = vec![callback_func.into()]; let instance = HostRef::new( - Instance::new(store.clone(), module, imports.as_slice()) - .expect("failed to instantiate module"), + Instance::new(&store, &module, imports.as_slice()).expect("failed to instantiate module"), ); let exports = Ref::map(instance.borrow(), |instance| instance.exports()); From d896cc34c2840403cb4b7adfec7477bb499d0b6b Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Thu, 7 Nov 2019 13:33:17 -0800 Subject: [PATCH 4/8] Remove an obsolete workaround. (#505) We needed a workaround when using wabt on old Linux distros due to using old versions of `strtof`. With the switch to `wat`, we no longer need these workarounds. --- Cargo.toml | 4 ---- build.rs | 25 ------------------------- 2 files changed, 29 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d4111d7472..603d5551f1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,10 +38,6 @@ libc = "0.2.60" rayon = "1.1" wasm-webidl-bindings = "0.6" -# build.rs tests whether to enable a workaround for the libc strtof function. -[target.'cfg(target_os = "linux")'.build-dependencies] -libc = "0.2.60" - [workspace] members = [ "misc/wasmtime-rust", diff --git a/build.rs b/build.rs index 86408d216b..4617b6ba87 100644 --- a/build.rs +++ b/build.rs @@ -210,30 +210,5 @@ fn ignore(testsuite: &str, testname: &str, strategy: &str) -> bool { }; } - #[cfg(target_os = "linux")] - { - // Test whether the libc correctly parses the following constant; if so, - // we can run the "const" test. If not, the "const" test will fail, since - // we use wabt to parse the tests and wabt uses strtof. - extern "C" { - pub fn strtof(s: *const libc::c_char, endp: *mut *mut libc::c_char) -> libc::c_float; - } - if unsafe { - strtof( - b"8.8817847263968443574e-16" as *const u8 as *const libc::c_char, - core::ptr::null_mut(), - ) - } - .to_bits() - != 0x26800001 - { - return match (testsuite, testname) { - ("spec_testsuite", "const") => true, - ("single_file_spec_test", "simd_const") => true, - (_, _) => false, - }; - } - } - false } From 6aabdaa0384fe93a7115bc0b919802dc969ffd25 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Thu, 7 Nov 2019 13:33:48 -0800 Subject: [PATCH 5/8] Update lightbeam's "bench" tests to use `wat`. (#504) The rest of lightbeam switched from `wabt` to `wat` already, this just updates some of the tests which aren't enabled by default. --- lightbeam/src/tests.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lightbeam/src/tests.rs b/lightbeam/src/tests.rs index a832062ec2..1fa6551106 100644 --- a/lightbeam/src/tests.rs +++ b/lightbeam/src/tests.rs @@ -1027,18 +1027,18 @@ test_select!(select64, i64); mod benches { extern crate test; - use super::{translate, wabt, FIBONACCI, FIBONACCI_OPT}; + use super::{translate, FIBONACCI, FIBONACCI_OPT}; #[bench] fn bench_fibonacci_compile(b: &mut test::Bencher) { - let wasm = wabt::wat2wasm(FIBONACCI).unwrap(); + let wasm = wat::parse_str(FIBONACCI).unwrap(); b.iter(|| test::black_box(translate(&wasm).unwrap())); } #[bench] fn bench_fibonacci_run(b: &mut test::Bencher) { - let wasm = wabt::wat2wasm(FIBONACCI_OPT).unwrap(); + let wasm = wat::parse_str(FIBONACCI_OPT).unwrap(); let module = translate(&wasm).unwrap(); b.iter(|| module.execute_func::<_, u32>(0, (20,))); @@ -1046,7 +1046,7 @@ mod benches { #[bench] fn bench_fibonacci_compile_run(b: &mut test::Bencher) { - let wasm = wabt::wat2wasm(FIBONACCI).unwrap(); + let wasm = wat::parse_str(FIBONACCI).unwrap(); b.iter(|| translate(&wasm).unwrap().execute_func::<_, u32>(0, (20,))); } From f579dac34f3cfe08af6b74b8975d4099513874c6 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 7 Nov 2019 15:51:09 -0600 Subject: [PATCH 6/8] Run beta/nightly test on ubuntu, not macos (#506) This commit switches the beta/nightly tests to happen on Ubuntu instead of macOS. Turns out GitHub Actions has scheduling limitations on macOS that limit repositories to 5 concurrent jobs per repository, so let's reduce the load a bit by running more builds on Linux than mac. --- .github/workflows/main.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 3900c29149..3635277501 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -65,19 +65,19 @@ jobs: strategy: fail-fast: false matrix: - build: [stable, beta, nightly, windows, linux] + build: [stable, beta, nightly, windows, macos] include: - build: stable - os: macos-latest + os: ubuntu-latest rust: stable - build: beta - os: macos-latest + os: ubuntu-latest rust: beta - build: nightly - os: macos-latest - rust: nightly - - build: linux os: ubuntu-latest + rust: nightly + - build: macos + os: macos-latest rust: stable - build: windows os: windows-latest From 59b15eab136cbdff2180c0bba3e7d60f1c821d10 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 7 Nov 2019 17:01:17 -0600 Subject: [PATCH 7/8] Simplify `#[test]` generation for `*.wast` files (#507) This commit simplifies the build script slightly for generating tests by doing a few dull refactorings: * Leaves formatting to `rustfmt` * Extract bulk of code execution into a top-level shared `run_wast` function so each test is a one-liner * Use `anyhow` for errors both in the script and in tests --- Cargo.toml | 3 + build.rs | 192 +++++++++++++++++---------------------- tests/wast_testsuites.rs | 19 +++- 3 files changed, 103 insertions(+), 111 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 603d5551f1..05322667f6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,6 +38,9 @@ libc = "0.2.60" rayon = "1.1" wasm-webidl-bindings = "0.6" +[build-dependencies] +anyhow = "1.0.19" + [workspace] members = [ "misc/wasmtime-rust", diff --git a/build.rs b/build.rs index 4617b6ba87..0c3bf69053 100644 --- a/build.rs +++ b/build.rs @@ -3,108 +3,105 @@ //! By generating a separate `#[test]` test for each file, we allow cargo test //! to automatically run the files in parallel. +use anyhow::Context; use std::env; -use std::fs::{read_dir, File}; -use std::io::{self, Write}; +use std::fmt::Write; +use std::fs; use std::path::{Path, PathBuf}; +use std::process::Command; -fn main() { - let out_dir = - PathBuf::from(env::var("OUT_DIR").expect("The OUT_DIR environment variable must be set")); - let mut out = File::create(out_dir.join("wast_testsuite_tests.rs")) - .expect("error generating test source file"); +fn main() -> anyhow::Result<()> { + let out_dir = PathBuf::from( + env::var_os("OUT_DIR").expect("The OUT_DIR environment variable must be set"), + ); + let mut out = String::new(); for strategy in &[ "Cranelift", #[cfg(feature = "lightbeam")] "Lightbeam", ] { - writeln!(out, "#[cfg(test)]").expect("generating tests"); - writeln!(out, "#[allow(non_snake_case)]").expect("generating tests"); - writeln!(out, "mod {} {{", strategy).expect("generating tests"); + writeln!(out, "#[cfg(test)]")?; + writeln!(out, "#[allow(non_snake_case)]")?; + writeln!(out, "mod {} {{", strategy)?; - test_directory(&mut out, "misc_testsuite", strategy).expect("generating tests"); - test_directory(&mut out, "spec_testsuite", strategy).expect("generating tests"); - // Skip running spec_testsuite tests if the submodule isn't checked out. - if read_dir("spec_testsuite") - .expect("reading testsuite directory") - .next() - .is_some() - { - test_file( + test_directory(&mut out, "misc_testsuite", strategy)?; + let spec_tests = test_directory(&mut out, "spec_testsuite", strategy)?; + // Skip running spec_testsuite tests if the submodule isn't checked + // out. + if spec_tests > 0 { + start_test_module(&mut out, "simd")?; + write_testsuite_tests( &mut out, - &to_os_path(&["spec_testsuite", "proposals", "simd", "simd_address.wast"]), + "spec_testsuite/proposals/simd/simd_address.wast", + "simd", strategy, - ) - .expect("generating tests"); - test_file( + )?; + write_testsuite_tests( &mut out, - &to_os_path(&["spec_testsuite", "proposals", "simd", "simd_align.wast"]), + "spec_testsuite/proposals/simd/simd_align.wast", + "simd", strategy, - ) - .expect("generating tests"); - test_file( + )?; + write_testsuite_tests( &mut out, - &to_os_path(&["spec_testsuite", "proposals", "simd", "simd_const.wast"]), + "spec_testsuite/proposals/simd/simd_const.wast", + "simd", strategy, - ) - .expect("generating tests"); + )?; + finish_test_module(&mut out)?; - let multi_value_suite = &to_os_path(&["spec_testsuite", "proposals", "multi-value"]); - test_directory(&mut out, &multi_value_suite, strategy).expect("generating tests"); + test_directory(&mut out, "spec_testsuite/proposals/multi-value", strategy) + .expect("generating tests"); } else { println!("cargo:warning=The spec testsuite is disabled. To enable, run `git submodule update --remote`."); } - writeln!(out, "}}").expect("generating tests"); + writeln!(out, "}}")?; } + + // Write out our auto-generated tests and opportunistically format them with + // `rustfmt` if it's installed. + let output = out_dir.join("wast_testsuite_tests.rs"); + fs::write(&output, out)?; + drop(Command::new("rustfmt").arg(&output).status()); + Ok(()) } -/// Helper for creating OS-independent paths. -fn to_os_path(components: &[&str]) -> String { - let path: PathBuf = components.iter().collect(); - path.display().to_string() -} - -fn test_directory(out: &mut File, path: &str, strategy: &str) -> io::Result<()> { - let mut dir_entries: Vec<_> = read_dir(path) - .expect("reading testsuite directory") +fn test_directory( + out: &mut String, + path: impl AsRef, + strategy: &str, +) -> anyhow::Result { + let path = path.as_ref(); + let mut dir_entries: Vec<_> = path + .read_dir() + .context(format!("failed to read {:?}", path))? .map(|r| r.expect("reading testsuite directory entry")) - .filter(|dir_entry| { + .filter_map(|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; - } - } - } - } + let ext = p.extension()?; + // Only look at wast files. + if ext != "wast" { + return None; } - false + // Ignore files starting with `.`, which could be editor temporary files + if p.file_stem()?.to_str()?.starts_with(".") { + return None; + } + Some(p) }) .collect(); - dir_entries.sort_by_key(|dir| dir.path()); + dir_entries.sort(); let testsuite = &extract_name(path); start_test_module(out, testsuite)?; - for dir_entry in dir_entries { - write_testsuite_tests(out, &dir_entry.path(), testsuite, strategy)?; + for entry in dir_entries.iter() { + write_testsuite_tests(out, entry, testsuite, strategy)?; } - finish_test_module(out) -} - -fn test_file(out: &mut File, testfile: &str, strategy: &str) -> io::Result<()> { - let path = Path::new(testfile); - let testsuite = format!("single_test_{}", extract_name(path)); - start_test_module(out, &testsuite)?; - write_testsuite_tests(out, path, &testsuite, strategy)?; - finish_test_module(out) + finish_test_module(out)?; + Ok(dir_entries.len()) } /// Extract a valid Rust identifier from the stem of a path. @@ -118,63 +115,38 @@ fn extract_name(path: impl AsRef) -> String { .replace("/", "_") } -fn start_test_module(out: &mut File, testsuite: &str) -> io::Result<()> { - writeln!(out, " mod {} {{", testsuite)?; - writeln!( - out, - " use super::super::{{native_isa, Path, WastContext, Compiler, Features, CompilationStrategy}};" - ) +fn start_test_module(out: &mut String, testsuite: &str) -> anyhow::Result<()> { + writeln!(out, "mod {} {{", testsuite)?; + Ok(()) } -fn finish_test_module(out: &mut File) -> io::Result<()> { - writeln!(out, " }}") +fn finish_test_module(out: &mut String) -> anyhow::Result<()> { + out.push_str("}\n"); + Ok(()) } fn write_testsuite_tests( - out: &mut File, - path: &Path, + out: &mut String, + path: impl AsRef, testsuite: &str, strategy: &str, -) -> io::Result<()> { +) -> anyhow::Result<()> { + let path = path.as_ref(); + println!("cargo:rerun-if-changed={}", path.display()); let testname = extract_name(path); - writeln!(out, " #[test]")?; + writeln!(out, "#[test]")?; if ignore(testsuite, &testname, strategy) { - writeln!(out, " #[ignore]")?; + writeln!(out, "#[ignore]")?; } - writeln!(out, " fn r#{}() {{", &testname)?; - writeln!(out, " let isa = native_isa();")?; + writeln!(out, "fn r#{}() -> anyhow::Result<()> {{", &testname)?; writeln!( out, - " let compiler = Compiler::new(isa, CompilationStrategy::{});", + "crate::run_wast(r#\"{}\"#, crate::CompilationStrategy::{})", + path.display(), strategy )?; - writeln!( - out, - " let features = Features {{ simd: {}, multi_value: {}, ..Default::default() }};", - testsuite.contains("simd"), - testsuite.contains("multi_value") - )?; - writeln!( - out, - " let mut wast_context = WastContext::new(Box::new(compiler)).with_features(features);" - )?; - writeln!(out, " wast_context")?; - writeln!(out, " .register_spectest()")?; - writeln!( - out, - " .expect(\"instantiating \\\"spectest\\\"\");" - )?; - writeln!(out, " wast_context")?; - write!(out, " .run_file(Path::new(\"")?; - // Write out the string with escape_debug to prevent special characters such - // as backslash from being reinterpreted. - for c in path.display().to_string().chars() { - write!(out, "{}", c.escape_debug())?; - } - writeln!(out, "\"))")?; - writeln!(out, " .expect(\"error running wast file\");",)?; - writeln!(out, " }}")?; + writeln!(out, "}}")?; writeln!(out)?; Ok(()) } diff --git a/tests/wast_testsuites.rs b/tests/wast_testsuites.rs index d319332ae8..69d0d0328e 100644 --- a/tests/wast_testsuites.rs +++ b/tests/wast_testsuites.rs @@ -9,7 +9,24 @@ use wasmtime_wast::WastContext; include!(concat!(env!("OUT_DIR"), "/wast_testsuite_tests.rs")); -#[cfg(test)] +// Each of the tests included from `wast_testsuite_tests` will call this +// function which actually executes the `wast` test suite given the `strategy` +// to compile it. +fn run_wast(wast: &str, strategy: CompilationStrategy) -> anyhow::Result<()> { + let wast = Path::new(wast); + let isa = native_isa(); + let compiler = Compiler::new(isa, strategy); + let features = Features { + simd: wast.iter().any(|s| s == "simd"), + multi_value: wast.iter().any(|s| s == "multi-value"), + ..Default::default() + }; + let mut wast_context = WastContext::new(Box::new(compiler)).with_features(features); + wast_context.register_spectest()?; + wast_context.run_file(wast)?; + Ok(()) +} + fn native_isa() -> Box { let mut flag_builder = settings::builder(); flag_builder.enable("enable_verifier").unwrap(); From 43b761ef5f2d8a4c6bf4aab82922f053d1852d59 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Thu, 7 Nov 2019 16:47:54 -0800 Subject: [PATCH 8/8] Update the top-level README.md and embedding documentation. (#508) * Update the top-level README.md and embedding documentation. wasmtime-api is now the primary external API crate, so recommend that instead of wasmtime-jit. Also, enable wasmtime-api's C API by default, so that it shows up on docs.rs, and to make it easier to use. And, add basic embedding documentation and link to it from the README.md. Credit to @yurydelendik for the content. * Use the new wasm-c-api URL. * Don't pass --features wasm-c-api, as it is now on by default. --- .github/workflows/main.yml | 2 +- README.md | 30 ++++------ docs/embed-rust.md | 98 +++++++++++++++++++++++++++++++- wasmtime-api/Cargo.toml | 1 - wasmtime-api/c-examples/Makefile | 4 +- wasmtime-api/src/lib.rs | 1 - 6 files changed, 110 insertions(+), 26 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 3635277501..a2fd551571 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -201,7 +201,7 @@ jobs: - run: $CENTOS cargo build --release --bin wasmtime --bin wasm2obj shell: bash # Build `libwasmtime_api.so` - - run: $CENTOS cargo build --release --features wasm-c-api --manifest-path wasmtime-api/Cargo.toml + - run: $CENTOS cargo build --release --manifest-path wasmtime-api/Cargo.toml shell: bash # Test what we just built - run: $CENTOS cargo test --release --all --exclude lightbeam --exclude wasmtime-wasi-c --exclude wasmtime-py --exclude wasmtime-api diff --git a/README.md b/README.md index 2ca99c5493..8e82aa6db8 100644 --- a/README.md +++ b/README.md @@ -35,34 +35,24 @@ of ongoing research. Additional goals for Wasmtime include: - Support a variety of host APIs (not just WASI), with fast calling sequences, and develop proposals for additional API modules to be part of WASI. - - Implement the [proposed WebAssembly C API]. - - Facilitate testing, experimentation, and development around the [Cranelift] and - [Lightbeam] JITs. + - Facilitate development and testing around the [Cranelift] and [Lightbeam] JITs, + and other WebAssembly execution strategies. - Develop a native ABI used for compiling WebAssembly suitable for use in both JIT and AOT to native object files. -[proposed WebAssembly C API]: https://github.com/rossberg/wasm-c-api [Cranelift]: https://github.com/CraneStation/cranelift -[Lightbeam]: https://github.com/CraneStation/lightbeam +[Lightbeam]: https://github.com/CraneStation/wasmtime/tree/master/lightbeam #### Including Wasmtime in your project -Wasmtime exposes an API for JIT compilation through the `wasmtime-jit` subcrate, which depends on `wasmtime-environ` and `wasmtime-runtime` for the ABI and runtime support respectively. However, this API is not documented and subject to change. Please use at your own risk! -Build the individual crates as such: +Wasmtime exposes an API for embedding as a library through the `wasmtime-api` subcrate, +which contains both a [high-level and safe Rust API], as well as a C-compatible API +compatible with the [proposed WebAssembly C API]. -``` -cargo build --package wasmtime-jit -``` +For more information, see the [Rust API embedding chapter] of the Wasmtime documentation. -Wasmtime does not currently publish these crates on crates.io. They may be included as a git dependency, like this: - -```toml -[dependencies] -wasmtime-environ = { git = "https://github.com/CraneStation/wasmtime", rev = "somecommithash" } -wasmtime-runtime = { git = "https://github.com/CraneStation/wasmtime", rev = "somecommithash" } -wasmtime-jit = { git = "https://github.com/CraneStation/wasmtime", rev = "somecommithash" } -``` - -All three crates must be specified as dependencies for `wasmtime-jit` to build correctly, at the moment. +[high-level and safe Rust API]: https://docs.rs/wasmtime-api/ +[proposed WebAssembly C API]: https://github.com/WebAssembly/wasm-c-api +[Rust API embedding chapter]: https://cranestation.github.io/wasmtime/embed-rust.html It's Wasmtime. diff --git a/docs/embed-rust.md b/docs/embed-rust.md index 34d837a1f5..3701871d0b 100644 --- a/docs/embed-rust.md +++ b/docs/embed-rust.md @@ -1,3 +1,99 @@ # Embedding Wasmtime in Rust -... more coming soon +This document shows how to embed Wasmtime using the Rust API, and run a simple +wasm program. + +# Create some wasm + +Let's create a simple WebAssembly file with a single exported function that returns an integer: + +```wat +(;; wat2wasm hello.wat -o $WASM_FILES/hello.wasm ;;) +(module + (func (export "answer") (result i32) + i32.const 42 + ) +) +``` + +# Create rust project + +``` +$ cargo new --bin wasmtime_hello +$ cd wasmtime_hello +$ cp $WASM_FILES/hello.wasm . +``` + +We will be using the wasmtime engine/API to run the wasm file, so we will add the dependency to `Cargo.toml`: + +``` +[dependencies] +wasmtime-api = { git = "https://github.com/CraneStation/wasmtime" } +``` + +It is time to add code to the `src/main.rs`. First, the engine and storage need to be activated: + +```rust +use wasmtime_api::*; + +let engine = HostRef::new(Engine::default()); +let store = HostRef::new(Store::new(&engine)); +``` + +The `HostRef` will be used a lot -- it is a "convenience" object to store and refer an object between the host and +the embedded environments. + +The `hello.wasm` can be read from the file system and provided to the `Module` object constructor as `&[u8]`: + +```rust +use std::fs::read; + +let hello_wasm = read("hello.wasm").expect("wasm file"); + +let module = HostRef::new(Module::new(&store, &hello_wasm).expect("wasm module")); +``` + +The module instance can now be created. Normally, you would provide exports, but in this case, there is none required: + +```rust +let instance = Instance::new(&store, &module, &[]).expect("wasm instance"); +``` + +Everything is set. If a WebAssembly module has a start function -- it was run. +The instance's exports can be used at this point. This wasm file has only one export, so we can index it directly: + +```rust +let answer_fn = instance.exports()[0].func().expect("answer function"); +``` + +The exported function can be called using the `call` method. Remember that in most of the cases, +a `HostRef<_>` object will be returned, so `borrow()` or `borrow_mut()` method has to be used to refer the +specific object. The exported "answer" function accepts no parameters and returns a single `i32` value. + +```rust +let result = answer_fn.borrow().call(&[]).expect("success"); +println!("Answer: {}", result[0].i32()); +``` + +The names of the WebAssembly module's imports and exports can be discovered by means of module's corresponding methods. + +# src/main.rs + +```rust +use std::fs::read; +use wasmtime_api::*; + +fn main() { + let engine = HostRef::new(Engine::default()); + let store = HostRef::new(Store::new(&engine)); + + let wasm = read("hello.wasm").expect("wasm file"); + + let module = HostRef::new(Module::new(&store, &wasm).expect("wasm module")); + let instance = Instance::new(&store, &module, &[]).expect("wasm instance"); + + let answer_fn = instance.exports()[0].func().expect("answer function"); + let result = answer_fn.borrow().call(&[]).expect("success"); + println!("Answer: {}", result[0].i32()); +} +``` diff --git a/wasmtime-api/Cargo.toml b/wasmtime-api/Cargo.toml index 8975cb2ba7..84131196c6 100644 --- a/wasmtime-api/Cargo.toml +++ b/wasmtime-api/Cargo.toml @@ -30,7 +30,6 @@ hashbrown = { version = "0.6.0", optional = true } [features] default = ["std"] std = ["cranelift-codegen/std", "cranelift-wasm/std", "wasmtime-environ/std", "wasmparser/std"] -wasm-c-api = [] core = ["hashbrown/nightly", "cranelift-codegen/core", "cranelift-wasm/core", "wasmtime-environ/core", "wasmparser/core"] [dev-dependencies] diff --git a/wasmtime-api/c-examples/Makefile b/wasmtime-api/c-examples/Makefile index 9fa9351753..d3ef772c46 100644 --- a/wasmtime-api/c-examples/Makefile +++ b/wasmtime-api/c-examples/Makefile @@ -46,9 +46,9 @@ WASM_CC_LIBS = $(error unsupported C++) # Compiler config ifeq (${WASMTIME_API_MODE},release) - CARGO_BUILD_FLAGS = --features "wasm-c-api" --release + CARGO_BUILD_FLAGS = --release else - CARGO_BUILD_FLAGS = --features "wasm-c-api" + CARGO_BUILD_FLAGS = endif ifeq (${C_COMP},clang) diff --git a/wasmtime-api/src/lib.rs b/wasmtime-api/src/lib.rs index 46866ea65d..866c17c8f8 100644 --- a/wasmtime-api/src/lib.rs +++ b/wasmtime-api/src/lib.rs @@ -14,7 +14,6 @@ mod trap; mod types; mod values; -#[cfg(feature = "wasm-c-api")] pub mod wasm; #[macro_use]