use crate::{
ffi, BoxedFn, CallingFrame, Engine, WasmEdgeResult, WasmValue, HOST_FUNCS, HOST_FUNC_FOOTPRINTS,
};
#[cfg(all(feature = "async", target_os = "linux"))]
use crate::{
r#async::fiber::{AsyncCx, AsyncState, FiberFuture},
BoxedAsyncFn, ASYNC_HOST_FUNCS,
};
use core::ffi::c_void;
use parking_lot::Mutex;
use rand::Rng;
use std::{convert::TryInto, sync::Arc};
use wasmedge_types::{
error::{FuncError, HostFuncError, WasmEdgeError},
ValType,
};
pub type CustomFnWrapper = unsafe extern "C" fn(
key_ptr: *mut c_void,
data_ptr: *mut c_void,
calling_frame_ctx: *const ffi::WasmEdge_CallingFrameContext,
params: *const ffi::WasmEdge_Value,
param_len: u32,
returns: *mut ffi::WasmEdge_Value,
return_len: u32,
) -> ffi::WasmEdge_Result;
extern "C" fn wrap_fn(
key_ptr: *mut c_void,
data: *mut std::os::raw::c_void,
call_frame_ctx: *const ffi::WasmEdge_CallingFrameContext,
params: *const ffi::WasmEdge_Value,
param_len: u32,
returns: *mut ffi::WasmEdge_Value,
return_len: u32,
) -> ffi::WasmEdge_Result {
let frame = CallingFrame::create(call_frame_ctx);
let key = key_ptr as *const usize as usize;
let input = {
let raw_input = unsafe {
std::slice::from_raw_parts(
params,
param_len
.try_into()
.expect("len of params should not greater than usize"),
)
};
raw_input.iter().map(|r| (*r).into()).collect::<Vec<_>>()
};
let return_len = return_len
.try_into()
.expect("len of returns should not greater than usize");
let raw_returns = unsafe { std::slice::from_raw_parts_mut(returns, return_len) };
let map_host_func = HOST_FUNCS.read();
match map_host_func.get(&key) {
None => unsafe { ffi::WasmEdge_ResultGen(ffi::WasmEdge_ErrCategory_WASM, 5) },
Some(host_func) => {
let real_fn = Arc::clone(host_func);
let real_fn_locked = real_fn.lock();
drop(map_host_func);
match real_fn_locked(frame, input, data) {
Ok(returns) => {
assert!(returns.len() == return_len, "[wasmedge-sys] check the number of returns of host function. Expected: {}, actual: {}", return_len, returns.len());
for (idx, wasm_value) in returns.into_iter().enumerate() {
raw_returns[idx] = wasm_value.as_raw();
}
ffi::WasmEdge_Result { Code: 0 }
}
Err(err) => match err {
HostFuncError::User(code) => unsafe {
ffi::WasmEdge_ResultGen(ffi::WasmEdge_ErrCategory_UserLevelError, code)
},
HostFuncError::Runtime(code) => unsafe {
ffi::WasmEdge_ResultGen(ffi::WasmEdge_ErrCategory_WASM, code)
},
},
}
}
}
}
#[cfg(all(feature = "async", target_os = "linux"))]
extern "C" fn wrap_async_fn(
key_ptr: *mut c_void,
data: *mut std::os::raw::c_void,
call_frame_ctx: *const ffi::WasmEdge_CallingFrameContext,
params: *const ffi::WasmEdge_Value,
param_len: u32,
returns: *mut ffi::WasmEdge_Value,
return_len: u32,
) -> ffi::WasmEdge_Result {
let input = {
let raw_input = unsafe {
std::slice::from_raw_parts(
params,
param_len
.try_into()
.expect("len of params should not greater than usize"),
)
};
raw_input.iter().map(|r| (*r).into()).collect::<Vec<_>>()
};
let return_len = return_len
.try_into()
.expect("len of returns should not greater than usize");
let raw_returns = unsafe { std::slice::from_raw_parts_mut(returns, return_len) };
let key = key_ptr as *const usize as usize;
let map_host_func = ASYNC_HOST_FUNCS.read();
match map_host_func.get(&key) {
None => unsafe { ffi::WasmEdge_ResultGen(ffi::WasmEdge_ErrCategory_WASM, 5) },
Some(host_func) => {
let real_fn = Arc::clone(host_func);
let real_fn_locked = real_fn.lock();
drop(map_host_func);
let frame = CallingFrame::create(call_frame_ctx);
let async_cx = AsyncCx::new();
let mut future = std::pin::Pin::from(real_fn_locked(frame, input, data));
let result = match unsafe { async_cx.block_on(future.as_mut()) } {
Ok(Ok(ret)) => Ok(ret),
Ok(Err(err)) => Err(err),
Err(_err) => Err(HostFuncError::Runtime(0x07)),
};
match result {
Ok(returns) => {
assert!(returns.len() == return_len, "[wasmedge-sys] check the number of returns of async host function. Expected: {}, actual: {}", return_len, returns.len());
for (idx, wasm_value) in returns.into_iter().enumerate() {
raw_returns[idx] = wasm_value.as_raw();
}
ffi::WasmEdge_Result { Code: 0 }
}
Err(err) => match err {
HostFuncError::User(code) => unsafe {
ffi::WasmEdge_ResultGen(ffi::WasmEdge_ErrCategory_UserLevelError, code)
},
HostFuncError::Runtime(code) => unsafe {
ffi::WasmEdge_ResultGen(ffi::WasmEdge_ErrCategory_WASM, code)
},
},
}
}
}
}
#[derive(Debug)]
pub struct Function {
pub(crate) inner: Arc<Mutex<InnerFunc>>,
pub(crate) registered: bool,
pub(crate) data_owner: bool,
}
impl Function {
pub fn create_sync_func<T>(
ty: &FuncType,
real_fn: BoxedFn,
data: Option<Box<T>>,
cost: u64,
) -> WasmEdgeResult<Self> {
let (data, data_owner) = match data {
Some(d) => (Box::into_raw(d) as *mut std::ffi::c_void, true),
None => (std::ptr::null_mut(), false),
};
unsafe { Self::create_with_data(ty, real_fn, data, data_owner, cost) }
}
unsafe fn create_with_data(
ty: &FuncType,
real_fn: BoxedFn,
data: *mut c_void,
data_owner: bool,
cost: u64,
) -> WasmEdgeResult<Self> {
let mut map_host_func = HOST_FUNCS.write();
let mut rng = rand::thread_rng();
let mut key: usize = rng.gen();
while map_host_func.contains_key(&key) {
key = rng.gen();
}
map_host_func.insert(key, Arc::new(Mutex::new(real_fn)));
drop(map_host_func);
let ctx = ffi::WasmEdge_FunctionInstanceCreateBinding(
ty.inner.0,
Some(wrap_fn),
key as *const usize as *mut c_void,
data,
cost,
);
let footprint = ctx as usize;
let mut footprint_to_id = HOST_FUNC_FOOTPRINTS.lock();
footprint_to_id.insert(footprint, key);
match ctx.is_null() {
true => Err(Box::new(WasmEdgeError::Func(FuncError::Create))),
false => Ok(Self {
inner: Arc::new(Mutex::new(InnerFunc(ctx))),
registered: false,
data_owner,
}),
}
}
#[cfg(all(feature = "async", target_os = "linux"))]
#[cfg_attr(docsrs, doc(cfg(all(feature = "async", target_os = "linux"))))]
pub fn create_async_func<T: Send + Sync>(
ty: &FuncType,
real_fn: BoxedAsyncFn,
data: Option<Box<T>>,
cost: u64,
) -> WasmEdgeResult<Self> {
let (data, data_owner) = match data {
Some(d) => (Box::into_raw(d) as *mut std::ffi::c_void, true),
None => (std::ptr::null_mut(), false),
};
let mut map_host_func = ASYNC_HOST_FUNCS.write();
let mut rng = rand::thread_rng();
let mut key: usize = rng.gen();
while map_host_func.contains_key(&key) {
key = rng.gen();
}
map_host_func.insert(key, Arc::new(Mutex::new(real_fn)));
drop(map_host_func);
let ctx = unsafe {
ffi::WasmEdge_FunctionInstanceCreateBinding(
ty.inner.0,
Some(wrap_async_fn),
key as *const usize as *mut c_void,
data,
cost,
)
};
let footprint = ctx as usize;
let mut footprint_to_id = HOST_FUNC_FOOTPRINTS.lock();
footprint_to_id.insert(footprint, key);
match ctx.is_null() {
true => Err(Box::new(WasmEdgeError::Func(FuncError::Create))),
false => Ok(Self {
inner: Arc::new(Mutex::new(InnerFunc(ctx))),
registered: false,
data_owner,
}),
}
}
pub unsafe fn create_with_custom_wrapper(
ty: &FuncType,
fn_wrapper: CustomFnWrapper,
real_fn: *mut c_void,
data: *mut c_void,
data_owner: bool,
cost: u64,
) -> WasmEdgeResult<Self> {
let ctx = ffi::WasmEdge_FunctionInstanceCreateBinding(
ty.inner.0,
Some(fn_wrapper),
real_fn,
data,
cost,
);
match ctx.is_null() {
true => Err(Box::new(WasmEdgeError::Func(FuncError::Create))),
false => Ok(Self {
inner: Arc::new(Mutex::new(InnerFunc(ctx))),
registered: false,
data_owner,
}),
}
}
pub fn ty(&self) -> WasmEdgeResult<FuncType> {
let ty = unsafe { ffi::WasmEdge_FunctionInstanceGetFunctionType(self.inner.lock().0) };
match ty.is_null() {
true => Err(Box::new(WasmEdgeError::Func(FuncError::Type))),
false => Ok(FuncType {
inner: InnerFuncType(ty as *mut _),
registered: true,
}),
}
}
pub fn call<E: Engine>(
&self,
engine: &E,
args: impl IntoIterator<Item = WasmValue>,
) -> WasmEdgeResult<Vec<WasmValue>> {
engine.run_func(self, args)
}
#[cfg(all(feature = "async", target_os = "linux"))]
#[cfg_attr(docsrs, doc(cfg(all(feature = "async", target_os = "linux"))))]
pub async fn call_async<E: Engine + Send + Sync>(
&self,
async_state: &AsyncState,
engine: &E,
args: impl IntoIterator<Item = WasmValue> + Send,
) -> WasmEdgeResult<Vec<WasmValue>> {
FiberFuture::on_fiber(async_state, || engine.run_func(self, args))
.await
.unwrap()
}
pub fn as_ref(&self) -> FuncRef {
FuncRef {
inner: InnerFuncRef(self.inner.lock().0 as *const _),
}
}
#[cfg(feature = "ffi")]
#[cfg_attr(docsrs, doc(cfg(feature = "ffi")))]
pub fn as_ptr(&self) -> *const ffi::WasmEdge_FunctionInstanceContext {
self.inner.lock().0 as *const _
}
}
impl Drop for Function {
#[allow(clippy::from_raw_with_void_ptr)]
fn drop(&mut self) {
if !self.registered && Arc::strong_count(&self.inner) == 1 {
let footprint = self.inner.lock().0 as usize;
if let Some(key) = HOST_FUNC_FOOTPRINTS.lock().remove(&footprint) {
let mut map_host_func = HOST_FUNCS.write();
if map_host_func.contains_key(&key) {
map_host_func.remove(&key).expect(
"[wasmedge-sys] Failed to remove the host function from HOST_FUNCS_NEW container",
);
}
#[cfg(all(feature = "async", target_os = "linux"))]
{
let mut map_host_func = ASYNC_HOST_FUNCS.write();
if map_host_func.contains_key(&key) {
map_host_func.remove(&key).expect(
"[wasmedge-sys] Failed to remove the host function from ASYNC_HOST_FUNCS container",
);
}
}
} else {
panic!("[wasmedge-sys] Failed to remove the host function from HOST_FUNC_FOOTPRINTS container");
}
if self.data_owner {
let _ = unsafe {
Box::from_raw(
ffi::WasmEdge_FunctionInstanceGetData(self.inner.lock().0) as *mut c_void
)
};
}
if !self.inner.lock().0.is_null() {
unsafe {
ffi::WasmEdge_FunctionInstanceDelete(self.inner.lock().0);
};
}
}
}
}
impl Clone for Function {
fn clone(&self) -> Self {
Self {
inner: self.inner.clone(),
registered: self.registered,
data_owner: self.data_owner,
}
}
}
#[derive(Debug)]
pub(crate) struct InnerFunc(pub(crate) *mut ffi::WasmEdge_FunctionInstanceContext);
unsafe impl Send for InnerFunc {}
unsafe impl Sync for InnerFunc {}
#[derive(Debug)]
pub struct FuncType {
pub(crate) inner: InnerFuncType,
pub(crate) registered: bool,
}
impl FuncType {
pub fn create<I: IntoIterator<Item = ValType>, R: IntoIterator<Item = ValType>>(
args: I,
returns: R,
) -> WasmEdgeResult<Self> {
let param_tys = args
.into_iter()
.map(|x| x.into())
.collect::<Vec<ffi::WasmEdge_ValType>>();
let ret_tys = returns
.into_iter()
.map(|x| x.into())
.collect::<Vec<ffi::WasmEdge_ValType>>();
let ctx = unsafe {
ffi::WasmEdge_FunctionTypeCreate(
param_tys.as_ptr() as *const _,
param_tys.len() as u32,
ret_tys.as_ptr() as *const _,
ret_tys.len() as u32,
)
};
match ctx.is_null() {
true => Err(Box::new(WasmEdgeError::FuncTypeCreate)),
false => Ok(Self {
inner: InnerFuncType(ctx),
registered: false,
}),
}
}
pub fn params_len(&self) -> u32 {
unsafe { ffi::WasmEdge_FunctionTypeGetParametersLength(self.inner.0) }
}
pub fn params_type_iter(&self) -> impl Iterator<Item = ValType> {
let len = self.params_len();
let mut types = Vec::with_capacity(len as usize);
unsafe {
ffi::WasmEdge_FunctionTypeGetParameters(self.inner.0, types.as_mut_ptr(), len);
types.set_len(len as usize);
}
types.into_iter().map(Into::into)
}
pub fn returns_len(&self) -> u32 {
unsafe { ffi::WasmEdge_FunctionTypeGetReturnsLength(self.inner.0) }
}
pub fn returns_type_iter(&self) -> impl Iterator<Item = ValType> {
let len = self.returns_len();
let mut types = Vec::with_capacity(len as usize);
unsafe {
ffi::WasmEdge_FunctionTypeGetReturns(self.inner.0, types.as_mut_ptr(), len);
types.set_len(len as usize);
}
types.into_iter().map(Into::into)
}
#[cfg(feature = "ffi")]
#[cfg_attr(docsrs, doc(cfg(feature = "ffi")))]
pub fn as_ptr(&self) -> *const ffi::WasmEdge_FunctionTypeContext {
self.inner.0 as *const _
}
}
impl Drop for FuncType {
fn drop(&mut self) {
if !self.registered && !self.inner.0.is_null() {
unsafe { ffi::WasmEdge_FunctionTypeDelete(self.inner.0) };
}
}
}
impl From<wasmedge_types::FuncType> for FuncType {
fn from(ty: wasmedge_types::FuncType) -> Self {
let param_tys: Vec<_> = match ty.args() {
Some(args) => args.to_vec(),
None => Vec::new(),
};
let ret_tys: Vec<_> = match ty.returns() {
Some(returns) => returns.to_vec(),
None => Vec::new(),
};
FuncType::create(param_tys, ret_tys).expect("[wasmedge-sys] Failed to convert wasmedge_types::FuncType into wasmedge_sys::FuncType.")
}
}
impl From<FuncType> for wasmedge_types::FuncType {
fn from(ty: FuncType) -> Self {
let args = if ty.params_len() > 0 {
let mut args = Vec::with_capacity(ty.params_len() as usize);
for ty in ty.params_type_iter() {
args.push(ty);
}
Some(args)
} else {
None
};
let returns = if ty.returns_len() > 0 {
let mut returns = Vec::with_capacity(ty.returns_len() as usize);
for ty in ty.returns_type_iter() {
returns.push(ty);
}
Some(returns)
} else {
None
};
wasmedge_types::FuncType::new(args, returns)
}
}
#[derive(Debug)]
pub(crate) struct InnerFuncType(pub(crate) *mut ffi::WasmEdge_FunctionTypeContext);
unsafe impl Send for InnerFuncType {}
unsafe impl Sync for InnerFuncType {}
#[derive(Debug, Clone)]
pub struct FuncRef {
pub(crate) inner: InnerFuncRef,
}
impl Drop for FuncRef {
fn drop(&mut self) {
self.inner.0 = std::ptr::null();
}
}
impl FuncRef {
pub fn ty(&self) -> WasmEdgeResult<FuncType> {
let ty = unsafe { ffi::WasmEdge_FunctionInstanceGetFunctionType(self.inner.0 as *mut _) };
match ty.is_null() {
true => Err(Box::new(WasmEdgeError::Func(FuncError::Type))),
false => Ok(FuncType {
inner: InnerFuncType(ty as *mut _),
registered: true,
}),
}
}
pub fn call<E: Engine>(
&self,
engine: &E,
args: impl IntoIterator<Item = WasmValue>,
) -> WasmEdgeResult<Vec<WasmValue>> {
engine.run_func_ref(self, args)
}
}
#[derive(Debug, Clone)]
pub(crate) struct InnerFuncRef(pub(crate) *const ffi::WasmEdge_FunctionInstanceContext);
unsafe impl Send for InnerFuncRef {}
unsafe impl Sync for InnerFuncRef {}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(all(feature = "async", target_os = "linux"))]
use crate::{r#async::AsyncWasiModule, WasiInstance, ASYNC_HOST_FUNCS};
use crate::{types::WasmValue, AsImport, Executor, ImportModule, Store, HOST_FUNC_FOOTPRINTS};
use std::{
sync::{Arc, Mutex},
thread,
};
use wasmedge_macro::sys_host_function;
use wasmedge_types::{NeverType, ValType};
#[test]
fn test_func_type() {
{
let param_tys = vec![
ValType::I32,
ValType::I64,
ValType::F32,
ValType::F64,
ValType::V128,
ValType::ExternRef,
];
let param_len = param_tys.len();
let ret_tys = vec![ValType::FuncRef, ValType::ExternRef, ValType::V128];
let ret_len = ret_tys.len();
let result = FuncType::create(param_tys, ret_tys);
assert!(result.is_ok());
let func_ty = result.unwrap();
assert_eq!(func_ty.params_len(), param_len as u32);
let param_tys = func_ty.params_type_iter().collect::<Vec<_>>();
assert_eq!(
param_tys,
vec![
ValType::I32,
ValType::I64,
ValType::F32,
ValType::F64,
ValType::V128,
ValType::ExternRef,
]
);
assert_eq!(func_ty.returns_len(), ret_len as u32);
let return_tys = func_ty.returns_type_iter().collect::<Vec<_>>();
assert_eq!(
return_tys,
vec![ValType::FuncRef, ValType::ExternRef, ValType::V128]
);
}
{
let result = FuncType::create([], []);
assert!(result.is_ok());
let func_ty = result.unwrap();
assert_eq!(func_ty.params_len(), 0);
assert_eq!(func_ty.returns_len(), 0);
}
}
#[test]
fn test_func_basic() {
#[derive(Debug)]
struct Data<T, S> {
_x: i32,
_y: String,
_v: Vec<T>,
_s: Vec<S>,
}
let data: Data<i32, &str> = Data {
_x: 12,
_y: "hello".to_string(),
_v: vec![1, 2, 3],
_s: vec!["macos", "linux", "windows"],
};
fn real_add<T: core::fmt::Debug>(
_frame: CallingFrame,
input: Vec<WasmValue>,
data: *mut std::ffi::c_void,
) -> Result<Vec<WasmValue>, HostFuncError> {
println!("Rust: Entering Rust function real_add");
let host_data = unsafe { &mut *(data as *mut T) };
println!("host_data: {:?}", host_data);
if input.len() != 2 {
return Err(HostFuncError::User(1));
}
let a = if input[0].ty() == ValType::I32 {
input[0].to_i32()
} else {
return Err(HostFuncError::User(2));
};
let b = if input[1].ty() == ValType::I32 {
input[1].to_i32()
} else {
return Err(HostFuncError::User(3));
};
let c = a + b;
println!("Rust: calcuating in real_add c: {c:?}");
println!("Rust: Leaving Rust function real_add");
Ok(vec![WasmValue::from_i32(c)])
}
assert_eq!(HOST_FUNCS.read().len(), 0);
assert_eq!(HOST_FUNC_FOOTPRINTS.lock().len(), 0);
let result = FuncType::create(vec![ValType::I32; 2], vec![ValType::I32]);
assert!(result.is_ok());
let func_ty = result.unwrap();
let result = Function::create_sync_func(
&func_ty,
Box::new(real_add::<Data<i32, &str>>),
Some(Box::new(data)),
0,
);
assert!(result.is_ok());
let host_func = result.unwrap();
let result = host_func.ty();
assert!(result.is_ok());
let ty = result.unwrap();
assert_eq!(ty.params_len(), 2);
let param_tys = ty.params_type_iter().collect::<Vec<_>>();
assert_eq!(param_tys, vec![ValType::I32; 2]);
assert_eq!(ty.returns_len(), 1);
let return_tys = ty.returns_type_iter().collect::<Vec<_>>();
assert_eq!(return_tys, vec![ValType::I32]);
let result = Executor::create(None, None);
assert!(result.is_ok());
let mut executor = result.unwrap();
let result = host_func.call(
&mut executor,
vec![WasmValue::from_i32(1), WasmValue::from_i32(2)],
);
assert!(result.is_ok());
let returns = result.unwrap();
assert_eq!(returns[0].to_i32(), 3);
}
#[test]
#[allow(clippy::assertions_on_result_states)]
fn test_func_create_host_func_in_host_func() {
#[sys_host_function]
fn func(
_frame: CallingFrame,
_input: Vec<WasmValue>,
) -> Result<Vec<WasmValue>, HostFuncError> {
println!("Entering host function: func");
let handler = std::thread::spawn(|| {
#[sys_host_function]
fn real_add(
_frame: CallingFrame,
input: Vec<WasmValue>,
) -> Result<Vec<WasmValue>, HostFuncError> {
println!("Rust: Entering Rust function real_add");
if input.len() != 2 {
return Err(HostFuncError::User(1));
}
let a = if input[0].ty() == ValType::I32 {
input[0].to_i32()
} else {
return Err(HostFuncError::User(2));
};
let b = if input[1].ty() == ValType::I32 {
input[1].to_i32()
} else {
return Err(HostFuncError::User(3));
};
let c = a + b;
println!("Rust: Leaving Rust function real_add");
Ok(vec![WasmValue::from_i32(c)])
}
let result = FuncType::create(vec![ValType::I32; 2], vec![ValType::I32]);
assert!(result.is_ok());
let func_ty = result.unwrap();
let result =
Function::create_sync_func::<NeverType>(&func_ty, Box::new(real_add), None, 0);
assert!(result.is_ok());
let host_func = result.unwrap();
let result = Executor::create(None, None);
assert!(result.is_ok());
let mut executor = result.unwrap();
let result = host_func.call(
&mut executor,
vec![WasmValue::from_i32(1), WasmValue::from_i32(2)],
);
assert!(result.is_ok());
let returns = result.unwrap();
assert_eq!(returns[0].to_i32(), 3);
});
handler.join().unwrap();
println!("Leaving host function: func");
Ok(vec![])
}
let result = FuncType::create(vec![], vec![]);
assert!(result.is_ok());
let func_ty = result.unwrap();
let result = Function::create_sync_func::<NeverType>(&func_ty, Box::new(func), None, 0);
assert!(result.is_ok());
let host_func = result.unwrap();
let result = Executor::create(None, None);
assert!(result.is_ok());
let mut executor = result.unwrap();
let result = host_func.call(&mut executor, vec![]);
assert!(result.is_ok());
}
#[test]
fn test_func_send() {
let result = FuncType::create(vec![ValType::I32; 2], vec![ValType::I32]);
assert!(result.is_ok());
let func_ty = result.unwrap();
let result = Function::create_sync_func::<NeverType>(&func_ty, Box::new(real_add), None, 0);
assert!(result.is_ok());
let host_func = result.unwrap();
let handle = thread::spawn(move || {
let result = host_func.ty();
assert!(result.is_ok());
let ty = result.unwrap();
assert_eq!(ty.params_len(), 2);
let param_tys = ty.params_type_iter().collect::<Vec<_>>();
assert_eq!(param_tys, vec![ValType::I32; 2]);
assert_eq!(ty.returns_len(), 1);
let return_tys = ty.returns_type_iter().collect::<Vec<_>>();
assert_eq!(return_tys, vec![ValType::I32]);
});
handle.join().unwrap()
}
#[test]
fn test_func_sync() {
let result = FuncType::create(vec![ValType::I32; 2], vec![ValType::I32]);
assert!(result.is_ok());
let func_ty = result.unwrap();
let result = Function::create_sync_func::<NeverType>(&func_ty, Box::new(real_add), None, 0);
assert!(result.is_ok());
let host_func = Arc::new(Mutex::new(result.unwrap()));
let host_func_cloned = Arc::clone(&host_func);
let handle = thread::spawn(move || {
let result = host_func_cloned.lock();
assert!(result.is_ok());
let host_func = result.unwrap();
let result = host_func.ty();
assert!(result.is_ok());
let ty = result.unwrap();
assert_eq!(ty.params_len(), 2);
let param_tys = ty.params_type_iter().collect::<Vec<_>>();
assert_eq!(param_tys, vec![ValType::I32; 2]);
assert_eq!(ty.returns_len(), 1);
let return_tys = ty.returns_type_iter().collect::<Vec<_>>();
assert_eq!(return_tys, vec![ValType::I32]);
});
handle.join().unwrap();
}
#[sys_host_function]
fn real_add(
_frame: CallingFrame,
input: Vec<WasmValue>,
) -> Result<Vec<WasmValue>, HostFuncError> {
println!("Rust: Entering Rust function real_add");
if input.len() != 2 {
return Err(HostFuncError::User(1));
}
let a = if input[0].ty() == ValType::I32 {
input[0].to_i32()
} else {
return Err(HostFuncError::User(2));
};
let b = if input[1].ty() == ValType::I32 {
input[1].to_i32()
} else {
return Err(HostFuncError::User(3));
};
let c = a + b;
println!("Rust: calcuating in real_add c: {c:?}");
println!("Rust: Leaving Rust function real_add");
Ok(vec![WasmValue::from_i32(c)])
}
#[test]
fn test_func_closure() -> Result<(), Box<dyn std::error::Error>> {
{
let real_add = |_: CallingFrame,
input: Vec<WasmValue>,
_: *mut std::os::raw::c_void|
-> Result<Vec<WasmValue>, HostFuncError> {
println!("Rust: Entering Rust function real_add");
if input.len() != 2 {
return Err(HostFuncError::User(1));
}
let a = if input[0].ty() == ValType::I32 {
input[0].to_i32()
} else {
return Err(HostFuncError::User(2));
};
let b = if input[1].ty() == ValType::I32 {
input[1].to_i32()
} else {
return Err(HostFuncError::User(3));
};
let c = a + b;
println!("Rust: calcuating in real_add c: {c:?}");
println!("Rust: Leaving Rust function real_add");
Ok(vec![WasmValue::from_i32(c)])
};
let result = FuncType::create(vec![ValType::I32; 2], vec![ValType::I32]);
assert!(result.is_ok());
let func_ty = result.unwrap();
let result =
Function::create_sync_func::<NeverType>(&func_ty, Box::new(real_add), None, 0);
assert!(result.is_ok());
let host_func = result.unwrap();
let result = Store::create();
assert!(result.is_ok());
let mut store = result.unwrap();
let mut import = ImportModule::<NeverType>::create("extern", None)?;
import.add_func("add", host_func);
let result = Executor::create(None, None);
assert!(result.is_ok());
let mut executor = result.unwrap();
executor.register_import_module(&mut store, &import)?;
let extern_instance = store.module("extern")?;
let add = extern_instance.get_func("add")?;
let result =
executor.call_func(&add, vec![WasmValue::from_i32(1), WasmValue::from_i32(2)]);
assert!(result.is_ok());
let returns = result.unwrap();
assert_eq!(returns[0].to_i32(), 3);
}
assert_eq!(HOST_FUNCS.read().len(), 0);
assert_eq!(HOST_FUNC_FOOTPRINTS.lock().len(), 0);
Ok(())
}
#[test]
fn test_func_drop_v1() -> Result<(), Box<dyn std::error::Error>> {
let real_add = |_: CallingFrame,
input: Vec<WasmValue>,
_: *mut std::os::raw::c_void|
-> Result<Vec<WasmValue>, HostFuncError> {
println!("Rust: Entering Rust function real_add");
if input.len() != 2 {
return Err(HostFuncError::User(1));
}
let a = if input[0].ty() == ValType::I32 {
input[0].to_i32()
} else {
return Err(HostFuncError::User(2));
};
let b = if input[1].ty() == ValType::I32 {
input[1].to_i32()
} else {
return Err(HostFuncError::User(3));
};
let c = a + b;
println!("Rust: calcuating in real_add c: {c:?}");
println!("Rust: Leaving Rust function real_add");
Ok(vec![WasmValue::from_i32(c)])
};
let result = FuncType::create(vec![ValType::I32; 2], vec![ValType::I32]);
assert!(result.is_ok());
let func_ty = result.unwrap();
let result = Function::create_sync_func::<NeverType>(&func_ty, Box::new(real_add), None, 0);
assert!(result.is_ok());
let host_func = result.unwrap();
assert_eq!(Arc::strong_count(&host_func.inner), 1);
assert!(!host_func.registered);
assert_eq!(HOST_FUNCS.read().len(), 1);
assert_eq!(HOST_FUNC_FOOTPRINTS.lock().len(), 1);
let host_func_cloned = host_func.clone();
assert_eq!(Arc::strong_count(&host_func_cloned.inner), 2);
assert!(!host_func_cloned.registered);
assert_eq!(HOST_FUNCS.read().len(), 1);
assert_eq!(HOST_FUNC_FOOTPRINTS.lock().len(), 1);
let mut import = ImportModule::<NeverType>::create("extern", None)?;
import.add_func("add", host_func);
assert_eq!(Arc::strong_count(&host_func_cloned.inner), 2);
assert!(!host_func_cloned.registered);
assert_eq!(HOST_FUNCS.read().len(), 1);
assert_eq!(HOST_FUNC_FOOTPRINTS.lock().len(), 1);
drop(host_func_cloned);
assert_eq!(HOST_FUNCS.read().len(), 1);
assert_eq!(HOST_FUNC_FOOTPRINTS.lock().len(), 1);
let result = Store::create();
assert!(result.is_ok());
let mut store = result.unwrap();
let result = Executor::create(None, None);
assert!(result.is_ok());
let mut executor = result.unwrap();
executor.register_import_module(&mut store, &import)?;
let extern_instance = store.module("extern")?;
let add = extern_instance.get_func("add")?;
assert_eq!(Arc::strong_count(&add.inner), 1);
assert!(add.registered);
assert_eq!(HOST_FUNCS.read().len(), 1);
assert_eq!(HOST_FUNC_FOOTPRINTS.lock().len(), 1);
let add_cloned = add.clone();
assert_eq!(Arc::strong_count(&add.inner), 2);
assert!(add.registered);
assert_eq!(Arc::strong_count(&add_cloned.inner), 2);
assert!(add_cloned.registered);
assert_eq!(HOST_FUNCS.read().len(), 1);
assert_eq!(HOST_FUNC_FOOTPRINTS.lock().len(), 1);
drop(add_cloned);
assert_eq!(Arc::strong_count(&add.inner), 1);
assert!(add.registered);
assert_eq!(HOST_FUNCS.read().len(), 1);
assert_eq!(HOST_FUNC_FOOTPRINTS.lock().len(), 1);
drop(add);
assert_eq!(HOST_FUNCS.read().len(), 1);
assert_eq!(HOST_FUNC_FOOTPRINTS.lock().len(), 1);
let extern_instance = store.module("extern")?;
let add_again = extern_instance.get_func("add")?;
assert_eq!(Arc::strong_count(&add_again.inner), 1);
assert!(add_again.registered);
assert_eq!(HOST_FUNCS.read().len(), 1);
assert_eq!(HOST_FUNC_FOOTPRINTS.lock().len(), 1);
dbg!("drop add_again");
drop(add_again);
assert_eq!(HOST_FUNCS.read().len(), 1);
assert_eq!(HOST_FUNC_FOOTPRINTS.lock().len(), 1);
dbg!("drop import");
drop(import);
assert!(store.module("extern").is_err());
assert_eq!(HOST_FUNCS.read().len(), 0);
assert_eq!(HOST_FUNC_FOOTPRINTS.lock().len(), 0);
Ok(())
}
#[test]
fn test_func_drop_v2() {
#[derive(Debug)]
struct Data<T, S> {
_x: i32,
_y: String,
_v: Vec<T>,
_s: Vec<S>,
}
let data: Data<i32, &str> = Data {
_x: 12,
_y: "hello".to_string(),
_v: vec![1, 2, 3],
_s: vec!["macos", "linux", "windows"],
};
fn real_add<T: core::fmt::Debug>(
_frame: CallingFrame,
input: Vec<WasmValue>,
data: *mut std::ffi::c_void,
) -> Result<Vec<WasmValue>, HostFuncError> {
println!("Rust: Entering Rust function real_add");
let host_data = unsafe { &mut *(data as *mut T) };
println!("host_data: {:?}", host_data);
if input.len() != 2 {
return Err(HostFuncError::User(1));
}
let a = if input[0].ty() == ValType::I32 {
input[0].to_i32()
} else {
return Err(HostFuncError::User(2));
};
let b = if input[1].ty() == ValType::I32 {
input[1].to_i32()
} else {
return Err(HostFuncError::User(3));
};
let c = a + b;
println!("Rust: calcuating in real_add c: {c:?}");
println!("Rust: Leaving Rust function real_add");
Ok(vec![WasmValue::from_i32(c)])
}
assert_eq!(HOST_FUNCS.read().len(), 0);
assert_eq!(HOST_FUNC_FOOTPRINTS.lock().len(), 0);
let result = FuncType::create(vec![ValType::I32; 2], vec![ValType::I32]);
assert!(result.is_ok());
let func_ty = result.unwrap();
let result = Function::create_sync_func(
&func_ty,
Box::new(real_add::<Data<i32, &str>>),
Some(Box::new(data)),
0,
);
assert!(result.is_ok());
let host_func = result.unwrap();
let host_func_cloned = host_func.clone();
drop(host_func);
drop(host_func_cloned);
}
#[cfg(all(feature = "async", target_os = "linux"))]
#[tokio::test]
async fn test_func_async_closure() -> Result<(), Box<dyn std::error::Error>> {
{
#[derive(Debug)]
struct Data<T, S> {
_x: i32,
_y: String,
_v: Vec<T>,
_s: Vec<S>,
}
impl<T, S> Drop for Data<T, S> {
fn drop(&mut self) {
println!("Dropping Data");
}
}
let data: Data<i32, &str> = Data {
_x: 12,
_y: "hello".to_string(),
_v: vec![1, 2, 3],
_s: vec!["macos", "linux", "windows"],
};
let c = |_frame: CallingFrame,
_args: Vec<WasmValue>,
data: *mut std::os::raw::c_void|
-> Box<
(dyn std::future::Future<Output = Result<Vec<WasmValue>, HostFuncError>> + Send),
> {
let host_data = unsafe { &mut *(data as *mut Data<i32, &str>) };
Box::new(async move {
for _ in 0..10 {
println!("[async hello] say hello");
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
println!("host_data: {:?}", host_data);
}
println!("[async hello] Done!");
Ok(vec![])
})
};
let result = FuncType::create(vec![], vec![]);
assert!(result.is_ok());
let func_ty = result.unwrap();
let result =
Function::create_async_func(&func_ty, Box::new(c), Some(Box::new(data)), 0);
assert!(result.is_ok());
let async_hello_func = result.unwrap();
let result = Executor::create(None, None);
assert!(result.is_ok());
let mut executor = result.unwrap();
assert!(!executor.inner.0.is_null());
let result = Store::create();
assert!(result.is_ok());
let mut store = result.unwrap();
let result = AsyncWasiModule::create(Some(vec!["abc"]), Some(vec![("a", "1")]), None);
assert!(result.is_ok());
let async_wasi_module = result.unwrap();
let wasi_import = WasiInstance::AsyncWasi(async_wasi_module);
let result = executor.register_wasi_instance(&mut store, &wasi_import);
assert!(result.is_ok());
let mut import = ImportModule::<NeverType>::create("extern", None)?;
import.add_func("async_hello", async_hello_func);
executor.register_import_module(&mut store, &import)?;
let extern_instance = store.module("extern")?;
let async_hello = extern_instance.get_func("async_hello")?;
async fn tick() {
let mut i = 0;
loop {
println!("[tick] i={i}");
tokio::time::sleep(std::time::Duration::from_millis(500)).await;
i += 1;
}
}
tokio::spawn(tick());
let async_state = AsyncState::new();
let _ = executor
.call_func_async(&async_state, &async_hello, [])
.await?;
}
assert_eq!(ASYNC_HOST_FUNCS.read().len(), 0);
assert_eq!(HOST_FUNC_FOOTPRINTS.lock().len(), 0);
Ok(())
}
#[cfg(all(feature = "async", target_os = "linux"))]
#[tokio::test]
async fn test_func_async_func() -> Result<(), Box<dyn std::error::Error>> {
{
#[derive(Debug)]
struct Data<T, S> {
_x: i32,
_y: String,
_v: Vec<T>,
_s: Vec<S>,
}
let data: Data<i32, &str> = Data {
_x: 12,
_y: "hello".to_string(),
_v: vec![1, 2, 3],
_s: vec!["macos", "linux", "windows"],
};
fn f<T: core::fmt::Debug + Send + Sync + 'static>(
_frame: CallingFrame,
_args: Vec<WasmValue>,
data: *mut std::ffi::c_void,
) -> Box<(dyn std::future::Future<Output = Result<Vec<WasmValue>, HostFuncError>> + Send)>
{
let data = unsafe { &mut *(data as *mut T) };
Box::new(async move {
for _ in 0..10 {
println!("[async hello] say hello");
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
println!("host_data: {:?}", data);
}
println!("[async hello] Done!");
Ok(vec![])
})
}
let result = FuncType::create(vec![], vec![]);
assert!(result.is_ok());
let func_ty = result.unwrap();
let result = Function::create_async_func(
&func_ty,
Box::new(f::<Data<i32, &str>>),
Some(Box::new(data)),
0,
);
assert!(result.is_ok());
let async_hello_func = result.unwrap();
assert_eq!(ASYNC_HOST_FUNCS.read().len(), 1);
assert_eq!(HOST_FUNC_FOOTPRINTS.lock().len(), 1);
let result = Executor::create(None, None);
assert!(result.is_ok());
let mut executor = result.unwrap();
assert!(!executor.inner.0.is_null());
let result = Store::create();
assert!(result.is_ok());
let mut store = result.unwrap();
let result = AsyncWasiModule::create(Some(vec!["abc"]), Some(vec![("a", "1")]), None);
assert!(result.is_ok());
let async_wasi_module = result.unwrap();
let wasi_import = WasiInstance::AsyncWasi(async_wasi_module);
let result = executor.register_wasi_instance(&mut store, &wasi_import);
assert!(result.is_ok());
let mut import = ImportModule::<NeverType>::create("extern", None)?;
import.add_func("async_hello", async_hello_func);
executor.register_import_module(&mut store, &import)?;
let extern_instance = store.module("extern")?;
let async_hello = extern_instance.get_func("async_hello")?;
async fn tick() {
let mut i = 0;
loop {
println!("[tick] i={i}");
tokio::time::sleep(std::time::Duration::from_millis(500)).await;
i += 1;
}
}
tokio::spawn(tick());
let async_state = AsyncState::new();
let _ = executor
.call_func_async(&async_state, &async_hello, [])
.await?;
assert_eq!(ASYNC_HOST_FUNCS.read().len(), 1);
assert_eq!(HOST_FUNC_FOOTPRINTS.lock().len(), 1);
drop(import);
}
assert_eq!(ASYNC_HOST_FUNCS.read().len(), 0);
assert_eq!(HOST_FUNC_FOOTPRINTS.lock().len(), 0);
Ok(())
}
}