diff --git a/lib/environ/Cargo.toml b/lib/environ/Cargo.toml index 0ab1a41fb8..fd711229cb 100644 --- a/lib/environ/Cargo.toml +++ b/lib/environ/Cargo.toml @@ -14,6 +14,8 @@ cranelift-codegen = { git = "https://github.com/sunfishcode/cranelift.git", bran cranelift-entity = { git = "https://github.com/sunfishcode/cranelift.git", branch = "guard-offset" } cranelift-wasm = { git = "https://github.com/sunfishcode/cranelift.git", branch = "guard-offset" } cast = { version = "0.2.2", default-features = false } +failure = "0.1.3" +failure_derive = "0.1.3" [features] default = ["std"] diff --git a/lib/environ/src/compilation.rs b/lib/environ/src/compilation.rs index 9ceee4da5e..c540f9a4f7 100644 --- a/lib/environ/src/compilation.rs +++ b/lib/environ/src/compilation.rs @@ -5,13 +5,11 @@ use cranelift_codegen::binemit; use cranelift_codegen::ir; use cranelift_codegen::ir::ExternalName; use cranelift_codegen::isa; -use cranelift_codegen::Context; +use cranelift_codegen::{CodegenError, Context}; use cranelift_entity::{EntityRef, PrimaryMap}; -use cranelift_wasm::{ - DefinedFuncIndex, FuncIndex, FuncTranslator, GlobalIndex, MemoryIndex, TableIndex, -}; -use environ::{get_func_name, get_memory_grow_name, get_memory_size_name, ModuleTranslation}; -use std::string::{String, ToString}; +use cranelift_wasm::{DefinedFuncIndex, FuncIndex, FuncTranslator, WasmError}; +use environ::{get_func_name, get_memory_grow_name, get_memory_size_name, FuncEnvironment}; +use module::Module; use std::vec::Vec; /// The result of compiling a WebAssemby module's functions. @@ -19,30 +17,12 @@ use std::vec::Vec; pub struct Compilation { /// Compiled machine code for the function bodies. pub functions: PrimaryMap>, - - /// Resolved function addresses for imported functions. - pub resolved_func_imports: PrimaryMap, - - /// Resolved function addresses for imported tables. - pub resolved_table_imports: PrimaryMap, - - /// Resolved function addresses for imported globals. - pub resolved_global_imports: PrimaryMap, - - /// Resolved function addresses for imported memories. - pub resolved_memory_imports: PrimaryMap, } impl Compilation { /// Allocates the compilation result with the given function bodies. pub fn new(functions: PrimaryMap>) -> Self { - Self { - functions, - resolved_func_imports: PrimaryMap::new(), - resolved_table_imports: PrimaryMap::new(), - resolved_memory_imports: PrimaryMap::new(), - resolved_global_imports: PrimaryMap::new(), - } + Self { functions } } } @@ -139,32 +119,49 @@ pub type Relocations = PrimaryMap>; /// Compile the module, producing a compilation result with associated /// relocations. pub fn compile_module<'data, 'module>( - translation: &ModuleTranslation<'data, 'module>, + module: &'module Module, + function_body_inputs: &PrimaryMap, isa: &isa::TargetIsa, -) -> Result<(Compilation, Relocations), String> { +) -> Result<(Compilation, Relocations), CompileError> { let mut functions = PrimaryMap::new(); let mut relocations = PrimaryMap::new(); - for (i, input) in translation.lazy.function_body_inputs.iter() { - let func_index = translation.module.func_index(i); + for (i, input) in function_body_inputs.iter() { + let func_index = module.func_index(i); let mut context = Context::new(); context.func.name = get_func_name(func_index); - context.func.signature = - translation.module.signatures[translation.module.functions[func_index]].clone(); + context.func.signature = module.signatures[module.functions[func_index]].clone(); let mut trans = FuncTranslator::new(); trans - .translate(input, &mut context.func, &mut translation.func_env()) - .map_err(|e| e.to_string())?; + .translate( + input, + &mut context.func, + &mut FuncEnvironment::new(isa, module), + ) + .map_err(CompileError::Wasm)?; let mut code_buf: Vec = Vec::new(); let mut reloc_sink = RelocSink::new(); let mut trap_sink = binemit::NullTrapSink {}; context .compile_and_emit(isa, &mut code_buf, &mut reloc_sink, &mut trap_sink) - .map_err(|e| e.to_string())?; + .map_err(CompileError::Codegen)?; functions.push(code_buf); relocations.push(reloc_sink.func_relocs); } + // TODO: Reorganize where we create the Vec for the resolved imports. Ok((Compilation::new(functions), relocations)) } + +/// An error while compiling WebAssembly to machine code. +#[derive(Fail, Debug)] +pub enum CompileError { + /// A wasm translation error occured. + #[fail(display = "WebAssembly translation error: {}", _0)] + Wasm(WasmError), + + /// A compilation error occured. + #[fail(display = "Compilation error: {}", _0)] + Codegen(CodegenError), +} diff --git a/lib/environ/src/lib.rs b/lib/environ/src/lib.rs index 8a16bf0fa0..dc797f5c82 100644 --- a/lib/environ/src/lib.rs +++ b/lib/environ/src/lib.rs @@ -34,6 +34,9 @@ extern crate cranelift_wasm; #[macro_use] extern crate alloc; extern crate cast; +extern crate failure; +#[macro_use] +extern crate failure_derive; mod compilation; mod environ; @@ -42,7 +45,7 @@ mod tunables; mod vmoffsets; pub use compilation::{ - compile_module, Compilation, RelocSink, Relocation, RelocationTarget, Relocations, + compile_module, Compilation, CompileError, RelocSink, Relocation, RelocationTarget, Relocations, }; pub use environ::{translate_signature, ModuleEnvironment, ModuleTranslation}; pub use module::{ diff --git a/lib/execute/Cargo.toml b/lib/execute/Cargo.toml index 62b020a06e..ef9c9de2af 100644 --- a/lib/execute/Cargo.toml +++ b/lib/execute/Cargo.toml @@ -21,6 +21,8 @@ libc = { version = "0.2.44", default-features = false } errno = "0.2.4" memoffset = "0.2.1" cast = { version = "0.2.2", default-features = false } +failure = "0.1.3" +failure_derive = "0.1.3" [build-dependencies] cmake = "0.1.35" diff --git a/lib/execute/src/action.rs b/lib/execute/src/action.rs index 7ec3e27da7..00bd4bd657 100644 --- a/lib/execute/src/action.rs +++ b/lib/execute/src/action.rs @@ -1,12 +1,15 @@ //! Support for performing actions with a wasm module from the outside. use cranelift_codegen::ir; +use link::LinkError; +use std::fmt; use std::string::String; use std::vec::Vec; +use wasmtime_environ::CompileError; /// A runtime value. #[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub enum Value { +pub enum RuntimeValue { /// A runtime value with type i32. I32(i32), /// A runtime value with type i64. @@ -17,61 +20,121 @@ pub enum Value { F64(u64), } -impl Value { - /// Return the type of this `Value`. +impl RuntimeValue { + /// Return the type of this `RuntimeValue`. 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, + RuntimeValue::I32(_) => ir::types::I32, + RuntimeValue::I64(_) => ir::types::I64, + RuntimeValue::F32(_) => ir::types::F32, + RuntimeValue::F64(_) => ir::types::F64, } } - /// Assuming this `Value` holds an `i32`, return that value. + /// Assuming this `RuntimeValue` holds an `i32`, return that value. pub fn unwrap_i32(self) -> i32 { match self { - Value::I32(x) => x, + RuntimeValue::I32(x) => x, _ => panic!("unwrapping value of type {} as i32", self.value_type()), } } - /// Assuming this `Value` holds an `i64`, return that value. + /// Assuming this `RuntimeValue` holds an `i64`, return that value. pub fn unwrap_i64(self) -> i64 { match self { - Value::I64(x) => x, + RuntimeValue::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 { + /// Assuming this `RuntimeValue` holds an `f32`, return that value. + pub fn unwrap_f32(self) -> f32 { + f32::from_bits(self.unwrap_f32_bits()) + } + + /// Assuming this `RuntimeValue` holds an `f32`, return the bits of that value as a `u32`. + pub fn unwrap_f32_bits(self) -> u32 { match self { - Value::F32(x) => x, + RuntimeValue::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 { + /// Assuming this `RuntimeValue` holds an `f64`, return that value. + pub fn unwrap_f64(self) -> f64 { + f64::from_bits(self.unwrap_f64_bits()) + } + + /// Assuming this `RuntimeValue` holds an `f64`, return the bits of that value as a `u64`. + pub fn unwrap_f64_bits(self) -> u64 { match self { - Value::F64(x) => x, + RuntimeValue::F64(x) => x, _ => panic!("unwrapping value of type {} as f64", self.value_type()), } } } +impl fmt::Display for RuntimeValue { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + RuntimeValue::I32(x) => write!(f, "{}: i32", x), + RuntimeValue::I64(x) => write!(f, "{}: i64", x), + RuntimeValue::F32(x) => write!(f, "{}: f32", x), + RuntimeValue::F64(x) => write!(f, "{}: f64", x), + } + } +} + /// The result of invoking a wasm function or reading a wasm global. #[derive(Debug)] pub enum ActionOutcome { /// The action returned normally. Its return values are provided. Returned { /// The return values. - values: Vec, + values: Vec, }, + /// A trap occurred while the action was executing. Trapped { /// The trap message. message: String, }, } + +/// An error detected while invoking a wasm function or reading a wasm global. +/// Note that at this level, traps are not reported errors, but are rather +/// returned through `ActionOutcome`. +#[derive(Fail, Debug)] +pub enum ActionError { + /// No field with the specified name was present. + #[fail(display = "Unknown field: {}", _0)] + Field(String), + + /// An index was out of bounds. + #[fail(display = "Index out of bounds: {}", _0)] + Index(u64), + + /// The field was present but was the wrong kind (eg. function, table, global, or memory). + #[fail(display = "Kind error: {}", _0)] + Kind(String), + + /// The field was present but was the wrong type (eg. i32, i64, f32, or f64). + #[fail(display = "Type error: {}", _0)] + Type(String), + + /// A wasm translation error occured. + #[fail(display = "WebAssembly compilation error: {}", _0)] + Compile(CompileError), + + /// Some runtime resource was unavailable or insufficient. + #[fail(display = "Runtime resource error: {}", _0)] + Resource(String), + + /// Link error. + #[fail(display = "Link error: {}", _0)] + Link(LinkError), + + /// Start function trapped. + #[fail(display = "Start function trapped: {}", _0)] + Start(String), +} diff --git a/lib/execute/src/code.rs b/lib/execute/src/code.rs index 0a0b9d8a0b..7f319ac95d 100644 --- a/lib/execute/src/code.rs +++ b/lib/execute/src/code.rs @@ -28,9 +28,12 @@ impl Code { } /// 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> { + /// 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, @@ -63,8 +66,8 @@ impl Code { if !m.as_ptr().is_null() { unsafe { region::protect(m.as_mut_ptr(), m.len(), region::Protection::ReadExecute) - .expect("unable to make memory readonly"); } + .expect("unable to make memory readonly and executable"); } } self.published = self.mmaps.len(); diff --git a/lib/execute/src/export.rs b/lib/execute/src/export.rs index 0b7197ddbf..2335e82d01 100644 --- a/lib/execute/src/export.rs +++ b/lib/execute/src/export.rs @@ -8,7 +8,7 @@ pub enum ExportValue { /// A function export value. Function { /// The address of the native-code function. - address: usize, + address: *const u8, /// The function signature declaration, used for compatibilty checking. signature: ir::Signature, }, @@ -40,7 +40,7 @@ pub enum ExportValue { impl ExportValue { /// Construct a function export value. - pub fn function(address: usize, signature: ir::Signature) -> Self { + pub fn function(address: *const u8, signature: ir::Signature) -> Self { ExportValue::Function { address, signature } } diff --git a/lib/execute/src/get.rs b/lib/execute/src/get.rs index dab34a029c..0626321d65 100644 --- a/lib/execute/src/get.rs +++ b/lib/execute/src/get.rs @@ -1,39 +1,65 @@ //! Support for reading the value of a wasm global from outside the module. -use action::Value; +use action::{ActionError, RuntimeValue}; use cranelift_codegen::ir; +use cranelift_entity::EntityRef; use cranelift_wasm::GlobalIndex; -use std::string::String; -use vmcontext::VMContext; +use instance::Instance; use wasmtime_environ::{Export, Module}; -/// Jumps to the code region of memory and invoke the exported function -pub fn get(module: &Module, vmctx: *mut VMContext, global_name: &str) -> Result { +/// Reads the value of the named global variable in `module`. +pub fn get( + module: &Module, + instance: &mut Instance, + global_name: &str, +) -> Result { let global_index = match module.exports.get(global_name) { Some(Export::Global(index)) => *index, - Some(_) => return Err(format!("exported item \"{}\" is not a global", global_name)), - None => return Err(format!("no export named \"{}\"", global_name)), + Some(_) => { + return Err(ActionError::Kind(format!( + "exported item \"{}\" is not a global", + global_name + ))) + } + None => { + return Err(ActionError::Field(format!( + "no export named \"{}\"", + global_name + ))) + } }; - get_by_index(module, vmctx, global_index) + get_by_index(module, instance, global_index) } +/// Reads the value of the indexed global variable in `module`. pub fn get_by_index( module: &Module, - vmctx: *mut VMContext, + instance: &mut Instance, global_index: GlobalIndex, -) -> Result { - // TODO: Return Err if the index is out of bounds. +) -> Result { unsafe { - let vmctx = &mut *vmctx; + let vmctx = &mut *instance.vmctx(); let vmglobal = vmctx.global(global_index); let definition = vmglobal.get_definition(module.is_imported_global(global_index)); - Ok(match module.globals[global_index].ty { - ir::types::I32 => Value::I32(*definition.as_i32()), - ir::types::I64 => Value::I64(*definition.as_i64()), - ir::types::F32 => Value::F32(*definition.as_f32_bits()), - ir::types::F64 => Value::F64(*definition.as_f64_bits()), - other => return Err(format!("global with type {} not supported", other)), - }) + Ok( + match module + .globals + .get(global_index) + .ok_or_else(|| ActionError::Index(global_index.index() as u64))? + .ty + { + ir::types::I32 => RuntimeValue::I32(*definition.as_i32()), + ir::types::I64 => RuntimeValue::I64(*definition.as_i64()), + ir::types::F32 => RuntimeValue::F32(*definition.as_f32_bits()), + ir::types::F64 => RuntimeValue::F64(*definition.as_f64_bits()), + other => { + return Err(ActionError::Type(format!( + "global with type {} not supported", + other + ))) + } + }, + ) } } diff --git a/lib/execute/src/imports.rs b/lib/execute/src/imports.rs new file mode 100644 index 0000000000..56b53798c7 --- /dev/null +++ b/lib/execute/src/imports.rs @@ -0,0 +1,30 @@ +use cranelift_entity::PrimaryMap; +use cranelift_wasm::{FuncIndex, GlobalIndex, MemoryIndex, TableIndex}; +use vmcontext::{VMGlobal, VMMemory, VMTable}; + +/// Resolved import pointers. +#[derive(Debug)] +pub struct Imports { + /// Resolved addresses for imported functions. + pub functions: PrimaryMap, + + /// Resolved addresses for imported tables. + pub tables: PrimaryMap, + + /// Resolved addresses for imported globals. + pub globals: PrimaryMap, + + /// Resolved addresses for imported memories. + pub memories: PrimaryMap, +} + +impl Imports { + pub fn new() -> Self { + Self { + functions: PrimaryMap::new(), + tables: PrimaryMap::new(), + globals: PrimaryMap::new(), + memories: PrimaryMap::new(), + } + } +} diff --git a/lib/execute/src/instance.rs b/lib/execute/src/instance.rs index 958287eb4a..ab701ae23a 100644 --- a/lib/execute/src/instance.rs +++ b/lib/execute/src/instance.rs @@ -3,14 +3,16 @@ use cranelift_entity::EntityRef; use cranelift_entity::PrimaryMap; -use cranelift_wasm::{GlobalIndex, MemoryIndex, TableIndex}; +use cranelift_wasm::{DefinedFuncIndex, FuncIndex, GlobalIndex, MemoryIndex, TableIndex}; +use imports::Imports; use memory::LinearMemory; use sig_registry::SignatureRegistry; use std::ptr; +use std::slice; use std::string::String; use table::Table; use vmcontext::{VMCallerCheckedAnyfunc, VMContext, VMGlobal, VMMemory, VMTable}; -use wasmtime_environ::{Compilation, DataInitializer, Module}; +use wasmtime_environ::{DataInitializer, Module}; /// An Instance of a WebAssemby module. #[derive(Debug)] @@ -34,20 +36,29 @@ pub struct Instance { /// Table storage base address vector pointed to by vmctx. vmctx_tables: PrimaryMap, + /// Pointer values for resolved imports. + imports: Imports, + + /// Pointers to functions in executable memory. + allocated_functions: PrimaryMap, + /// Context pointer used by JIT code. vmctx: VMContext, } impl Instance { - /// Create a new `Instance`. + /// Create a new `Instance`. In order to complete instantiation, call + /// `invoke_start_function`. `allocated_functions` holds the function bodies + /// which have been placed in executable memory. pub fn new( module: &Module, - compilation: &Compilation, + allocated_functions: PrimaryMap, data_initializers: &[DataInitializer], + imports: Imports, ) -> Result { let mut sig_registry = instantiate_signatures(module); let mut memories = instantiate_memories(module, data_initializers)?; - let mut tables = instantiate_tables(module, compilation, &mut sig_registry); + let mut tables = instantiate_tables(module, &allocated_functions, &mut sig_registry); let mut vmctx_memories = memories .values_mut() @@ -73,6 +84,8 @@ impl Instance { vmctx_memories, vmctx_globals, vmctx_tables, + imports, + allocated_functions, vmctx: VMContext::new( vmctx_memories_ptr, vmctx_globals_ptr, @@ -83,15 +96,27 @@ impl Instance { } /// Return the vmctx pointer to be passed into JIT code. - pub fn vmctx(&mut self) -> *mut VMContext { - &mut self.vmctx as *mut VMContext + pub fn vmctx(&mut self) -> &mut VMContext { + &mut self.vmctx } /// Return the offset from the vmctx pointer to its containing Instance. - pub fn vmctx_offset() -> isize { + pub(crate) fn vmctx_offset() -> isize { offset_of!(Self, vmctx) as isize } + /// Return the pointer to executable memory for the given function index. + pub(crate) fn get_allocated_function(&self, index: DefinedFuncIndex) -> Option<&[u8]> { + self.allocated_functions + .get(index) + .map(|(ptr, len)| unsafe { slice::from_raw_parts(*ptr, *len) }) + } + + /// Return the pointer to executable memory for the given function index. + pub(crate) fn get_imported_function(&self, index: FuncIndex) -> Option<*const u8> { + self.imports.functions.get(index).cloned() + } + /// Grow memory by the specified amount of pages. /// /// Returns `None` if memory can't be grown by the specified amount @@ -163,7 +188,7 @@ fn instantiate_memories( /// Allocate memory for just the tables of the current module. fn instantiate_tables( module: &Module, - compilation: &Compilation, + allocated_functions: &PrimaryMap, sig_registry: &mut SignatureRegistry, ) -> PrimaryMap { let mut tables = PrimaryMap::with_capacity(module.table_plans.len()); @@ -177,14 +202,12 @@ fn instantiate_tables( let subslice = &mut slice[init.offset..init.offset + init.elements.len()]; for (i, func_idx) in init.elements.iter().enumerate() { let callee_sig = module.functions[*func_idx]; - let code_buf = &compilation.functions[module + let func_ptr = allocated_functions[module .defined_func_index(*func_idx) - .expect("table element initializer with imported function not supported yet")]; + .expect("table element initializer with imported function not supported yet")] + .0; let type_id = sig_registry.lookup(callee_sig); - subslice[i] = VMCallerCheckedAnyfunc { - func_ptr: code_buf.as_ptr(), - type_id, - }; + subslice[i] = VMCallerCheckedAnyfunc { func_ptr, type_id }; } } diff --git a/lib/execute/src/invoke.rs b/lib/execute/src/invoke.rs index 0a3be0da14..fc52a5582d 100644 --- a/lib/execute/src/invoke.rs +++ b/lib/execute/src/invoke.rs @@ -1,55 +1,87 @@ //! Support for invoking wasm functions from outside a wasm module. -use action::{ActionOutcome, Value}; +use action::{ActionError, ActionOutcome, RuntimeValue}; use code::Code; use cranelift_codegen::ir::InstBuilder; use cranelift_codegen::{binemit, ir, isa, Context}; +use cranelift_entity::EntityRef; use cranelift_frontend::{FunctionBuilder, FunctionBuilderContext}; use cranelift_wasm::FuncIndex; +use instance::Instance; use signalhandlers::{ensure_eager_signal_handlers, ensure_full_signal_handlers, TrapContext}; use std::mem; use std::ptr; -use std::string::String; use std::vec::Vec; use traphandlers::call_wasm; use vmcontext::VMContext; -use wasmtime_environ::{Compilation, Export, Module, RelocSink}; +use wasmtime_environ::{CompileError, Export, Module, RelocSink}; -/// Jumps to the code region of memory and invoke the exported function +/// Calls the given named function, passing its return values and returning +/// its results. pub fn invoke( code: &mut Code, isa: &isa::TargetIsa, module: &Module, - compilation: &Compilation, - vmctx: *mut VMContext, + instance: &mut Instance, function: &str, - args: &[Value], -) -> Result { + args: &[RuntimeValue], +) -> 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)), + Some(_) => { + return Err(ActionError::Kind(format!( + "exported item \"{}\" is not a function", + function + ))) + } + None => { + return Err(ActionError::Field(format!( + "no export named \"{}\"", + function + ))) + } }; - invoke_by_index(code, isa, module, compilation, vmctx, fn_index, args) + invoke_by_index(code, isa, module, instance, fn_index, args) } +/// Invoke the WebAssembly start function of the instance, if one is present. +pub fn invoke_start_function( + code: &mut Code, + isa: &isa::TargetIsa, + module: &Module, + instance: &mut Instance, +) -> Result { + if let Some(start_index) = module.start_func { + invoke_by_index(code, isa, module, instance, start_index, &[]) + } else { + // No start function, just return nothing. + Ok(ActionOutcome::Returned { values: vec![] }) + } +} + +/// Calls the given indexed function, passing its return values and returning +/// its results. pub fn invoke_by_index( code: &mut Code, isa: &isa::TargetIsa, module: &Module, - compilation: &Compilation, - vmctx: *mut VMContext, + instance: &mut Instance, fn_index: FuncIndex, - args: &[Value], -) -> Result { - // TODO: Return Err if fn_index is out of bounds. + args: &[RuntimeValue], +) -> Result { let exec_code_buf = match module.defined_func_index(fn_index) { Some(def_fn_index) => { - let code_buf = &compilation.functions[def_fn_index]; - code.allocate_copy_of_slice(&code_buf)?.as_ptr() as usize + let slice = instance + .get_allocated_function(def_fn_index) + .ok_or_else(|| ActionError::Index(def_fn_index.index() as u64))?; + code.allocate_copy_of_slice(slice) + .map_err(ActionError::Resource)? + .as_ptr() } - None => compilation.resolved_func_imports[fn_index], + None => instance + .get_imported_function(fn_index) + .ok_or_else(|| ActionError::Index(fn_index.index() as u64))?, }; let sig = &module.signatures[module.functions[fn_index]]; @@ -68,20 +100,24 @@ pub fn invoke_by_index( ensure_eager_signal_handlers(); ensure_full_signal_handlers(&mut traps); if !traps.haveSignalHandlers { - return Err("failed to install signal handlers".to_string()); + return Err(ActionError::Resource( + "failed to install signal handlers".to_string(), + )); } - call_through_wrapper(code, isa, exec_code_buf, vmctx, args, &sig) + call_through_wrapper(code, isa, exec_code_buf, instance, args, &sig) } fn call_through_wrapper( code: &mut Code, isa: &isa::TargetIsa, - callee: usize, - vmctx: *mut VMContext, - args: &[Value], + callee: *const u8, + instance: &mut Instance, + args: &[RuntimeValue], sig: &ir::Signature, -) -> Result { +) -> Result { + let vmctx = instance.vmctx() as *mut VMContext; + for (index, value) in args.iter().enumerate() { assert_eq!(value.value_type(), sig.params[index].value_type); } @@ -111,16 +147,16 @@ fn call_through_wrapper( for value in args { match value { - Value::I32(i) => { + RuntimeValue::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( + RuntimeValue::I64(i) => callee_args.push(builder.ins().iconst(ir::types::I64, *i)), + RuntimeValue::F32(i) => callee_args.push( builder .ins() .f32const(ir::immediates::Ieee32::with_bits(*i)), ), - Value::F64(i) => callee_args.push( + RuntimeValue::F64(i) => callee_args.push( builder .ins() .f64const(ir::immediates::Ieee64::with_bits(*i)), @@ -162,10 +198,13 @@ fn call_through_wrapper( 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())?; + .map_err(|error| ActionError::Compile(CompileError::Codegen(error)))?; assert!(reloc_sink.func_relocs.is_empty()); - let exec_code_buf = code.allocate_copy_of_slice(&code_buf)?.as_ptr(); + let exec_code_buf = code + .allocate_copy_of_slice(&code_buf) + .map_err(ActionError::Resource)? + .as_ptr(); code.publish(); let func = unsafe { mem::transmute::<_, fn()>(exec_code_buf) }; @@ -179,10 +218,10 @@ fn call_through_wrapper( 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)), + ir::types::I32 => RuntimeValue::I32(ptr::read(ptr as *const i32)), + ir::types::I64 => RuntimeValue::I64(ptr::read(ptr as *const i64)), + ir::types::F32 => RuntimeValue::F32(ptr::read(ptr as *const u32)), + ir::types::F64 => RuntimeValue::F64(ptr::read(ptr as *const u64)), other => panic!("unsupported value type {:?}", other), } }; diff --git a/lib/execute/src/lib.rs b/lib/execute/src/lib.rs index 18bf5ff3b5..5f94575763 100644 --- a/lib/execute/src/lib.rs +++ b/lib/execute/src/lib.rs @@ -40,15 +40,19 @@ extern crate libc; #[macro_use] extern crate memoffset; extern crate cast; +extern crate failure; +#[macro_use] +extern crate failure_derive; mod action; mod code; -mod execute; mod export; mod get; +mod imports; mod instance; mod invoke; mod libcalls; +mod link; mod memory; mod mmap; mod sig_registry; @@ -58,13 +62,13 @@ mod traphandlers; mod vmcontext; mod world; -pub use action::{ActionOutcome, Value}; +pub use action::{ActionError, ActionOutcome, RuntimeValue}; pub use code::Code; -pub use execute::{compile_and_link_module, finish_instantiation}; pub use export::{ExportValue, NullResolver, Resolver}; -pub use get::get; +pub use get::{get, get_by_index}; pub use instance::Instance; -pub use invoke::invoke; +pub use invoke::{invoke, invoke_by_index, invoke_start_function}; +pub use link::link_module; pub use traphandlers::{call_wasm, LookupCodeSegment, RecordTrap, Unwind}; pub use vmcontext::{VMContext, VMGlobal, VMMemory, VMTable}; pub use world::InstanceWorld; diff --git a/lib/execute/src/execute.rs b/lib/execute/src/link.rs similarity index 63% rename from lib/execute/src/execute.rs rename to lib/execute/src/link.rs index 13ebe191bd..67c92440fb 100644 --- a/lib/execute/src/execute.rs +++ b/lib/execute/src/link.rs @@ -1,153 +1,162 @@ -//! TODO: Move the contents of this file to other files, as "execute.rs" is -//! no longer a descriptive filename. - -use action::ActionOutcome; -use code::Code; use cranelift_codegen::binemit::Reloc; -use cranelift_codegen::isa::TargetIsa; use cranelift_entity::{EntityRef, PrimaryMap}; use cranelift_wasm::{ DefinedFuncIndex, Global, GlobalInit, Memory, MemoryIndex, Table, TableElementType, }; use export::{ExportValue, Resolver}; -use instance::Instance; -use invoke::invoke_by_index; -use region::{protect, Protection}; +use imports::Imports; use std::ptr::write_unaligned; -use std::string::String; use std::vec::Vec; use vmcontext::VMContext; +use vmcontext::{VMGlobal, VMMemory, VMTable}; use wasmtime_environ::{ - compile_module, Compilation, MemoryPlan, MemoryStyle, Module, ModuleTranslation, Relocation, - RelocationTarget, TablePlan, TableStyle, + MemoryPlan, MemoryStyle, Module, Relocation, RelocationTarget, Relocations, TablePlan, + TableStyle, }; -/// Executes a module that has been translated with the `wasmtime-environ` environment -/// implementation. -pub fn compile_and_link_module<'data, 'module>( - isa: &TargetIsa, - translation: &ModuleTranslation<'data, 'module>, - resolver: &mut Resolver, -) -> Result { - let (mut compilation, relocations) = compile_module(&translation, isa)?; +/// A link error, such as incompatible or unmatched imports/exports. +#[derive(Fail, Debug)] +#[fail(display = "Link error: {}", _0)] +pub struct LinkError(String); - for (index, (ref module, ref field)) in translation.module.imported_funcs.iter() { - match resolver.resolve(module, field) { +/// Links a module that has been compiled with `compiled_module` in `wasmtime-environ`. +pub fn link_module( + module: &Module, + allocated_functions: &PrimaryMap, + relocations: Relocations, + resolver: &mut Resolver, +) -> Result { + let mut imports = Imports::new(); + + for (index, (ref module_name, ref field)) in module.imported_funcs.iter() { + match resolver.resolve(module_name, field) { Some(export_value) => match export_value { ExportValue::Function { address, signature } => { - let import_signature = - &translation.module.signatures[translation.module.functions[index]]; + let import_signature = &module.signatures[module.functions[index]]; if signature != *import_signature { - return Err(format!( - "{}/{}: exported function with signature {} incompatible with function import with signature {}", - module, field, - signature, import_signature, + return Err(LinkError( + format!("{}/{}: exported function with signature {} incompatible with function import with signature {}", + module_name, field, + signature, import_signature) )); } - compilation.resolved_func_imports.push(address); + imports.functions.push(address); } ExportValue::Table { .. } | ExportValue::Memory { .. } | ExportValue::Global { .. } => { - return Err(format!( + return Err(LinkError(format!( "{}/{}: export not compatible with function import", - module, field - )); + module_name, field + ))); } }, - None => return Err(format!("{}/{}: no provided import function", module, field)), + None => { + return Err(LinkError(format!( + "{}/{}: no provided import function", + module_name, field + ))) + } } } - for (index, (ref module, ref field)) in translation.module.imported_globals.iter() { - match resolver.resolve(module, field) { + + for (index, (ref module_name, ref field)) in module.imported_globals.iter() { + match resolver.resolve(module_name, field) { Some(export_value) => match export_value { ExportValue::Global { address, global } => { - let imported_global = translation.module.globals[index]; + let imported_global = module.globals[index]; if !is_global_compatible(&global, &imported_global) { - return Err(format!( + return Err(LinkError(format!( "{}/{}: exported global incompatible with global import", - module, field, - )); + module_name, field + ))); } - compilation.resolved_global_imports.push(address as usize); + imports.globals.push(address as *mut VMGlobal); } ExportValue::Table { .. } | ExportValue::Memory { .. } | ExportValue::Function { .. } => { - return Err(format!( + return Err(LinkError(format!( "{}/{}: exported global incompatible with global import", - module, field - )); + module_name, field + ))); } }, None => { - return Err(format!( + return Err(LinkError(format!( "no provided import global for {}/{}", - module, field - )) + module_name, field + ))) } } } - for (index, (ref module, ref field)) in translation.module.imported_tables.iter() { - match resolver.resolve(module, field) { + + for (index, (ref module_name, ref field)) in module.imported_tables.iter() { + match resolver.resolve(module_name, field) { Some(export_value) => match export_value { ExportValue::Table { address, table } => { - let import_table = &translation.module.table_plans[index]; + let import_table = &module.table_plans[index]; if !is_table_compatible(&table, import_table) { - return Err(format!( + return Err(LinkError(format!( "{}/{}: exported table incompatible with table import", - module, field, - )); + module_name, field, + ))); } - compilation.resolved_table_imports.push(address as usize); + imports.tables.push(address as *mut VMTable); } ExportValue::Global { .. } | ExportValue::Memory { .. } | ExportValue::Function { .. } => { - return Err(format!( + return Err(LinkError(format!( "{}/{}: export not compatible with table import", - module, field - )); + module_name, field + ))); } }, - None => return Err(format!("no provided import table for {}/{}", module, field)), + None => { + return Err(LinkError(format!( + "no provided import table for {}/{}", + module_name, field + ))) + } } } - for (index, (ref module, ref field)) in translation.module.imported_memories.iter() { - match resolver.resolve(module, field) { + + for (index, (ref module_name, ref field)) in module.imported_memories.iter() { + match resolver.resolve(module_name, field) { Some(export_value) => match export_value { ExportValue::Memory { address, memory } => { - let import_memory = &translation.module.memory_plans[index]; + let import_memory = &module.memory_plans[index]; if is_memory_compatible(&memory, import_memory) { - return Err(format!( + return Err(LinkError(format!( "{}/{}: exported memory incompatible with memory import", - module, field - )); + module_name, field + ))); } - compilation.resolved_memory_imports.push(address as usize); + imports.memories.push(address as *mut VMMemory); } ExportValue::Table { .. } | ExportValue::Global { .. } | ExportValue::Function { .. } => { - return Err(format!( + return Err(LinkError(format!( "{}/{}: export not compatible with memory import", - module, field - )); + module_name, field + ))); } }, None => { - return Err(format!( + return Err(LinkError(format!( "no provided import memory for {}/{}", - module, field - )) + module_name, field + ))) } } } // Apply relocations, now that we have virtual addresses for everything. - relocate(&mut compilation, &relocations, &translation.module)?; + relocate(&imports, allocated_functions, relocations, &module); - Ok(compilation) + Ok(imports) } fn is_global_compatible(exported: &Global, imported: &Global) -> bool { @@ -265,23 +274,19 @@ fn is_memory_compatible(exported: &MemoryPlan, imported: &MemoryPlan) -> bool { && exported_offset_guard_size >= imported_offset_guard_size } -extern "C" { - pub fn __rust_probestack(); -} - /// Performs the relocations inside the function bytecode, provided the necessary metadata. fn relocate( - compilation: &mut Compilation, - relocations: &PrimaryMap>, + imports: &Imports, + allocated_functions: &PrimaryMap, + relocations: PrimaryMap>, module: &Module, -) -> Result<(), String> { - // The relocations are relative to the relocation's address plus four bytes. - for (i, function_relocs) in relocations.iter() { +) { + for (i, function_relocs) in relocations.into_iter() { for r in function_relocs { let target_func_address: usize = match r.reloc_target { RelocationTarget::UserFunc(index) => match module.defined_func_index(index) { - Some(f) => compilation.functions[f].as_ptr() as usize, - None => compilation.resolved_func_imports[index], + Some(f) => allocated_functions[f].0 as usize, + None => imports.functions[index] as usize, }, RelocationTarget::MemoryGrow => wasmtime_memory_grow as usize, RelocationTarget::MemorySize => wasmtime_memory_size as usize, @@ -303,11 +308,11 @@ fn relocate( } }; - let body = &mut compilation.functions[i]; + let body = allocated_functions[i].0; 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_address = body.add(r.offset as usize) as usize; let reloc_addend = r.addend as isize; let reloc_abs = (target_func_address as u64) .checked_add(reloc_addend as u64) @@ -316,7 +321,7 @@ fn relocate( }, #[cfg(target_pointer_width = "32")] Reloc::X86PCRel4 => unsafe { - let reloc_address = body.as_mut_ptr().add(r.offset as usize) as usize; + let reloc_address = body.add(r.offset as usize) as usize; let reloc_addend = r.addend as isize; let reloc_delta_u32 = (target_func_address as u32) .wrapping_sub(reloc_address as u32) @@ -328,9 +333,15 @@ fn relocate( } } } - Ok(()) } +/// A declaration for the stack probe function in Rust's standard library, for +/// catching callstack overflow. +extern "C" { + pub fn __rust_probestack(); +} + +/// The implementation of memory.grow. 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); @@ -340,53 +351,10 @@ extern "C" fn wasmtime_memory_grow(size: u32, memory_index: u32, vmctx: *mut VMC .unwrap_or(u32::max_value()) } +/// The implementation of memory.size. 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); instance.memory_size(memory_index) } - -/// prepares the execution context -pub fn finish_instantiation( - code: &mut Code, - isa: &TargetIsa, - module: &Module, - compilation: &Compilation, - instance: &mut Instance, -) -> Result<(), String> { - // 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() { - match unsafe { - protect( - code_buf.as_ptr(), - code_buf.len(), - Protection::ReadWriteExecute, - ) - } { - Ok(()) => (), - Err(err) => { - return Err(format!( - "failed to give executable permission to code: {}", - err - )) - } - } - } - - if let Some(start_index) = module.start_func { - let vmctx = instance.vmctx(); - let result = invoke_by_index(code, isa, module, compilation, vmctx, start_index, &[])?; - match result { - ActionOutcome::Returned { values } => { - assert!(values.is_empty()); - } - ActionOutcome::Trapped { message } => { - return Err(format!("start function trapped: {}", message)); - } - } - } - - Ok(()) -} diff --git a/lib/execute/src/memory.rs b/lib/execute/src/memory.rs index e5f7c78ef3..31dc5c4014 100644 --- a/lib/execute/src/memory.rs +++ b/lib/execute/src/memory.rs @@ -50,8 +50,8 @@ impl LinearMemory { inaccessible_bytes, region::Protection::None, ) - .expect("unable to make memory inaccessible"); } + .expect("unable to make memory inaccessible"); Ok(Self { mmap, diff --git a/lib/execute/src/world.rs b/lib/execute/src/world.rs index e0eb300767..19e2d89da5 100644 --- a/lib/execute/src/world.rs +++ b/lib/execute/src/world.rs @@ -1,15 +1,18 @@ -use action::{ActionOutcome, Value}; +use action::{ActionError, ActionOutcome, RuntimeValue}; use code::Code; use cranelift_codegen::isa; -use cranelift_wasm::{GlobalIndex, MemoryIndex}; -use execute::{compile_and_link_module, finish_instantiation}; +use cranelift_entity::PrimaryMap; +use cranelift_wasm::{DefinedFuncIndex, GlobalIndex, MemoryIndex}; use export::Resolver; use get::get; use instance::Instance; -use invoke::invoke; +use invoke::{invoke, invoke_start_function}; +use link::link_module; use std::str; use vmcontext::VMGlobal; -use wasmtime_environ::{Compilation, Module, ModuleEnvironment, Tunables}; +use wasmtime_environ::{ + compile_module, Compilation, CompileError, Module, ModuleEnvironment, Tunables, +}; /// A module, an instance of that module, and accompanying compilation artifacts. /// @@ -17,7 +20,6 @@ use wasmtime_environ::{Compilation, Module, ModuleEnvironment, Tunables}; pub struct InstanceWorld { module: Module, instance: Instance, - compilation: Compilation, } impl InstanceWorld { @@ -27,34 +29,61 @@ impl InstanceWorld { isa: &isa::TargetIsa, data: &[u8], resolver: &mut Resolver, - ) -> Result { + ) -> Result { let mut module = Module::new(); // TODO: Allow the tunables to be overridden. let tunables = Tunables::default(); - let (instance, compilation) = { - let translation = { - let environ = ModuleEnvironment::new(isa, &mut module, tunables); + let instance = { + // TODO: Untie this. + let ((mut compilation, relocations), lazy_data_initializers) = { + let (lazy_function_body_inputs, lazy_data_initializers) = { + let environ = ModuleEnvironment::new(isa, &mut module, tunables); - environ.translate(&data).map_err(|e| e.to_string())? + let translation = environ + .translate(&data) + .map_err(|error| ActionError::Compile(CompileError::Wasm(error)))?; + + ( + translation.lazy.function_body_inputs, + translation.lazy.data_initializers, + ) + }; + + ( + compile_module(&module, &lazy_function_body_inputs, isa) + .map_err(ActionError::Compile)?, + lazy_data_initializers, + ) }; - let compilation = compile_and_link_module(isa, &translation, resolver)?; + let allocated_functions = + allocate_functions(code, compilation).map_err(ActionError::Resource)?; + + let resolved = link_module(&module, &allocated_functions, relocations, resolver) + .map_err(ActionError::Link)?; + let mut instance = Instance::new( - translation.module, - &compilation, - &translation.lazy.data_initializers, - )?; + &module, + allocated_functions, + &lazy_data_initializers, + resolved, + ) + .map_err(ActionError::Resource)?; - finish_instantiation(code, isa, &translation.module, &compilation, &mut instance)?; + // The WebAssembly spec specifies that the start function is + // invoked automatically at instantiation time. + match invoke_start_function(code, isa, &module, &mut instance)? { + ActionOutcome::Returned { .. } => {} + ActionOutcome::Trapped { message } => { + // Instantiation fails if the start function traps. + return Err(ActionError::Start(message)); + } + } - (instance, compilation) + instance }; - Ok(Self { - module, - instance, - compilation, - }) + Ok(Self { module, instance }) } /// Invoke a function in this `InstanceWorld` by name. @@ -63,23 +92,21 @@ impl InstanceWorld { code: &mut Code, isa: &isa::TargetIsa, function_name: &str, - args: &[Value], - ) -> Result { + args: &[RuntimeValue], + ) -> Result { invoke( code, isa, &self.module, - &self.compilation, - self.instance.vmctx(), + &mut self.instance, &function_name, args, ) - .map_err(|e| e.to_string()) } /// Read a global in this `InstanceWorld` by name. - pub fn get(&mut self, global_name: &str) -> Result { - get(&self.module, self.instance.vmctx(), global_name).map_err(|e| e.to_string()) + pub fn get(&mut self, global_name: &str) -> Result { + get(&self.module, &mut self.instance, global_name) } /// Returns a slice of the contents of allocated linear memory. @@ -92,3 +119,15 @@ impl InstanceWorld { self.instance.inspect_global(global_index) } } + +fn allocate_functions( + code: &mut Code, + compilation: Compilation, +) -> Result, String> { + let mut result = PrimaryMap::with_capacity(compilation.functions.len()); + for (_, body) in compilation.functions.into_iter() { + let slice = code.allocate_copy_of_slice(&body)?; + result.push((slice.as_mut_ptr(), slice.len())); + } + Ok(result) +} diff --git a/lib/wast/Cargo.toml b/lib/wast/Cargo.toml index dce4c44b86..7447375c3f 100644 --- a/lib/wast/Cargo.toml +++ b/lib/wast/Cargo.toml @@ -13,10 +13,13 @@ readme = "README.md" 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-wasm = { git = "https://github.com/sunfishcode/cranelift.git", branch = "guard-offset" } +cranelift-entity = { git = "https://github.com/sunfishcode/cranelift.git", branch = "guard-offset" } wasmtime-execute = { path = "../execute" } wasmtime-environ = { path = "../environ" } wabt = "0.7" target-lexicon = "0.2.0" +failure = "0.1.3" +failure_derive = "0.1.3" [badges] maintenance = { status = "experimental" } diff --git a/lib/wast/build.rs b/lib/wast/build.rs index 4d681c1ed7..09577e0509 100644 --- a/lib/wast/build.rs +++ b/lib/wast/build.rs @@ -48,7 +48,7 @@ fn test_directory(out: &mut File, testsuite: &str) -> io::Result<()> { .unwrap() .replace("-", "_") )?; - writeln!(out, " use super::{{native_isa, wast_file, Path}};")?; + writeln!(out, " use super::{{native_isa, WastContext, Path}};")?; for dir_entry in dir_entries { let path = dir_entry.path(); let stemstr = path @@ -66,9 +66,10 @@ fn test_directory(out: &mut File, testsuite: &str) -> io::Result<()> { " fn {}() {{", avoid_keywords(&stemstr.replace("-", "_")) )?; + writeln!(out, " let mut wast_context = WastContext::new();")?; writeln!( out, - " wast_file(Path::new(\"{}\"), &*native_isa()).expect(\"error loading wast file {}\");", + " wast_context.run_file(&*native_isa(), Path::new(\"{}\")).expect(\"error running wast file: {}\");", path.display(), path.display() )?; diff --git a/lib/wast/src/lib.rs b/lib/wast/src/lib.rs index 1f8fc8f832..6e6126ae1f 100644 --- a/lib/wast/src/lib.rs +++ b/lib/wast/src/lib.rs @@ -24,6 +24,11 @@ extern crate cranelift_codegen; extern crate cranelift_wasm; +#[macro_use] +extern crate cranelift_entity; +extern crate failure; +#[macro_use] +extern crate failure_derive; extern crate target_lexicon; extern crate wabt; extern crate wasmtime_environ; @@ -32,4 +37,4 @@ extern crate wasmtime_execute; mod spectest; mod wast; -pub use wast::{wast_buffer, wast_file}; +pub use wast::{WastContext, WastError}; diff --git a/lib/wast/src/spectest.rs b/lib/wast/src/spectest.rs index af05c97143..0f8d89357b 100644 --- a/lib/wast/src/spectest.rs +++ b/lib/wast/src/spectest.rs @@ -53,17 +53,17 @@ impl SpecTest { Self { spectest_global_i32: VMGlobal::definition(&Global { ty: types::I32, - mutability: false, + mutability: true, initializer: GlobalInit::I32Const(0), }), spectest_global_f32: VMGlobal::definition(&Global { ty: types::I32, - mutability: false, + mutability: true, initializer: GlobalInit::F32Const(0), }), spectest_global_f64: VMGlobal::definition(&Global { ty: types::I32, - mutability: false, + mutability: true, initializer: GlobalInit::F64Const(0), }), spectest_table: VMTable::definition(ptr::null_mut(), 0), @@ -79,7 +79,7 @@ impl Resolver for SpecTest { match module { "spectest" => match field { "print" => Some(ExportValue::function( - spectest_print as usize, + spectest_print as *const u8, translate_signature( ir::Signature { params: vec![], @@ -90,7 +90,7 @@ impl Resolver for SpecTest { ), )), "print_i32" => Some(ExportValue::function( - spectest_print_i32 as usize, + spectest_print_i32 as *const u8, translate_signature( ir::Signature { params: vec![ir::AbiParam::new(types::I32)], @@ -101,7 +101,7 @@ impl Resolver for SpecTest { ), )), "print_i64" => Some(ExportValue::function( - spectest_print_i64 as usize, + spectest_print_i64 as *const u8, translate_signature( ir::Signature { params: vec![ir::AbiParam::new(types::I64)], @@ -112,7 +112,7 @@ impl Resolver for SpecTest { ), )), "print_f32" => Some(ExportValue::function( - spectest_print_f32 as usize, + spectest_print_f32 as *const u8, translate_signature( ir::Signature { params: vec![ir::AbiParam::new(types::F32)], @@ -123,7 +123,7 @@ impl Resolver for SpecTest { ), )), "print_f64" => Some(ExportValue::function( - spectest_print_f64 as usize, + spectest_print_f64 as *const u8, translate_signature( ir::Signature { params: vec![ir::AbiParam::new(types::F64)], @@ -134,7 +134,7 @@ impl Resolver for SpecTest { ), )), "print_i32_f32" => Some(ExportValue::function( - spectest_print_i32_f32 as usize, + spectest_print_i32_f32 as *const u8, translate_signature( ir::Signature { params: vec![ @@ -148,7 +148,7 @@ impl Resolver for SpecTest { ), )), "print_f64_f64" => Some(ExportValue::function( - spectest_print_f64_f64 as usize, + spectest_print_f64_f64 as *const u8, translate_signature( ir::Signature { params: vec![ diff --git a/lib/wast/src/wast.rs b/lib/wast/src/wast.rs index a026b8627b..ff1145539d 100644 --- a/lib/wast/src/wast.rs +++ b/lib/wast/src/wast.rs @@ -1,24 +1,98 @@ use cranelift_codegen::isa; +use cranelift_entity::PrimaryMap; use spectest::SpecTest; 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::{ActionOutcome, Code, InstanceWorld, Value}; +use std::{fmt, fs, io, str}; +use wabt::script::{Action, Command, CommandKind, ModuleBinary, ScriptParser, Value}; +use wasmtime_execute::{ActionError, ActionOutcome, Code, InstanceWorld, RuntimeValue}; -struct Instances { - current: Option, - namespace: HashMap, +/// Translate from a script::Value to a RuntimeValue. +fn runtime_value(v: Value) -> RuntimeValue { + match v { + Value::I32(x) => RuntimeValue::I32(x), + Value::I64(x) => RuntimeValue::I64(x), + Value::F32(x) => RuntimeValue::F32(x.to_bits()), + Value::F64(x) => RuntimeValue::F64(x.to_bits()), + } +} + +/// Indicates an unknown module was specified. +#[derive(Fail, Debug)] +pub struct UnknownModule { + module: Option, +} + +impl fmt::Display for UnknownModule { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self.module { + None => write!(f, "no default module present"), + Some(ref name) => write!(f, "no module {} present", name), + } + } +} + +/// Error message used by `WastContext`. +#[derive(Fail, Debug)] +pub enum WastError { + /// An assert command was not satisfied. + Assert(String), + /// An unknown module name was used. + Module(UnknownModule), + /// An error occured while performing an action. + Action(ActionError), + /// An action trapped. + Trap(String), + /// There was a type error in inputs or outputs of an action. + Type(String), + /// The was an I/O error while reading the wast file. + IO(io::Error), +} + +impl fmt::Display for WastError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + WastError::Assert(ref message) => write!(f, "Assert command failed: {}", message), + WastError::Module(ref error) => error.fmt(f), + WastError::Action(ref error) => error.fmt(f), + WastError::Trap(ref message) => write!(f, "trap: {}", message), + WastError::Type(ref message) => write!(f, "type error: {}", message), + WastError::IO(ref error) => write!(f, "I/O error: {}", error), + } + } +} + +/// Error message with a source file and line number. +#[derive(Fail, Debug)] +#[fail(display = "{}:{}: {}", filename, line, error)] +pub struct WastFileError { + filename: String, + line: u64, + error: WastError, +} + +/// An opaque reference to an `InstanceWorld`. +#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct WorldIndex(u32); +entity_impl!(WorldIndex, "world"); + +/// The wast test script language allows modules to be defined and actions +/// to be performed on them. +pub struct WastContext { + /// A namespace of wasm modules, keyed by an optional name. + worlds: PrimaryMap, + current: Option, + namespace: HashMap, code: Code, spectest: SpecTest, } -impl Instances { +impl WastContext { + /// Construct a new instance of `WastContext`. pub fn new() -> Self { Self { + worlds: PrimaryMap::new(), current: None, namespace: HashMap::new(), code: Code::new(), @@ -30,290 +104,380 @@ impl Instances { &mut self, isa: &isa::TargetIsa, module: ModuleBinary, - ) -> Result { + ) -> Result { InstanceWorld::new(&mut self.code, isa, &module.into_vec(), &mut self.spectest) } - pub fn define_unnamed_module( - &mut self, - isa: &isa::TargetIsa, - module: ModuleBinary, - ) -> Result<(), String> { - self.current = Some(self.instantiate(isa, module)?); - Ok(()) + fn get_world(&mut self, module: &Option) -> Result { + let index = *if let Some(name) = module { + self.namespace.get_mut(name).ok_or_else(|| { + WastError::Module(UnknownModule { + module: Some(name.to_owned()), + }) + }) + } else { + self.current + .as_mut() + .ok_or_else(|| WastError::Module(UnknownModule { module: None })) + }?; + + Ok(index) } - pub fn define_named_module( + /// Define a module and register it. + pub fn module( &mut self, isa: &isa::TargetIsa, - name: String, + name: Option, module: ModuleBinary, - ) -> Result<(), String> { + ) -> Result<(), ActionError> { let world = self.instantiate(isa, module)?; - self.namespace.insert(name, world); + let index = if let Some(name) = name { + self.register(name, world) + } else { + self.worlds.push(world) + }; + self.current = Some(index); Ok(()) } - pub fn perform_action( + /// Register a module to make it available for performing actions. + pub fn register(&mut self, name: String, world: InstanceWorld) -> WorldIndex { + let index = self.worlds.push(world); + self.namespace.insert(name, index); + index + } + + /// Invoke an exported function from a defined module. + pub fn invoke( + &mut self, + isa: &isa::TargetIsa, + module: Option, + field: &str, + args: &[Value], + ) -> Result { + let mut value_args = Vec::with_capacity(args.len()); + for arg in args { + value_args.push(runtime_value(*arg)); + } + let index = self.get_world(&module)?; + self.worlds[index] + .invoke(&mut self.code, isa, &field, &value_args) + .map_err(WastError::Action) + } + + /// Get the value of an exported global from a defined module. + pub fn get(&mut self, module: Option, field: &str) -> Result { + let index = self.get_world(&module)?; + self.worlds[index].get(&field).map_err(WastError::Action) + } + + fn perform_action( &mut self, isa: &isa::TargetIsa, action: Action, - ) -> Result { + ) -> Result { 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 => Err("invoke performed with no module present".to_string()), - Some(ref mut instance_world) => instance_world - .invoke(&mut self.code, isa, &field, &value_args) - .map_err(|e| { - format!("error invoking {} in current module: {}", field, e) - }), - }, - Some(name) => self - .namespace - .get_mut(&name) - .ok_or_else(|| format!("module {} not declared", name))? - .invoke(&mut self.code, isa, &field, &value_args) - .map_err(|e| format!("error invoking {} in module {}: {}", field, name, e)), - } - } + } => self.invoke(isa, module, &field, &args), Action::Get { module, field } => { - let value = match module { - None => match self.current { - None => return Err("get performed with no module present".to_string()), - Some(ref mut instance_world) => { - instance_world.get(&field).map_err(|e| { - format!("error getting {} in current module: {}", field, e) - })? - } - }, - Some(name) => self - .namespace - .get_mut(&name) - .ok_or_else(|| format!("module {} not declared", name))? - .get(&field) - .map_err(|e| { - format!("error getting {} in module {}: {}", field, name, e) - })?, - }; + let value = self.get(module, &field)?; Ok(ActionOutcome::Returned { values: vec![value], }) } } } -} -/// Run a wast script from a byte buffer. -pub fn wast_buffer(name: &str, isa: &isa::TargetIsa, wast: &[u8]) -> Result<(), String> { - let mut parser = ScriptParser::from_str(str::from_utf8(wast).unwrap()).unwrap(); - let mut instances = Instances::new(); + /// Run a wast script from a byte buffer. + pub fn run_buffer( + &mut self, + isa: &isa::TargetIsa, + filename: &str, + wast: &[u8], + ) -> Result<(), WastFileError> { + let mut parser = ScriptParser::from_str(str::from_utf8(wast).unwrap()).unwrap(); - while let Some(Command { kind, line }) = parser.next().unwrap() { - match kind { - CommandKind::Module { module, name } => { - if let Some(name) = name { - instances.define_named_module(isa, name, module.clone())?; + while let Some(Command { kind, line }) = parser.next().unwrap() { + match kind { + CommandKind::Module { module, name } => { + self.module(isa, name, module) + .map_err(|error| WastFileError { + filename: filename.to_string(), + line, + error: WastError::Action(error), + })?; } - - instances.define_unnamed_module(isa, module)?; - } - CommandKind::Register { - name: _name, - as_name: _as_name, - } => { - println!("{}:{}: TODO: Implement register", name, line); - } - CommandKind::PerformAction(action) => match instances.perform_action(isa, action)? { - ActionOutcome::Returned { .. } => {} - ActionOutcome::Trapped { message } => { - panic!("{}:{}: a trap occurred: {}", name, line, message); + CommandKind::Register { + name: _name, + as_name: _as_name, + } => { + println!("{}:{}: TODO: Implement register", filename, line); } - }, - CommandKind::AssertReturn { action, expected } => { - match instances.perform_action(isa, action)? { - ActionOutcome::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) + CommandKind::PerformAction(action) => match self + .perform_action(isa, action) + .map_err(|error| WastFileError { + filename: filename.to_string(), + line, + error, + })? { + ActionOutcome::Returned { .. } => {} + ActionOutcome::Trapped { message } => { + return Err(WastFileError { + filename: filename.to_string(), + line, + error: WastError::Trap(message), + }); + } + }, + CommandKind::AssertReturn { action, expected } => { + match self + .perform_action(isa, action) + .map_err(|error| WastFileError { + filename: filename.to_string(), + line, + error, + })? { + ActionOutcome::Returned { values } => { + for (v, e) in values + .iter() + .cloned() + .zip(expected.iter().cloned().map(runtime_value)) + { + if v != e { + return Err(WastFileError { + filename: filename.to_string(), + line, + error: WastError::Assert(format!( + "expected {}, got {}", + e, v + )), + }); } - 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) - } - }; + } + } + ActionOutcome::Trapped { message } => { + return Err(WastFileError { + filename: filename.to_string(), + line, + error: WastError::Assert(format!("unexpected trap: {}", message)), + }); } } - ActionOutcome::Trapped { message } => { - panic!( - "{}:{}: expected normal return, but a trap occurred: {}", - name, line, message - ); - } } - } - CommandKind::AssertTrap { action, message } => { - match instances.perform_action(isa, action)? { - ActionOutcome::Returned { values } => panic!( - "{}:{}: expected trap, but invoke returned with {:?}", - name, line, values - ), - ActionOutcome::Trapped { - message: trap_message, - } => { - println!( - "{}:{}: TODO: Check the assert_trap message: expected {}, got {}", - name, line, message, trap_message - ); - } - } - } - CommandKind::AssertExhaustion { action } => { - match instances.perform_action(isa, action)? { - ActionOutcome::Returned { values } => panic!( - "{}:{}: expected exhaustion, but invoke returned with {:?}", - name, line, values - ), - ActionOutcome::Trapped { message } => { - println!( - "{}:{}: TODO: Check the assert_exhaustion message: {}", - name, line, message - ); - } - } - } - CommandKind::AssertReturnCanonicalNan { action } => { - match instances.perform_action(isa, action)? { - ActionOutcome::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 - ), - }; + CommandKind::AssertTrap { action, message } => { + match self + .perform_action(isa, action) + .map_err(|error| WastFileError { + filename: filename.to_string(), + line, + error, + })? { + ActionOutcome::Returned { values } => { + return Err(WastFileError { + filename: filename.to_string(), + line, + error: WastError::Assert(format!( + "expected trap, but invoke returned with {:?}", + values + )), + }); + } + ActionOutcome::Trapped { + message: trap_message, + } => { + println!( + "{}:{}: TODO: Check the assert_trap message: expected {}, got {}", + filename, line, message, trap_message + ); } } - ActionOutcome::Trapped { message } => { - panic!( - "{}:{}: expected canonical NaN return, but a trap occurred: {}", - name, line, message - ); - } } - } - CommandKind::AssertReturnArithmeticNan { action } => { - match instances.perform_action(isa, action)? { - ActionOutcome::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 - ), - }; + CommandKind::AssertExhaustion { action } => { + match self + .perform_action(isa, action) + .map_err(|error| WastFileError { + filename: filename.to_string(), + line, + error, + })? { + ActionOutcome::Returned { values } => { + return Err(WastFileError { + filename: filename.to_string(), + line, + error: WastError::Assert(format!( + "expected callstack exhaustion, but invoke returned with {:?}", + values + )), + }); + } + ActionOutcome::Trapped { message } => { + println!( + "{}:{}: TODO: Check the assert_exhaustion message: {}", + filename, line, message + ); } } - ActionOutcome::Trapped { message } => { - panic!( - "{}:{}: expected canonical NaN return, but a trap occurred: {}", - name, line, message - ); + } + CommandKind::AssertReturnCanonicalNan { action } => { + match self + .perform_action(isa, action) + .map_err(|error| WastFileError { + filename: filename.to_string(), + line, + error, + })? { + ActionOutcome::Returned { values } => { + for v in values.iter() { + match v { + RuntimeValue::I32(_) | RuntimeValue::I64(_) => { + return Err(WastFileError { + filename: filename.to_string(), + line, + error: WastError::Type(format!( + "unexpected integer type in NaN test" + )), + }); + } + RuntimeValue::F32(x) => { + if (x & 0x7fffffff) != 0x7fc00000 { + return Err(WastFileError { + filename: filename.to_string(), + line, + error: WastError::Assert(format!( + "expected canonical NaN" + )), + }); + } + } + RuntimeValue::F64(x) => { + if (x & 0x7fffffffffffffff) != 0x7ff8000000000000 { + return Err(WastFileError { + filename: filename.to_string(), + line, + error: WastError::Assert(format!( + "expected canonical NaN" + )), + }); + } + } + }; + } + } + ActionOutcome::Trapped { message } => { + return Err(WastFileError { + filename: filename.to_string(), + line, + error: WastError::Assert(format!("unexpected trap: {}", message)), + }); + } } } - } - CommandKind::AssertInvalid { - module: _module, - message: _message, - } => { - println!("{}:{}: TODO: Implement assert_invalid", name, line); - } - CommandKind::AssertMalformed { - module: _module, - message: _message, - } => { - println!("{}:{}: TODO: Implement assert_malformed", name, line); - } - CommandKind::AssertUninstantiable { module, message } => { - let _err = instances - .define_unnamed_module(isa, module) - .expect_err(&format!( + CommandKind::AssertReturnArithmeticNan { action } => { + match self + .perform_action(isa, action) + .map_err(|error| WastFileError { + filename: filename.to_string(), + line, + error, + })? { + ActionOutcome::Returned { values } => { + for v in values.iter() { + match v { + RuntimeValue::I32(_) | RuntimeValue::I64(_) => { + return Err(WastFileError { + filename: filename.to_string(), + line, + error: WastError::Type(format!( + "unexpected integer type in NaN test", + )), + }); + } + RuntimeValue::F32(x) => { + if (x & 0x00400000) != 0x00400000 { + return Err(WastFileError { + filename: filename.to_string(), + line, + error: WastError::Assert(format!( + "expected arithmetic NaN" + )), + }); + } + } + RuntimeValue::F64(x) => { + if (x & 0x0008000000000000) != 0x0008000000000000 { + return Err(WastFileError { + filename: filename.to_string(), + line, + error: WastError::Assert(format!( + "expected arithmetic NaN" + )), + }); + } + } + }; + } + } + ActionOutcome::Trapped { message } => { + return Err(WastFileError { + filename: filename.to_string(), + line, + error: WastError::Assert(format!("unexpected trap: {}", message)), + }); + } + } + } + CommandKind::AssertInvalid { + module: _module, + message: _message, + } => { + println!("{}:{}: TODO: Implement assert_invalid", filename, line); + } + CommandKind::AssertMalformed { + module: _module, + message: _message, + } => { + println!("{}:{}: TODO: Implement assert_malformed", filename, line); + } + CommandKind::AssertUninstantiable { module, message } => { + let _err = self.module(isa, None, module).expect_err(&format!( "{}:{}: uninstantiable module was successfully instantiated", - name, line + filename, line )); - println!( - "{}:{}: TODO: Check the assert_uninstantiable message: {}", - name, line, message - ); - } - CommandKind::AssertUnlinkable { module, message } => { - let _err = instances - .define_unnamed_module(isa, module) - .expect_err(&format!( + println!( + "{}:{}: TODO: Check the assert_uninstantiable message: {}", + filename, line, message + ); + } + CommandKind::AssertUnlinkable { module, message } => { + let _err = self.module(isa, None, module).expect_err(&format!( "{}:{}: unlinkable module was successfully linked", - name, line + filename, line )); - println!( - "{}:{}: TODO: Check the assert_unlinkable message: {}", - name, line, message - ); + println!( + "{}:{}: TODO: Check the assert_unlinkable message: {}", + filename, line, message + ); + } } } + + Ok(()) } - Ok(()) -} - -/// 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) + /// Run a wast script from a file. + pub fn run_file(&mut self, isa: &isa::TargetIsa, path: &Path) -> Result<(), WastFileError> { + let filename = path.display().to_string(); + let buffer = read_to_end(path).map_err(|e| WastFileError { + filename, + line: 0, + error: WastError::IO(e), + })?; + self.run_buffer(isa, &path.display().to_string(), &buffer) + } } fn read_to_end(path: &Path) -> Result, io::Error> { diff --git a/lib/wast/tests/wast_files.rs b/lib/wast/tests/wast_files.rs index 9cdf59ab06..8e15fe73b2 100644 --- a/lib/wast/tests/wast_files.rs +++ b/lib/wast/tests/wast_files.rs @@ -6,7 +6,7 @@ use cranelift_codegen::isa; use cranelift_codegen::settings; use cranelift_codegen::settings::Configurable; use std::path::Path; -use wasmtime_wast::wast_file; +use wasmtime_wast::WastContext; include!(concat!(env!("OUT_DIR"), "/run_wast_files.rs")); diff --git a/src/run_wast.rs b/src/run_wast.rs index df4a971b0d..3e2bbaed57 100644 --- a/src/run_wast.rs +++ b/src/run_wast.rs @@ -38,7 +38,7 @@ use cranelift_codegen::settings; use cranelift_codegen::settings::Configurable; use docopt::Docopt; use std::path::Path; -use wasmtime_wast::wast_file; +use wasmtime_wast::WastContext; static LOG_FILENAME_PREFIX: &str = "cranelift.dbg."; @@ -94,9 +94,10 @@ fn main() { } let isa = isa_builder.finish(settings::Flags::new(flag_builder)); + let mut wast_context = WastContext::new(); for filename in &args.arg_file { - let path = Path::new(&filename); - wast_file(path, &*isa) - .unwrap_or_else(|e| panic!("error reading file {}: {}", path.display(), e)); + wast_context + .run_file(&*isa, Path::new(&filename)) + .unwrap_or_else(|e| panic!("{}", e)); } } diff --git a/src/wasm2obj.rs b/src/wasm2obj.rs index 70b6b15d1a..08be4b605a 100644 --- a/src/wasm2obj.rs +++ b/src/wasm2obj.rs @@ -146,7 +146,12 @@ fn handle_module(path: PathBuf, target: &Option, output: &str) -> Result .map_err(|err| format!("{}", err))?; } - let (compilation, relocations) = compile_module(&translation, &*isa)?; + let (compilation, relocations) = compile_module( + &translation.module, + &translation.lazy.function_body_inputs, + &*isa, + ) + .map_err(|e| e.to_string())?; emit_module(&mut obj, &translation.module, &compilation, &relocations)?; diff --git a/src/wasmtime.rs b/src/wasmtime.rs index 2318e495de..47391ba2ed 100644 --- a/src/wasmtime.rs +++ b/src/wasmtime.rs @@ -105,6 +105,13 @@ fn main() { .deserialize() }) .unwrap_or_else(|e| e.exit()); + + if args.flag_debug { + pretty_env_logger::init(); + } else { + file_per_thread_logger::initialize(LOG_FILENAME_PREFIX); + } + let isa_builder = cranelift_native::builder().unwrap_or_else(|_| { panic!("host machine is not a supported target"); }); @@ -115,18 +122,13 @@ fn main() { 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); match handle_module(&args, path, &*isa) { @@ -149,10 +151,14 @@ fn handle_module(args: &Args, path: &Path, isa: &TargetIsa) -> Result<(), String } let mut resolver = NullResolver {}; let mut code = Code::new(); - let mut world = InstanceWorld::new(&mut code, isa, &data, &mut resolver)?; + let mut world = + InstanceWorld::new(&mut code, isa, &data, &mut resolver).map_err(|e| e.to_string())?; if let Some(ref f) = args.flag_invoke { - match world.invoke(&mut code, isa, &f, &[])? { + match world + .invoke(&mut code, isa, &f, &[]) + .map_err(|e| e.to_string())? + { ActionOutcome::Returned { .. } => {} ActionOutcome::Trapped { message } => { return Err(format!("Trap from within function {}: {}", f, message));