//! Support for a calling of a bounds (exported) function. use crate::value::{pyobj_to_value, value_to_pyobj}; use pyo3::exceptions::Exception; use pyo3::prelude::*; use pyo3::types::{PyAny, PyDict, PyTuple}; use std::rc::Rc; use wasmtime_interface_types::ModuleData; // TODO support non-export functions #[pyclass] pub struct Function { pub instance: wasmtime::Instance, pub export_name: String, pub args_types: Vec, pub data: Rc, } impl Function { pub fn func(&self) -> wasmtime::Func { let e = self .instance .get_export(&self.export_name) .expect("named export") .clone(); e.func().expect("function export").clone() } } #[pymethods] impl Function { #[__call__] #[args(args = "*")] fn call(&self, py: Python, args: &PyTuple) -> PyResult { let mut runtime_args = Vec::new(); for item in args.iter() { runtime_args.push(pyobj_to_value(py, item)?); } let results = self .data .invoke_export(&self.instance, self.export_name.as_str(), &runtime_args) .map_err(crate::err2py)?; let mut py_results = Vec::new(); for result in results { py_results.push(value_to_pyobj(py, result)?); } if py_results.len() == 1 { Ok(py_results[0].clone_ref(py)) } else { Ok(PyTuple::new(py, py_results).to_object(py)) } } } fn parse_annotation_type(s: &str) -> wasmtime::ValType { match s { "I32" | "i32" => wasmtime::ValType::I32, "I64" | "i64" => wasmtime::ValType::I64, "F32" | "f32" => wasmtime::ValType::F32, "F64" | "f64" => wasmtime::ValType::F64, _ => panic!("unknown type in annotations"), } } struct WrappedFn { func: PyObject, returns_types: Vec, } impl WrappedFn { pub fn new(func: PyObject, returns_types: Vec) -> Self { WrappedFn { func, returns_types, } } } impl wasmtime::Callable for WrappedFn { fn call( &self, params: &[wasmtime::Val], returns: &mut [wasmtime::Val], ) -> Result<(), wasmtime::Trap> { let gil = Python::acquire_gil(); let py = gil.python(); let params = params .iter() .map(|p| match p { wasmtime::Val::I32(i) => i.clone().into_py(py), wasmtime::Val::I64(i) => i.clone().into_py(py), _ => { panic!(); } }) .collect::>(); let result = self .func .call(py, PyTuple::new(py, params), None) .expect("TODO: convert result to trap"); let result = if let Ok(t) = result.cast_as::(py) { t } else { if result.is_none() { PyTuple::empty(py) } else { PyTuple::new(py, &[result]) } }; for (i, ty) in self.returns_types.iter().enumerate() { let result_item = result.get_item(i); returns[i] = match ty { wasmtime::ValType::I32 => wasmtime::Val::I32(result_item.extract::().unwrap()), wasmtime::ValType::I64 => wasmtime::Val::I64(result_item.extract::().unwrap()), _ => { panic!(); } }; } Ok(()) } } pub fn wrap_into_pyfunction(store: &wasmtime::Store, callable: &PyAny) -> PyResult { if !callable.hasattr("__annotations__")? { // TODO support calls without annotations? return Err(PyErr::new::( "import is not a function".to_string(), )); } let annot = callable.getattr("__annotations__")?.cast_as::()?; let mut params = Vec::new(); let mut returns = Vec::new(); for (name, value) in annot.iter() { let ty = parse_annotation_type(&value.to_string()); match name.to_string().as_str() { "return" => returns.push(ty), _ => params.push(ty), } } let ft = wasmtime::FuncType::new( params.into_boxed_slice(), returns.clone().into_boxed_slice(), ); let gil = Python::acquire_gil(); let wrapped = WrappedFn::new(callable.to_object(gil.python()), returns); Ok(wasmtime::Func::new(store, ft, Rc::new(wrapped))) }