Add basic API tests with reference types with dynamic functions

This commit is contained in:
Mark McCaskey
2021-02-17 08:11:07 -08:00
parent 7ee1e14254
commit 828d265428
4 changed files with 177 additions and 0 deletions

66
lib/api/src/extern_ref.rs Normal file
View File

@@ -0,0 +1,66 @@
use crate::FromToNativeWasmType;
use std::any::Any;
use std::marker::PhantomData;
use wasmer_vm::VMExternRef;
#[derive(Debug)]
#[repr(transparent)]
/// An opaque reference to some data. This reference can be passed through Wasm.
pub struct ExternRef<T: Any + Send + Sync + 'static + Sized> {
inner: VMExternRef,
_phantom: PhantomData<T>,
}
impl<T> ExternRef<T>
where
T: Any + Send + Sync + 'static + Sized,
{
/// Checks if the given ExternRef is null.
pub fn is_null(&self) -> bool {
self.inner.is_null()
}
/// New null extern ref
pub fn null() -> Self {
Self {
inner: VMExternRef::null(),
_phantom: PhantomData,
}
}
/// Make a new extern reference
pub fn new(value: T) -> Self {
Self {
inner: VMExternRef::new(value),
_phantom: PhantomData,
}
}
/// Try to downcast to the given value
pub fn downcast(&self) -> Option<&T> {
self.inner.downcast::<T>()
}
}
/*
unsafe impl<T> FromToNativeWasmType for ExternRef<T>
where
T: Any + Send + Sync + 'static + Sized,
{
type Native = usize;
#[inline]
fn from_native(native: Self::Native) -> Self {
let inner = VMExternRef::from_ne_bytes(Self::Native::to_ne_bytes(native));
Self {
inner,
_phantom: PhantomData,
}
}
#[inline]
fn to_native(self) -> Self::Native {
Self::Native::from_ne_bytes(VMExternRef::to_ne_bytes(self.inner))
}
}
*/

View File

@@ -255,6 +255,7 @@
mod env;
mod exports;
mod extern_ref;
mod externals;
mod import_object;
mod instance;
@@ -285,6 +286,7 @@ pub mod internals {
pub use crate::env::{HostEnvInitError, LazyInit, WasmerEnv};
pub use crate::exports::{ExportError, Exportable, Exports, ExportsIterator};
pub use crate::extern_ref::ExternRef;
pub use crate::externals::{
Extern, FromToNativeWasmType, Function, Global, HostFunction, Memory, Table, WasmTypeList,
};

View File

@@ -0,0 +1,83 @@
use anyhow::Result;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use wasmer::*;
#[test]
fn func_ref_passed_and_returned() -> Result<()> {
let store = Store::default();
let wat = r#"(module
(import "env" "func_ref_identity" (func (param funcref) (result funcref)))
(type $ret_i32_ty (func (result i32)))
(table $table (export "table") 2 2 funcref)
(func (export "run") (param) (result funcref)
(call 0 (ref.null func)))
(func (export "call_set_value") (param $fr funcref) (result i32)
(table.set $table (i32.const 0) (local.get $fr))
(call_indirect $table (type $ret_i32_ty) (i32.const 0)))
)"#;
let module = Module::new(&store, wat)?;
let imports = imports! {
"env" => {
"func_ref_identity" => Function::new(&store, FunctionType::new([Type::FuncRef], [Type::FuncRef]), |values| -> Result<Vec<_>, _> {
Ok(vec![values[0].clone()])
})
},
};
let instance = Instance::new(&module, &imports)?;
let f: &Function = instance.exports.get_function("run")?;
let results = f.call(&[]).unwrap();
if let Value::FuncRef(fr) = &results[0] {
assert!(fr.is_none());
} else {
panic!("funcref not found!");
}
#[derive(Clone, Debug, WasmerEnv)]
pub struct Env(Arc<AtomicBool>);
let env = Env(Arc::new(AtomicBool::new(false)));
let func_to_call = Function::new_native_with_env(&store, env.clone(), |env: &Env| -> i32 {
env.0.store(true, Ordering::SeqCst);
343
});
let call_set_value: &Function = instance.exports.get_function("call_set_value")?;
let results: Box<[Value]> = call_set_value.call(&[Value::FuncRef(Some(func_to_call))])?;
assert!(env.0.load(Ordering::SeqCst));
assert_eq!(&*results, &[Value::I32(343)]);
Ok(())
}
#[test]
fn extern_ref_passed_and_returned() -> Result<()> {
let store = Store::default();
let wat = r#"(module
(import "env" "extern_ref_identity" (func (param externref) (result externref)))
(func (export "run") (param) (result externref)
(call 0 (ref.null extern)))
)"#;
let module = Module::new(&store, wat)?;
let imports = imports! {
"env" => {
"extern_ref_identity" => Function::new(&store, FunctionType::new([Type::ExternRef], [Type::ExternRef]), |values| -> Result<Vec<_>, _> {
Ok(vec![values[0].clone()])
})
},
};
let instance = Instance::new(&module, &imports)?;
let f: &Function = instance.exports.get_function("run")?;
let results = f.call(&[]).unwrap();
if let Value::ExternRef(er) = results[0] {
assert!(er.is_null());
} else {
panic!("result is not an extern ref!");
}
Ok(())
}

View File

@@ -30,6 +30,32 @@ impl VMExternRef {
Self(Box::into_raw(Box::new(VMExternRefInner::new::<T>(value))))
}
/// Try to downcast to the given value
pub fn downcast<T>(&self) -> Option<&T>
where
T: Any + Send + Sync + 'static + Sized,
{
if self.is_null() {
return None;
}
unsafe {
let inner = &*self.0;
inner.data.downcast_ref::<T>()
}
}
/// Return the memory representation of this extern ref as a byte array in
/// native byte order.
pub fn to_ne_bytes(&self) -> [u8; 8] {
usize::to_ne_bytes(self.0 as usize)
}
/// Convert native endian bytes to an extern ref.
// TODO: this should be unsafe!!
pub fn from_ne_bytes(bytes: [u8; 8]) -> Self {
Self(usize::from_ne_bytes(bytes) as *const _)
}
/// A deep copy of the reference, increments the strong count.
pub fn ref_clone(&self) -> Self {
if self.0.is_null() {