Define runtime error values explicitly in Singlepass

This commit is contained in:
Mark McCaskey 2020-04-24 13:21:45 -07:00
parent bfb6814f23
commit b9ec8f9845
12 changed files with 129 additions and 102 deletions

View File

@ -1,12 +1,12 @@
use crate::{
relocation::{TrapData, TrapSink, TrapCode},
relocation::{TrapData, TrapSink},
resolver::FuncResolver,
trampoline::Trampolines,
};
use libc::c_void;
use std::{any::Any, cell::Cell, ptr::NonNull, sync::Arc};
use std::{cell::Cell, ptr::NonNull, sync::Arc};
use wasmer_runtime_core::{
backend::{RunnableModule, ExceptionCode},
backend::RunnableModule,
error::{InvokeError, RuntimeError},
module::ModuleInfo,
typed_func::{Trampoline, Wasm},
@ -30,23 +30,6 @@ thread_local! {
pub static TRAP_EARLY_DATA: Cell<Option<RuntimeError>> = Cell::new(None);
}
pub enum CallProtError {
UnknownTrap {
address: usize,
signal: &'static str,
},
TrapCode {
code: ExceptionCode,
srcloc: u32,
},
UnknownTrapCode {
trap_code: TrapCode,
srcloc: u32,
},
EarlyTrap(RuntimeError),
Misc(Box<dyn Any + Send>),
}
pub struct Caller {
handler_data: HandlerData,
trampolines: Arc<Trampolines>,
@ -99,7 +82,7 @@ impl RunnableModule for Caller {
// probably makes the most sense to actually do a translation here to a
// a generic type defined in runtime-core
// TODO: figure out _this_ error return story
*error_out = Some(err.0);
*error_out = Some(err);
false
}
Ok(()) => true,

View File

@ -10,7 +10,7 @@
//! unless you have memory unsafety elsewhere in your code.
//!
use crate::relocation::{TrapCode, TrapData};
use crate::signal::{CallProtError, HandlerData};
use crate::signal::HandlerData;
use libc::{c_int, c_void, siginfo_t};
use nix::sys::signal::{
sigaction, SaFlags, SigAction, SigHandler, SigSet, Signal, SIGBUS, SIGFPE, SIGILL, SIGSEGV,
@ -19,6 +19,7 @@ use std::cell::{Cell, UnsafeCell};
use std::ptr;
use std::sync::Once;
use wasmer_runtime_core::backend::ExceptionCode;
use wasmer_runtime_core::error::InvokeError;
extern "C" fn signal_trap_handler(
signum: ::nix::libc::c_int,
@ -65,7 +66,7 @@ pub unsafe fn trigger_trap() -> ! {
pub fn call_protected<T>(
handler_data: &HandlerData,
f: impl FnOnce() -> T,
) -> Result<T, CallProtError> {
) -> Result<T, InvokeError> {
unsafe {
let jmp_buf = SETJMP_BUFFER.with(|buf| buf.get());
let prev_jmp_buf = *jmp_buf;
@ -79,15 +80,11 @@ pub fn call_protected<T>(
*jmp_buf = prev_jmp_buf;
if let Some(data) = super::TRAP_EARLY_DATA.with(|cell| cell.replace(None)) {
Err(CallProtError::EarlyTrap(data))
Err(InvokeError::EarlyTrap(Box::new(data)))
} else {
let (faulting_addr, inst_ptr) = CAUGHT_ADDRESSES.with(|cell| cell.get());
if let Some(TrapData {
trapcode,
srcloc,
}) = handler_data.lookup(inst_ptr)
{
if let Some(TrapData { trapcode, srcloc }) = handler_data.lookup(inst_ptr) {
let code = match Signal::from_c_int(signum) {
Ok(SIGILL) => match trapcode {
TrapCode::StackOverflow => ExceptionCode::MemoryOutOfBounds,
@ -101,8 +98,8 @@ pub fn call_protected<T>(
TrapCode::BadConversionToInteger => ExceptionCode::IllegalArithmetic,
TrapCode::UnreachableCodeReached => ExceptionCode::Unreachable,
_ => {
return Err(CallProtError::UnknownTrapCode {
trap_code: trapcode,
return Err(InvokeError::UnknownTrapCode {
trap_code: format!("{:?}", trapcode),
srcloc,
})
}
@ -114,10 +111,7 @@ pub fn call_protected<T>(
Signal::from_c_int(signum)
),
};
Err(CallProtError::TrapCode {
srcloc,
code,
})
Err(InvokeError::TrapCode { srcloc, code })
} else {
let signal = match Signal::from_c_int(signum) {
Ok(SIGFPE) => "floating-point exception",
@ -128,7 +122,7 @@ pub fn call_protected<T>(
_ => "unknown trapped signal",
};
// When the trap-handler is fully implemented, this will return more information.
Err(CallProtError::UnknownTrap {
Err(InvokeError::UnknownTrap {
address: faulting_addr as usize,
signal,
})

View File

@ -11,7 +11,6 @@ use inkwell::{
};
use libc::c_char;
use std::{
any::Any,
cell::RefCell,
ffi::{c_void, CString},
mem,
@ -27,6 +26,7 @@ use wasmer_runtime_core::{
CacheGen, ExceptionCode, RunnableModule,
},
cache::Error as CacheError,
error::{InvokeError, RuntimeError},
module::ModuleInfo,
state::ModuleStateMap,
structures::TypedIndex,
@ -56,7 +56,7 @@ extern "C" {
/// but this is cleaner, I think?
#[cfg_attr(nightly, unwind(allowed))]
#[allow(improper_ctypes)]
fn throw_any(data: *mut dyn Any) -> !;
fn throw_runtime_error(data: RuntimeError) -> !;
#[allow(improper_ctypes)]
fn cxx_invoke_trampoline(
@ -66,7 +66,7 @@ extern "C" {
params: *const u64,
results: *mut u64,
trap_out: *mut i32,
error_out: *mut Option<Box<dyn Any + Send>>,
error_out: *mut Option<InvokeError>,
invoke_env: Option<NonNull<c_void>>,
) -> bool;
}
@ -79,7 +79,7 @@ unsafe extern "C" fn invoke_trampoline(
func_ptr: NonNull<vm::Func>,
params: *const u64,
results: *mut u64,
error_out: *mut Option<Box<dyn Any + Send>>,
error_out: *mut Option<InvokeError>,
invoke_env: Option<NonNull<c_void>>,
) -> bool {
let mut trap_out: i32 = -1;
@ -95,15 +95,22 @@ unsafe extern "C" fn invoke_trampoline(
);
// Translate trap code if an error occurred.
if !ret && (*error_out).is_none() && trap_out != -1 {
*error_out = Some(Box::new(match trap_out {
0 => ExceptionCode::Unreachable,
1 => ExceptionCode::IncorrectCallIndirectSignature,
2 => ExceptionCode::MemoryOutOfBounds,
3 => ExceptionCode::CallIndirectOOB,
4 => ExceptionCode::IllegalArithmetic,
5 => ExceptionCode::MisalignedAtomicAccess,
_ => return ret,
}));
*error_out = {
let exception_code = match trap_out {
0 => ExceptionCode::Unreachable,
1 => ExceptionCode::IncorrectCallIndirectSignature,
2 => ExceptionCode::MemoryOutOfBounds,
3 => ExceptionCode::CallIndirectOOB,
4 => ExceptionCode::IllegalArithmetic,
5 => ExceptionCode::MisalignedAtomicAccess,
_ => return ret,
};
Some(InvokeError::TrapCode {
code: exception_code,
// TODO:
srcloc: 0,
})
};
}
ret
}
@ -467,8 +474,9 @@ impl RunnableModule for LLVMBackend {
self.msm.clone()
}
unsafe fn do_early_trap(&self, data: Box<dyn Any + Send>) -> ! {
throw_any(Box::leak(data))
unsafe fn do_early_trap(&self, data: RuntimeError) -> ! {
// maybe need to box leak it?
throw_runtime_error(data)
}
}

View File

@ -1,5 +1,6 @@
use wasmer_runtime_core::{
codegen::{Event, EventSink, FunctionMiddleware, InternalEvent},
error::RuntimeError,
module::ModuleInfo,
vm::{Ctx, InternalField},
wasmparser::{Operator, Type as WpType, TypeOrFuncType as WpTypeOrFuncType},
@ -96,7 +97,7 @@ impl FunctionMiddleware for Metering {
ty: WpTypeOrFuncType::Type(WpType::EmptyBlockType),
}));
sink.push(Event::Internal(InternalEvent::Breakpoint(Box::new(|_| {
Err(Box::new(ExecutionLimitExceededError))
Err(RuntimeError::Metering(Box::new(ExecutionLimitExceededError)))
}))));
sink.push(Event::WasmOwned(Operator::End));
}

View File

@ -6,13 +6,12 @@ use crate::{
backend::RunnableModule,
backend::{CacheGen, Compiler, CompilerConfig, Features, Token},
cache::{Artifact, Error as CacheError},
error::{CompileError, CompileResult},
error::{CompileError, CompileResult, RuntimeError},
module::{ModuleInfo, ModuleInner},
structures::Map,
types::{FuncIndex, FuncSig, SigIndex},
};
use smallvec::SmallVec;
use std::any::Any;
use std::collections::HashMap;
use std::fmt;
use std::fmt::Debug;
@ -23,7 +22,7 @@ use wasmparser::{Operator, Type as WpType};
/// A type that defines a function pointer, which is called when breakpoints occur.
pub type BreakpointHandler =
Box<dyn Fn(BreakpointInfo) -> Result<(), Box<dyn Any + Send>> + Send + Sync + 'static>;
Box<dyn Fn(BreakpointInfo) -> Result<(), RuntimeError> + Send + Sync + 'static>;
/// Maps instruction pointers to their breakpoint handlers.
pub type BreakpointMap = Arc<HashMap<usize, BreakpointHandler>>;

View File

@ -1,6 +1,6 @@
//! The error module contains the data structures and helper functions used to implement errors that
//! are produced and returned from the wasmer runtime core.
//use crate::backend::ExceptionCode;
use crate::backend::ExceptionCode;
use crate::types::{FuncSig, GlobalDescriptor, MemoryDescriptor, TableDescriptor, Type};
use core::borrow::Borrow;
use std::any::Any;
@ -222,31 +222,63 @@ impl std::error::Error for RuntimeError {}
/// extremely rare and impossible to handle.
#[derive(Debug)]
pub enum RuntimeError {
/// When an invoke returns an error (this is where exception codes come from?)
/// When an invoke returns an error
InvokeError(InvokeError),
/// A metering triggered error value.
///
/// An error of this type indicates that it was returned by the metering system.
Metering(Box<dyn Any + Send>),
/// A user triggered error value.
///
/// An error returned from a host function.
User(Box<dyn Any + Send>)
User(Box<dyn Any + Send>),
}
/// TODO:
#[derive(Debug)]
pub enum InvokeError {
/// not yet handled error cases, ideally we should be able to handle them all
Misc(Box<dyn Any + Send>),
/// Indicates an exceptional circumstance such as a bug that should be reported or
/// a hardware failure.
FailedWithNoError,
/// TODO:
UnknownTrap {
/// TODO:
address: usize,
/// TODO:
signal: &'static str,
},
/// TODO:
TrapCode {
/// TODO:
code: ExceptionCode,
/// TODO:
srcloc: u32,
},
/// TODO:
UnknownTrapCode {
/// TODO:
trap_code: String,
/// TODO:
srcloc: u32,
},
/// extra TODO: investigate if this can be a `Box<InvokeError>` instead (looks like probably no)
/// TODO:
EarlyTrap(Box<RuntimeError>),
/// Indicates an error that ocurred related to breakpoints. (currently Singlepass only)
Breakpoint(Box<RuntimeError>),
}
//impl std::error::Error for InvokeError {}
impl std::error::Error for RuntimeError {}
impl std::fmt::Display for RuntimeError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
// TODO: ideally improve the error type of invoke
// TODO: update invoke error formatting
RuntimeError::InvokeError(_) => write!(f, "Error when calling invoke"),
RuntimeError::Metering(_) => write!(f, "unknown metering error type"),
RuntimeError::User(user_error) => {
write!(f, "User supplied error: ")?;
if let Some(s) = user_error.downcast_ref::<String>() {
@ -256,9 +288,9 @@ impl std::fmt::Display for RuntimeError {
} else if let Some(n) = user_error.downcast_ref::<i32>() {
write!(f, "{}", n)
} else {
write!(f, "unknown error type")
write!(f, "unknown user error type")
}
},
}
}
}
}
@ -270,8 +302,6 @@ impl From<InternalError> for RuntimeError {
}
}
*/
/// This error type is produced by resolving a wasm function
/// given its name.

View File

@ -29,14 +29,14 @@ pub mod raw {
use crate::codegen::{BreakpointInfo, BreakpointMap};
use crate::state::x64::{build_instance_image, read_stack, X64Register, GPR};
use crate::state::{CodeVersion, ExecutionStateImage};
use crate::state::{CodeVersion, ExecutionStateImage, InstanceImage};
use crate::error::{RuntimeError, InvokeError};
use crate::vm;
use libc::{mmap, mprotect, siginfo_t, MAP_ANON, MAP_PRIVATE, PROT_NONE, PROT_READ, PROT_WRITE};
use nix::sys::signal::{
sigaction, SaFlags, SigAction, SigHandler, SigSet, Signal, SIGBUS, SIGFPE, SIGILL, SIGINT,
SIGSEGV, SIGTRAP,
};
use std::any::Any;
use std::cell::{Cell, RefCell, UnsafeCell};
use std::ffi::c_void;
use std::process;
@ -61,7 +61,7 @@ type SetJmpBuffer = [i32; SETJMP_BUFFER_LEN];
struct UnwindInfo {
jmpbuf: SetJmpBuffer, // in
breakpoints: Option<BreakpointMap>,
payload: Option<Box<dyn Any + Send>>, // out
payload: Option<RuntimeError>, // out
}
/// A store for boundary register preservation.
@ -182,7 +182,7 @@ pub unsafe fn clear_wasm_interrupt() {
pub unsafe fn catch_unsafe_unwind<R, F: FnOnce() -> R>(
f: F,
breakpoints: Option<BreakpointMap>,
) -> Result<R, Box<dyn Any + Send>> {
) -> Result<R, RuntimeError> {
let unwind = UNWIND.with(|x| x.get());
let old = (*unwind).take();
*unwind = Some(UnwindInfo {
@ -205,7 +205,7 @@ pub unsafe fn catch_unsafe_unwind<R, F: FnOnce() -> R>(
}
/// Begins an unsafe unwind.
pub unsafe fn begin_unsafe_unwind(e: Box<dyn Any + Send>) -> ! {
pub unsafe fn begin_unsafe_unwind(e: RuntimeError) -> ! {
let unwind = UNWIND.with(|x| x.get());
let inner = (*unwind)
.as_mut()
@ -279,7 +279,7 @@ extern "C" fn signal_trap_handler(
static ARCH: Architecture = Architecture::Aarch64;
let mut should_unwind = false;
let mut unwind_result: Box<dyn Any + Send> = Box::new(());
let mut unwind_result: Option<Result<InstanceImage, RuntimeError>> = None;
unsafe {
let fault = get_fault_info(siginfo as _, ucontext);
@ -302,7 +302,7 @@ extern "C" fn signal_trap_handler(
) {
match ib.ty {
InlineBreakpointType::Middleware => {
let out: Option<Result<(), Box<dyn Any + Send>>> =
let out: Option<Result<(), RuntimeError>> =
with_breakpoint_map(|bkpt_map| {
bkpt_map.and_then(|x| x.get(&ip)).map(|x| {
x(BreakpointInfo {
@ -313,7 +313,7 @@ extern "C" fn signal_trap_handler(
if let Some(Ok(())) = out {
} else if let Some(Err(e)) = out {
should_unwind = true;
unwind_result = e;
unwind_result = Some(Err(e));
}
}
}
@ -328,7 +328,7 @@ extern "C" fn signal_trap_handler(
})
});
if should_unwind {
begin_unsafe_unwind(unwind_result);
begin_unsafe_unwind(unwind_result.unwrap().unwrap_err());
}
if early_return {
return;
@ -342,9 +342,9 @@ extern "C" fn signal_trap_handler(
match Signal::from_c_int(signum) {
Ok(SIGTRAP) => {
// breakpoint
let out: Option<Result<(), Box<dyn Any + Send>>> =
with_breakpoint_map(|bkpt_map| {
bkpt_map.and_then(|x| x.get(&(fault.ip.get()))).map(|x| {
let out: Option<Result<(), RuntimeError>> =
with_breakpoint_map(|bkpt_map| -> Option<Result<(), RuntimeError>> {
bkpt_map.and_then(|x| x.get(&(fault.ip.get()))).map(|x| -> Result<(), RuntimeError> {
x(BreakpointInfo {
fault: Some(&fault),
})
@ -355,7 +355,7 @@ extern "C" fn signal_trap_handler(
return false;
}
Some(Err(e)) => {
unwind_result = e;
unwind_result = Some(Err(e));
return true;
}
None => {}
@ -387,7 +387,7 @@ extern "C" fn signal_trap_handler(
if is_suspend_signal {
// If this is a suspend signal, we parse the runtime state and return the resulting image.
let image = build_instance_image(ctx, es_image);
unwind_result = Box::new(image);
unwind_result = Some(Ok(image));
} else {
// Otherwise, this is a real exception and we just throw it to the caller.
if !es_image.frames.is_empty() {
@ -415,7 +415,11 @@ extern "C" fn signal_trap_handler(
None
});
if let Some(code) = exc_code {
unwind_result = Box::new(code);
unwind_result = Some(Err(RuntimeError::InvokeError(InvokeError::TrapCode {
code,
// TODO:
srcloc: 0,
})));
}
}
@ -423,7 +427,7 @@ extern "C" fn signal_trap_handler(
});
if should_unwind {
begin_unsafe_unwind(unwind_result);
begin_unsafe_unwind(unwind_result.unwrap().unwrap_err());
}
}
}

View File

@ -5,7 +5,7 @@
use crate::{
backend::RunnableModule,
backing::{ImportBacking, LocalBacking},
error::{CallResult, ResolveError, ResolveResult, Result, RuntimeError, InvokeError},
error::{CallResult, InvokeError, ResolveError, ResolveResult, Result, RuntimeError},
export::{Context, Export, ExportIter, Exportable, FuncPointer},
global::Global,
import::{ImportObject, LikeNamespace},
@ -587,15 +587,17 @@ pub(crate) fn call_func_with_index_inner(
let run_wasm = |result_space: *mut u64| -> CallResult<()> {
let mut error_out = None;
let success = unsafe { invoke(
trampoline,
ctx_ptr,
func_ptr,
raw_args.as_ptr(),
result_space,
&mut error_out,
invoke_env,
)};
let success = unsafe {
invoke(
trampoline,
ctx_ptr,
func_ptr,
raw_args.as_ptr(),
result_space,
&mut error_out,
invoke_env,
)
};
if success {
Ok(())

View File

@ -705,7 +705,7 @@ pub mod x64 {
use crate::structures::TypedIndex;
use crate::types::LocalGlobalIndex;
use crate::vm::Ctx;
use std::any::Any;
use crate::error::RuntimeError;
#[allow(clippy::cast_ptr_alignment)]
unsafe fn compute_vmctx_deref(vmctx: *const Ctx, seq: &[usize]) -> u64 {
@ -738,7 +738,7 @@ pub mod x64 {
image: InstanceImage,
vmctx: &mut Ctx,
breakpoints: Option<BreakpointMap>,
) -> Result<u64, Box<dyn Any + Send>> {
) -> Result<u64, RuntimeError> {
let mut stack: Vec<u64> = vec![0; 1048576 * 8 / 8]; // 8MB stack
let mut stack_offset: usize = stack.len();

View File

@ -1,7 +1,7 @@
//! The typed func module implements a way of representing a wasm function
//! with the correct types from rust. Function calls using a typed func have a low overhead.
use crate::{
error::{RuntimeError, InvokeError},
error::{InvokeError, RuntimeError},
export::{Context, Export, FuncPointer},
import::IsExport,
types::{FuncSig, NativeWasmType, Type, WasmExternType},
@ -340,7 +340,9 @@ impl<'a> DynamicFunc<'a> {
Err(e) => {
// At this point, there is an error that needs to be trapped.
drop(args); // Release the Vec which will leak otherwise.
(&*vmctx.module).runnable_module.do_early_trap(RuntimeError::User(e))
(&*vmctx.module)
.runnable_module
.do_early_trap(RuntimeError::User(e))
}
}
}

View File

@ -10,7 +10,6 @@ use dynasmrt::x64::Assembler;
use dynasmrt::{AssemblyOffset, DynamicLabel, DynasmApi, DynasmLabelApi};
use smallvec::SmallVec;
use std::{
any::Any,
collections::{BTreeMap, HashMap},
ffi::c_void,
iter, mem,
@ -29,6 +28,7 @@ use wasmer_runtime_core::{
codegen::*,
fault::{self, raw::register_preservation_trampoline},
loader::CodeMemory,
error::{InvokeError, RuntimeError},
memory::MemoryType,
module::{ModuleInfo, ModuleInner},
state::{
@ -214,7 +214,7 @@ pub struct X64FunctionCode {
breakpoints: Option<
HashMap<
AssemblyOffset,
Box<dyn Fn(BreakpointInfo) -> Result<(), Box<dyn Any + Send>> + Send + Sync + 'static>,
Box<dyn Fn(BreakpointInfo) -> Result<(), RuntimeError> + Send + Sync + 'static>,
>,
>,
returns: SmallVec<[WpType; 1]>,
@ -507,7 +507,7 @@ impl RunnableModule for X64ExecutionContext {
func: NonNull<vm::Func>,
args: *const u64,
rets: *mut u64,
error_out: *mut Option<Box<dyn Any + Send>>,
error_out: *mut Option<InvokeError>,
num_params_plus_one: Option<NonNull<c_void>>,
) -> bool {
let rm: &Box<dyn RunnableModule> = &(&*(*ctx).module).runnable_module;
@ -655,7 +655,7 @@ impl RunnableModule for X64ExecutionContext {
true
}
Err(err) => {
*error_out = Some(err);
*error_out = Some(InvokeError::Breakpoint(Box::new(err)));
false
}
};
@ -680,7 +680,7 @@ impl RunnableModule for X64ExecutionContext {
})
}
unsafe fn do_early_trap(&self, data: Box<dyn Any + Send>) -> ! {
unsafe fn do_early_trap(&self, data: RuntimeError) -> ! {
fault::begin_unsafe_unwind(data);
}

View File

@ -20,6 +20,7 @@ use wasmer_runtime::{
};
use wasmer_runtime_core::{
self,
error::RuntimeError,
backend::{Compiler, CompilerConfig, MemoryBoundCheckMode},
loader::{Instance as LoadedInstance, LocalLoader},
Module,
@ -437,9 +438,12 @@ fn execute_wasi(
}
if let Err(ref err) = result {
if let Some(error_code) = err.0.downcast_ref::<wasmer_wasi::ExitCode>() {
std::process::exit(error_code.code as i32)
if let RuntimeError::User(user_error) = err {
if let Some(error_code) = user_error.downcast_ref::<wasmer_wasi::ExitCode>() {
std::process::exit(error_code.code as i32)
}
}
return Err(format!("error: {:?}", err));
}
}