diff --git a/lib/environ/src/cranelift.rs b/lib/environ/src/cranelift.rs
index 7cc35a9..6dcd780 100644
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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())?;