wasmtime 0.3.0

Command-line interface for Wasmtime
Documentation
diff --git a/lib/environ/src/cranelift.rs b/lib/environ/src/cranelift.rs
index 7cc35a9..6dcd780 100644
--- a/lib/environ/src/cranelift.rs
+++ b/lib/environ/src/cranelift.rs
@@ -80,15 +80,52 @@ impl RelocSink {
     }
 }
 
+/// A record of a possible trap.
+pub struct Trap {
+    /// The offset in the function where the trap occurs.
+    pub offset: binemit::CodeOffset,
+    /// The kind of trap.
+    pub code: ir::TrapCode,
+    /// The wasm bytecode offset within the containing function for the trap.
+    pub srcloc: ir::SourceLoc,
+}
+
+/// Traps recorded for function bodies.
+pub type Traps = PrimaryMap<DefinedFuncIndex, Vec<Trap>>;
+
+struct WasmtimeTrapSink {
+    pub func_traps: Vec<Trap>,
+}
+
+impl WasmtimeTrapSink {
+    /// Return a new `TrapSink` instance.
+    pub fn new() -> Self {
+        Self {
+            func_traps: Vec::new(),
+        }
+    }
+}
+
+impl binemit::TrapSink for WasmtimeTrapSink {
+    fn trap(&mut self, offset: binemit::CodeOffset, srcloc: ir::SourceLoc, code: ir::TrapCode) {
+        self.func_traps.push(Trap {
+            offset,
+            srcloc,
+            code,
+        })
+    }
+}
+
 /// Compile the module using Cranelift, producing a compilation result with
-/// associated relocations.
+/// associated relocations and trap records.
 pub fn compile_module<'data, 'module>(
     module: &'module Module,
     function_body_inputs: PrimaryMap<DefinedFuncIndex, &'data [u8]>,
     isa: &dyn isa::TargetIsa,
-) -> Result<(Compilation, Relocations), CompileError> {
+) -> Result<(Compilation, Relocations, Traps), CompileError> {
     let mut functions = PrimaryMap::with_capacity(function_body_inputs.len());
     let mut relocations = PrimaryMap::with_capacity(function_body_inputs.len());
+    let mut traps = PrimaryMap::with_capacity(function_body_inputs.len());
     for (i, input) in function_body_inputs.into_iter() {
         let func_index = module.func_index(i);
         let mut context = Context::new();
@@ -106,14 +143,15 @@ pub fn compile_module<'data, 'module>(
 
         let mut code_buf: Vec<u8> = Vec::new();
         let mut reloc_sink = RelocSink::new();
-        let mut trap_sink = binemit::NullTrapSink {};
+        let mut trap_sink = WasmtimeTrapSink::new();
         context
             .compile_and_emit(isa, &mut code_buf, &mut reloc_sink, &mut trap_sink)
             .map_err(CompileError::Codegen)?;
         functions.push(code_buf);
         relocations.push(reloc_sink.func_relocs);
+        traps.push(trap_sink.func_traps);
     }
 
     // TODO: Reorganize where we create the Vec for the resolved imports.
-    Ok((Compilation::new(functions), relocations))
+    Ok((Compilation::new(functions), relocations, traps))
 }
diff --git a/lib/environ/src/lib.rs b/lib/environ/src/lib.rs
index 04111ce..ab4e4f6 100644
--- a/lib/environ/src/lib.rs
+++ b/lib/environ/src/lib.rs
@@ -49,6 +49,7 @@ pub mod cranelift;
 pub use crate::compilation::{
     Compilation, CompileError, Relocation, RelocationTarget, Relocations,
 };
+pub use crate::cranelift::Traps; // fixme: move this out of cranelift
 pub use crate::module::{
     Export, MemoryPlan, MemoryStyle, Module, TableElements, TablePlan, TableStyle,
 };
diff --git a/lib/jit/Cargo.toml b/lib/jit/Cargo.toml
index 45df39d..f8800fb 100644
--- a/lib/jit/Cargo.toml
+++ b/lib/jit/Cargo.toml
@@ -24,6 +24,7 @@ failure_derive = { version = "0.1.3", default-features = false }
 target-lexicon = { version = "0.2.0", default-features = false }
 hashbrown = { version = "0.1.8", optional = true }
 wasmparser = "0.29.2"
+cast = { version = "0.2.2", default-features = false }
 
 [features]
 default = ["std"]
diff --git a/lib/jit/src/compiler.rs b/lib/jit/src/compiler.rs
index f7b94d3..dddd1e8 100644
--- a/lib/jit/src/compiler.rs
+++ b/lib/jit/src/compiler.rs
@@ -4,6 +4,7 @@ use super::HashMap;
 use crate::code_memory::CodeMemory;
 use crate::instantiate::SetupError;
 use crate::target_tunables::target_tunables;
+use cast;
 use cranelift_codegen::ir::InstBuilder;
 use cranelift_codegen::isa::{TargetFrontendConfig, TargetIsa};
 use cranelift_codegen::Context;
@@ -15,7 +16,7 @@ use std::boxed::Box;
 use std::string::String;
 use std::vec::Vec;
 use wasmtime_environ::cranelift;
-use wasmtime_environ::{Compilation, CompileError, Module, Relocations, Tunables};
+use wasmtime_environ::{Compilation, CompileError, Module, Relocations, Traps, Tunables};
 use wasmtime_runtime::{InstantiationError, SignatureRegistry, VMFunctionBody};
 
 /// A WebAssembly code JIT compiler.
@@ -70,22 +71,23 @@ impl Compiler {
         (
             PrimaryMap<DefinedFuncIndex, *mut [VMFunctionBody]>,
             Relocations,
+            HashMap<*const u8, (ir::TrapCode, ir::SourceLoc)>,
         ),
         SetupError,
     > {
-        let (compilation, relocations) =
+        let (compilation, relocations, traps) =
             cranelift::compile_module(module, function_body_inputs, &*self.isa)
                 .map_err(SetupError::Compile)?;
 
-        let allocated_functions =
-            allocate_functions(&mut self.code_memory, compilation).map_err(|message| {
+        let (allocated_functions, adjusted_traps) =
+            allocate_functions(&mut self.code_memory, compilation, traps).map_err(|message| {
                 SetupError::Instantiate(InstantiationError::Resource(format!(
                     "failed to allocate memory for functions: {}",
                     message
                 )))
             })?;
 
-        Ok((allocated_functions, relocations))
+        Ok((allocated_functions, relocations, adjusted_traps))
     }
 
     /// Create a trampoline for invoking a function.
@@ -220,13 +222,29 @@ fn make_trampoline(
 fn allocate_functions(
     code_memory: &mut CodeMemory,
     compilation: Compilation,
-) -> Result<PrimaryMap<DefinedFuncIndex, *mut [VMFunctionBody]>, String> {
+    traps: Traps,
+) -> Result<
+    (
+        PrimaryMap<DefinedFuncIndex, *mut [VMFunctionBody]>,
+        HashMap<*const u8, (ir::TrapCode, ir::SourceLoc)>,
+    ),
+    String,
+> {
     let mut result = PrimaryMap::with_capacity(compilation.functions.len());
-    for (_, body) in compilation.functions.into_iter() {
+    let mut trap_map = HashMap::new();
+
+    for (i, body) in compilation.functions.into_iter() {
         let fatptr: *mut [VMFunctionBody] = code_memory.allocate_copy_of_byte_slice(body)?;
         result.push(fatptr);
+
+        for trap in &traps[i] {
+            let base = fatptr as *const u8;
+            let trap_pc = unsafe { base.add(cast::usize(trap.offset)) };
+            trap_map.insert(trap_pc, (trap.code, trap.srcloc));
+        }
     }
-    Ok(result)
+
+    Ok((result, trap_map))
 }
 
 /// We don't expect trampoline compilation to produce any relocations, so
diff --git a/lib/jit/src/instantiate.rs b/lib/jit/src/instantiate.rs
index a2ccea3..c37ef8a 100644
--- a/lib/jit/src/instantiate.rs
+++ b/lib/jit/src/instantiate.rs
@@ -8,6 +8,7 @@ use crate::compiler::Compiler;
 use crate::link::link_module;
 use crate::resolver::Resolver;
 use core::cell::RefCell;
+use cranelift_codegen::ir;
 use cranelift_entity::{BoxedSlice, PrimaryMap};
 use cranelift_wasm::{DefinedFuncIndex, SignatureIndex};
 use std::boxed::Box;
@@ -44,6 +45,7 @@ pub enum SetupError {
 struct RawCompiledModule<'data> {
     module: Module,
     finished_functions: BoxedSlice<DefinedFuncIndex, *const VMFunctionBody>,
+    traps: HashMap<*const u8, (ir::TrapCode, ir::SourceLoc)>,
     imports: Imports,
     data_initializers: Box<[DataInitializer<'data>]>,
     signatures: BoxedSlice<SignatureIndex, VMSharedSignatureIndex>,
@@ -62,7 +64,7 @@ impl<'data> RawCompiledModule<'data> {
             .translate(data)
             .map_err(|error| SetupError::Compile(CompileError::Wasm(error)))?;
 
-        let (allocated_functions, relocations) =
+        let (allocated_functions, relocations, traps) =
             compiler.compile(&translation.module, translation.function_body_inputs)?;
 
         let imports = link_module(
@@ -101,6 +103,7 @@ impl<'data> RawCompiledModule<'data> {
         Ok(Self {
             module: translation.module,
             finished_functions,
+            traps,
             imports,
             data_initializers: translation.data_initializers.into_boxed_slice(),
             signatures: signatures.into_boxed_slice(),
@@ -113,6 +116,7 @@ pub struct CompiledModule {
     module: Rc<Module>,
     finished_functions: BoxedSlice<DefinedFuncIndex, *const VMFunctionBody>,
     imports: Imports,
+    traps: HashMap<*const u8, (ir::TrapCode, ir::SourceLoc)>,
     data_initializers: Box<[OwnedDataInitializer]>,
     signatures: BoxedSlice<SignatureIndex, VMSharedSignatureIndex>,
     global_exports: Rc<RefCell<HashMap<String, Option<Export>>>>,
@@ -132,6 +136,7 @@ impl CompiledModule {
             raw.module,
             global_exports,
             raw.finished_functions,
+            raw.traps,
             raw.imports,
             raw.data_initializers
                 .iter()
@@ -147,6 +152,7 @@ impl CompiledModule {
         module: Module,
         global_exports: Rc<RefCell<HashMap<String, Option<Export>>>>,
         finished_functions: BoxedSlice<DefinedFuncIndex, *const VMFunctionBody>,
+        traps: HashMap<*const u8, (ir::TrapCode, ir::SourceLoc)>,
         imports: Imports,
         data_initializers: Box<[OwnedDataInitializer]>,
         signatures: BoxedSlice<SignatureIndex, VMSharedSignatureIndex>,
@@ -155,6 +161,7 @@ impl CompiledModule {
             module: Rc::new(module),
             global_exports: Rc::clone(&global_exports),
             finished_functions,
+            traps,
             imports,
             data_initializers,
             signatures,
@@ -179,6 +186,7 @@ impl CompiledModule {
             Rc::clone(&self.module),
             Rc::clone(&self.global_exports),
             self.finished_functions.clone(),
+            self.traps.clone(),
             self.imports.clone(),
             &data_initializers,
             self.signatures.clone(),
@@ -222,6 +230,7 @@ pub fn instantiate(
         Rc::new(raw.module),
         global_exports,
         raw.finished_functions,
+        raw.traps,
         raw.imports,
         &*raw.data_initializers,
         raw.signatures,
diff --git a/lib/runtime/src/instance.rs b/lib/runtime/src/instance.rs
index d917143..381738a 100644
--- a/lib/runtime/src/instance.rs
+++ b/lib/runtime/src/instance.rs
@@ -18,8 +18,8 @@ use core::borrow::Borrow;
 use core::cell::RefCell;
 use core::slice;
 use core::{mem, ptr};
-use cranelift_entity::EntityRef;
-use cranelift_entity::{BoxedSlice, PrimaryMap};
+use cranelift_codegen::ir;
+use cranelift_entity::{BoxedSlice, EntityRef, PrimaryMap};
 use cranelift_wasm::{
     DefinedFuncIndex, DefinedGlobalIndex, DefinedMemoryIndex, DefinedTableIndex, FuncIndex,
     GlobalIndex, GlobalInit, MemoryIndex, SignatureIndex, TableIndex,
@@ -192,6 +192,9 @@ pub struct InstanceContents {
     /// make its memory available too, that will be obviated by host-bindings.
     global_exports: Rc<RefCell<HashMap<String, Option<Export>>>>,
 
+    /// Trap locations.
+    traps: HashMap<*const u8, (ir::TrapCode, ir::SourceLoc)>,
+
     /// WebAssembly linear memory data.
     memories: BoxedSlice<DefinedMemoryIndex, LinearMemory>,
 
@@ -548,6 +551,12 @@ impl InstanceContents {
         }
         None
     }
+
+    pub(crate) fn lookup_trap(&self, pc: *const u8) -> Option<(ir::TrapCode, ir::SourceLoc)> {
+        // fixme: if not found, search through our dependencies. once we have those. which should
+        // be acyclic.
+        self.traps.get(&pc).cloned()
+    }
 }
 
 /// A wrapper around an `Mmap` holding an `InstanceContents`.
@@ -597,6 +606,7 @@ impl Instance {
         module: Rc<Module>,
         global_exports: Rc<RefCell<HashMap<String, Option<Export>>>>,
         finished_functions: BoxedSlice<DefinedFuncIndex, *const VMFunctionBody>,
+        traps: HashMap<*const u8, (ir::TrapCode, ir::SourceLoc)>,
         imports: Imports,
         data_initializers: &[DataInitializer<'_>],
         vmshared_signatures: BoxedSlice<SignatureIndex, VMSharedSignatureIndex>,
@@ -634,6 +644,7 @@ impl Instance {
             let contents = InstanceContents {
                 global_exports,
                 offsets,
+                traps,
                 memories,
                 tables,
                 finished_functions,
diff --git a/lib/runtime/src/traphandlers.rs b/lib/runtime/src/traphandlers.rs
index 76a4cde..2c8b747 100644
--- a/lib/runtime/src/traphandlers.rs
+++ b/lib/runtime/src/traphandlers.rs
@@ -6,6 +6,7 @@ use crate::vmcontext::{VMContext, VMFunctionBody};
 use core::cell::{Cell, RefCell};
 use core::mem;
 use core::ptr;
+use cranelift_codegen::ir;
 use libc::c_int;
 use std::string::String;
 use std::vec::Vec;
@@ -30,7 +31,6 @@ thread_local! {
 #[allow(non_snake_case)]
 #[no_mangle]
 pub extern "C" fn RecordTrap(pc: *const u8) {
-    // TODO: Look up the wasm bytecode offset and trap code and record them instead.
     TRAP_PC.with(|data| data.set(pc));
 }
 
@@ -73,13 +73,33 @@ impl Drop for ScopeGuard {
     }
 }
 
-fn trap_message(_vmctx: *mut VMContext) -> String {
-    let pc = TRAP_PC.with(|data| data.replace(ptr::null()));
+fn trap_code_message(code: ir::TrapCode) -> &'static str {
+    match code {
+        ir::TrapCode::HeapOutOfBounds => "out of bounds memory access",
+        ir::TrapCode::IntegerOverflow => "integer overflow",
+        ir::TrapCode::IntegerDivisionByZero => "integer divide by zero",
+        ir::TrapCode::BadConversionToInteger => "invalid conversion to integer",
+        ir::TrapCode::StackOverflow => "call stack exhausted",
+        ir::TrapCode::TableOutOfBounds => "out of bounds table access",
+        ir::TrapCode::IndirectCallToNull => "indirect call to null",
+        ir::TrapCode::BadSignature => "indirect call type mismatch",
+        ir::TrapCode::UnreachableCodeReached => "unreachable executed",
+        _ => panic!("unexpected trap kind: {}", code),
+    }
+}
 
-    // TODO: Record trap metadata in the VMContext, and look up the
-    // pc to obtain the TrapCode and SourceLoc.
+fn trap_message(vmctx: *mut VMContext) -> String {
+    let pc = TRAP_PC.with(|data| data.replace(ptr::null()));
 
-    format!("wasm trap at {:?}", pc)
+    if let Some((code, srcloc)) = unsafe { (&mut *vmctx).instance_contents() }.lookup_trap(pc) {
+        format!(
+            "wasm runtime trap at offset {}: {}",
+            srcloc,
+            trap_code_message(code)
+        )
+    } else {
+        panic!("unknown trap at address {:?}", pc)
+    }
 }
 
 fn push_jmp_buf(buf: jmp_buf) {
diff --git a/lib/wast/src/spectest.rs b/lib/wast/src/spectest.rs
index 066c755..49aa2ce 100644
--- a/lib/wast/src/spectest.rs
+++ b/lib/wast/src/spectest.rs
@@ -215,11 +215,13 @@ pub fn instantiate_spectest() -> Result<Instance, InstantiationError> {
     let imports = Imports::none();
     let data_initializers = Vec::new();
     let signatures = PrimaryMap::new();
+    let traps = HashMap::new();
 
     Instance::new(
         Rc::new(module),
         Rc::new(RefCell::new(HashMap::new())),
         finished_functions.into_boxed_slice(),
+        traps,
         imports,
         &data_initializers,
         signatures.into_boxed_slice(),
diff --git a/lib/wast/src/wast.rs b/lib/wast/src/wast.rs
index 187330f..58507bf 100644
--- a/lib/wast/src/wast.rs
+++ b/lib/wast/src/wast.rs
@@ -48,7 +48,7 @@ impl fmt::Display for WastError {
             WastError::Instance(ref error) => error.fmt(f),
             WastError::NoDefaultInstance => write!(f, "no default instance defined yet"),
             WastError::Action(ref error) => error.fmt(f),
-            WastError::Trap(ref message) => write!(f, "trap: {}", message),
+            WastError::Trap(ref message) => write!(f, "{}", message),
             WastError::Type(ref message) => write!(f, "type error: {}", message),
             WastError::Syntax(ref message) => write!(f, "syntax error: {}", message),
             WastError::Utf8(ref message) => write!(f, "UTF-8 decoding error: {}", message),
diff --git a/src/wasm2obj.rs b/src/wasm2obj.rs
index 89e584d..9fc3d84 100644
--- a/src/wasm2obj.rs
+++ b/src/wasm2obj.rs
@@ -151,7 +151,7 @@ fn handle_module(path: PathBuf, target: &Option<String>, output: &str) -> Result
             .map_err(|err| format!("{}", err))?;
     }
 
-    let (compilation, relocations) =
+    let (compilation, relocations, _traps) =
         cranelift::compile_module(&module, lazy_function_body_inputs, &*isa)
             .map_err(|e| e.to_string())?;