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

Add early exit example #1657

Merged
merged 5 commits into from
Sep 30, 2020
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions .tarpaulin.toml
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
[cranelift_coverage]
features = "cranelift,singlepass,llvm,test-no-traps,test-cranelift"
examples = ["early-exit", "engine-jit", "engine-native", "engine-headless", "cross-compilation", "compiler-cranelift", "exported-function", "wasi"]
release = true

[llvm_coverage]
features = "cranelift,singlepass,llvm,test-no-traps,test-llvm"
examples = ["compiler-llvm"]
release = true

[singlepass_coverage]
features = "cranelift,singlepass,llvm,test-no-traps,test-singlepass"
release = true

[feature_a_and_b_coverage]
features = "feature_a feature_b"
examples = ["compiler-singlepass"]
release = true

[report]
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Changelog

## **[Unreleased]**
- [#1657](https://github.com/wasmerio/wasmer/pull/1657) Implement `wasm_trap_t` and `wasm_frame_t` for Wasm C API; add examples in Rust and C of exiting early with a host function.
- [#1645](https://github.com/wasmerio/wasmer/pull/1645) Move the install script to https://github.com/wasmerio/wasmer-install

## 1.0.0-alpha3 - 2020-09-14
Expand Down
5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,11 @@ test-no-traps = ["wasmer-wast/test-no-traps"]
name = "static_and_dynamic_functions"
harness = false

[[example]]
name = "early-exit"
path = "examples/early_exit.rs"
required-features = ["cranelift"]

[[example]]
name = "engine-jit"
path = "examples/engine_jit.rs"
Expand Down
82 changes: 82 additions & 0 deletions examples/early_exit.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
//! This example shows how the host can terminate execution of Wasm early from
//! inside a host function called by the Wasm.
use anyhow::bail;
use std::fmt;
use wasmer::{imports, wat2wasm, Function, Instance, Module, NativeFunc, RuntimeError, Store};
use wasmer_compiler_cranelift::Cranelift;
use wasmer_engine_jit::JIT;

// First we need to create an error type that we'll use to signal the end of execution.
#[derive(Debug, Clone, Copy)]
struct ExitCode(u32);

// This type must implement `std::error::Error` so we must also implement `std::fmt::Display` for it.
impl fmt::Display for ExitCode {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.0)
}
}

// And then we implement `std::error::Error`.
impl std::error::Error for ExitCode {}

// The host function that we'll use to terminate execution.
fn early_exit() {
// This is where it happens.
RuntimeError::raise(Box::new(ExitCode(1)));
}

fn main() -> anyhow::Result<()> {
// Let's declare the Wasm module with the text representation.
let wasm_bytes = wat2wasm(
br#"
(module
(type $run_t (func (param i32 i32) (result i32)))
(type $early_exit_t (func (param) (result)))
(import "env" "early_exit" (func $early_exit (type $early_exit_t)))
(func $run (type $run_t) (param $x i32) (param $y i32) (result i32)
(call $early_exit)
(i32.add
local.get $x
local.get $y))
(export "run" (func $run)))
"#,
)?;

let store = Store::new(&JIT::new(&Cranelift::default()).engine());
let module = Module::new(&store, wasm_bytes)?;

let import_object = imports! {
"env" => {
"early_exit" => Function::new_native(&store, early_exit),
}
};
let instance = Instance::new(&module, &import_object)?;

// get the `run` function which we'll use as our entrypoint.
let run_func: NativeFunc<(i32, i32), i32> =
instance.exports.get_native_function("run").unwrap();

// When we call a function it can either succeed or fail.
match run_func.call(1, 7) {
Ok(result) => {
bail!(
"Expected early termination with `ExitCode`, found: {}",
result
);
}
// we're expecting it to fail.
MarkMcCaskey marked this conversation as resolved.
Show resolved Hide resolved
// we attempt to downcast the error into the error type that we were expecting.
MarkMcCaskey marked this conversation as resolved.
Show resolved Hide resolved
Err(e) => match e.downcast::<ExitCode>() {
// we found the exit code used to terminate execution.
MarkMcCaskey marked this conversation as resolved.
Show resolved Hide resolved
Ok(exit_code) => {
println!("Exited early with exit code: {}", exit_code);
Ok(())
}
Err(e) => {
bail!("Unknown error `{}` found. expected `ErrorCode`", e);
}
},
}
}
5 changes: 2 additions & 3 deletions examples/engine_cross_compilation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,15 @@ use wasmer_engine_native::Native;
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Let's declare the Wasm module with the text representation.
let wasm_bytes = wat2wasm(
r#"
br#"
(module
(type $sum_t (func (param i32 i32) (result i32)))
(func $sum_f (type $sum_t) (param $x i32) (param $y i32) (result i32)
local.get $x
local.get $y
i32.add)
(export "sum" (func $sum_f)))
"#
.as_bytes(),
"#,
)?;

// Define a compiler configuration.
Expand Down
6 changes: 3 additions & 3 deletions examples/wasi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ use wasmer_engine_jit::JIT;
use wasmer_wasi::WasiState;

fn main() -> Result<(), Box<dyn std::error::Error>> {
let wasm_path = format!(
"{}/tests/wasi-wast/wasi/unstable/hello.wasm",
std::env::var("CARGO_MANIFEST_DIR").unwrap()
let wasm_path = concat!(
env!("CARGO_MANIFEST_DIR"),
"/tests/wasi-wast/wasi/unstable/hello.wasm"
);
// Let's declare the Wasm module with the text representation.
let wasm_bytes = std::fs::read(wasm_path)?;
Expand Down
4 changes: 2 additions & 2 deletions lib/api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,8 @@ pub use wasmer_compiler::{
};
pub use wasmer_compiler::{CpuFeature, Features, Target};
pub use wasmer_engine::{
ChainableNamedResolver, DeserializeError, Engine, InstantiationError, LinkError, NamedResolver,
NamedResolverChain, Resolver, RuntimeError, SerializeError,
ChainableNamedResolver, DeserializeError, Engine, FrameInfo, InstantiationError, LinkError,
NamedResolver, NamedResolverChain, Resolver, RuntimeError, SerializeError,
};
pub use wasmer_types::{
Atomically, Bytes, GlobalInit, LocalFunctionIndex, MemoryView, Pages, ValueType,
Expand Down
137 changes: 107 additions & 30 deletions lib/c-api/src/wasm_c_api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ use crate::c_try;

use crate::ordered_resolver::OrderedResolver;
use wasmer::{
Engine, ExportType, Extern, ExternType, Function, FunctionType, Global, GlobalType, ImportType,
Instance, Memory, MemoryType, Module, Mutability, Pages, RuntimeError, Store, Table, TableType,
Val, ValType,
Engine, ExportType, Extern, ExternType, FrameInfo, Function, FunctionType, Global, GlobalType,
ImportType, Instance, Memory, MemoryType, Module, Mutability, Pages, RuntimeError, Store,
Table, TableType, Val, ValType,
};
#[cfg(feature = "jit")]
use wasmer_engine_jit::JIT;
Expand Down Expand Up @@ -712,7 +712,13 @@ pub unsafe extern "C" fn wasm_func_new(
num_rets
];

let _traps = callback(processed_args.as_ptr(), results.as_mut_ptr());
let trap = callback(processed_args.as_ptr(), results.as_mut_ptr());
dbg!(&trap);
MarkMcCaskey marked this conversation as resolved.
Show resolved Hide resolved
if !trap.is_null() {
let trap: Box<wasm_trap_t> = Box::from_raw(trap);
dbg!("raising trap!");
MarkMcCaskey marked this conversation as resolved.
Show resolved Hide resolved
RuntimeError::raise(Box::new(trap.inner));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optional: If wasm_trap_t was #repr transparent, we could avoid reboxing?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm, I don't know... I'm going to say probably not. repr(transparent) works at the ABI level whereas in order to be passed to RuntimeError::raise you have to implement std::error::Error at the type level. So with unsafe / C FFI everything would just work, but in safe Rust, I don't believe it would

}
// TODO: do something with `traps`

let processed_results = results
Expand Down Expand Up @@ -783,7 +789,7 @@ pub unsafe extern "C" fn wasm_func_call(
func: &wasm_func_t,
args: *const wasm_val_t,
results: *mut wasm_val_t,
) -> Option<NonNull<wasm_trap_t>> {
) -> Option<Box<wasm_trap_t>> {
let num_params = func.inner.ty().params().len();
let params: Vec<Val> = (0..num_params)
.map(|i| (&(*args.add(i))).try_into())
Expand All @@ -798,7 +804,7 @@ pub unsafe extern "C" fn wasm_func_call(
}
None
}
Err(e) => Some(NonNull::new_unchecked(Box::into_raw(Box::new(e)) as _)),
Err(e) => Some(Box::new(e.into())),
}
}

Expand Down Expand Up @@ -1150,31 +1156,48 @@ pub struct wasm_ref_t;

// opaque type which is a `RuntimeError`
#[repr(C)]
pub struct wasm_trap_t {}
pub struct wasm_trap_t {
inner: RuntimeError,
}

#[no_mangle]
pub unsafe extern "C" fn wasm_trap_delete(trap: Option<NonNull<wasm_trap_t>>) {
if let Some(t_inner) = trap {
let _ = Box::from_raw(t_inner.cast::<RuntimeError>().as_ptr());
impl From<RuntimeError> for wasm_trap_t {
fn from(other: RuntimeError) -> Self {
Self { inner: other }
}
}

#[no_mangle]
pub unsafe extern "C" fn wasm_trap_message(
trap: *const wasm_trap_t,
out_ptr: *mut wasm_byte_vec_t,
) {
let re = &*(trap as *const RuntimeError);
// this code assumes no nul bytes appear in the message
let mut message = format!("{}\0", re);
message.shrink_to_fit();
pub unsafe extern "C" fn wasm_trap_new(
_store: &mut wasm_store_t,
MarkMcCaskey marked this conversation as resolved.
Show resolved Hide resolved
message: &wasm_message_t,
) -> Option<Box<wasm_trap_t>> {
let message_bytes: &[u8] = message.into_slice()?;
let message_str = c_try!(std::str::from_utf8(message_bytes));
let runtime_error = RuntimeError::new(message_str);
let trap = runtime_error.into();

Some(Box::new(trap))
}

#[no_mangle]
pub unsafe extern "C" fn wasm_trap_delete(_trap: Option<Box<wasm_trap_t>>) {}

#[no_mangle]
pub unsafe extern "C" fn wasm_trap_message(trap: &wasm_trap_t, out_ptr: &mut wasm_byte_vec_t) {
let message = trap.inner.message();
out_ptr.size = message.len();
// TODO: make helper function for converting `Vec<T>` into owned `wasm_T_vec_t`
MarkMcCaskey marked this conversation as resolved.
Show resolved Hide resolved
let mut dupe_data: Box<[u8]> = message.into_bytes().into_boxed_slice();
out_ptr.data = dupe_data.as_mut_ptr();
mem::forget(dupe_data);
MarkMcCaskey marked this conversation as resolved.
Show resolved Hide resolved
}

// TODO use `String::into_raw_parts` when it gets stabilized
(*out_ptr).size = message.as_bytes().len();
(*out_ptr).data = message.as_mut_ptr();
mem::forget(message);
#[no_mangle]
pub unsafe extern "C" fn wasm_trap_origin(trap: &wasm_trap_t) -> Option<Box<wasm_frame_t>> {
trap.inner.trace().first().map(Into::into).map(Box::new)
}

// TODO: old comment, rm after finish implementation
MarkMcCaskey marked this conversation as resolved.
Show resolved Hide resolved
// in trap/RuntimeError we need to store
// 1. message
// 2. origin (frame); frame contains:
Expand All @@ -1183,11 +1206,19 @@ pub unsafe extern "C" fn wasm_trap_message(
// 3. module offset
// 4. which instance this was apart of

/*#[no_mangle]
pub unsafe extern "C" fn wasm_trap_trace(trap: *const wasm_trap_t, out_ptr: *mut wasm_frame_vec_t) {
let re = &*(trap as *const RuntimeError);
todo!()
}*/
#[no_mangle]
pub unsafe extern "C" fn wasm_trap_trace(trap: &wasm_trap_t, out_ptr: &mut wasm_frame_vec_t) {
let frames = trap.inner.trace();
out_ptr.size = frames.len();
// TODO: make helper function for converting `Vec<T>` into owned `wasm_T_vec_t`
MarkMcCaskey marked this conversation as resolved.
Show resolved Hide resolved
let mut dupe_data: Box<[wasm_frame_t]> = frames
.iter()
.map(Into::into)
.collect::<Vec<wasm_frame_t>>()
.into_boxed_slice();
out_ptr.data = dupe_data.as_mut_ptr();
mem::forget(dupe_data);
MarkMcCaskey marked this conversation as resolved.
Show resolved Hide resolved
}

#[repr(C)]
pub struct wasm_extern_t {
Expand Down Expand Up @@ -1543,9 +1574,52 @@ pub unsafe extern "C" fn wasm_functype_results(ft: &wasm_functype_t) -> *const w
out as *const _
}

#[derive(Debug)]
#[derive(Debug, Clone)]
#[repr(C)]
pub struct wasm_frame_t {}
pub struct wasm_frame_t {
info: FrameInfo,
}

impl<'a> From<&'a FrameInfo> for wasm_frame_t {
fn from(other: &'a FrameInfo) -> Self {
other.clone().into()
}
}

impl From<FrameInfo> for wasm_frame_t {
fn from(other: FrameInfo) -> Self {
Self { info: other }
}
}

#[no_mangle]
pub unsafe extern "C" fn wasm_frame_copy(frame: &wasm_frame_t) -> Box<wasm_frame_t> {
Box::new(frame.clone())
}

#[no_mangle]
pub unsafe extern "C" fn wasm_frame_delete(_frame: Option<Box<wasm_frame_t>>) {}

#[no_mangle]
pub unsafe extern "C" fn wasm_frame_instance(frame: &wasm_frame_t) -> *const wasm_instance_t {
//todo!("wasm_frame_instance")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Either this whole line should be removed or the // should be removed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, the idea was to stop it from crashing by commenting it out but it's still clearly unimplemented so I wanted it to be obvious that it's not really done

std::ptr::null()
}

#[no_mangle]
pub unsafe extern "C" fn wasm_frame_func_index(frame: &wasm_frame_t) -> u32 {
frame.info.func_index()
}

#[no_mangle]
pub unsafe extern "C" fn wasm_frame_func_offset(frame: &wasm_frame_t) -> usize {
frame.info.func_offset()
}

#[no_mangle]
pub unsafe extern "C" fn wasm_frame_module_offset(frame: &wasm_frame_t) -> usize {
frame.info.module_offset()
}

wasm_declare_vec!(frame);

Expand Down Expand Up @@ -1769,6 +1843,9 @@ pub unsafe extern "C" fn wasm_tabletype_as_externtype(
#[allow(non_camel_case_types)]
type wasm_name_t = wasm_byte_vec_t;

#[allow(non_camel_case_types)]
type wasm_message_t = wasm_byte_vec_t;

#[repr(C)]
#[allow(non_camel_case_types)]
pub struct wasm_exporttype_t {
Expand Down
Loading