Skip to content

Commit

Permalink
Change proc_exit to unwind instead of exit the host process.
Browse files Browse the repository at this point in the history
This implements the semantics in WebAssembly/WASI#235.

Fixes bytecodealliance#783.
Fixes bytecodealliance#993.
  • Loading branch information
sunfishcode committed May 1, 2020
1 parent 6dd85ef commit 9fe5fe9
Show file tree
Hide file tree
Showing 26 changed files with 400 additions and 135 deletions.
54 changes: 54 additions & 0 deletions crates/api/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
use crate::frame_info::FRAME_INFO;
use crate::{Exit, Trap};
use anyhow::Error;
use wasmtime_environ::ir::TrapCode;

/// Convert from an internal unwinding code into an `Error`.
pub(crate) fn from_runtime(jit: wasmtime_runtime::Trap) -> Error {
let info = FRAME_INFO.read().unwrap();
match jit {
wasmtime_runtime::Trap::User(error) => {
// Since we're the only one using the wasmtime internals (in
// theory) we should only see user errors which were originally
// created from our own `Trap` and `Exit` types (see the trampoline
// module with functions).
let e = match error.downcast::<Exit>() {
Ok(exit) => Error::new(exit),
Err(e) => match e.downcast::<wasmtime_runtime::InvalidWASIExitStatus>() {
Ok(invalid) => Error::new(invalid),
Err(e) => Error::new(dbg!(e
.downcast::<Trap>()
.expect("only `Trap` and `Exit` user errors are supported"))),
},
};
dbg!(&e);
dbg!(e.is::<Trap>());
e
}
wasmtime_runtime::Trap::Jit {
pc,
backtrace,
maybe_interrupted,
} => {
let mut code = info
.lookup_trap_info(pc)
.map(|info| info.trap_code)
.unwrap_or(TrapCode::StackOverflow);
if maybe_interrupted && code == TrapCode::StackOverflow {
code = TrapCode::Interrupt;
}
Error::new(Trap::new_wasm(&info, Some(pc), code, backtrace))
}
wasmtime_runtime::Trap::Wasm {
trap_code,
backtrace,
} => Error::new(Trap::new_wasm(&info, None, trap_code, backtrace)),
wasmtime_runtime::Trap::OOM { backtrace } => Error::new(Trap::new_with_trace(
&info,
None,
"out of memory".to_string(),
backtrace,
)),
wasmtime_runtime::Trap::Exit { status } => Error::new(Exit::new(status)),
}
}
53 changes: 53 additions & 0 deletions crates/api/src/exit.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
use std::fmt;
use std::num::NonZeroI32;

/// A struct representing an explicit program exit, with a code
/// indicating the status.
#[derive(Clone, Debug)]
pub struct Exit {
status: NonZeroI32,
}

impl Exit {
/// Creates a new `Exit` with `status`. The status value must be in the range [1..126].
/// # Example
/// ```
/// let exit = wasmtime::Exit::new(NonZeroI32::new(1).unwrap());
/// assert_eq!(1, trap.status());
/// ```
pub fn new(status: NonZeroI32) -> Self {
assert!(status.get() > 0 && status.get() < 126);
Self { status }
}

/// Returns a reference the `status` stored in `Exit`.
pub fn status(&self) -> NonZeroI32 {
self.status
}
}

impl fmt::Display for Exit {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// Error codes traditionally defined in <sysexits.h>, though somewhat generalized.
match self.status.get() {
64 => write!(f, "command line usage error"),
65 => write!(f, "input data format error"),
66 => write!(f, "inadequate input"),
67 => write!(f, "named identity not recognized"),
68 => write!(f, "named resource could not be located"),
69 => write!(f, "service unavailable"),
70 => write!(f, "internal software error"),
71 => write!(f, "system error"),
72 => write!(f, "unexpected host environment configuration"),
73 => write!(f, "inadequate output"),
74 => write!(f, "input/output error"),
75 => write!(f, "temporary failure; user is invited to retry"),
76 => write!(f, "interactive protocol error"),
77 => write!(f, "permission denied"),
78 => write!(f, "application configuration error"),
_ => write!(f, "non-zero status {}", self.status),
}
}
}

impl std::error::Error for Exit {}
4 changes: 2 additions & 2 deletions crates/api/src/externals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ use crate::trampoline::{
generate_global_export, generate_memory_export, generate_table_export, StoreInstanceHandle,
};
use crate::values::{from_checked_anyfunc, into_checked_anyfunc, Val};
use crate::{error, Func, Store};
use crate::{ExternType, GlobalType, MemoryType, Mutability, TableType, ValType};
use crate::{Func, Store, Trap};
use anyhow::{anyhow, bail, Result};
use std::slice;
use wasmtime_environ::wasm;
Expand Down Expand Up @@ -419,7 +419,7 @@ impl Table {
let src_table = src_table.instance.get_defined_table(src_table_index);

runtime::Table::copy(dst_table, src_table, dst_index, src_index, len)
.map_err(Trap::from_jit)?;
.map_err(error::from_runtime)?;
Ok(())
}

Expand Down
72 changes: 56 additions & 16 deletions crates/api/src/func.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
use crate::runtime::StoreInner;
use crate::trampoline::StoreInstanceHandle;
use crate::{Extern, FuncType, Memory, Store, Trap, Val, ValType};
use crate::{error, Extern, FuncType, Memory, Store, Trap, Val, ValType};
use anyhow::{bail, ensure, Context as _, Result};
use std::cmp::max;
use std::fmt;
use std::mem;
use std::panic::{self, AssertUnwindSafe};
use std::ptr;
use std::rc::Weak;
use wasmtime_runtime::{raise_user_trap, wasi_proc_exit, ExportFunction, VMTrampoline};
use wasmtime_runtime::{Export, InstanceHandle, VMContext, VMFunctionBody};
use wasmtime_runtime::{ExportFunction, VMTrampoline};

/// A WebAssembly function which can be called.
///
Expand Down Expand Up @@ -151,7 +151,7 @@ macro_rules! getters {
$(#[$doc])*
#[allow(non_snake_case)]
pub fn $name<$($args,)* R>(&self)
-> anyhow::Result<impl Fn($($args,)*) -> Result<R, Trap>>
-> anyhow::Result<impl Fn($($args,)*) -> Result<R>>
where
$($args: WasmTy,)*
R: WasmTy,
Expand Down Expand Up @@ -181,7 +181,7 @@ macro_rules! getters {

// ... and then once we've passed the typechecks we can hand out our
// object since our `transmute` below should be safe!
Ok(move |$($args: $args),*| -> Result<R, Trap> {
Ok(move |$($args: $args),*| -> Result<R> {
unsafe {
let fnptr = mem::transmute::<
*const VMFunctionBody,
Expand Down Expand Up @@ -474,6 +474,46 @@ impl Func {
func.into_func(store)
}

/// Creates a new `Func` for a function which performs a program exit,
/// unwinding the stack up the point where wasm was most recently entered.
pub fn exit_func(store: &Store) -> Func {
unsafe extern "C" fn trampoline(
callee_vmctx: *mut VMContext,
caller_vmctx: *mut VMContext,
ptr: *const VMFunctionBody,
args: *mut u128,
) {
let ptr = mem::transmute::<
*const VMFunctionBody,
unsafe extern "C" fn(*mut VMContext, *mut VMContext, i32) -> !,
>(ptr);

let mut next = args as *const u128;
let status = i32::load(&mut next);
ptr(callee_vmctx, caller_vmctx, status)
}

let mut args = Vec::new();
<i32 as WasmTy>::push(&mut args);
let ret = Vec::new();
let ty = FuncType::new(args.into(), ret.into());
let (instance, export) = unsafe {
crate::trampoline::generate_raw_func_export(
&ty,
std::slice::from_raw_parts_mut(wasi_proc_exit as *mut _, 0),
trampoline,
store,
Box::new(()),
)
.expect("failed to generate export")
};
Func {
instance,
export,
trampoline,
}
}

/// Returns the underlying wasm type that this `Func` has.
pub fn ty(&self) -> FuncType {
// Signatures should always be registered in the store's registry of
Expand Down Expand Up @@ -737,7 +777,7 @@ pub(crate) fn catch_traps(
vmctx: *mut VMContext,
store: &Store,
closure: impl FnMut(),
) -> Result<(), Trap> {
) -> Result<()> {
let signalhandler = store.signal_handler();
unsafe {
wasmtime_runtime::catch_traps(
Expand All @@ -747,7 +787,7 @@ pub(crate) fn catch_traps(
signalhandler.as_deref(),
closure,
)
.map_err(Trap::from_jit)
.map_err(error::from_runtime)
}
}

Expand Down Expand Up @@ -941,7 +981,7 @@ unsafe impl<T: WasmTy> WasmRet for Result<T, Trap> {
}

fn handle_trap(trap: Trap) -> ! {
unsafe { wasmtime_runtime::raise_user_trap(Box::new(trap)) }
unsafe { raise_user_trap(Box::new(trap)) }
}
}

Expand Down Expand Up @@ -1133,9 +1173,9 @@ macro_rules! impl_into_func {
R::push(&mut ret);
let ty = FuncType::new(_args.into(), ret.into());
let store_weak = store.weak();
unsafe {
let trampoline = trampoline::<$($args,)* R>;
let (instance, export) = crate::trampoline::generate_raw_func_export(
let trampoline = trampoline::<$($args,)* R>;
let (instance, export) = unsafe {
crate::trampoline::generate_raw_func_export(
&ty,
std::slice::from_raw_parts_mut(
shim::<F, $($args,)* R> as *mut _,
Expand All @@ -1145,12 +1185,12 @@ macro_rules! impl_into_func {
store,
Box::new((self, store_weak)),
)
.expect("failed to generate export");
Func {
instance,
export,
trampoline,
}
.expect("failed to generate export")
};
Func {
instance,
export,
trampoline,
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions crates/api/src/instance.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::trampoline::StoreInstanceHandle;
use crate::{Export, Extern, Func, Global, Memory, Module, Store, Table, Trap};
use crate::{error, Export, Extern, Func, Global, Memory, Module, Store, Table};
use anyhow::{bail, Error, Result};
use std::any::Any;
use std::mem;
Expand Down Expand Up @@ -52,7 +52,7 @@ fn instantiate(
.map_err(|e| -> Error {
match e {
InstantiationError::StartTrap(trap) | InstantiationError::Trap(trap) => {
Trap::from_jit(trap).into()
error::from_runtime(trap).into()
}
other => other.into(),
}
Expand Down
3 changes: 3 additions & 0 deletions crates/api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
#![doc(test(attr(deny(warnings))))]
#![doc(test(attr(allow(dead_code, unused_variables, unused_mut))))]

mod error;
mod exit;
mod externals;
mod frame_info;
mod func;
Expand All @@ -23,6 +25,7 @@ mod trap;
mod types;
mod values;

pub use crate::exit::Exit;
pub use crate::externals::*;
pub use crate::frame_info::FrameInfo;
pub use crate::func::*;
Expand Down
2 changes: 1 addition & 1 deletion crates/api/src/trampoline/func.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ unsafe extern "C" fn stub_fn(

// If a trap was raised (an error returned from the imported function)
// then we smuggle the trap through `Box<dyn Error>` through to the
// call-site, which gets unwrapped in `Trap::from_jit` later on as we
// call-site, which gets unwrapped in `error::from_runtime` later on as we
// convert from the internal `Trap` type to our own `Trap` type in this
// crate.
Ok(Err(trap)) => wasmtime_runtime::raise_user_trap(Box::new(trap)),
Expand Down
44 changes: 2 additions & 42 deletions crates/api/src/trap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,47 +34,7 @@ impl Trap {
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
// theory) we should only see user errors which were originally
// created from our own `Trap` type (see the trampoline module
// with functions).
//
// If this unwrap trips for someone we'll need to tweak the
// return type of this function to probably be `anyhow::Error`
// or something like that.
*error
.downcast()
.expect("only `Trap` user errors are supported")
}
wasmtime_runtime::Trap::Jit {
pc,
backtrace,
maybe_interrupted,
} => {
let mut code = info
.lookup_trap_info(pc)
.map(|info| info.trap_code)
.unwrap_or(TrapCode::StackOverflow);
if maybe_interrupted && code == TrapCode::StackOverflow {
code = TrapCode::Interrupt;
}
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(&info, None, "out of memory".to_string(), backtrace)
}
}
}

fn new_wasm(
pub(crate) fn new_wasm(
info: &GlobalFrameInfo,
trap_pc: Option<usize>,
code: TrapCode,
Expand All @@ -98,7 +58,7 @@ impl Trap {
Trap::new_with_trace(info, trap_pc, msg, backtrace)
}

fn new_with_trace(
pub(crate) fn new_with_trace(
info: &GlobalFrameInfo,
trap_pc: Option<usize>,
message: String,
Expand Down
3 changes: 2 additions & 1 deletion crates/runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ pub use crate::mmap::Mmap;
pub use crate::sig_registry::SignatureRegistry;
pub use crate::table::Table;
pub use crate::traphandlers::{
catch_traps, raise_lib_trap, raise_user_trap, resume_panic, SignalHandler, Trap,
catch_traps, raise_lib_trap, raise_user_trap, resume_panic, wasi_proc_exit,
InvalidWASIExitStatus, SignalHandler, Trap,
};
pub use crate::vmcontext::{
VMCallerCheckedAnyfunc, VMContext, VMFunctionBody, VMFunctionImport, VMGlobalDefinition,
Expand Down
Loading

0 comments on commit 9fe5fe9

Please sign in to comment.