Native function now works fully

This commit is contained in:
Syrus Akbary
2021-07-13 19:16:49 -07:00
parent e7670b58b6
commit 917667ad8c
6 changed files with 147 additions and 258 deletions

View File

@@ -1,7 +1,7 @@
use crate::export::Export;
use crate::externals::{Extern, Function /* , Global, Table */, Memory};
use crate::externals::{Extern, Function, Global, Memory, Table};
use crate::import_object::LikeNamespace;
// use crate::native::NativeFunc;
use crate::native::NativeFunc;
use crate::WasmTypeList;
use indexmap::IndexMap;
use std::fmt;
@@ -118,39 +118,39 @@ impl Exports {
}
}
// /// Get an export as a `Global`.
// pub fn get_global(&self, name: &str) -> Result<&Global, ExportError> {
// self.get(name)
// }
/// Get an export as a `Global`.
pub fn get_global(&self, name: &str) -> Result<&Global, ExportError> {
self.get(name)
}
/// Get an export as a `Memory`.
pub fn get_memory(&self, name: &str) -> Result<&Memory, ExportError> {
self.get(name)
}
// /// Get an export as a `Table`.
// pub fn get_table(&self, name: &str) -> Result<&Table, ExportError> {
// self.get(name)
// }
/// Get an export as a `Table`.
pub fn get_table(&self, name: &str) -> Result<&Table, ExportError> {
self.get(name)
}
/// Get an export as a `Func`.
pub fn get_function(&self, name: &str) -> Result<&Function, ExportError> {
self.get(name)
}
// /// Get an export as a `NativeFunc`.
// pub fn get_native_function<Args, Rets>(
// &self,
// name: &str,
// ) -> Result<NativeFunc<Args, Rets>, ExportError>
// where
// Args: WasmTypeList,
// Rets: WasmTypeList,
// {
// self.get_function(name)?
// .native()
// .map_err(|_| ExportError::IncompatibleType)
// }
/// Get an export as a `NativeFunc`.
pub fn get_native_function<Args, Rets>(
&self,
name: &str,
) -> Result<NativeFunc<Args, Rets>, ExportError>
where
Args: WasmTypeList,
Rets: WasmTypeList,
{
self.get_function(name)?
.native()
.map_err(|_| ExportError::IncompatibleType)
}
/// Hack to get this working with nativefunc too
pub fn get_with_generics<'a, T, Args, Rets>(&'a self, name: &str) -> Result<T, ExportError>

View File

@@ -1,16 +1,16 @@
use crate::exports::{ExportError, Exportable};
use crate::externals::Extern;
use crate::store::Store;
use crate::types::{AsJs /* ValFuncRef */, Val};
use crate::{FunctionType, ValType};
use js_sys::{Array, Function as JSFunction};
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
// use crate::NativeFunc;
use crate::types::{param_from_js, AsJs /* ValFuncRef */, Val};
use crate::NativeFunc;
use crate::RuntimeError;
use crate::WasmerEnv;
use crate::{FunctionType, ValType};
pub use inner::{FromToNativeWasmType, HostFunction, WasmTypeList, WithEnv, WithoutEnv};
use js_sys::{Array, Function as JSFunction};
use std::iter::FromIterator;
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use crate::export::{Export, VMFunction};
use std::fmt;
@@ -18,17 +18,6 @@ use std::fmt;
#[repr(C)]
pub struct VMFunctionBody(u8);
#[inline]
fn param_from_js(ty: &ValType, js_val: &JsValue) -> Val {
match ty {
ValType::I32 => Val::I32(js_val.as_f64().unwrap() as _),
ValType::I64 => Val::I64(js_val.as_f64().unwrap() as _),
ValType::F32 => Val::F32(js_val.as_f64().unwrap() as _),
ValType::F64 => Val::F64(js_val.as_f64().unwrap()),
_ => unimplemented!("The type is not yet supported in the JS Function API"),
}
}
#[inline]
fn result_to_js(val: &Val) -> JsValue {
match val {
@@ -731,42 +720,41 @@ impl Function {
/// // This results in an error: `RuntimeError`
/// let sum_native = sum.native::<(i32, i32), i64>().unwrap();
/// ```
// pub fn native<Args, Rets>(&self) -> Result<NativeFunc<Args, Rets>, RuntimeError>
// where
// Args: WasmTypeList,
// Rets: WasmTypeList,
// {
// unimplemented!();
// // // type check
// // {
// // let expected = self.exported.vm_function.signature.params();
// // let given = Args::wasm_types();
pub fn native<Args, Rets>(&self) -> Result<NativeFunc<Args, Rets>, RuntimeError>
where
Args: WasmTypeList,
Rets: WasmTypeList,
{
// type check
{
let expected = self.exported.ty.params();
let given = Args::wasm_types();
// // if expected != given {
// // return Err(RuntimeError::new(format!(
// // "given types (`{:?}`) for the function arguments don't match the actual types (`{:?}`)",
// // given,
// // expected,
// // )));
// // }
// // }
if expected != given {
return Err(RuntimeError::from_str(&format!(
"given types (`{:?}`) for the function arguments don't match the actual types (`{:?}`)",
given,
expected,
)));
}
}
// // {
// // let expected = self.exported.vm_function.signature.results();
// // let given = Rets::wasm_types();
{
let expected = self.exported.ty.results();
let given = Rets::wasm_types();
// // if expected != given {
// // // todo: error result types don't match
// // return Err(RuntimeError::new(format!(
// // "given types (`{:?}`) for the function results don't match the actual types (`{:?}`)",
// // given,
// // expected,
// // )));
// // }
// // }
if expected != given {
// todo: error result types don't match
return Err(RuntimeError::from_str(&format!(
"given types (`{:?}`) for the function results don't match the actual types (`{:?}`)",
given,
expected,
)));
}
}
// // Ok(NativeFunc::new(self.store.clone(), self.exported.clone()))
// }
Ok(NativeFunc::new(self.store.clone(), self.exported.clone()))
}
#[track_caller]
fn closures_unsupported_panic() -> ! {
@@ -1058,6 +1046,9 @@ mod inner {
/// Note that all values are stored in their binary form.
type Array: AsMut<[i128]>;
/// The size of the array
fn size() -> u32;
/// Constructs `Self` based on an array of values.
fn from_array(array: Self::Array) -> Self;
@@ -1279,6 +1270,10 @@ mod inner {
type Array = [i128; count_idents!( $( $x ),* )];
fn size() -> u32 {
count_idents!( $( $x ),* ) as _
}
fn from_array(array: Self::Array) -> Self {
// Unpack items of the array.
#[allow(non_snake_case)]
@@ -1487,6 +1482,10 @@ mod inner {
type CStruct = Self;
type Array = [i128; 0];
fn size() -> u32 {
0
}
fn from_array(_: Self::Array) -> Self {
unreachable!()
}

View File

@@ -278,9 +278,6 @@ mod lib {
}
}
// #[cfg(test)]
// wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
mod cell;
mod env;
mod error;
@@ -293,13 +290,13 @@ mod iterators;
mod module;
#[cfg(feature = "wasm-types-polyfill")]
mod module_info_polyfill;
mod resolver;
mod wasm_bindgen_polyfill;
// mod native;
mod native;
mod ptr;
mod resolver;
mod store;
mod types;
mod utils;
mod wasm_bindgen_polyfill;
/// Implement [`WasmerEnv`] for your type with `#[derive(WasmerEnv)]`.
///
@@ -316,10 +313,10 @@ pub use crate::externals::{
pub use crate::import_object::{ImportObject, ImportObjectIterator, LikeNamespace};
pub use crate::instance::{Instance, InstantiationError};
pub use crate::module::{Module, ModuleTypeHints};
pub use wasm_bindgen::JsValue as RuntimeError;
// pub use crate::native::NativeFunc;
pub use crate::native::NativeFunc;
pub use crate::ptr::{Array, Item, WasmPtr};
pub use crate::resolver::{ChainableNamedResolver, NamedResolver, NamedResolverChain, Resolver};
pub use wasm_bindgen::JsValue as RuntimeError;
pub use crate::store::{Store, StoreObject};
pub use crate::types::{
@@ -342,6 +339,9 @@ pub use wat::parse_bytes as wat2wasm;
/// Version number of this crate.
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
// #[cfg(test)]
// wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
// use wasm_bindgen::prelude::*;
// #[wasm_bindgen]

View File

@@ -9,18 +9,21 @@
//! ```
use std::marker::PhantomData;
use crate::externals::function::{DynamicFunction, VMDynamicFunction};
use crate::{FromToNativeWasmType, Function, RuntimeError, Store, WasmTypeList};
use std::panic::{catch_unwind, AssertUnwindSafe};
use wasmer_engine::ExportFunction;
// use std::panic::{catch_unwind, AssertUnwindSafe};
use crate::export::VMFunction;
use crate::types::{param_from_js, AsJs};
use js_sys::Array;
use std::iter::FromIterator;
use wasm_bindgen::JsValue;
use wasmer_types::NativeWasmType;
use wasmer_vm::{VMDynamicFunctionContext, VMFunctionBody, VMFunctionEnvironment, VMFunctionKind};
/// A WebAssembly function that can be called natively
/// (using the Native ABI).
#[derive(Clone)]
pub struct NativeFunc<Args = (), Rets = ()> {
store: Store,
exported: ExportFunction,
exported: VMFunction,
_phantom: PhantomData<(Args, Rets)>,
}
@@ -31,76 +34,16 @@ where
Args: WasmTypeList,
Rets: WasmTypeList,
{
pub(crate) fn new(store: Store, exported: ExportFunction) -> Self {
pub(crate) fn new(store: Store, exported: VMFunction) -> Self {
Self {
store,
exported,
_phantom: PhantomData,
}
}
pub(crate) fn is_host(&self) -> bool {
self.exported.vm_function.instance_ref.is_none()
}
pub(crate) fn vmctx(&self) -> VMFunctionEnvironment {
self.exported.vm_function.vmctx
}
pub(crate) fn address(&self) -> *const VMFunctionBody {
self.exported.vm_function.address
}
pub(crate) fn arg_kind(&self) -> VMFunctionKind {
self.exported.vm_function.kind
}
/// Get access to the backing VM value for this extern. This function is for
/// tests it should not be called by users of the Wasmer API.
///
/// # Safety
/// This function is unsafe to call outside of tests for the wasmer crate
/// because there is no stability guarantee for the returned type and we may
/// make breaking changes to it at any time or remove this method.
#[doc(hidden)]
pub unsafe fn get_vm_function(&self) -> &wasmer_vm::VMFunction {
&self.exported.vm_function
}
}
/*
impl<Args, Rets> From<&NativeFunc<Args, Rets>> for VMFunction
where
Args: WasmTypeList,
Rets: WasmTypeList,
{
fn from(other: &NativeFunc<Args, Rets>) -> Self {
let signature = FunctionType::new(Args::wasm_types(), Rets::wasm_types());
Self {
address: other.address,
vmctx: other.vmctx,
signature,
kind: other.arg_kind,
call_trampoline: None,
instance_ref: None,
}
}
}*/
impl<Args: WasmTypeList, Rets: WasmTypeList> Clone for NativeFunc<Args, Rets> {
fn clone(&self) -> Self {
let mut exported = self.exported.clone();
exported.vm_function.upgrade_instance_ref().unwrap();
Self {
store: self.store.clone(),
exported,
_phantom: PhantomData,
}
}
}
impl<Args, Rets> From<&NativeFunc<Args, Rets>> for ExportFunction
where
Args: WasmTypeList,
Rets: WasmTypeList,
@@ -133,91 +76,33 @@ macro_rules! impl_native_traits {
{
/// Call the typed func and return results.
pub fn call(&self, $( $x: $x, )* ) -> Result<Rets, RuntimeError> {
if !self.is_host() {
// We assume the trampoline is always going to be present for
// Wasm functions
let trampoline = self.exported.vm_function.call_trampoline.expect("Call trampoline not found in wasm function");
// TODO: when `const fn` related features mature more, we can declare a single array
// of the correct size here.
let mut params_list = [ $( $x.to_native().to_binary() ),* ];
let mut rets_list_array = Rets::empty_array();
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()
};
unsafe {
wasmer_vm::wasmer_call_trampoline(
&self.store,
self.vmctx(),
trampoline,
self.address(),
args_rets.as_mut_ptr() as *mut u8,
)
}?;
let num_rets = rets_list.len();
if !using_rets_array && num_rets > 0 {
let src_pointer = params_list.as_ptr();
let rets_list = &mut rets_list_array.as_mut()[0] as *mut i128;
unsafe {
// TODO: we can probably remove this copy by doing some clever `transmute`s.
// we know it's not overlapping because `using_rets_array` is false
std::ptr::copy_nonoverlapping(src_pointer,
rets_list,
num_rets);
}
let params_list: Vec<JsValue> = vec![ $( JsValue::from_f64($x.to_native().to_binary() as f64) ),* ];
let results = self.exported.function.apply(
&JsValue::UNDEFINED,
&Array::from_iter(params_list.iter())
).unwrap();
let mut rets_list_array = Rets::empty_array();
let mut_rets = rets_list_array.as_mut() as *mut [i128] as *mut i128;
match Rets::size() {
0 => {},
1 => unsafe {
let ty = Rets::wasm_types()[0];
let val = param_from_js(&ty, &results);
val.write_value_to(mut_rets);
}
Ok(Rets::from_array(rets_list_array))
// TODO: When the Host ABI and Wasm ABI are the same, we could do this instead:
// but we can't currently detect whether that's safe.
//
// let results = unsafe {
// wasmer_vm::catch_traps_with_result(self.vmctx, || {
// let f = std::mem::transmute::<_, unsafe extern "C" fn( *mut VMContext, $( $x, )*) -> Rets::CStruct>(self.address());
// // We always pass the vmctx
// f( self.vmctx, $( $x, )* )
// }).map_err(RuntimeError::from_trap)?
// };
// Ok(Rets::from_c_struct(results))
}
else {
match self.arg_kind() {
VMFunctionKind::Static => {
let results = catch_unwind(AssertUnwindSafe(|| unsafe {
let f = std::mem::transmute::<_, unsafe extern "C" fn( VMFunctionEnvironment, $( $x, )*) -> Rets::CStruct>(self.address());
// We always pass the vmctx
f( self.vmctx(), $( $x, )* )
})).map_err(|e| RuntimeError::new(format!("{:?}", e)))?;
Ok(Rets::from_c_struct(results))
},
VMFunctionKind::Dynamic => {
let params_list = [ $( $x.to_native().to_value() ),* ];
let results = {
type VMContextWithEnv = VMDynamicFunctionContext<DynamicFunction<std::ffi::c_void>>;
unsafe {
let ctx = self.vmctx().host_env as *mut VMContextWithEnv;
(*ctx).ctx.call(&params_list)?
}
};
let mut rets_list_array = Rets::empty_array();
let mut_rets = rets_list_array.as_mut() as *mut [i128] as *mut i128;
for (i, ret) in results.iter().enumerate() {
unsafe {
ret.write_value_to(mut_rets.add(i));
}
n => {
let results: Array = results.into();
for (i, ret_type) in Rets::wasm_types().iter().enumerate() {
let ret = results.get(i as u32);
unsafe {
let val = param_from_js(&ret_type, &ret);
let p = mut_rets.add(i);
val.write_value_to(mut_rets.add(i));
}
Ok(Rets::from_array(rets_list_array))
}
}
}
Ok(Rets::from_array(rets_list_array))
}
}
@@ -232,10 +117,6 @@ macro_rules! impl_native_traits {
use crate::exports::Exportable;
crate::Function::get_self_from_extern(_extern)?.native().map_err(|_| crate::exports::ExportError::IncompatibleType)
}
fn into_weak_instance_ref(&mut self) {
self.exported.vm_function.instance_ref.as_mut().map(|v| *v = v.downgrade());
}
}
};
}

View File

@@ -22,6 +22,17 @@ pub trait AsJs {
fn as_jsvalue(&self) -> JsValue;
}
#[inline]
pub fn param_from_js(ty: &ValType, js_val: &JsValue) -> Val {
match ty {
ValType::I32 => Val::I32(js_val.as_f64().unwrap() as _),
ValType::I64 => Val::I64(js_val.as_f64().unwrap() as _),
ValType::F32 => Val::F32(js_val.as_f64().unwrap() as _),
ValType::F64 => Val::F64(js_val.as_f64().unwrap()),
_ => unimplemented!("The type is not yet supported in the JS Function API"),
}
}
impl AsJs for Val {
fn as_jsvalue(&self) -> JsValue {
match self {

View File

@@ -327,39 +327,37 @@ fn function_new_dynamic_env() {
assert_eq!(function.ty().results(), [Type::I32, Type::F32, Type::F64]);
}
// #[test]
// fn native_function_works() -> Result<()> {
// let store = Store::default();
// let function = Function::new_native(&store, || {});
// let native_function: NativeFunc<(), ()> = function.native().unwrap();
// let result = native_function.call();
// assert!(result.is_ok());
#[wasm_bindgen_test]
fn native_function_works() {
let store = Store::default();
let function = Function::new_native(&store, || {});
let native_function: NativeFunc<(), ()> = function.native().unwrap();
let result = native_function.call();
assert!(result.is_ok());
// let function = Function::new_native(&store, |a: i32| -> i32 { a + 1 });
// let native_function: NativeFunc<i32, i32> = function.native().unwrap();
// assert_eq!(native_function.call(3).unwrap(), 4);
let function = Function::new_native(&store, |a: i32| -> i32 { a + 1 });
let native_function: NativeFunc<i32, i32> = function.native().unwrap();
assert_eq!(native_function.call(3).unwrap(), 4);
// fn rust_abi(a: i32, b: i64, c: f32, d: f64) -> u64 {
// (a as u64 * 1000) + (b as u64 * 100) + (c as u64 * 10) + (d as u64)
// }
// let function = Function::new_native(&store, rust_abi);
// let native_function: NativeFunc<(i32, i64, f32, f64), u64> = function.native().unwrap();
// assert_eq!(native_function.call(8, 4, 1.5, 5.).unwrap(), 8415);
// fn rust_abi(a: i32, b: i64, c: f32, d: f64) -> u64 {
// (a as u64 * 1000) + (b as u64 * 100) + (c as u64 * 10) + (d as u64)
// }
// let function = Function::new_native(&store, rust_abi);
// let native_function: NativeFunc<(i32, i64, f32, f64), u64> = function.native().unwrap();
// assert_eq!(native_function.call(8, 4, 1.5, 5.).unwrap(), 8415);
// let function = Function::new_native(&store, || -> i32 { 1 });
// let native_function: NativeFunc<(), i32> = function.native().unwrap();
// assert_eq!(native_function.call().unwrap(), 1);
let function = Function::new_native(&store, || -> i32 { 1 });
let native_function: NativeFunc<(), i32> = function.native().unwrap();
assert_eq!(native_function.call().unwrap(), 1);
// let function = Function::new_native(&store, |_a: i32| {});
// let native_function: NativeFunc<i32, ()> = function.native().unwrap();
// assert!(native_function.call(4).is_ok());
let function = Function::new_native(&store, |_a: i32| {});
let native_function: NativeFunc<i32, ()> = function.native().unwrap();
assert!(native_function.call(4).is_ok());
// let function = Function::new_native(&store, || -> (i32, i64, f32, f64) { (1, 2, 3.0, 4.0) });
// let native_function: NativeFunc<(), (i32, i64, f32, f64)> = function.native().unwrap();
// assert_eq!(native_function.call().unwrap(), (1, 2, 3.0, 4.0));
// Ok(())
// }
// let function = Function::new_native(&store, || -> (i32, i64, f32, f64) { (1, 2, 3.0, 4.0) });
// let native_function: NativeFunc<(), (i32, i64, f32, f64)> = function.native().unwrap();
// assert_eq!(native_function.call().unwrap(), (1, 2, 3.0, 4.0));
}
#[wasm_bindgen_test]
fn function_outlives_instance() {