diff --git a/crates/api/src/externals.rs b/crates/api/src/externals.rs index cef95f85d0..4ea3ca3dd1 100644 --- a/crates/api/src/externals.rs +++ b/crates/api/src/externals.rs @@ -2,10 +2,10 @@ use crate::trampoline::{generate_global_export, generate_memory_export, generate use crate::values::{from_checked_anyfunc, into_checked_anyfunc, Val}; use crate::Mutability; use crate::{ExternType, GlobalType, MemoryType, TableType, ValType}; -use crate::{Func, Store}; +use crate::{Func, Store, Trap}; use anyhow::{anyhow, bail, Result}; use std::slice; -use wasmtime_environ::{ir, wasm}; +use wasmtime_environ::wasm; use wasmtime_runtime::{self as runtime, InstanceHandle}; // Externals @@ -422,14 +422,8 @@ impl Table { let src_table_index = src_table.wasmtime_table_index(); let src_table = src_table.wasmtime_handle.get_defined_table(src_table_index); - runtime::Table::copy( - dst_table, - src_table, - dst_index, - src_index, - len, - ir::SourceLoc::default(), - )?; + runtime::Table::copy(dst_table, src_table, dst_index, src_index, len) + .map_err(Trap::from_jit)?; Ok(()) } diff --git a/crates/api/src/frame_info.rs b/crates/api/src/frame_info.rs index 1797c77f5a..0a4b2b6cf6 100644 --- a/crates/api/src/frame_info.rs +++ b/crates/api/src/frame_info.rs @@ -1,8 +1,10 @@ +use std::cmp; use std::collections::BTreeMap; use std::sync::{Arc, RwLock}; use wasmtime_environ::entity::EntityRef; +use wasmtime_environ::ir; use wasmtime_environ::wasm::FuncIndex; -use wasmtime_environ::Module; +use wasmtime_environ::{FunctionAddressMap, Module, TrapInformation}; use wasmtime_jit::CompiledModule; lazy_static::lazy_static! { @@ -11,7 +13,7 @@ lazy_static::lazy_static! { /// This global cache is used during `Trap` creation to symbolicate frames. /// This is populated on module compilation, and it is cleared out whenever /// all references to a module are dropped. - pub static ref FRAME_INFO: GlobalFrameInfo = GlobalFrameInfo::default(); + pub static ref FRAME_INFO: RwLock = Default::default(); } #[derive(Default)] @@ -25,7 +27,7 @@ pub struct GlobalFrameInfo { /// /// The key of this map is the highest address in the module and the value /// is the module's information, which also contains the start address. - ranges: RwLock>, + ranges: BTreeMap, } /// An RAII structure used to unregister a module's frame information when the @@ -38,93 +40,164 @@ pub struct GlobalFrameInfoRegistration { struct ModuleFrameInfo { start: usize, - functions: BTreeMap, + functions: BTreeMap, module: Arc, } +struct FunctionInfo { + start: usize, + index: FuncIndex, + traps: Vec, + instr_map: FunctionAddressMap, +} + impl GlobalFrameInfo { - /// Registers a new compiled module's frame information. - /// - /// This function will register the `names` information for all of the - /// compiled functions within `module`. If the `module` has no functions - /// then `None` will be returned. Otherwise the returned object, when - /// dropped, will be used to unregister all name information from this map. - pub fn register(&self, module: &CompiledModule) -> Option { - let mut min = usize::max_value(); - let mut max = 0; - let mut functions = BTreeMap::new(); - for (i, allocated) in module.finished_functions() { - let (start, end) = unsafe { - let ptr = (**allocated).as_ptr(); - let len = (**allocated).len(); - (ptr as usize, ptr as usize + len) - }; - if start < min { - min = start; - } - if end > max { - max = end; - } - let func_index = module.module().local.func_index(i); - assert!(functions.insert(end, (start, func_index)).is_none()); - } - if functions.len() == 0 { - return None; - } - - let mut ranges = self.ranges.write().unwrap(); - // First up assert that our chunk of jit functions doesn't collide with - // any other known chunks of jit functions... - if let Some((_, prev)) = ranges.range(max..).next() { - assert!(prev.start > max); - } - if let Some((prev_end, _)) = ranges.range(..=min).next_back() { - assert!(*prev_end < min); - } - - // ... then insert our range and assert nothing was there previously - let prev = ranges.insert( - max, - ModuleFrameInfo { - start: min, - functions, - module: module.module().clone(), - }, - ); - assert!(prev.is_none()); - Some(GlobalFrameInfoRegistration { key: max }) - } - - /// Fetches information about a program counter in a backtrace. + /// Fetches frame information about a program counter in a backtrace. /// /// Returns an object if this `pc` is known to some previously registered /// module, or returns `None` if no information can be found. - pub fn lookup(&self, pc: usize) -> Option { - let ranges = self.ranges.read().ok()?; - let (end, info) = ranges.range(pc..).next()?; + pub fn lookup_frame_info(&self, pc: usize) -> Option { + let (module, func) = self.func(pc)?; + + // Use our relative position from the start of the function to find the + // machine instruction that corresponds to `pc`, which then allows us to + // map that to a wasm original source location. + let rel_pos = pc - func.start; + let pos = match func + .instr_map + .instructions + .binary_search_by_key(&rel_pos, |map| map.code_offset) + { + // Exact hit! + Ok(pos) => Some(pos), + + // This *would* be at the first slot in the array, so no + // instructions cover `pc`. + Err(0) => None, + + // This would be at the `nth` slot, so check `n-1` to see if we're + // part of that instruction. This happens due to the minus one when + // this function is called form trap symbolication, where we don't + // always get called with a `pc` that's an exact instruction + // boundary. + Err(n) => { + let instr = &func.instr_map.instructions[n - 1]; + if instr.code_offset <= rel_pos && rel_pos < instr.code_offset + instr.code_len { + Some(n - 1) + } else { + None + } + } + }; + + // In debug mode for now assert that we found a mapping for `pc` within + // the function, because otherwise something is buggy along the way and + // not accounting for all the instructions. This isn't super critical + // though so we can omit this check in release mode. + debug_assert!(pos.is_some(), "failed to find instruction for {:x}", pc); + + let instr = match pos { + Some(pos) => func.instr_map.instructions[pos].srcloc, + None => func.instr_map.start_srcloc, + }; + Some(FrameInfo { + module_name: module.module.name.clone(), + func_index: func.index.index() as u32, + func_name: module.module.func_names.get(&func.index).cloned(), + instr, + func_start: func.instr_map.start_srcloc, + }) + } + + /// Fetches trap information about a program counter in a backtrace. + pub fn lookup_trap_info(&self, pc: usize) -> Option<&TrapInformation> { + let (_module, func) = self.func(pc)?; + let idx = func + .traps + .binary_search_by_key(&((pc - func.start) as u32), |info| info.code_offset) + .ok()?; + Some(&func.traps[idx]) + } + + fn func(&self, pc: usize) -> Option<(&ModuleFrameInfo, &FunctionInfo)> { + let (end, info) = self.ranges.range(pc..).next()?; if pc < info.start || *end < pc { return None; } - let (end, (start, func_index)) = info.functions.range(pc..).next()?; - if pc < *start || *end < pc { + let (end, func) = info.functions.range(pc..).next()?; + if pc < func.start || *end < pc { return None; } - Some(FrameInfo { - module_name: info.module.name.clone(), - func_index: func_index.index() as u32, - func_name: info.module.func_names.get(func_index).cloned(), - }) + Some((info, func)) } } impl Drop for GlobalFrameInfoRegistration { fn drop(&mut self) { - if let Ok(mut map) = FRAME_INFO.ranges.write() { - map.remove(&self.key); + if let Ok(mut info) = FRAME_INFO.write() { + info.ranges.remove(&self.key); } } } +/// Registers a new compiled module's frame information. +/// +/// This function will register the `names` information for all of the +/// compiled functions within `module`. If the `module` has no functions +/// then `None` will be returned. Otherwise the returned object, when +/// dropped, will be used to unregister all name information from this map. +pub fn register(module: &CompiledModule) -> Option { + let mut min = usize::max_value(); + let mut max = 0; + let mut functions = BTreeMap::new(); + for (((i, allocated), traps), instrs) in module + .finished_functions() + .iter() + .zip(module.traps().values()) + .zip(module.address_transform().values()) + { + let (start, end) = unsafe { + let ptr = (**allocated).as_ptr(); + let len = (**allocated).len(); + (ptr as usize, ptr as usize + len) + }; + min = cmp::min(min, start); + max = cmp::max(max, end); + let func = FunctionInfo { + start, + index: module.module().local.func_index(i), + traps: traps.to_vec(), + instr_map: (*instrs).clone(), + }; + assert!(functions.insert(end, func).is_none()); + } + if functions.len() == 0 { + return None; + } + + let mut info = FRAME_INFO.write().unwrap(); + // First up assert that our chunk of jit functions doesn't collide with + // any other known chunks of jit functions... + if let Some((_, prev)) = info.ranges.range(max..).next() { + assert!(prev.start > max); + } + if let Some((prev_end, _)) = info.ranges.range(..=min).next_back() { + assert!(*prev_end < min); + } + + // ... then insert our range and assert nothing was there previously + let prev = info.ranges.insert( + max, + ModuleFrameInfo { + start: min, + functions, + module: module.module().clone(), + }, + ); + assert!(prev.is_none()); + Some(GlobalFrameInfoRegistration { key: max }) +} + /// Description of a frame in a backtrace for a [`Trap`]. /// /// Whenever a WebAssembly trap occurs an instance of [`Trap`] is created. Each @@ -137,6 +210,8 @@ pub struct FrameInfo { module_name: Option, func_index: u32, func_name: Option, + func_start: ir::SourceLoc, + instr: ir::SourceLoc, } impl FrameInfo { @@ -178,4 +253,23 @@ impl FrameInfo { pub fn func_name(&self) -> Option<&str> { self.func_name.as_deref() } + + /// Returns the offset within the original wasm module this frame's program + /// counter was at. + /// + /// The offset here is the offset from the beginning of the original wasm + /// module to the instruction that this frame points to. + pub fn module_offset(&self) -> usize { + self.instr.bits() as usize + } + + /// Returns the offset from the original wasm module's function to this + /// frame's program counter. + /// + /// The offset here is the offset from the beginning of the defining + /// function of this frame (within the wasm module) to the instruction this + /// frame points to. + pub fn func_offset(&self) -> usize { + (self.instr.bits() - self.func_start.bits()) as usize + } } diff --git a/crates/api/src/module.rs b/crates/api/src/module.rs index a618791fd8..a92d469ce9 100644 --- a/crates/api/src/module.rs +++ b/crates/api/src/module.rs @@ -1,4 +1,4 @@ -use crate::frame_info::{GlobalFrameInfoRegistration, FRAME_INFO}; +use crate::frame_info::GlobalFrameInfoRegistration; use crate::runtime::Store; use crate::types::{ ExportType, ExternType, FuncType, GlobalType, ImportType, Limits, MemoryType, Mutability, @@ -670,6 +670,6 @@ and for re-adding support for interface types you can see this issue: if info.is_some() { return; } - *info = Some(FRAME_INFO.register(&self.inner.compiled)); + *info = Some(super::frame_info::register(&self.inner.compiled)); } } diff --git a/crates/api/src/trampoline/create_handle.rs b/crates/api/src/trampoline/create_handle.rs index b0d24d5088..d410a4c8b4 100644 --- a/crates/api/src/trampoline/create_handle.rs +++ b/crates/api/src/trampoline/create_handle.rs @@ -39,7 +39,6 @@ pub(crate) fn create_handle( unsafe { Ok(InstanceHandle::new( Arc::new(module), - store.compiler().trap_registry().register_traps(Vec::new()), finished_functions.into_boxed_slice(), trampolines, imports, diff --git a/crates/api/src/trap.rs b/crates/api/src/trap.rs index 4b299dbc89..322e38739d 100644 --- a/crates/api/src/trap.rs +++ b/crates/api/src/trap.rs @@ -1,8 +1,9 @@ -use crate::frame_info::FRAME_INFO; +use crate::frame_info::{GlobalFrameInfo, FRAME_INFO}; use crate::FrameInfo; use backtrace::Backtrace; use std::fmt; use std::sync::Arc; +use wasmtime_environ::ir::TrapCode; /// A struct representing an aborted instruction execution, with a message /// indicating the cause. @@ -29,10 +30,12 @@ impl Trap { /// assert_eq!("unexpected error", trap.message()); /// ``` pub fn new>(message: I) -> Self { - Trap::new_with_trace(message.into(), Backtrace::new_unresolved()) + let info = FRAME_INFO.read().unwrap(); + Trap::new_with_trace(&info, None, message.into(), Backtrace::new_unresolved()) } pub(crate) fn from_jit(jit: wasmtime_runtime::Trap) -> Self { + let info = FRAME_INFO.read().unwrap(); match jit { wasmtime_runtime::Trap::User(error) => { // Since we're the only one using the wasmtime internals (in @@ -47,20 +50,72 @@ impl Trap { .downcast() .expect("only `Trap` user errors are supported") } - wasmtime_runtime::Trap::Wasm { desc, backtrace } => { - Trap::new_with_trace(desc.to_string(), backtrace) + wasmtime_runtime::Trap::Jit { pc, backtrace } => { + let code = info + .lookup_trap_info(pc) + .map(|info| info.trap_code) + .unwrap_or(TrapCode::StackOverflow); + Trap::new_wasm(&info, Some(pc), code, backtrace) } + wasmtime_runtime::Trap::Wasm { + trap_code, + backtrace, + } => Trap::new_wasm(&info, None, trap_code, backtrace), wasmtime_runtime::Trap::OOM { backtrace } => { - Trap::new_with_trace("out of memory".to_string(), backtrace) + Trap::new_with_trace(&info, None, "out of memory".to_string(), backtrace) } } } - fn new_with_trace(message: String, native_trace: Backtrace) -> Self { + fn new_wasm( + info: &GlobalFrameInfo, + trap_pc: Option, + code: TrapCode, + backtrace: Backtrace, + ) -> Self { + use wasmtime_environ::ir::TrapCode::*; + let desc = match code { + StackOverflow => "call stack exhausted", + HeapOutOfBounds => "out of bounds memory access", + TableOutOfBounds => "undefined element: out of bounds table access", + OutOfBounds => "out of bounds", + IndirectCallToNull => "uninitialized element", + BadSignature => "indirect call type mismatch", + IntegerOverflow => "integer overflow", + IntegerDivisionByZero => "integer divide by zero", + BadConversionToInteger => "invalid conversion to integer", + UnreachableCodeReached => "unreachable", + Interrupt => "interrupt", + User(_) => unreachable!(), + }; + let msg = format!("wasm trap: {}", desc); + Trap::new_with_trace(info, trap_pc, msg, backtrace) + } + + fn new_with_trace( + info: &GlobalFrameInfo, + trap_pc: Option, + message: String, + native_trace: Backtrace, + ) -> Self { let mut wasm_trace = Vec::new(); for frame in native_trace.frames() { let pc = frame.ip() as usize; - if let Some(info) = FRAME_INFO.lookup(pc) { + if pc == 0 { + continue; + } + // Note that we need to be careful about the pc we pass in here to + // lookup frame information. This program counter is used to + // translate back to an original source location in the origin wasm + // module. If this pc is the exact pc that the trap happened at, + // then we look up that pc precisely. Otherwise backtrace + // information typically points at the pc *after* the call + // instruction (because otherwise it's likely a call instruction on + // the stack). In that case we want to lookup information for the + // previous instruction (the call instruction) so we subtract one as + // the lookup. + let pc_to_lookup = if Some(pc) == trap_pc { pc } else { pc - 1 }; + if let Some(info) = info.lookup_frame_info(pc_to_lookup) { wasm_trace.push(info); } } @@ -105,7 +160,7 @@ impl fmt::Display for Trap { writeln!(f, "\nwasm backtrace:")?; for (i, frame) in self.trace().iter().enumerate() { let name = frame.module_name().unwrap_or(""); - write!(f, " {}: {}!", i, name)?; + write!(f, " {}: {:#6x} - {}!", i, frame.module_offset(), name)?; match frame.func_name() { Some(name) => match rustc_demangle::try_demangle(name) { Ok(name) => write!(f, "{}", name)?, diff --git a/crates/api/tests/traps.rs b/crates/api/tests/traps.rs index e76f6a3dcd..f1f836bc14 100644 --- a/crates/api/tests/traps.rs +++ b/crates/api/tests/traps.rs @@ -59,9 +59,13 @@ fn test_trap_trace() -> Result<()> { assert_eq!(trace[0].module_name().unwrap(), "hello_mod"); assert_eq!(trace[0].func_index(), 1); assert_eq!(trace[0].func_name(), Some("hello")); + assert_eq!(trace[0].func_offset(), 1); + assert_eq!(trace[0].module_offset(), 0x26); assert_eq!(trace[1].module_name().unwrap(), "hello_mod"); assert_eq!(trace[1].func_index(), 0); assert_eq!(trace[1].func_name(), None); + assert_eq!(trace[1].func_offset(), 1); + assert_eq!(trace[1].module_offset(), 0x21); assert!( e.message().contains("unreachable"), "wrong message: {}", @@ -163,12 +167,12 @@ fn trap_display_pretty() -> Result<()> { assert_eq!( e.to_string(), "\ -wasm trap: unreachable, source location: @0023 +wasm trap: unreachable wasm backtrace: - 0: m!die - 1: m! - 2: m!foo - 3: m! + 0: 0x23 - m!die + 1: 0x27 - m! + 2: 0x2c - m!foo + 3: 0x31 - m! " ); Ok(()) @@ -207,14 +211,14 @@ fn trap_display_multi_module() -> Result<()> { assert_eq!( e.to_string(), "\ -wasm trap: unreachable, source location: @0023 +wasm trap: unreachable wasm backtrace: - 0: a!die - 1: a! - 2: a!foo - 3: a! - 4: b!middle - 5: b! + 0: 0x23 - a!die + 1: 0x27 - a! + 2: 0x2c - a!foo + 3: 0x31 - a! + 4: 0x29 - b!middle + 5: 0x2e - b! " ); Ok(()) @@ -371,10 +375,7 @@ fn call_signature_mismatch() -> Result<()> { .unwrap() .downcast::() .unwrap(); - assert_eq!( - err.message(), - "wasm trap: indirect call type mismatch, source location: @0030" - ); + assert_eq!(err.message(), "wasm trap: indirect call type mismatch"); Ok(()) } @@ -400,12 +401,12 @@ fn start_trap_pretty() -> Result<()> { assert_eq!( e.to_string(), "\ -wasm trap: unreachable, source location: @001d +wasm trap: unreachable wasm backtrace: - 0: m!die - 1: m! - 2: m!foo - 3: m!start + 0: 0x1d - m!die + 1: 0x21 - m! + 2: 0x26 - m!foo + 3: 0x2b - m!start " ); Ok(()) diff --git a/crates/c-api/src/trap.rs b/crates/c-api/src/trap.rs index 51b82fe6aa..67ca334d7b 100644 --- a/crates/c-api/src/trap.rs +++ b/crates/c-api/src/trap.rs @@ -122,8 +122,9 @@ pub extern "C" fn wasmtime_frame_module_name(frame: &wasm_frame_t) -> Option<&wa } #[no_mangle] -pub extern "C" fn wasm_frame_func_offset(_arg1: *const wasm_frame_t) -> usize { - unimplemented!("wasm_frame_func_offset") +pub extern "C" fn wasm_frame_func_offset(frame: &wasm_frame_t) -> usize { + let trap = frame.trap.borrow(); + trap.trace()[frame.idx].func_offset() } #[no_mangle] @@ -132,6 +133,7 @@ pub extern "C" fn wasm_frame_instance(_arg1: *const wasm_frame_t) -> *mut wasm_i } #[no_mangle] -pub extern "C" fn wasm_frame_module_offset(_arg1: *const wasm_frame_t) -> usize { - unimplemented!("wasm_frame_module_offset") +pub extern "C" fn wasm_frame_module_offset(frame: &wasm_frame_t) -> usize { + let trap = frame.trap.borrow(); + trap.trace()[frame.idx].module_offset() } diff --git a/crates/environ/src/compilation.rs b/crates/environ/src/compilation.rs index 82f8786d17..b56ac7b097 100644 --- a/crates/environ/src/compilation.rs +++ b/crates/environ/src/compilation.rs @@ -256,7 +256,7 @@ pub enum RelocationTarget { pub type Relocations = PrimaryMap>; /// Information about trap. -#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)] +#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] pub struct TrapInformation { /// The offset of the trapping instruction in native code. It is relative to the beginning of the function. pub code_offset: binemit::CodeOffset, diff --git a/crates/environ/src/cranelift.rs b/crates/environ/src/cranelift.rs index 7f979c12a5..1b5a83072a 100644 --- a/crates/environ/src/cranelift.rs +++ b/crates/environ/src/cranelift.rs @@ -268,12 +268,7 @@ fn compile(env: CompileEnv<'_>) -> Result) -> Result FuncEnvironment<'module_environment> { AbiParam::new(I32), // Number of elements to copy. AbiParam::new(I32), - // Source location. - AbiParam::new(I32), ], returns: vec![], call_conv: self.target_config.default_call_conv, @@ -303,8 +301,6 @@ impl<'module_environment> FuncEnvironment<'module_environment> { AbiParam::new(I32), // Number of elements to initialize. AbiParam::new(I32), - // Source location. - AbiParam::new(I32), ], returns: vec![], call_conv: self.target_config.default_call_conv, @@ -362,8 +358,6 @@ impl<'module_environment> FuncEnvironment<'module_environment> { AbiParam::new(I32), // Length. AbiParam::new(I32), - // Source location. - AbiParam::new(I32), ], returns: vec![], call_conv: self.target_config.default_call_conv, @@ -407,8 +401,6 @@ impl<'module_environment> FuncEnvironment<'module_environment> { AbiParam::new(I32), // Length. AbiParam::new(I32), - // Source location. - AbiParam::new(I32), ], returns: vec![], call_conv: self.target_config.default_call_conv, @@ -454,8 +446,6 @@ impl<'module_environment> FuncEnvironment<'module_environment> { AbiParam::new(I32), // Length. AbiParam::new(I32), - // Source location. - AbiParam::new(I32), ], returns: vec![], call_conv: self.target_config.default_call_conv, @@ -1102,13 +1092,10 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m let (vmctx, func_addr) = self.translate_load_builtin_function_address(&mut pos, func_idx); - let src_loc = pos.srcloc(); - let src_loc_arg = pos.ins().iconst(I32, src_loc.bits() as i64); - pos.ins().call_indirect( func_sig, func_addr, - &[vmctx, memory_index_arg, dst, src, len, src_loc_arg], + &[vmctx, memory_index_arg, dst, src, len], ); Ok(()) @@ -1130,13 +1117,10 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m let (vmctx, func_addr) = self.translate_load_builtin_function_address(&mut pos, func_idx); - let src_loc = pos.srcloc(); - let src_loc_arg = pos.ins().iconst(I32, src_loc.bits() as i64); - pos.ins().call_indirect( func_sig, func_addr, - &[vmctx, memory_index_arg, dst, val, len, src_loc_arg], + &[vmctx, memory_index_arg, dst, val, len], ); Ok(()) @@ -1156,23 +1140,13 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m let memory_index_arg = pos.ins().iconst(I32, memory_index.index() as i64); let seg_index_arg = pos.ins().iconst(I32, seg_index as i64); - let src_loc = pos.srcloc(); - let src_loc_arg = pos.ins().iconst(I32, src_loc.bits() as i64); let (vmctx, func_addr) = self.translate_load_builtin_function_address(&mut pos, func_idx); pos.ins().call_indirect( func_sig, func_addr, - &[ - vmctx, - memory_index_arg, - seg_index_arg, - dst, - src, - len, - src_loc_arg, - ], + &[vmctx, memory_index_arg, seg_index_arg, dst, src, len], ); Ok(()) @@ -1215,9 +1189,6 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m let dst_table_index_arg = pos.ins().iconst(I32, dst_table_index_arg as i64); let src_table_index_arg = pos.ins().iconst(I32, src_table_index_arg as i64); - let src_loc = pos.srcloc(); - let src_loc_arg = pos.ins().iconst(I32, src_loc.bits() as i64); - let (vmctx, func_addr) = self.translate_load_builtin_function_address(&mut pos, func_idx); pos.ins().call_indirect( @@ -1230,7 +1201,6 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m dst, src, len, - src_loc_arg, ], ); @@ -1253,23 +1223,12 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m let table_index_arg = pos.ins().iconst(I32, table_index_arg as i64); let seg_index_arg = pos.ins().iconst(I32, seg_index as i64); - let src_loc = pos.srcloc(); - let src_loc_arg = pos.ins().iconst(I32, src_loc.bits() as i64); - let (vmctx, func_addr) = self.translate_load_builtin_function_address(&mut pos, func_idx); pos.ins().call_indirect( func_sig, func_addr, - &[ - vmctx, - table_index_arg, - seg_index_arg, - dst, - src, - len, - src_loc_arg, - ], + &[vmctx, table_index_arg, seg_index_arg, dst, src, len], ); Ok(()) diff --git a/crates/jit/src/compiler.rs b/crates/jit/src/compiler.rs index ef12f079a9..e5c45b0a7c 100644 --- a/crates/jit/src/compiler.rs +++ b/crates/jit/src/compiler.rs @@ -9,19 +9,17 @@ use cranelift_codegen::Context; use cranelift_codegen::{binemit, ir}; use cranelift_frontend::{FunctionBuilder, FunctionBuilderContext}; use std::collections::HashMap; -use std::convert::TryFrom; use wasmtime_debug::{emit_debugsections_image, DebugInfoData}; use wasmtime_environ::entity::{EntityRef, PrimaryMap}; use wasmtime_environ::isa::{TargetFrontendConfig, TargetIsa}; use wasmtime_environ::wasm::{DefinedFuncIndex, DefinedMemoryIndex, MemoryIndex}; use wasmtime_environ::{ CacheConfig, CompileError, CompiledFunction, CompiledFunctionUnwindInfo, Compiler as _C, - ModuleMemoryOffset, ModuleTranslation, ModuleVmctxInfo, Relocation, RelocationTarget, - Relocations, Traps, Tunables, VMOffsets, + ModuleAddressMap, ModuleMemoryOffset, ModuleTranslation, ModuleVmctxInfo, Relocation, + RelocationTarget, Relocations, Traps, Tunables, VMOffsets, }; use wasmtime_runtime::{ - InstantiationError, SignatureRegistry, TrapRegistration, TrapRegistry, VMFunctionBody, - VMSharedSignatureIndex, VMTrampoline, + InstantiationError, SignatureRegistry, VMFunctionBody, VMSharedSignatureIndex, VMTrampoline, }; /// Select which kind of compilation to use. @@ -50,7 +48,6 @@ pub struct Compiler { isa: Box, code_memory: CodeMemory, - trap_registry: TrapRegistry, signatures: SignatureRegistry, strategy: CompilationStrategy, cache_config: CacheConfig, @@ -70,7 +67,6 @@ impl Compiler { code_memory: CodeMemory::new(), signatures: SignatureRegistry::new(), strategy, - trap_registry: TrapRegistry::default(), cache_config, tunables, } @@ -85,7 +81,8 @@ pub struct Compilation { pub trampoline_relocations: HashMap>, pub jt_offsets: PrimaryMap, pub dbg_image: Option>, - pub trap_registration: TrapRegistration, + pub traps: Traps, + pub address_transform: ModuleAddressMap, } impl Compiler { @@ -144,11 +141,6 @@ impl Compiler { ))) })?; - // Create a registration value for all traps in our allocated - // functions. This registration will allow us to map a trapping PC - // value to what the trap actually means if it came from JIT code. - let trap_registration = register_traps(&finished_functions, &traps, &self.trap_registry); - // Eagerly generate a entry trampoline for every type signature in the // module. This should be "relatively lightweight" for most modules and // guarantees that all functions (including indirect ones through @@ -228,7 +220,8 @@ impl Compiler { trampoline_relocations, jt_offsets, dbg_image, - trap_registration, + traps, + address_transform, }) } @@ -241,11 +234,6 @@ impl Compiler { pub fn signatures(&self) -> &SignatureRegistry { &self.signatures } - - /// Shared registration of trap information - pub fn trap_registry(&self) -> &TrapRegistry { - &self.trap_registry - } } /// Create a trampoline for invoking a function. @@ -392,26 +380,6 @@ fn allocate_functions( Ok(result) } -fn register_traps( - allocated_functions: &PrimaryMap, - traps: &Traps, - registry: &TrapRegistry, -) -> TrapRegistration { - let traps = - allocated_functions - .values() - .zip(traps.values()) - .flat_map(|(func_addr, func_traps)| { - func_traps.iter().map(move |trap_desc| { - let func_addr = *func_addr as *const u8 as usize; - let offset = usize::try_from(trap_desc.code_offset).unwrap(); - let trap_addr = func_addr + offset; - (trap_addr, trap_desc.source_loc, trap_desc.trap_code) - }) - }); - registry.register_traps(traps) -} - /// We don't expect trampoline compilation to produce many relocations, so /// this `RelocSink` just asserts that it doesn't recieve most of them, but /// handles libcall ones. diff --git a/crates/jit/src/instantiate.rs b/crates/jit/src/instantiate.rs index e2b51d8db2..8aa6487160 100644 --- a/crates/jit/src/instantiate.rs +++ b/crates/jit/src/instantiate.rs @@ -16,12 +16,13 @@ use wasmtime_debug::read_debuginfo; use wasmtime_environ::entity::{BoxedSlice, PrimaryMap}; use wasmtime_environ::wasm::{DefinedFuncIndex, SignatureIndex}; use wasmtime_environ::{ - CompileError, DataInitializer, DataInitializerLocation, Module, ModuleEnvironment, + CompileError, DataInitializer, DataInitializerLocation, Module, ModuleAddressMap, + ModuleEnvironment, Traps, }; use wasmtime_profiling::ProfilingAgent; use wasmtime_runtime::{ GdbJitImageRegistration, InstanceHandle, InstantiationError, RuntimeMemoryCreator, - SignatureRegistry, TrapRegistration, VMFunctionBody, VMSharedSignatureIndex, VMTrampoline, + SignatureRegistry, VMFunctionBody, VMSharedSignatureIndex, VMTrampoline, }; /// An error condition while setting up a wasm instance, be it validation, @@ -55,7 +56,8 @@ struct RawCompiledModule<'data> { data_initializers: Box<[DataInitializer<'data>]>, signatures: BoxedSlice, dbg_jit_registration: Option, - trap_registration: TrapRegistration, + traps: Traps, + address_transform: ModuleAddressMap, } impl<'data> RawCompiledModule<'data> { @@ -119,7 +121,8 @@ impl<'data> RawCompiledModule<'data> { data_initializers: translation.data_initializers.into_boxed_slice(), signatures: signatures.into_boxed_slice(), dbg_jit_registration, - trap_registration: compilation.trap_registration, + traps: compilation.traps, + address_transform: compilation.address_transform, }) } } @@ -132,7 +135,8 @@ pub struct CompiledModule { data_initializers: Box<[OwnedDataInitializer]>, signatures: BoxedSlice, dbg_jit_registration: Option>, - trap_registration: TrapRegistration, + traps: Traps, + address_transform: ModuleAddressMap, } impl CompiledModule { @@ -155,7 +159,8 @@ impl CompiledModule { .into_boxed_slice(), raw.signatures.clone(), raw.dbg_jit_registration, - raw.trap_registration, + raw.traps, + raw.address_transform, )) } @@ -167,7 +172,8 @@ impl CompiledModule { data_initializers: Box<[OwnedDataInitializer]>, signatures: BoxedSlice, dbg_jit_registration: Option, - trap_registration: TrapRegistration, + traps: Traps, + address_transform: ModuleAddressMap, ) -> Self { Self { module: Arc::new(module), @@ -176,7 +182,8 @@ impl CompiledModule { data_initializers, signatures, dbg_jit_registration: dbg_jit_registration.map(Rc::new), - trap_registration, + traps, + address_transform, } } @@ -207,7 +214,6 @@ impl CompiledModule { let imports = resolve_imports(&self.module, &sig_registry, resolver)?; InstanceHandle::new( Arc::clone(&self.module), - self.trap_registration.clone(), self.finished_functions.clone(), self.trampolines.clone(), imports, @@ -239,6 +245,16 @@ impl CompiledModule { pub fn finished_functions(&self) -> &BoxedSlice { &self.finished_functions } + + /// Returns the a map for all traps in this module. + pub fn traps(&self) -> &Traps { + &self.traps + } + + /// Returns a map of compiled addresses back to original bytecode offsets. + pub fn address_transform(&self) -> &ModuleAddressMap { + &self.address_transform + } } /// Similar to `DataInitializer`, but owns its own copy of the data rather diff --git a/crates/runtime/src/instance.rs b/crates/runtime/src/instance.rs index caa56b4eff..205215c5cb 100644 --- a/crates/runtime/src/instance.rs +++ b/crates/runtime/src/instance.rs @@ -14,7 +14,6 @@ use crate::vmcontext::{ VMGlobalDefinition, VMGlobalImport, VMMemoryDefinition, VMMemoryImport, VMSharedSignatureIndex, VMTableDefinition, VMTableImport, VMTrampoline, }; -use crate::TrapRegistration; use crate::{ExportFunction, ExportGlobal, ExportMemory, ExportTable}; use memoffset::offset_of; use more_asserts::assert_lt; @@ -111,10 +110,6 @@ pub(crate) struct Instance { /// Handler run when `SIGBUS`, `SIGFPE`, `SIGILL`, or `SIGSEGV` are caught by the instance thread. pub(crate) signal_handler: Cell>>, - /// Handle to our registration of traps so signals know what trap to return - /// when a segfault/sigill happens. - pub(crate) trap_registration: TrapRegistration, - /// Additional context used by compiled wasm code. This field is last, and /// represents a dynamically-sized array that extends beyond the nominal /// end of the struct (similar to a flexible array member). @@ -603,7 +598,6 @@ impl Instance { dst: u32, src: u32, len: u32, - source_loc: ir::SourceLoc, ) -> Result<(), Trap> { // https://webassembly.github.io/bulk-memory-operations/core/exec/instructions.html#exec-table-init @@ -619,7 +613,7 @@ impl Instance { .map_or(true, |n| n as usize > elem.len()) || dst.checked_add(len).map_or(true, |m| m > table.size()) { - return Err(Trap::wasm(source_loc, ir::TrapCode::TableOutOfBounds)); + return Err(Trap::wasm(ir::TrapCode::TableOutOfBounds)); } // TODO(#983): investigate replacing this get/set loop with a `memcpy`. @@ -654,7 +648,6 @@ impl Instance { dst: u32, src: u32, len: u32, - source_loc: ir::SourceLoc, ) -> Result<(), Trap> { // https://webassembly.github.io/reference-types/core/exec/instructions.html#exec-memory-copy @@ -667,7 +660,7 @@ impl Instance { .checked_add(len) .map_or(true, |m| m as usize > memory.current_length) { - return Err(Trap::wasm(source_loc, ir::TrapCode::HeapOutOfBounds)); + return Err(Trap::wasm(ir::TrapCode::HeapOutOfBounds)); } let dst = usize::try_from(dst).unwrap(); @@ -691,14 +684,13 @@ impl Instance { dst: u32, src: u32, len: u32, - source_loc: ir::SourceLoc, ) -> Result<(), Trap> { let import = self.imported_memory(memory_index); unsafe { let foreign_instance = (&*import.vmctx).instance(); let foreign_memory = &*import.from; let foreign_index = foreign_instance.memory_index(foreign_memory); - foreign_instance.defined_memory_copy(foreign_index, dst, src, len, source_loc) + foreign_instance.defined_memory_copy(foreign_index, dst, src, len) } } @@ -713,7 +705,6 @@ impl Instance { dst: u32, val: u32, len: u32, - source_loc: ir::SourceLoc, ) -> Result<(), Trap> { let memory = self.memory(memory_index); @@ -721,7 +712,7 @@ impl Instance { .checked_add(len) .map_or(true, |m| m as usize > memory.current_length) { - return Err(Trap::wasm(source_loc, ir::TrapCode::HeapOutOfBounds)); + return Err(Trap::wasm(ir::TrapCode::HeapOutOfBounds)); } let dst = isize::try_from(dst).unwrap(); @@ -748,14 +739,13 @@ impl Instance { dst: u32, val: u32, len: u32, - source_loc: ir::SourceLoc, ) -> Result<(), Trap> { let import = self.imported_memory(memory_index); unsafe { let foreign_instance = (&*import.vmctx).instance(); let foreign_memory = &*import.from; let foreign_index = foreign_instance.memory_index(foreign_memory); - foreign_instance.defined_memory_fill(foreign_index, dst, val, len, source_loc) + foreign_instance.defined_memory_fill(foreign_index, dst, val, len) } } @@ -773,7 +763,6 @@ impl Instance { dst: u32, src: u32, len: u32, - source_loc: ir::SourceLoc, ) -> Result<(), Trap> { // https://webassembly.github.io/bulk-memory-operations/core/exec/instructions.html#exec-memory-init @@ -790,7 +779,7 @@ impl Instance { .checked_add(len) .map_or(true, |m| m as usize > memory.current_length) { - return Err(Trap::wasm(source_loc, ir::TrapCode::HeapOutOfBounds)); + return Err(Trap::wasm(ir::TrapCode::HeapOutOfBounds)); } let src_slice = &data[src as usize..(src + len) as usize]; @@ -859,7 +848,6 @@ impl InstanceHandle { /// safety. pub unsafe fn new( module: Arc, - trap_registration: TrapRegistration, finished_functions: BoxedSlice, trampolines: HashMap, imports: Imports, @@ -906,7 +894,6 @@ impl InstanceHandle { dbg_jit_registration, host_state, signal_handler: Cell::new(None), - trap_registration, vmctx: VMContext {}, }; let layout = instance.alloc_layout(); @@ -1256,7 +1243,6 @@ fn initialize_tables(instance: &Instance) -> Result<(), InstantiationError> { .map_or(true, |end| end > table.size() as usize) { return Err(InstantiationError::Trap(Trap::wasm( - ir::SourceLoc::default(), ir::TrapCode::HeapOutOfBounds, ))); } @@ -1332,7 +1318,6 @@ fn initialize_memories( .map_or(true, |end| end > memory.current_length) { return Err(InstantiationError::Trap(Trap::wasm( - ir::SourceLoc::default(), ir::TrapCode::HeapOutOfBounds, ))); } @@ -1407,9 +1392,9 @@ pub enum InstantiationError { /// A trap ocurred during instantiation, after linking. #[error("Trap occurred during instantiation")] - Trap(#[source] Trap), + Trap(Trap), /// A compilation error occured. #[error("Trap occurred while invoking start function")] - StartTrap(#[source] Trap), + StartTrap(Trap), } diff --git a/crates/runtime/src/lib.rs b/crates/runtime/src/lib.rs index 7cdd331acf..b89023a962 100644 --- a/crates/runtime/src/lib.rs +++ b/crates/runtime/src/lib.rs @@ -29,7 +29,6 @@ mod memory; mod mmap; mod sig_registry; mod table; -mod trap_registry; mod traphandlers; mod vmcontext; @@ -44,7 +43,6 @@ pub use crate::memory::{RuntimeLinearMemory, RuntimeMemoryCreator}; pub use crate::mmap::Mmap; pub use crate::sig_registry::SignatureRegistry; pub use crate::table::Table; -pub use crate::trap_registry::{TrapDescription, TrapRegistration, TrapRegistry}; pub use crate::traphandlers::resume_panic; pub use crate::traphandlers::{catch_traps, raise_lib_trap, raise_user_trap, Trap}; pub use crate::vmcontext::{ diff --git a/crates/runtime/src/libcalls.rs b/crates/runtime/src/libcalls.rs index a21a7e2289..826e42f9c2 100644 --- a/crates/runtime/src/libcalls.rs +++ b/crates/runtime/src/libcalls.rs @@ -35,7 +35,6 @@ use crate::table::Table; use crate::traphandlers::raise_lib_trap; use crate::vmcontext::VMContext; -use wasmtime_environ::ir; use wasmtime_environ::wasm::{DataIndex, DefinedMemoryIndex, ElemIndex, MemoryIndex, TableIndex}; /// Implementation of f32.ceil @@ -175,16 +174,14 @@ pub unsafe extern "C" fn wasmtime_table_copy( dst: u32, src: u32, len: u32, - source_loc: u32, ) { let result = { let dst_table_index = TableIndex::from_u32(dst_table_index); let src_table_index = TableIndex::from_u32(src_table_index); - let source_loc = ir::SourceLoc::new(source_loc); let instance = (&mut *vmctx).instance(); let dst_table = instance.get_table(dst_table_index); let src_table = instance.get_table(src_table_index); - Table::copy(dst_table, src_table, dst, src, len, source_loc) + Table::copy(dst_table, src_table, dst, src, len) }; if let Err(trap) = result { raise_lib_trap(trap); @@ -199,14 +196,12 @@ pub unsafe extern "C" fn wasmtime_table_init( dst: u32, src: u32, len: u32, - source_loc: u32, ) { let result = { let table_index = TableIndex::from_u32(table_index); - let source_loc = ir::SourceLoc::new(source_loc); let elem_index = ElemIndex::from_u32(elem_index); let instance = (&mut *vmctx).instance(); - instance.table_init(table_index, elem_index, dst, src, len, source_loc) + instance.table_init(table_index, elem_index, dst, src, len) }; if let Err(trap) = result { raise_lib_trap(trap); @@ -227,13 +222,11 @@ pub unsafe extern "C" fn wasmtime_defined_memory_copy( dst: u32, src: u32, len: u32, - source_loc: u32, ) { let result = { let memory_index = DefinedMemoryIndex::from_u32(memory_index); - let source_loc = ir::SourceLoc::new(source_loc); let instance = (&mut *vmctx).instance(); - instance.defined_memory_copy(memory_index, dst, src, len, source_loc) + instance.defined_memory_copy(memory_index, dst, src, len) }; if let Err(trap) = result { raise_lib_trap(trap); @@ -247,13 +240,11 @@ pub unsafe extern "C" fn wasmtime_imported_memory_copy( dst: u32, src: u32, len: u32, - source_loc: u32, ) { let result = { let memory_index = MemoryIndex::from_u32(memory_index); - let source_loc = ir::SourceLoc::new(source_loc); let instance = (&mut *vmctx).instance(); - instance.imported_memory_copy(memory_index, dst, src, len, source_loc) + instance.imported_memory_copy(memory_index, dst, src, len) }; if let Err(trap) = result { raise_lib_trap(trap); @@ -267,13 +258,11 @@ pub unsafe extern "C" fn wasmtime_memory_fill( dst: u32, val: u32, len: u32, - source_loc: u32, ) { let result = { let memory_index = DefinedMemoryIndex::from_u32(memory_index); - let source_loc = ir::SourceLoc::new(source_loc); let instance = (&mut *vmctx).instance(); - instance.defined_memory_fill(memory_index, dst, val, len, source_loc) + instance.defined_memory_fill(memory_index, dst, val, len) }; if let Err(trap) = result { raise_lib_trap(trap); @@ -287,13 +276,11 @@ pub unsafe extern "C" fn wasmtime_imported_memory_fill( dst: u32, val: u32, len: u32, - source_loc: u32, ) { let result = { let memory_index = MemoryIndex::from_u32(memory_index); - let source_loc = ir::SourceLoc::new(source_loc); let instance = (&mut *vmctx).instance(); - instance.imported_memory_fill(memory_index, dst, val, len, source_loc) + instance.imported_memory_fill(memory_index, dst, val, len) }; if let Err(trap) = result { raise_lib_trap(trap); @@ -308,14 +295,12 @@ pub unsafe extern "C" fn wasmtime_memory_init( dst: u32, src: u32, len: u32, - source_loc: u32, ) { let result = { let memory_index = MemoryIndex::from_u32(memory_index); let data_index = DataIndex::from_u32(data_index); - let source_loc = ir::SourceLoc::new(source_loc); let instance = (&mut *vmctx).instance(); - instance.memory_init(memory_index, data_index, dst, src, len, source_loc) + instance.memory_init(memory_index, data_index, dst, src, len) }; if let Err(trap) = result { raise_lib_trap(trap); diff --git a/crates/runtime/src/table.rs b/crates/runtime/src/table.rs index e938b5dd29..494ff3691a 100644 --- a/crates/runtime/src/table.rs +++ b/crates/runtime/src/table.rs @@ -100,7 +100,6 @@ impl Table { dst_index: u32, src_index: u32, len: u32, - source_loc: ir::SourceLoc, ) -> Result<(), Trap> { // https://webassembly.github.io/bulk-memory-operations/core/exec/instructions.html#exec-table-copy @@ -111,7 +110,7 @@ impl Table { .checked_add(len) .map_or(true, |m| m > dst_table.size()) { - return Err(Trap::wasm(source_loc, ir::TrapCode::TableOutOfBounds)); + return Err(Trap::wasm(ir::TrapCode::TableOutOfBounds)); } let srcs = src_index..src_index + len; diff --git a/crates/runtime/src/trap_registry.rs b/crates/runtime/src/trap_registry.rs deleted file mode 100644 index d686acb290..0000000000 --- a/crates/runtime/src/trap_registry.rs +++ /dev/null @@ -1,173 +0,0 @@ -use std::collections::{BTreeMap, HashMap}; -use std::fmt; -use std::sync::{Arc, RwLock}; -use wasmtime_environ::ir; - -/// The registry maintains descriptions of traps in currently allocated functions. -#[derive(Default)] -pub struct TrapRegistry { - // This data structure is intended to be safe to use across many threads - // since this is stored inside of a `Compiler` which, eventually, will be - // used across many threads. To that end this is internally use an `Arc` - // plus an `RwLock`. - // - // The problem that this data structure is solving is that when a - // segfault/illegal instruction happens we need to answer "given this - // hardware program counter what is the wasm reason this trap is being - // raised"? - // - // The way this is answered here is done to minimize the amount of - // synchronization (in theory) and have something like so: - // - // * Each module bulk-registers a list of in-memory pc addresses that have - // traps. We assume that the range of traps for each module are always - // disjoint. - // * Each key in this `BTreeMap` is the highest trapping address and the - // value contains the lowest address as well as all the individual - // addresses in their own `HashMap`. - // * Registration then looks by calculating the start/end and inserting - // into this map (with some assertions about disjointed-ness) - // * Lookup is done in two layers. First we find the corresponding entry - // in the map and verify that a program counter falls in the start/end - // range. Next we look up the address in the `traps` hash map below. - // - // The `register_traps` function works by returning an RAII guard that owns - // a handle to this `Arc` as well, and when that type is dropped it will - // automatically remove all trap information from this `ranges` list. - ranges: Arc>>, -} - -#[derive(Debug)] -struct TrapGroup { - /// The lowest key in the `trap` field. - /// - /// This represents the start of the range of this group of traps, and the - /// end of the range for this group of traps is stored as the key in the - /// `ranges` struct above in `TrapRegistry`. - start: usize, - - /// All known traps in this group, mapped from program counter to the - /// description of the trap itself. - traps: HashMap, -} - -/// RAII structure returned from `TrapRegistry::register_trap` to unregister -/// trap information on drop. -#[derive(Clone)] -pub struct TrapRegistration { - ranges: Arc>>, - end: Option, -} - -/// Description of a trap. -#[derive(Clone, Copy, PartialEq, Debug)] -pub struct TrapDescription { - /// Location of the trap in source binary module. - pub source_loc: ir::SourceLoc, - /// Code of the trap. - pub trap_code: ir::TrapCode, -} - -impl fmt::Display for TrapDescription { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "wasm trap: {}, source location: {}", - trap_code_to_expected_string(self.trap_code), - self.source_loc - ) - } -} - -fn trap_code_to_expected_string(trap_code: ir::TrapCode) -> String { - use ir::TrapCode::*; - match trap_code { - StackOverflow => "call stack exhausted".to_string(), - HeapOutOfBounds => "out of bounds memory access".to_string(), - TableOutOfBounds => "undefined element: out of bounds table access".to_string(), - OutOfBounds => "out of bounds".to_string(), // Note: not covered by the test suite - IndirectCallToNull => "uninitialized element".to_string(), - BadSignature => "indirect call type mismatch".to_string(), - IntegerOverflow => "integer overflow".to_string(), - IntegerDivisionByZero => "integer divide by zero".to_string(), - BadConversionToInteger => "invalid conversion to integer".to_string(), - UnreachableCodeReached => "unreachable".to_string(), - Interrupt => "interrupt".to_string(), // Note: not covered by the test suite - User(x) => format!("user trap {}", x), // Note: not covered by the test suite - } -} - -impl TrapRegistry { - /// Registers a list of traps. - /// - /// Returns a RAII guard that deregisters all traps when dropped. - pub fn register_traps( - &self, - list: impl IntoIterator, - ) -> TrapRegistration { - let mut start = usize::max_value(); - let mut end = 0; - let mut traps = HashMap::new(); - for (addr, source_loc, trap_code) in list.into_iter() { - traps.insert( - addr, - TrapDescription { - source_loc, - trap_code, - }, - ); - if addr < start { - start = addr; - } - if addr > end { - end = addr; - } - } - if traps.len() == 0 { - return TrapRegistration { - ranges: self.ranges.clone(), - end: None, - }; - } - let mut ranges = self.ranges.write().unwrap(); - - // Sanity check that no other group of traps overlaps with our - // registration... - if let Some((_, prev)) = ranges.range(end..).next() { - assert!(prev.start > end); - } - if let Some((prev_end, _)) = ranges.range(..=start).next_back() { - assert!(*prev_end < start); - } - - // ... and then register ourselves - assert!(ranges.insert(end, TrapGroup { start, traps }).is_none()); - TrapRegistration { - ranges: self.ranges.clone(), - end: Some(end), - } - } -} - -impl TrapRegistration { - /// Gets a trap description at given address. - pub fn get_trap(&self, address: usize) -> Option { - let ranges = self.ranges.read().ok()?; - let (end, group) = ranges.range(address..).next()?; - if group.start <= address && address <= *end { - group.traps.get(&address).copied() - } else { - None - } - } -} - -impl Drop for TrapRegistration { - fn drop(&mut self) { - if let Some(end) = self.end { - if let Ok(mut ranges) = self.ranges.write() { - ranges.remove(&end); - } - } - } -} diff --git a/crates/runtime/src/traphandlers.rs b/crates/runtime/src/traphandlers.rs index 657abb6221..107f3e82f0 100644 --- a/crates/runtime/src/traphandlers.rs +++ b/crates/runtime/src/traphandlers.rs @@ -2,13 +2,11 @@ //! signalhandling mechanisms. use crate::instance::{InstanceHandle, SignalHandler}; -use crate::trap_registry::TrapDescription; use crate::vmcontext::VMContext; use backtrace::Backtrace; use std::any::Any; use std::cell::Cell; use std::error::Error; -use std::fmt; use std::io; use std::ptr; use std::sync::Once; @@ -319,11 +317,19 @@ fn reset_guard_page() {} pub enum Trap { /// A user-raised trap through `raise_user_trap`. User(Box), - /// A wasm-originating trap from wasm code itself. + + /// A trap raised from jit code + Jit { + /// The program counter in JIT code where this trap happened. + pc: usize, + /// Native stack backtrace at the time the trap occurred + backtrace: Backtrace, + }, + + /// A trap raised from a wasm libcall Wasm { - /// What sort of trap happened, as well as where in the original wasm module - /// it happened. - desc: TrapDescription, + /// Code of the trap. + trap_code: ir::TrapCode, /// Native stack backtrace at the time the trap occurred backtrace: Backtrace, }, @@ -335,36 +341,23 @@ pub enum Trap { }, } -impl fmt::Display for Trap { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Trap::User(user) => user.fmt(f), - Trap::Wasm { desc, .. } => desc.fmt(f), - Trap::OOM { .. } => write!(f, "Out of memory"), - } - } -} - -impl std::error::Error for Trap {} - impl Trap { /// Construct a new Wasm trap with the given source location and trap code. /// /// Internally saves a backtrace when constructed. - pub fn wasm(source_loc: ir::SourceLoc, trap_code: ir::TrapCode) -> Self { - let desc = TrapDescription { - source_loc, + pub fn wasm(trap_code: ir::TrapCode) -> Self { + let backtrace = Backtrace::new_unresolved(); + Trap::Wasm { trap_code, - }; - let backtrace = Backtrace::new(); - Trap::Wasm { desc, backtrace } + backtrace, + } } /// Construct a new OOM trap with the given source location and trap code. /// /// Internally saves a backtrace when constructed. pub fn oom() -> Self { - let backtrace = Backtrace::new(); + let backtrace = Backtrace::new_unresolved(); Trap::OOM { backtrace } } } @@ -413,7 +406,7 @@ enum UnwindReason { Panic(Box), UserTrap(Box), LibTrap(Trap), - Trap { backtrace: Backtrace, pc: usize }, + JitTrap { backtrace: Backtrace, pc: usize }, } impl CallThreadState { @@ -442,21 +435,9 @@ impl CallThreadState { Err(Trap::User(data)) } UnwindReason::LibTrap(trap) => Err(trap), - UnwindReason::Trap { backtrace, pc } => { + UnwindReason::JitTrap { backtrace, pc } => { debug_assert_eq!(ret, 0); - let instance = unsafe { InstanceHandle::from_vmctx(self.vmctx) }; - - Err(Trap::Wasm { - desc: instance - .instance() - .trap_registration - .get_trap(pc) - .unwrap_or_else(|| TrapDescription { - source_loc: ir::SourceLoc::default(), - trap_code: ir::TrapCode::StackOverflow, - }), - backtrace, - }) + Err(Trap::Jit { pc, backtrace }) } UnwindReason::Panic(panic) => { debug_assert_eq!(ret, 0); @@ -546,7 +527,7 @@ impl CallThreadState { } let backtrace = Backtrace::new_unresolved(); self.reset_guard_page.set(reset_guard_page); - self.unwind.replace(UnwindReason::Trap { + self.unwind.replace(UnwindReason::JitTrap { backtrace, pc: pc as usize, });