diff --git a/lib/js-api/src/exports.rs b/lib/js-api/src/exports.rs index 22ddef593..0ada990f2 100644 --- a/lib/js-api/src/exports.rs +++ b/lib/js-api/src/exports.rs @@ -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( - // &self, - // name: &str, - // ) -> Result, 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( + &self, + name: &str, + ) -> Result, 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 diff --git a/lib/js-api/src/externals/function.rs b/lib/js-api/src/externals/function.rs index 1bcfdfb54..e0b564efe 100644 --- a/lib/js-api/src/externals/function.rs +++ b/lib/js-api/src/externals/function.rs @@ -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(&self) -> Result, 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(&self) -> Result, 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!() } diff --git a/lib/js-api/src/lib.rs b/lib/js-api/src/lib.rs index 01f19a503..f37e8c793 100644 --- a/lib/js-api/src/lib.rs +++ b/lib/js-api/src/lib.rs @@ -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] diff --git a/lib/js-api/src/native.rs b/lib/js-api/src/native.rs index a350c6f09..4dff7da0c 100644 --- a/lib/js-api/src/native.rs +++ b/lib/js-api/src/native.rs @@ -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 { 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 From<&NativeFunc> for VMFunction -where - Args: WasmTypeList, - Rets: WasmTypeList, -{ - fn from(other: &NativeFunc) -> 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 Clone for NativeFunc { - 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 From<&NativeFunc> 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 { - 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 = 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>; - unsafe { - let ctx = self.vmctx().host_env as *mut VMContextWithEnv; - (*ctx).ctx.call(¶ms_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()); - } } }; } diff --git a/lib/js-api/src/types.rs b/lib/js-api/src/types.rs index dcfef24b2..1e637a270 100644 --- a/lib/js-api/src/types.rs +++ b/lib/js-api/src/types.rs @@ -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 { diff --git a/lib/js-api/tests/externals.rs b/lib/js-api/tests/externals.rs index 05b2bb3e6..bbdeb0a22 100644 --- a/lib/js-api/tests/externals.rs +++ b/lib/js-api/tests/externals.rs @@ -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 = 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 = 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 = function.native().unwrap(); -// assert!(native_function.call(4).is_ok()); + let function = Function::new_native(&store, |_a: i32| {}); + let native_function: NativeFunc = 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() {