Implement host functions in 1 case, optimize NativeFunc::call

`NativeFunc::call` is about 8% faster in the case we're benchmarking
by avoiding allocating a vector for the params / returns, instead we
do logic to determine which is larger and use that, conditionally
copying it back to the rets array if needed.
This commit is contained in:
Mark McCaskey
2020-06-09 11:35:58 -07:00
parent 85b901d17d
commit eb928e739c
3 changed files with 54 additions and 38 deletions

View File

@@ -1,5 +1,4 @@
use criterion::{black_box, criterion_group, criterion_main, Criterion}; use criterion::{black_box, criterion_group, criterion_main, Criterion};
use std::sync::Arc;
use test_utils::{get_compiler_config_from_str, wasmer_compilers}; use test_utils::{get_compiler_config_from_str, wasmer_compilers};
use wasmer::*; use wasmer::*;
use wasmer_engine_jit::JITEngine; use wasmer_engine_jit::JITEngine;

View File

@@ -4,7 +4,7 @@ use std::marker::PhantomData;
use crate::exports::{ExportError, Exportable}; use crate::exports::{ExportError, Exportable};
use crate::externals::function::{FunctionDefinition, WasmFunctionDefinition}; use crate::externals::function::{FunctionDefinition, WasmFunctionDefinition};
use crate::{Extern, Function, FunctionType, Store}; use crate::{Extern, Function, FunctionType, RuntimeError, Store};
use wasm_common::{NativeWasmType, WasmExternType, WasmTypeList}; use wasm_common::{NativeWasmType, WasmExternType, WasmTypeList};
use wasmer_runtime::{ use wasmer_runtime::{
wasmer_call_trampoline, Export, ExportFunction, VMContext, VMFunctionBody, VMFunctionKind, wasmer_call_trampoline, Export, ExportFunction, VMContext, VMFunctionBody, VMFunctionKind,
@@ -27,7 +27,11 @@ pub struct NativeFunc<'a, Args = UnprovidedArgs, Rets = UnprovidedRets> {
unsafe impl<'a, Args, Rets> Send for NativeFunc<'a, Args, Rets> {} unsafe impl<'a, Args, Rets> Send for NativeFunc<'a, Args, Rets> {}
impl<'a, Args, Rets> NativeFunc<'a, Args, Rets> { impl<'a, Args, Rets> NativeFunc<'a, Args, Rets>
where
Args: WasmTypeList,
Rets: WasmTypeList,
{
pub(crate) fn new( pub(crate) fn new(
store: Store, store: Store,
address: *const VMFunctionBody, address: *const VMFunctionBody,
@@ -115,56 +119,70 @@ macro_rules! impl_native_traits {
Rets: WasmTypeList, Rets: WasmTypeList,
{ {
/// Call the typed func and return results. /// Call the typed func and return results.
pub fn call(&self, $( $x: $x, )* ) -> Result<Rets, ()> { pub fn call(&self, $( $x: $x, )* ) -> Result<Rets, RuntimeError> {
let params = [ $( $x.to_native().to_binary() ),* ]; // TODO: when `const fn` related features mature more, we can declare a single array
let mut values_vec: Vec<i128> = vec![0; std::cmp::max(params.len(), Rets::wasm_types().len())]; // of the correct size here.
let mut params_list = [ $( $x.to_native().to_binary() ),* ];
for (i, &arg) in params.iter().enumerate() { let mut rets_list_array = Rets::empty_array();
values_vec[i] = arg; let rets_list = rets_list_array.as_mut();
} let using_rets_array;
let args_rets: &mut [i128] = if params_list.len() > rets_list.len() {
using_rets_array = false;
params_list.as_mut()
} else {
using_rets_array = true;
for (i, &arg) in params_list.iter().enumerate() {
rets_list[i] = arg;
}
rets_list.as_mut()
};
match self.definition { match self.definition {
FunctionDefinition::Wasm(WasmFunctionDefinition { FunctionDefinition::Wasm(WasmFunctionDefinition {
trampoline trampoline
}) => { }) => {
if let Err(error) = unsafe { unsafe {
wasmer_call_trampoline( wasmer_call_trampoline(
self.vmctx, self.vmctx,
trampoline, trampoline,
self.address, self.address,
values_vec.as_mut_ptr() as *mut u8, args_rets.as_mut_ptr() as *mut u8,
) )
} { }?;
dbg!(error); let num_rets = rets_list.len();
return Err(()); if !using_rets_array && num_rets > 0 {
} else { let src_pointer = params_list.as_ptr();
let mut results = Rets::empty_array(); let rets_list = &mut rets_list_array.as_mut()[0] as *mut i128;
let num_results = Rets::wasm_types().len(); unsafe {
if num_results > 0 { // TODO: we can probably remove this copy by doing some clever `transmute`s.
unsafe { // we know it's not overlapping because `using_rets_array` is false
std::ptr::copy_nonoverlapping(values_vec.as_ptr(), std::ptr::copy_nonoverlapping(src_pointer,
&mut results.as_mut()[0] as *mut i128, rets_list,
num_results); num_rets);
}
} }
return Ok(Rets::from_array(results));
} }
return Ok(Rets::from_array(rets_list_array));
} }
FunctionDefinition::Host => { FunctionDefinition::Host => {
/*unsafe { if self.arg_kind == VMFunctionKind::Static {
let f = std::mem::transmute::<_, unsafe extern "C" fn( *mut VMContext, $( $x, )*) -> Result<Rets, RuntimeError>>(self.address); unsafe {
match f( self.vmctx, $( $x, )* ) { let f = std::mem::transmute::<_, unsafe fn( $( $x, )*) -> Rets>(self.address);
Err(error) => {
dbg!(error); let results = f( $( $x, )* );
return Err(()); return Ok(results);
} /* match f( $( $x, )* ) {
Ok(results) => { Err(error) => {
return Ok(results); dbg!(error);
} return Err(());
}
Ok(results) => {
return Ok(results);
}
}*/
} }
} else {
todo!("dynamic host functions not yet implemented")
} }
*/
todo!("host functions not yet implemented")
}, },
} }

View File

@@ -331,7 +331,6 @@ fn function_new_dynamic_env() -> Result<()> {
} }
// TODO: unignore this when calling host functions has been implemented // TODO: unignore this when calling host functions has been implemented
#[ignore]
#[test] #[test]
fn native_function_works() -> Result<()> { fn native_function_works() -> Result<()> {
let store = Store::default(); let store = Store::default();