Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Naive short circuiting implementation for user panics and results. #167

Merged
merged 9 commits into from
Feb 8, 2019
6 changes: 5 additions & 1 deletion lib/clif-backend/src/module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use wasmer_runtime_core::{
cache::{Cache, Error as CacheError},
};
use wasmer_runtime_core::{
backend::{Backend, FuncResolver, ProtectedCaller, Token},
backend::{Backend, FuncResolver, ProtectedCaller, Token, UserTrapper},
error::{CompileResult, RuntimeResult},
module::{ModuleInfo, ModuleInner, StringTable},
structures::{Map, TypedIndex},
Expand Down Expand Up @@ -51,6 +51,10 @@ impl ProtectedCaller for Placeholder {
) -> RuntimeResult<Vec<Value>> {
Ok(vec![])
}

fn get_early_trapper(&self) -> Box<dyn UserTrapper> {
unimplemented!()
}
}

/// This contains all of the items in a `ModuleInner` except the `func_resolver`.
Expand Down
21 changes: 19 additions & 2 deletions lib/clif-backend/src/signal/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ use crate::relocation::{TrapData, TrapSink};
use crate::trampoline::Trampolines;
use hashbrown::HashSet;
use libc::c_void;
use std::sync::Arc;
use std::{cell::Cell, sync::Arc};
use wasmer_runtime_core::{
backend::{ProtectedCaller, Token},
backend::{ProtectedCaller, Token, UserTrapper},
error::RuntimeResult,
export::Context,
module::{ExportIndex, ModuleInfo, ModuleInner},
Expand All @@ -24,6 +24,19 @@ pub use self::unix::*;
#[cfg(windows)]
pub use self::windows::*;

thread_local! {
pub static TRAP_EARLY_DATA: Cell<Option<String>> = Cell::new(None);
}

pub struct Trapper;

impl UserTrapper for Trapper {
unsafe fn do_early_trap(&self, msg: String) -> ! {
TRAP_EARLY_DATA.with(|cell| cell.set(Some(msg)));
trigger_trap()
}
}

pub struct Caller {
func_export_set: HashSet<FuncIndex>,
handler_data: HandlerData,
Expand Down Expand Up @@ -118,6 +131,10 @@ impl ProtectedCaller for Caller {
})
.collect())
}

fn get_early_trapper(&self) -> Box<dyn UserTrapper> {
Box::new(Trapper)
}
lachlansneff marked this conversation as resolved.
Show resolved Hide resolved
}

fn get_func_from_index(
Expand Down
99 changes: 55 additions & 44 deletions lib/clif-backend/src/signal/unix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
//! are very special, the async signal unsafety of Rust's TLS implementation generally does not affect the correctness here
//! unless you have memory unsafety elsewhere in your code.
//!
use crate::relocation::{TrapCode, TrapData, TrapSink};
use crate::relocation::{TrapCode, TrapData};
use crate::signal::HandlerData;
use libc::{c_int, c_void, siginfo_t};
use nix::sys::signal::{
Expand Down Expand Up @@ -60,6 +60,12 @@ thread_local! {
pub static CURRENT_EXECUTABLE_BUFFER: Cell<*const c_void> = Cell::new(ptr::null());
}

pub unsafe fn trigger_trap() -> ! {
let jmp_buf = SETJMP_BUFFER.with(|buf| buf.get());

longjmp(jmp_buf as *mut c_void, 0)
}

pub fn call_protected<T>(handler_data: &HandlerData, f: impl FnOnce() -> T) -> RuntimeResult<T> {
unsafe {
let jmp_buf = SETJMP_BUFFER.with(|buf| buf.get());
Expand All @@ -72,54 +78,59 @@ pub fn call_protected<T>(handler_data: &HandlerData, f: impl FnOnce() -> T) -> R
let signum = setjmp(jmp_buf as *mut _);
if signum != 0 {
*jmp_buf = prev_jmp_buf;
let (faulting_addr, inst_ptr) = CAUGHT_ADDRESSES.with(|cell| cell.get());

if let Some(TrapData {
trapcode,
srcloc: _,
}) = handler_data.lookup(inst_ptr)
{
Err(match Signal::from_c_int(signum) {
Ok(SIGILL) => match trapcode {
TrapCode::BadSignature => RuntimeError::IndirectCallSignature {
table: TableIndex::new(0),
},
TrapCode::IndirectCallToNull => RuntimeError::IndirectCallToNull {
table: TableIndex::new(0),

if let Some(msg) = super::TRAP_EARLY_DATA.with(|cell| cell.replace(None)) {
Err(RuntimeError::User { msg })
} else {
let (faulting_addr, inst_ptr) = CAUGHT_ADDRESSES.with(|cell| cell.get());

if let Some(TrapData {
trapcode,
srcloc: _,
}) = handler_data.lookup(inst_ptr)
{
Err(match Signal::from_c_int(signum) {
Ok(SIGILL) => match trapcode {
TrapCode::BadSignature => RuntimeError::IndirectCallSignature {
table: TableIndex::new(0),
},
TrapCode::IndirectCallToNull => RuntimeError::IndirectCallToNull {
table: TableIndex::new(0),
},
TrapCode::HeapOutOfBounds => RuntimeError::OutOfBoundsAccess {
memory: MemoryIndex::new(0),
addr: None,
},
TrapCode::TableOutOfBounds => RuntimeError::TableOutOfBounds {
table: TableIndex::new(0),
},
_ => RuntimeError::Unknown {
msg: "unknown trap".to_string(),
},
},
TrapCode::HeapOutOfBounds => RuntimeError::OutOfBoundsAccess {
Ok(SIGSEGV) | Ok(SIGBUS) => RuntimeError::OutOfBoundsAccess {
memory: MemoryIndex::new(0),
addr: None,
},
TrapCode::TableOutOfBounds => RuntimeError::TableOutOfBounds {
table: TableIndex::new(0),
},
_ => RuntimeError::Unknown {
msg: "unknown trap".to_string(),
},
},
Ok(SIGSEGV) | Ok(SIGBUS) => RuntimeError::OutOfBoundsAccess {
memory: MemoryIndex::new(0),
addr: None,
},
Ok(SIGFPE) => RuntimeError::IllegalArithmeticOperation,
_ => unimplemented!(),
}
.into())
} else {
let signal = match Signal::from_c_int(signum) {
Ok(SIGFPE) => "floating-point exception",
Ok(SIGILL) => "illegal instruction",
Ok(SIGSEGV) => "segmentation violation",
Ok(SIGBUS) => "bus error",
Err(_) => "error while getting the Signal",
_ => "unkown trapped signal",
};
// When the trap-handler is fully implemented, this will return more information.
Err(RuntimeError::Unknown {
msg: format!("trap at {:p} - {}", faulting_addr, signal),
Ok(SIGFPE) => RuntimeError::IllegalArithmeticOperation,
_ => unimplemented!(),
}
.into())
} else {
let signal = match Signal::from_c_int(signum) {
Ok(SIGFPE) => "floating-point exception",
Ok(SIGILL) => "illegal instruction",
Ok(SIGSEGV) => "segmentation violation",
Ok(SIGBUS) => "bus error",
Err(_) => "error while getting the Signal",
_ => "unkown trapped signal",
};
// When the trap-handler is fully implemented, this will return more information.
Err(RuntimeError::Unknown {
msg: format!("trap at {:p} - {}", faulting_addr, signal),
}
.into())
}
.into())
}
} else {
let ret = f(); // TODO: Switch stack?
Expand Down
6 changes: 6 additions & 0 deletions lib/runtime-core/src/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,12 @@ pub trait ProtectedCaller: Send + Sync {
vmctx: *mut vm::Ctx,
_: Token,
) -> RuntimeResult<Vec<Value>>;

fn get_early_trapper(&self) -> Box<dyn UserTrapper>;
}

pub trait UserTrapper {
unsafe fn do_early_trap(&self, msg: String) -> !;
}

pub trait FuncResolver: Send + Sync {
Expand Down
3 changes: 3 additions & 0 deletions lib/runtime-core/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,9 @@ pub enum RuntimeError {
table: TableIndex,
},
IllegalArithmeticOperation,
User {
msg: String,
},
Unknown {
msg: String,
},
Expand Down
1 change: 0 additions & 1 deletion lib/runtime-core/src/instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ use crate::{
import::{ImportObject, LikeNamespace},
memory::Memory,
module::{ExportIndex, Module, ModuleInner},
sig_registry::SigRegistry,
table::Table,
typed_func::{Func, Safe, WasmTypeList},
types::{FuncIndex, FuncSig, GlobalIndex, LocalOrImport, MemoryIndex, TableIndex, Value},
Expand Down
5 changes: 5 additions & 0 deletions lib/runtime-core/src/module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use crate::{
error::Result,
import::ImportObject,
structures::{Map, TypedIndex},
typed_func::EARLY_TRAPPER,
types::{
FuncIndex, FuncSig, GlobalDescriptor, GlobalIndex, GlobalInit, ImportedFuncIndex,
ImportedGlobalIndex, ImportedMemoryIndex, ImportedTableIndex, Initializer,
Expand Down Expand Up @@ -63,6 +64,10 @@ pub struct Module(#[doc(hidden)] pub Arc<ModuleInner>);

impl Module {
pub(crate) fn new(inner: Arc<ModuleInner>) -> Self {
unsafe {
EARLY_TRAPPER
.with(|ucell| *ucell.get() = Some(inner.protected_caller.get_early_trapper()));
}
Module(inner)
}

Expand Down
79 changes: 71 additions & 8 deletions lib/runtime-core/src/typed_func.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
use crate::{
backend::UserTrapper,
error::RuntimeError,
export::{Context, Export, FuncPointer},
import::IsExport,
types::{FuncSig, Type, WasmExternType},
vm::Ctx,
};
use std::{marker::PhantomData, mem, ptr, sync::Arc};
use std::{cell::UnsafeCell, fmt, marker::PhantomData, mem, panic, ptr, sync::Arc};

thread_local! {
pub static EARLY_TRAPPER: UnsafeCell<Option<Box<dyn UserTrapper>>> = UnsafeCell::new(None);
}

pub trait Safeness {}
pub struct Safe;
Expand All @@ -28,9 +33,44 @@ where
Args: WasmTypeList,
Rets: WasmTypeList,
{
fn to_raw(self) -> *const ();
fn to_raw(&self) -> *const ();
}

pub trait TrapEarly<Rets>
where
Rets: WasmTypeList,
{
fn report(self) -> Result<Rets, String>;
}

impl<Rets> TrapEarly<Rets> for Rets
where
Rets: WasmTypeList,
{
fn report(self) -> Result<Rets, String> {
Ok(self)
}
}

impl<Rets, E> TrapEarly<Rets> for Result<Rets, E>
where
Rets: WasmTypeList,
E: fmt::Debug,
{
fn report(self) -> Result<Rets, String> {
self.map_err(|err| format!("Error: {:?}", err))
}
}

// pub fn Func<'a, Args, Rets, F>(f: F) -> Func<'a, Args, Rets, Unsafe>
// where
// Args: WasmTypeList,
// Rets: WasmTypeList,
// F: ExternalFunction<Args, Rets>
// {
// Func::new(f)
// }

pub struct Func<'a, Args = (), Rets = (), Safety: Safeness = Safe> {
f: *const (),
ctx: *mut Ctx,
Expand Down Expand Up @@ -143,18 +183,41 @@ macro_rules! impl_traits {
}
}

impl< $( $x: WasmExternType, )* Rets: WasmTypeList, FN: Fn( $( $x, )* &mut Ctx) -> Rets> ExternalFunction<($( $x ),*), Rets> for FN {
impl< $( $x: WasmExternType, )* Rets: WasmTypeList, Trap: TrapEarly<Rets>, FN: Fn( $( $x, )* &mut Ctx) -> Trap> ExternalFunction<($( $x ),*), Rets> for FN {
#[allow(non_snake_case)]
fn to_raw(self) -> *const () {
fn to_raw(&self) -> *const () {
assert_eq!(mem::size_of::<Self>(), 0, "you cannot use a closure that captures state for `Func`.");

extern fn wrap<$( $x: WasmExternType, )* Rets: WasmTypeList, FN: Fn( $( $x, )* &mut Ctx) -> Rets>( $( $x: $x, )* ctx: &mut Ctx) -> Rets::CStruct {
extern fn wrap<$( $x: WasmExternType, )* Rets: WasmTypeList, Trap: TrapEarly<Rets>, FN: Fn( $( $x, )* &mut Ctx) -> Trap>( $( $x: $x, )* ctx: &mut Ctx) -> Rets::CStruct {
let f: FN = unsafe { mem::transmute_copy(&()) };
let rets = f( $( $x, )* ctx);
rets.into_c_struct()

let msg = match panic::catch_unwind(panic::AssertUnwindSafe(|| {
f( $( $x, )* ctx).report()
})) {
Ok(Ok(returns)) => return returns.into_c_struct(),
Ok(Err(err)) => err,
Err(err) => {
if let Some(s) = err.downcast_ref::<&str>() {
s.to_string()
} else if let Some(s) = err.downcast_ref::<String>() {
s.clone()
} else {
"a panic occurred, but no additional information is available".to_string()
}
},
};

unsafe {
if let Some(early_trapper) = &*EARLY_TRAPPER.with(|ucell| ucell.get()) {
early_trapper.do_early_trap(msg)
} else {
eprintln!("panic handling not setup");
std::process::exit(1)
}
}
}

wrap::<$( $x, )* Rets, Self> as *const ()
wrap::<$( $x, )* Rets, Trap, Self> as *const ()
}
}

Expand Down
5 changes: 4 additions & 1 deletion lib/runtime-core/src/vm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -493,7 +493,7 @@ mod vm_ctx_tests {

fn generate_module() -> ModuleInner {
use super::Func;
use crate::backend::{Backend, FuncResolver, ProtectedCaller, Token};
use crate::backend::{Backend, FuncResolver, ProtectedCaller, Token, UserTrapper};
use crate::error::RuntimeResult;
use crate::types::{FuncIndex, LocalFuncIndex, Value};
use hashbrown::HashMap;
Expand All @@ -520,6 +520,9 @@ mod vm_ctx_tests {
) -> RuntimeResult<Vec<Value>> {
Ok(vec![])
}
fn get_early_trapper(&self) -> Box<dyn UserTrapper> {
unimplemented!()
}
}

ModuleInner {
Expand Down
Loading