Skip to content

Commit

Permalink
Refactor Trap to be of pointer size (#406)
Browse files Browse the repository at this point in the history
* refactor Trap to be of pointer size

* fix compile errors by introducing a proper getter API for host errors
  • Loading branch information
Robbepop committed Aug 12, 2022
1 parent 4d1f2ad commit 4a5d113
Show file tree
Hide file tree
Showing 4 changed files with 131 additions and 27 deletions.
5 changes: 3 additions & 2 deletions core/src/host_error.rs
Expand Up @@ -37,7 +37,8 @@ use downcast_rs::{impl_downcast, DowncastSync};
///
/// // Get a reference to the concrete error
/// match failable_fn() {
/// Err(Trap::Host(host_error)) => {
/// Err(trap) if trap.is_host() => {
/// let host_error = trap.as_host().unwrap();
/// let my_error: &MyError = host_error.downcast_ref::<MyError>().unwrap();
/// assert_eq!(my_error.code, 1312);
/// }
Expand All @@ -48,7 +49,7 @@ use downcast_rs::{impl_downcast, DowncastSync};
/// match failable_fn() {
/// Err(err) => {
/// let my_error = match err {
/// Trap::Host(host_error) => host_error.downcast::<MyError>().unwrap(),
/// trap if trap.is_host() => trap.into_host().unwrap().downcast::<MyError>().unwrap(),
/// unexpected => panic!("expected host error but found: {}", unexpected),
/// };
/// assert_eq!(my_error.code, 1312);
Expand Down
131 changes: 115 additions & 16 deletions core/src/trap.rs
Expand Up @@ -10,43 +10,136 @@ use std::error::Error as StdError;
/// Under some conditions, wasm execution may produce a `Trap`, which immediately aborts execution.
/// Traps can't be handled by WebAssembly code, but are reported to the embedder.
#[derive(Debug)]
pub enum Trap {
pub struct Trap {
/// The internal data structure of a [`Trap`].
inner: Box<TrapInner>,
}

#[test]
fn trap_size() {
assert_eq!(
core::mem::size_of::<Trap>(),
core::mem::size_of::<*const ()>()
);
}

/// The internal of a [`Trap`].
#[derive(Debug)]
enum TrapInner {
/// Traps during Wasm execution.
Code(TrapCode),
/// Traps and errors during host execution.
Host(Box<dyn HostError>),
}

impl TrapInner {
/// Returns `true` if `self` trap originating from host code.
#[inline]
pub fn is_host(&self) -> bool {
matches!(self, TrapInner::Host(_))
}

/// Returns `true` if `self` trap originating from Wasm code.
#[inline]
pub fn is_code(&self) -> bool {
matches!(self, TrapInner::Code(_))
}

/// Returns a shared reference to the [`HostError`] if any.
#[inline]
pub fn as_host(&self) -> Option<&dyn HostError> {
if let Self::Host(host_error) = self {
return Some(&**host_error);
}
None
}

/// Returns an exclusive reference to the [`HostError`] if any.
#[inline]
pub fn as_host_mut(&mut self) -> Option<&mut dyn HostError> {
if let Self::Host(host_error) = self {
return Some(&mut **host_error);
}
None
}

/// Converts into the [`HostError`] if any.
#[inline]
pub fn into_host(self) -> Option<Box<dyn HostError>> {
if let Self::Host(host_error) = self {
return Some(host_error);
}
None
}

/// Returns the [`TrapCode`] traps originating from Wasm execution.
#[inline]
pub fn as_code(&self) -> Option<TrapCode> {
if let Self::Code(trap_code) = self {
return Some(*trap_code);
}
None
}
}

impl Trap {
/// Create a new [`Trap`] from the [`TrapInner`].
fn new(inner: TrapInner) -> Self {
Self {
inner: Box::new(inner),
}
}

/// Wraps the host error in a [`Trap`].
#[inline]
#[cold]
pub fn host<U>(host_error: U) -> Self
where
U: HostError + Sized,
{
Self::Host(Box::new(host_error))
Self::new(TrapInner::Host(Box::new(host_error)))
}

/// Returns `true` if `self` trap originating from host code.
#[inline]
pub fn is_host(&self) -> bool {
matches!(self, Self::Host(_))
self.inner.is_host()
}

/// Returns `true` if `self` trap originating from Wasm code.
#[inline]
pub fn is_code(&self) -> bool {
self.inner.is_code()
}

/// Returns a shared reference to the [`HostError`] if any.
#[inline]
pub fn as_host(&self) -> Option<&dyn HostError> {
self.inner.as_host()
}

/// Returns an exclusive reference to the [`HostError`] if any.
#[inline]
pub fn as_host_mut(&mut self) -> Option<&mut dyn HostError> {
self.inner.as_host_mut()
}

/// Converts into the [`HostError`] if any.
#[inline]
pub fn into_host(self) -> Option<Box<dyn HostError>> {
self.inner.into_host()
}

/// Returns the [`TrapCode`] traps originating from Wasm execution.
#[inline]
pub fn code(&self) -> Option<TrapCode> {
if let Self::Code(trap_code) = self {
return Some(*trap_code);
}
None
pub fn as_code(&self) -> Option<TrapCode> {
self.inner.as_code()
}
}

impl From<TrapCode> for Trap {
#[inline]
#[cold]
fn from(error: TrapCode) -> Self {
Self::Code(error)
Self::new(TrapInner::Code(error))
}
}

Expand All @@ -56,23 +149,29 @@ where
{
#[inline]
fn from(e: U) -> Self {
Trap::host(e)
Self::host(e)
}
}

impl Display for Trap {
impl Display for TrapInner {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Trap::Code(trap_code) => Display::fmt(trap_code, f),
Trap::Host(host_error) => Display::fmt(host_error, f),
Self::Code(trap_code) => Display::fmt(trap_code, f),
Self::Host(host_error) => Display::fmt(host_error, f),
}
}
}

impl Display for Trap {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
<TrapInner as Display>::fmt(&self.inner, f)
}
}

#[cfg(feature = "std")]
impl StdError for Trap {
fn description(&self) -> &str {
self.code().map(|code| code.trap_message()).unwrap_or("")
self.as_code().map(|code| code.trap_message()).unwrap_or("")
}
}

Expand Down
15 changes: 9 additions & 6 deletions wasmi_v1/benches/benches.rs
Expand Up @@ -10,7 +10,7 @@ use self::bench::{
use criterion::{criterion_group, criterion_main, Bencher, Criterion};
use std::{slice, time::Duration};
use wasmi as v1;
use wasmi::core::{Trap, Value};
use wasmi::core::Value;

const WASM_KERNEL: &str =
"benches/wasm/wasm_kernel/target/wasm32-unknown-unknown/release/wasm_kernel.wasm";
Expand Down Expand Up @@ -341,15 +341,18 @@ fn bench_execute_recursive_trap_v1(c: &mut Criterion) {
.and_then(v1::Extern::into_func)
.unwrap();
let mut result = [Value::I32(0)];

b.iter(|| {
let error = bench_call
.call(&mut store, &[Value::I32(1000)], &mut result)
.unwrap_err();
assert!(matches!(
error,
v1::Error::Trap(Trap::Code(v1::core::TrapCode::Unreachable))
));
match error {
v1::Error::Trap(trap) => assert_matches::assert_matches!(
trap.as_code(),
Some(v1::core::TrapCode::Unreachable),
"expected unreachable trap",
),
_ => panic!("expected unreachable trap"),
}
})
});
}
Expand Down
7 changes: 4 additions & 3 deletions wasmi_v1/tests/spec/run.rs
@@ -1,7 +1,7 @@
use super::{error::TestError, TestContext, TestDescriptor};
use anyhow::Result;
use wasmi::{Config, Error as WasmiError};
use wasmi_core::{Trap, Value, F32, F64};
use wasmi_core::{Value, F32, F64};
use wast::{
lexer::Lexer,
parser::ParseBuffer,
Expand Down Expand Up @@ -200,9 +200,10 @@ fn execute_directives(wast: Wast, test_context: &mut TestContext) -> Result<()>
/// - If the trap message of the `error` is not as expected.
fn assert_trap(test_context: &TestContext, span: Span, error: TestError, message: &str) {
match error {
TestError::Wasmi(WasmiError::Trap(Trap::Code(trap_code))) => {
TestError::Wasmi(WasmiError::Trap(trap)) if trap.is_code() => {
let code = trap.as_code().unwrap();
assert_eq!(
trap_code.trap_message(),
code.trap_message(),
message,
"{}: the directive trapped as expected but with an unexpected message",
test_context.spanned(span),
Expand Down

0 comments on commit 4a5d113

Please sign in to comment.