Skip to content

Commit

Permalink
Merge #917
Browse files Browse the repository at this point in the history
917: feat(runtime-core) Host function without a `vm::Ctx` argument r=Hywan a=Hywan

Extracted from #882.
~Includes #916. Must not be merged until #916 lands in `master`.~
~If you want to compare only the different commits, see https://github.com/Hywan/wasmer/compare/feat-runtime-core-tests...Hywan:feat-runtime-core-host-function-without-vmctx?expand=1.~

This patch updates the `ExternalFunction` implementation to support host functions (aka imported functions) without having an explicit `vm::Ctx` argument.

It is thus possible to write:

```rust
fn add_one(x: i32) -> i32 {
    x + 1
}

let import_object = imports! {
    "env" => {
        "add_one" => func!(add_one);
    },
};
```

The previous form is still working though:

```rust
fn add_one(_: &mut vm::Ctx, x: i32) -> i32 {
    x + 1
}
```

The backends aren't modified. It means that the pointer to `vm::Ctx` is still inserted in the stack, but it is not used when the function doesn't need it.

We thought this is an acceptable trade-off.

About the C API. It has not check to ensure the type signature. Since the pointer to `vm::Ctx` is always inserted, the C API can digest host functions with or without a `vm::Ctx` argument. The C API uses [the `Export` + `FuncPointer` API](https://github.com/wasmerio/wasmer/blob/cf5b3cc09eff149ce415bd81846d84b2d2cdf3a3/lib/runtime-c-api/src/import/mod.rs#L630-L653) instead of going through the `Func` API (which is modified by this API), which is only possible since the C API only receives an opaque function pointer. Conclusion is: We can't ensure anything on the C side, but it will work with or without the `vm::Ctx` argument in theory.

Co-authored-by: Ivan Enderlin <[email protected]>
  • Loading branch information
bors[bot] and Hywan authored Oct 31, 2019
2 parents fb0615d + 912eb32 commit 656491a
Show file tree
Hide file tree
Showing 3 changed files with 187 additions and 22 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

25 changes: 25 additions & 0 deletions lib/runtime-core-tests/tests/imports.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,19 @@ fn imported_functions_forms() {
(module
(type $type (func (param i32) (result i32)))
(import "env" "memory" (memory 1 1))
(import "env" "callback_fn" (func $callback_fn (type $type)))
(import "env" "callback_fn_with_vmctx" (func $callback_fn_with_vmctx (type $type)))
(import "env" "callback_fn_trap" (func $callback_fn_trap (type $type)))
(import "env" "callback_fn_trap_with_vmctx" (func $callback_fn_trap_with_vmctx (type $type)))
(func (export "function_fn") (type $type)
get_local 0
call $callback_fn)
(func (export "function_fn_with_vmctx") (type $type)
get_local 0
call $callback_fn_with_vmctx)
(func (export "function_fn_trap") (type $type)
get_local 0
call $callback_fn_trap)
(func (export "function_fn_trap_with_vmctx") (type $type)
get_local 0
call $callback_fn_trap_with_vmctx))
Expand All @@ -31,7 +39,9 @@ fn imported_functions_forms() {
let import_object = imports! {
"env" => {
"memory" => memory.clone(),
"callback_fn" => Func::new(callback_fn),
"callback_fn_with_vmctx" => Func::new(callback_fn_with_vmctx),
"callback_fn_trap" => Func::new(callback_fn_trap),
"callback_fn_trap_with_vmctx" => Func::new(callback_fn_trap_with_vmctx),
},
};
Expand Down Expand Up @@ -88,7 +98,14 @@ fn imported_functions_forms() {
};
}

call_and_assert!(function_fn, Ok(2));
call_and_assert!(function_fn_with_vmctx, Ok(2 + SHIFT));
call_and_assert!(
function_fn_trap,
Err(RuntimeError::Error {
data: Box::new(format!("foo {}", 1))
})
);
call_and_assert!(
function_fn_trap_with_vmctx,
Err(RuntimeError::Error {
Expand All @@ -97,13 +114,21 @@ fn imported_functions_forms() {
);
}

fn callback_fn(n: i32) -> Result<i32, ()> {
Ok(n + 1)
}

fn callback_fn_with_vmctx(vmctx: &mut vm::Ctx, n: i32) -> Result<i32, ()> {
let memory = vmctx.memory(0);
let shift: i32 = memory.view()[0].get();

Ok(shift + n + 1)
}

fn callback_fn_trap(n: i32) -> Result<i32, String> {
Err(format!("foo {}", n))
}

fn callback_fn_trap_with_vmctx(vmctx: &mut vm::Ctx, n: i32) -> Result<i32, String> {
let memory = vmctx.memory(0);
let shift: i32 = memory.view()[0].get();
Expand Down
182 changes: 161 additions & 21 deletions lib/runtime-core/src/typed_func.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use crate::{
export::{Context, Export, FuncPointer},
import::IsExport,
types::{FuncSig, NativeWasmType, Type, WasmExternType},
vm::{self, Ctx},
vm,
};
use std::{
any::Any,
Expand Down Expand Up @@ -53,14 +53,14 @@ impl fmt::Display for WasmTrapInfo {
pub trait Kind {}

pub type Trampoline = unsafe extern "C" fn(
vmctx: *mut Ctx,
vmctx: *mut vm::Ctx,
func: NonNull<vm::Func>,
args: *const u64,
rets: *mut u64,
);
pub type Invoke = unsafe extern "C" fn(
trampoline: Trampoline,
vmctx: *mut Ctx,
vmctx: *mut vm::Ctx,
func: NonNull<vm::Func>,
args: *const u64,
rets: *mut u64,
Expand Down Expand Up @@ -129,16 +129,49 @@ pub trait WasmTypeList {
self,
f: NonNull<vm::Func>,
wasm: Wasm,
ctx: *mut Ctx,
ctx: *mut vm::Ctx,
) -> Result<Rets, RuntimeError>
where
Rets: WasmTypeList;
}

/// Empty trait to specify the kind of `ExternalFunction`: With or
/// without a `vm::Ctx` argument. See the `ExplicitVmCtx` and the
/// `ImplicitVmCtx` structures.
///
/// This type is never aimed to be used by a user. It is used by the
/// trait system to automatically generate an appropriate `wrap`
/// function.
pub trait ExternalFunctionKind {}

/// This empty structure indicates that an external function must
/// contain an explicit `vm::Ctx` argument (at first position).
///
/// ```rs,ignore
/// fn add_one(_: mut &vm::Ctx, x: i32) -> i32 {
/// x + 1
/// }
/// ```
pub struct ExplicitVmCtx {}

/// This empty structure indicates that an external function has no
/// `vm::Ctx` argument (at first position). Its signature is:
///
/// ```rs,ignore
/// fn add_one(x: i32) -> i32 {
/// x + 1
/// }
/// ```
pub struct ImplicitVmCtx {}

impl ExternalFunctionKind for ExplicitVmCtx {}
impl ExternalFunctionKind for ImplicitVmCtx {}

/// Represents a function that can be converted to a `vm::Func`
/// (function pointer) that can be called within WebAssembly.
pub trait ExternalFunction<Args, Rets>
pub trait ExternalFunction<Kind, Args, Rets>
where
Kind: ExternalFunctionKind,
Args: WasmTypeList,
Rets: WasmTypeList,
{
Expand Down Expand Up @@ -178,7 +211,7 @@ where
pub struct Func<'a, Args = (), Rets = (), Inner: Kind = Wasm> {
inner: Inner,
f: NonNull<vm::Func>,
ctx: *mut Ctx,
ctx: *mut vm::Ctx,
_phantom: PhantomData<(&'a (), Args, Rets)>,
}

Expand All @@ -193,7 +226,7 @@ where
pub(crate) unsafe fn from_raw_parts(
inner: Wasm,
f: NonNull<vm::Func>,
ctx: *mut Ctx,
ctx: *mut vm::Ctx,
) -> Func<'a, Args, Rets, Wasm> {
Func {
inner,
Expand All @@ -213,9 +246,10 @@ where
Args: WasmTypeList,
Rets: WasmTypeList,
{
pub fn new<F>(f: F) -> Func<'a, Args, Rets, Host>
pub fn new<F, Kind>(f: F) -> Func<'a, Args, Rets, Host>
where
F: ExternalFunction<Args, Rets>,
Kind: ExternalFunctionKind,
F: ExternalFunction<Kind, Args, Rets>,
{
Func {
inner: Host(()),
Expand Down Expand Up @@ -272,7 +306,7 @@ impl WasmTypeList for Infallible {
self,
_: NonNull<vm::Func>,
_: Wasm,
_: *mut Ctx,
_: *mut vm::Ctx,
) -> Result<Rets, RuntimeError>
where
Rets: WasmTypeList,
Expand Down Expand Up @@ -318,7 +352,7 @@ where
self,
f: NonNull<vm::Func>,
wasm: Wasm,
ctx: *mut Ctx,
ctx: *mut vm::Ctx,
) -> Result<Rets, RuntimeError>
where
Rets: WasmTypeList,
Expand Down Expand Up @@ -410,7 +444,7 @@ macro_rules! impl_traits {
self,
f: NonNull<vm::Func>,
wasm: Wasm,
ctx: *mut Ctx,
ctx: *mut vm::Ctx,
) -> Result<Rets, RuntimeError>
where
Rets: WasmTypeList
Expand Down Expand Up @@ -443,33 +477,91 @@ macro_rules! impl_traits {
}
}

impl< $( $x, )* Rets, Trap, FN > ExternalFunction<( $( $x ),* ), Rets> for FN
impl< $( $x, )* Rets, Trap, FN > ExternalFunction<ExplicitVmCtx, ( $( $x ),* ), Rets> for FN
where
$( $x: WasmExternType, )*
Rets: WasmTypeList,
Trap: TrapEarly<Rets>,
FN: Fn(&mut vm::Ctx $( , $x )*) -> Trap,
{
#[allow(non_snake_case)]
fn to_raw(&self) -> NonNull<vm::Func> {
if mem::size_of::<Self>() == 0 {
/// This is required for the llvm backend to be able to unwind through this function.
#[cfg_attr(nightly, unwind(allowed))]
extern fn wrap<$( $x, )* Rets, Trap, FN>(
vmctx: &mut vm::Ctx $( , $x: <$x as WasmExternType>::Native )*
) -> Rets::CStruct
where
$( $x: WasmExternType, )*
Rets: WasmTypeList,
Trap: TrapEarly<Rets>,
FN: Fn(&mut vm::Ctx, $( $x, )*) -> Trap,
{
let f: FN = unsafe { mem::transmute_copy(&()) };

let err = match panic::catch_unwind(
panic::AssertUnwindSafe(
|| {
f(vmctx $( , WasmExternType::from_native($x) )* ).report()
}
)
) {
Ok(Ok(returns)) => return returns.into_c_struct(),
Ok(Err(err)) => {
let b: Box<_> = err.into();
b as Box<dyn Any>
},
Err(err) => err,
};

unsafe {
(&*vmctx.module).runnable_module.do_early_trap(err)
}
}

NonNull::new(wrap::<$( $x, )* Rets, Trap, Self> as *mut vm::Func).unwrap()
} else {
assert_eq!(
mem::size_of::<Self>(),
mem::size_of::<usize>(),
"you cannot use a closure that captures state for `Func`."
);

NonNull::new(unsafe {
mem::transmute_copy::<_, *mut vm::Func>(self)
}).unwrap()
}
}
}

impl< $( $x, )* Rets, Trap, FN > ExternalFunction<ImplicitVmCtx, ( $( $x ),* ), Rets> for FN
where
$( $x: WasmExternType, )*
Rets: WasmTypeList,
Trap: TrapEarly<Rets>,
FN: Fn(&mut Ctx $( , $x )*) -> Trap,
FN: Fn($( $x, )*) -> Trap,
{
#[allow(non_snake_case)]
fn to_raw(&self) -> NonNull<vm::Func> {
if mem::size_of::<Self>() == 0 {
/// This is required for the llvm backend to be able to unwind through this function.
#[cfg_attr(nightly, unwind(allowed))]
extern fn wrap<$( $x, )* Rets, Trap, FN>(
ctx: &mut Ctx $( , $x: <$x as WasmExternType>::Native )*
vmctx: &mut vm::Ctx $( , $x: <$x as WasmExternType>::Native )*
) -> Rets::CStruct
where
$( $x: WasmExternType, )*
Rets: WasmTypeList,
Trap: TrapEarly<Rets>,
FN: Fn(&mut Ctx $( , $x )*) -> Trap,
FN: Fn($( $x, )*) -> Trap,
{
let f: FN = unsafe { mem::transmute_copy(&()) };

let err = match panic::catch_unwind(
panic::AssertUnwindSafe(
|| {
f(ctx $( , WasmExternType::from_native($x) )* ).report()
f($( WasmExternType::from_native($x), )* ).report()
}
)
) {
Expand All @@ -482,7 +574,7 @@ macro_rules! impl_traits {
};

unsafe {
(&*ctx.module).runnable_module.do_early_trap(err)
(&*vmctx.module).runnable_module.do_early_trap(err)
}
}

Expand All @@ -495,7 +587,7 @@ macro_rules! impl_traits {
);

NonNull::new(unsafe {
::std::mem::transmute_copy::<_, *mut vm::Func>(self)
mem::transmute_copy::<_, *mut vm::Func>(self)
}).unwrap()
}
}
Expand Down Expand Up @@ -567,9 +659,57 @@ where
#[cfg(test)]
mod tests {
use super::*;

macro_rules! test_func_arity_n {
($test_name:ident, $($x:ident),*) => {
#[test]
fn $test_name() {
use crate::vm;

fn with_vmctx(_: &mut vm::Ctx, $($x: i32),*) -> i32 {
vec![$($x),*].iter().sum()
}

fn without_vmctx($($x: i32),*) -> i32 {
vec![$($x),*].iter().sum()
}

let _func = Func::new(with_vmctx);
let _func = Func::new(without_vmctx);
}
}
}

#[test]
fn test_func_arity_0() {
fn foo(_: &mut vm::Ctx) -> i32 {
0
}

fn bar() -> i32 {
0
}

let _ = Func::new(foo);
let _ = Func::new(bar);
}

test_func_arity_n!(test_func_arity_1, a);
test_func_arity_n!(test_func_arity_2, a, b);
test_func_arity_n!(test_func_arity_3, a, b, c);
test_func_arity_n!(test_func_arity_4, a, b, c, d);
test_func_arity_n!(test_func_arity_5, a, b, c, d, e);
test_func_arity_n!(test_func_arity_6, a, b, c, d, e, f);
test_func_arity_n!(test_func_arity_7, a, b, c, d, e, f, g);
test_func_arity_n!(test_func_arity_8, a, b, c, d, e, f, g, h);
test_func_arity_n!(test_func_arity_9, a, b, c, d, e, f, g, h, i);
test_func_arity_n!(test_func_arity_10, a, b, c, d, e, f, g, h, i, j);
test_func_arity_n!(test_func_arity_11, a, b, c, d, e, f, g, h, i, j, k);
test_func_arity_n!(test_func_arity_12, a, b, c, d, e, f, g, h, i, j, k, l);

#[test]
fn test_call() {
fn foo(_ctx: &mut Ctx, a: i32, b: i32) -> (i32, i32) {
fn foo(_ctx: &mut vm::Ctx, a: i32, b: i32) -> (i32, i32) {
(a, b)
}

Expand All @@ -580,7 +720,7 @@ mod tests {
fn test_imports() {
use crate::{func, imports};

fn foo(_ctx: &mut Ctx, a: i32) -> i32 {
fn foo(_ctx: &mut vm::Ctx, a: i32) -> i32 {
a
}

Expand Down

0 comments on commit 656491a

Please sign in to comment.