From 36cb806c54244d5f6321576720557ddb192ad2dd Mon Sep 17 00:00:00 2001 From: Yury Delendik Date: Thu, 14 Nov 2019 10:40:04 -0600 Subject: [PATCH] Use embedding api in python extension (#569) Now embedding API is used in the Python extension, this allows us to remove ModuleData::invoke() from wasmtime-interface-types --- crates/api/src/instance.rs | 4 + crates/interface-types/src/lib.rs | 72 +----- crates/misc/py/Cargo.toml | 1 + crates/misc/py/src/function.rs | 142 ++++++++++-- crates/misc/py/src/import.rs | 374 ------------------------------ crates/misc/py/src/instance.rs | 82 +++---- crates/misc/py/src/lib.rs | 136 +++++++---- crates/misc/py/src/memory.rs | 58 +---- crates/misc/py/src/module.rs | 4 +- crates/misc/py/src/value.rs | 22 -- 10 files changed, 254 insertions(+), 641 deletions(-) delete mode 100644 crates/misc/py/src/import.rs diff --git a/crates/api/src/instance.rs b/crates/api/src/instance.rs index 767b7d22ea..e28d6e8b28 100644 --- a/crates/api/src/instance.rs +++ b/crates/api/src/instance.rs @@ -106,6 +106,10 @@ impl Instance { &self.exports } + pub fn module(&self) -> &HostRef { + &self.module + } + pub fn find_export_by_name(&self, name: &str) -> Option<&Extern> { let (i, _) = self .module diff --git a/crates/interface-types/src/lib.rs b/crates/interface-types/src/lib.rs index e4aa84910c..2ff18a200b 100644 --- a/crates/interface-types/src/lib.rs +++ b/crates/interface-types/src/lib.rs @@ -14,12 +14,11 @@ use alloc::string::ToString; use alloc::vec::Vec; use anyhow::{bail, format_err, Result}; use core::convert::TryFrom; -use core::slice; use core::str; use cranelift_codegen::ir; use wasm_webidl_bindings::ast; use wasmtime_api as api; -use wasmtime_jit::{ActionOutcome, Context, RuntimeValue}; +use wasmtime_jit::RuntimeValue; use wasmtime_runtime::{Export, InstanceHandle}; mod value; @@ -128,33 +127,6 @@ impl ModuleData { self.wasi_module_name.clone() } - /// Same as `Context::invoke` except that this works with a `&[Value]` list - /// instead of a `&[RuntimeValue]` list. (in this case `Value` is the set of - /// wasm interface types) - pub fn invoke( - &self, - cx: &mut Context, - handle: &mut InstanceHandle, - export: &str, - args: &[Value], - ) -> Result> { - let binding = self.binding_for_export(handle, export)?; - let incoming = binding.param_bindings()?; - let outgoing = binding.result_bindings()?; - - let wasm_args = - translate_incoming(&mut RawTranslateContext::new(cx, handle), &incoming, args)?; - let wasm_results = match cx.invoke(handle, export, &wasm_args)? { - ActionOutcome::Returned { values } => values, - ActionOutcome::Trapped { message } => bail!("trapped: {}", message), - }; - translate_outgoing( - &mut RawTranslateContext::new(cx, handle), - &outgoing, - &wasm_results, - ) - } - /// Invokes wasmtime function with a `&[Value]` list. `Value` the set of /// wasm interface types. pub fn invoke_export( @@ -349,48 +321,6 @@ trait TranslateContext { unsafe fn get_memory(&mut self) -> Result<&mut [u8]>; } -struct RawTranslateContext<'a> { - cx: &'a mut Context, - handle: &'a mut InstanceHandle, -} - -impl<'a> RawTranslateContext<'a> { - fn new(cx: &'a mut Context, handle: &'a mut InstanceHandle) -> Self { - RawTranslateContext { cx, handle } - } -} - -impl TranslateContext for RawTranslateContext<'_> { - fn invoke_alloc(&mut self, alloc_func_name: &str, len: i32) -> Result { - let alloc_args = vec![RuntimeValue::I32(len)]; - let results = match self.cx.invoke(self.handle, alloc_func_name, &alloc_args)? { - ActionOutcome::Returned { values } => values, - ActionOutcome::Trapped { message } => bail!("trapped: {}", message), - }; - if results.len() != 1 { - bail!("allocator function wrong number of results"); - } - Ok(match results[0] { - RuntimeValue::I32(i) => i, - _ => bail!("allocator function bad return type"), - }) - } - unsafe fn get_memory(&mut self) -> Result<&mut [u8]> { - let memory = self - .handle - .lookup("memory") - .ok_or_else(|| format_err!("no exported `memory`"))?; - let definition = match memory { - wasmtime_runtime::Export::Memory { definition, .. } => definition, - _ => bail!("export `memory` wasn't a `Memory`"), - }; - Ok(slice::from_raw_parts_mut( - (*definition).base, - (*definition).current_length, - )) - } -} - struct InstanceTranslateContext(pub api::HostRef); impl TranslateContext for InstanceTranslateContext { diff --git a/crates/misc/py/Cargo.toml b/crates/misc/py/Cargo.toml index 309cf0c139..8d8b008ce5 100644 --- a/crates/misc/py/Cargo.toml +++ b/crates/misc/py/Cargo.toml @@ -20,6 +20,7 @@ cranelift-native = "0.50.0" cranelift-entity = "0.50.0" cranelift-wasm = "0.50.0" cranelift-frontend = "0.50.0" +wasmtime = { path = "../../api" } wasmtime-environ = { path = "../../environ" } wasmtime-interface-types = { path = "../../interface-types" } wasmtime-jit = { path = "../../jit" } diff --git a/crates/misc/py/src/function.rs b/crates/misc/py/src/function.rs index 0b2a4f7d20..856377f7ef 100644 --- a/crates/misc/py/src/function.rs +++ b/crates/misc/py/src/function.rs @@ -4,32 +4,30 @@ extern crate alloc; use crate::value::{pyobj_to_value, value_to_pyobj}; use alloc::rc::Rc; -use core::cell::RefCell; -use cranelift_codegen::ir; +use pyo3::exceptions::Exception; use pyo3::prelude::*; -use pyo3::types::PyTuple; +use pyo3::types::{PyAny, PyDict, PyTuple}; +use wasmtime_api as api; use wasmtime_interface_types::ModuleData; -use wasmtime_jit::{Context, InstanceHandle}; -use wasmtime_runtime::Export; // TODO support non-export functions #[pyclass] pub struct Function { - pub context: Rc>, - pub instance: InstanceHandle, + pub instance: api::HostRef, pub export_name: String, - pub args_types: Vec, + pub args_types: Vec, pub data: Rc, } impl Function { - pub fn get_signature(&self) -> ir::Signature { - let mut instance = self.instance.clone(); - if let Some(Export::Function { signature, .. }) = instance.lookup(&self.export_name) { - signature - } else { - panic!() - } + pub fn func(&self) -> api::HostRef { + let e = self + .instance + .borrow() + .find_export_by_name(&self.export_name) + .expect("named export") + .clone(); + e.func().expect("function export").clone() } } @@ -42,16 +40,9 @@ impl Function { for item in args.iter() { runtime_args.push(pyobj_to_value(py, item)?); } - let mut instance = self.instance.clone(); - let mut cx = self.context.borrow_mut(); let results = self .data - .invoke( - &mut cx, - &mut instance, - self.export_name.as_str(), - &runtime_args, - ) + .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 { @@ -64,3 +55,108 @@ impl Function { } } } + +fn parse_annotation_type(s: &str) -> api::ValType { + match s { + "I32" | "i32" => api::ValType::I32, + "I64" | "i64" => api::ValType::I64, + "F32" | "f32" => api::ValType::F32, + "F64" | "f64" => api::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 api::Callable for WrappedFn { + fn call( + &self, + params: &[api::Val], + returns: &mut [api::Val], + ) -> Result<(), api::HostRef> { + let gil = Python::acquire_gil(); + let py = gil.python(); + + let params = params + .iter() + .map(|p| match p { + api::Val::I32(i) => i.clone().into_py(py), + api::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 { + api::ValType::I32 => api::Val::I32(result_item.extract::().unwrap()), + api::ValType::I64 => api::Val::I64(result_item.extract::().unwrap()), + _ => { + panic!(); + } + }; + } + Ok(()) + } +} + +pub fn wrap_into_pyfunction( + store: &api::HostRef, + 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 = api::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); + let f = api::Func::new(store, ft, Rc::new(wrapped)); + Ok(api::HostRef::new(f)) +} diff --git a/crates/misc/py/src/import.rs b/crates/misc/py/src/import.rs deleted file mode 100644 index c5ddb33260..0000000000 --- a/crates/misc/py/src/import.rs +++ /dev/null @@ -1,374 +0,0 @@ -//! Support for a calling of an imported function. - -extern crate alloc; - -use crate::function::Function; -use crate::memory::Memory; -use crate::value::{read_value_from, write_value_to}; -use alloc::rc::Rc; -use core::cell::RefCell; -use core::cmp; -use cranelift_codegen::ir::types; -use cranelift_codegen::ir::{InstBuilder, StackSlotData, StackSlotKind}; -use cranelift_codegen::Context; -use cranelift_codegen::{binemit, ir, isa}; -use cranelift_entity::{EntityRef, PrimaryMap}; -use cranelift_frontend::{FunctionBuilder, FunctionBuilderContext}; -use cranelift_wasm::{DefinedFuncIndex, FuncIndex}; -use pyo3::prelude::*; -use pyo3::types::{PyAny, PyDict, PyTuple}; -use std::collections::{HashMap, HashSet}; -use target_lexicon::HOST; -use wasmtime_environ::{CompiledFunction, Export, Module}; -use wasmtime_jit::CodeMemory; -use wasmtime_runtime::{Imports, InstanceHandle, VMContext, VMFunctionBody}; - -struct BoundPyFunction { - name: String, - obj: PyObject, -} - -struct ImportObjState { - calls: Vec, - #[allow(dead_code)] - code_memory: CodeMemory, -} - -unsafe extern "C" fn stub_fn(vmctx: *mut VMContext, call_id: u32, values_vec: *mut i64) { - let gil = Python::acquire_gil(); - let py = gil.python(); - - let mut instance = InstanceHandle::from_vmctx(vmctx); - let (_name, obj) = { - let state = instance - .host_state() - .downcast_mut::() - .expect("state"); - let name = state.calls[call_id as usize].name.to_owned(); - let obj = state.calls[call_id as usize].obj.clone_ref(py); - (name, obj) - }; - let module = instance.module_ref(); - let signature = &module.signatures[module.functions[FuncIndex::new(call_id as usize)]]; - - let mut args = Vec::new(); - for i in 1..signature.params.len() { - args.push(read_value_from( - py, - values_vec.offset(i as isize - 1), - signature.params[i].value_type, - )) - } - let result = obj.call(py, PyTuple::new(py, args), None).expect("result"); - for i in 0..signature.returns.len() { - let val = if result.is_none() { - 0.into_py(py) // FIXME default ??? - } else { - if i > 0 { - panic!("multiple returns unsupported"); - } - result.clone_ref(py) - }; - write_value_to(py, values_vec.add(i), signature.returns[i].value_type, val); - } -} - -/// Create a trampoline for invoking a python function. -fn make_trampoline( - isa: &dyn isa::TargetIsa, - code_memory: &mut CodeMemory, - fn_builder_ctx: &mut FunctionBuilderContext, - call_id: u32, - signature: &ir::Signature, -) -> *const VMFunctionBody { - // Mostly reverse copy of the similar method from wasmtime's - // wasmtime-jit/src/compiler.rs. - let pointer_type = isa.pointer_type(); - let mut stub_sig = ir::Signature::new(isa.frontend_config().default_call_conv); - - // Add the `vmctx` parameter. - stub_sig.params.push(ir::AbiParam::special( - pointer_type, - ir::ArgumentPurpose::VMContext, - )); - - // Add the `call_id` parameter. - stub_sig.params.push(ir::AbiParam::new(types::I32)); - - // Add the `values_vec` parameter. - stub_sig.params.push(ir::AbiParam::new(pointer_type)); - - let values_vec_len = 8 * cmp::max(signature.params.len() - 1, signature.returns.len()) as u32; - - let mut context = Context::new(); - context.func = - ir::Function::with_name_signature(ir::ExternalName::user(0, 0), signature.clone()); - - let ss = context.func.create_stack_slot(StackSlotData::new( - StackSlotKind::ExplicitSlot, - values_vec_len, - )); - let value_size = 8; - - { - let mut builder = FunctionBuilder::new(&mut context.func, fn_builder_ctx); - let block0 = builder.create_ebb(); - - builder.append_ebb_params_for_function_params(block0); - builder.switch_to_block(block0); - builder.seal_block(block0); - - let values_vec_ptr_val = builder.ins().stack_addr(pointer_type, ss, 0); - let mflags = ir::MemFlags::trusted(); - for i in 1..signature.params.len() { - if i == 0 { - continue; - } - - let val = builder.func.dfg.ebb_params(block0)[i]; - builder.ins().store( - mflags, - val, - values_vec_ptr_val, - ((i - 1) * value_size) as i32, - ); - } - - let vmctx_ptr_val = builder.func.dfg.ebb_params(block0)[0]; - let call_id_val = builder.ins().iconst(types::I32, call_id as i64); - - let callee_args = vec![vmctx_ptr_val, call_id_val, values_vec_ptr_val]; - - let new_sig = builder.import_signature(stub_sig.clone()); - - let callee_value = builder - .ins() - .iconst(pointer_type, stub_fn as *const VMFunctionBody as i64); - builder - .ins() - .call_indirect(new_sig, callee_value, &callee_args); - - let mflags = ir::MemFlags::trusted(); - let mut results = Vec::new(); - for (i, r) in signature.returns.iter().enumerate() { - let load = builder.ins().load( - r.value_type, - mflags, - values_vec_ptr_val, - (i * value_size) as i32, - ); - results.push(load); - } - builder.ins().return_(&results); - builder.finalize() - } - - let mut code_buf: Vec = Vec::new(); - let mut reloc_sink = RelocSink {}; - let mut trap_sink = binemit::NullTrapSink {}; - let mut stackmap_sink = binemit::NullStackmapSink {}; - context - .compile_and_emit( - isa, - &mut code_buf, - &mut reloc_sink, - &mut trap_sink, - &mut stackmap_sink, - ) - .expect("compile_and_emit"); - - let mut unwind_info = Vec::new(); - context.emit_unwind_info(isa, &mut unwind_info); - - code_memory - .allocate_for_function(&CompiledFunction { - body: code_buf, - jt_offsets: context.func.jt_offsets, - unwind_info, - }) - .expect("allocate_for_function") - .as_ptr() -} - -fn parse_annotation_type(s: &str) -> ir::Type { - match s { - "I32" | "i32" => types::I32, - "I64" | "i64" => types::I64, - "F32" | "f32" => types::F32, - "F64" | "f64" => types::F64, - _ => panic!("unknown type in annotations"), - } -} - -fn get_signature_from_py_annotation( - annot: &PyDict, - pointer_type: ir::Type, - call_conv: isa::CallConv, -) -> PyResult { - let mut params = Vec::new(); - params.push(ir::AbiParam::special( - pointer_type, - ir::ArgumentPurpose::VMContext, - )); - let mut returns = None; - for (name, value) in annot.iter() { - let ty = parse_annotation_type(&value.to_string()); - match name.to_string().as_str() { - "return" => returns = Some(ty), - _ => params.push(ir::AbiParam::new(ty)), - } - } - Ok(ir::Signature { - params, - returns: match returns { - Some(r) => vec![ir::AbiParam::new(r)], - None => vec![], - }, - call_conv, - }) -} - -pub fn into_instance_from_obj( - py: Python, - global_exports: Rc>>>, - obj: &PyAny, -) -> PyResult { - let isa = { - let isa_builder = - cranelift_native::builder().expect("host machine is not a supported target"); - let flag_builder = cranelift_codegen::settings::builder(); - isa_builder.finish(cranelift_codegen::settings::Flags::new(flag_builder)) - }; - - let mut fn_builder_ctx = FunctionBuilderContext::new(); - let mut module = Module::new(); - let mut finished_functions: PrimaryMap = - PrimaryMap::new(); - let mut code_memory = CodeMemory::new(); - - let pointer_type = types::Type::triple_pointer_type(&HOST); - let call_conv = isa::CallConv::triple_default(&HOST); - - let obj = obj.cast_as::()?; - let mut bound_functions = Vec::new(); - let mut dependencies = HashSet::new(); - let mut memories = PrimaryMap::new(); - for (name, item) in obj.iter() { - if item.is_callable() { - let sig = if item.get_type().is_subclass::()? { - // TODO faster calls? - let wasm_fn = item.cast_as::()?; - dependencies.insert(wasm_fn.instance.clone()); - wasm_fn.get_signature() - } else if item.hasattr("__annotations__")? { - let annot = item.getattr("__annotations__")?.cast_as::()?; - get_signature_from_py_annotation(&annot, pointer_type, call_conv)? - } else { - // TODO support calls without annotations? - continue; - }; - - let sig_id = module.signatures.push(sig.clone()); - let func_id = module.functions.push(sig_id); - module - .exports - .insert(name.to_string(), Export::Function(func_id)); - let trampoline = make_trampoline( - isa.as_ref(), - &mut code_memory, - &mut fn_builder_ctx, - func_id.index() as u32, - &sig, - ); - finished_functions.push(trampoline); - - bound_functions.push(BoundPyFunction { - name: name.to_string(), - obj: item.into_py(py), - }); - } else if item.get_type().is_subclass::()? { - let wasm_mem = item.cast_as::()?; - dependencies.insert(wasm_mem.instance.clone()); - let plan = wasm_mem.get_plan(); - let mem_id = module.memory_plans.push(plan); - let _mem_id_2 = memories.push(wasm_mem.into_import()); - assert_eq!(mem_id, _mem_id_2); - let _mem_id_3 = module - .imported_memories - .push((String::from(""), String::from(""))); - assert_eq!(mem_id, _mem_id_3); - module - .exports - .insert(name.to_string(), Export::Memory(mem_id)); - } - } - - let imports = Imports::new( - dependencies, - PrimaryMap::new(), - PrimaryMap::new(), - memories, - PrimaryMap::new(), - ); - let data_initializers = Vec::new(); - let signatures = PrimaryMap::new(); - - code_memory.publish(); - - let import_obj_state = ImportObjState { - calls: bound_functions, - code_memory, - }; - - Ok(InstanceHandle::new( - Rc::new(module), - global_exports, - finished_functions.into_boxed_slice(), - imports, - &data_initializers, - signatures.into_boxed_slice(), - None, - Box::new(import_obj_state), - ) - .expect("instance")) -} - -/// We don't expect trampoline compilation to produce any relocations, so -/// this `RelocSink` just asserts that it doesn't recieve any. -struct RelocSink {} - -impl binemit::RelocSink for RelocSink { - fn reloc_ebb( - &mut self, - _offset: binemit::CodeOffset, - _reloc: binemit::Reloc, - _ebb_offset: binemit::CodeOffset, - ) { - panic!("trampoline compilation should not produce ebb relocs"); - } - fn reloc_external( - &mut self, - _offset: binemit::CodeOffset, - _reloc: binemit::Reloc, - _name: &ir::ExternalName, - _addend: binemit::Addend, - ) { - panic!("trampoline compilation should not produce external symbol relocs"); - } - fn reloc_constant( - &mut self, - _code_offset: binemit::CodeOffset, - _reloc: binemit::Reloc, - _constant_offset: ir::ConstantOffset, - ) { - panic!("trampoline compilation should not produce constant relocs"); - } - fn reloc_jt( - &mut self, - _offset: binemit::CodeOffset, - _reloc: binemit::Reloc, - _jt: ir::JumpTable, - ) { - panic!("trampoline compilation should not produce jump table relocs"); - } -} diff --git a/crates/misc/py/src/instance.rs b/crates/misc/py/src/instance.rs index 83616bc67a..e789edec10 100644 --- a/crates/misc/py/src/instance.rs +++ b/crates/misc/py/src/instance.rs @@ -5,18 +5,14 @@ extern crate alloc; use crate::function::Function; use crate::memory::Memory; use alloc::rc::Rc; -use core::cell::RefCell; use pyo3::prelude::*; use pyo3::types::PyDict; -use wasmtime_environ::Export; +use wasmtime_api as api; use wasmtime_interface_types::ModuleData; -use wasmtime_jit::{Context, InstanceHandle}; -use wasmtime_runtime::Export as RuntimeExport; #[pyclass] pub struct Instance { - pub context: Rc>, - pub instance: InstanceHandle, + pub instance: api::HostRef, pub data: Rc, } @@ -27,55 +23,43 @@ impl Instance { let gil = Python::acquire_gil(); let py = gil.python(); let exports = PyDict::new(py); - let mut function_exports = Vec::new(); - let mut memory_exports = Vec::new(); - for (name, export) in self.instance.exports() { - match export { - Export::Function(_) => function_exports.push(name.to_string()), - Export::Memory(_) => memory_exports.push(name.to_string()), + let module = self.instance.borrow().module().clone(); + for (i, e) in module.borrow().exports().iter().enumerate() { + match e.r#type() { + api::ExternType::ExternFunc(ft) => { + let mut args_types = Vec::new(); + for ty in ft.params().iter() { + args_types.push(ty.clone()); + } + let f = Py::new( + py, + Function { + instance: self.instance.clone(), + data: self.data.clone(), + export_name: e.name().to_string(), + args_types, + }, + )?; + exports.set_item(e.name().to_string(), f)?; + } + api::ExternType::ExternMemory(_) => { + let f = Py::new( + py, + Memory { + memory: self.instance.borrow().exports()[i] + .memory() + .unwrap() + .clone(), + }, + )?; + exports.set_item(e.name().to_string(), f)?; + } _ => { // Skip unknown export type. continue; } } } - for name in memory_exports { - if let Some(RuntimeExport::Memory { .. }) = self.instance.lookup(&name) { - let f = Py::new( - py, - Memory { - context: self.context.clone(), - instance: self.instance.clone(), - export_name: name.clone(), - }, - )?; - exports.set_item(name, f)?; - } else { - panic!("memory"); - } - } - for name in function_exports { - if let Some(RuntimeExport::Function { signature, .. }) = self.instance.lookup(&name) { - let mut args_types = Vec::new(); - for index in 1..signature.params.len() { - let ty = signature.params[index].value_type; - args_types.push(ty); - } - let f = Py::new( - py, - Function { - context: self.context.clone(), - instance: self.instance.clone(), - data: self.data.clone(), - export_name: name.clone(), - args_types, - }, - )?; - exports.set_item(name, f)?; - } else { - panic!("function"); - } - } Ok(exports.to_object(py)) } diff --git a/crates/misc/py/src/lib.rs b/crates/misc/py/src/lib.rs index a3bc0be36f..c250acd37c 100644 --- a/crates/misc/py/src/lib.rs +++ b/crates/misc/py/src/lib.rs @@ -1,20 +1,19 @@ #![allow(improper_ctypes)] -use crate::import::into_instance_from_obj; +use crate::function::{wrap_into_pyfunction, Function}; use crate::instance::Instance; use crate::memory::Memory; use crate::module::Module; -use core::cell::RefCell; use pyo3::exceptions::Exception; use pyo3::prelude::*; -use pyo3::types::{PyBytes, PyDict, PySet}; +use pyo3::types::{PyAny, PyBytes, PyDict, PySet}; use pyo3::wrap_pyfunction; use std::rc::Rc; +use wasmtime_api as api; use wasmtime_interface_types::ModuleData; use wasmtime_jit::Features; mod function; -mod import; mod instance; mod memory; mod module; @@ -47,6 +46,38 @@ impl InstantiateResultObject { } } +fn find_export_in( + obj: &PyAny, + store: &api::HostRef, + name: &str, +) -> PyResult { + let obj = obj.cast_as::()?; + + Ok(if let Some(item) = obj.get_item(name) { + if item.is_callable() { + if item.get_type().is_subclass::()? { + let wasm_fn = item.cast_as::()?; + wasm_fn.func().into() + } else { + wrap_into_pyfunction(store, item)?.into() + } + } else if item.get_type().is_subclass::()? { + let wasm_mem = item.cast_as::()?; + wasm_mem.memory.clone().into() + } else { + return Err(PyErr::new::(format!( + "unsupported import type {}", + name + ))); + } + } else { + return Err(PyErr::new::(format!( + "import {} is not found", + name + ))); + }) +} + /// WebAssembly instantiate API method. #[pyfunction] pub fn instantiate( @@ -56,58 +87,67 @@ pub fn instantiate( ) -> PyResult> { let wasm_data = buffer_source.as_bytes(); - let generate_debug_info = false; + let mut config = api::Config::new(); + config.features(Features { + multi_value: true, + ..Default::default() + }); - let isa = { - let isa_builder = cranelift_native::builder().map_err(|s| PyErr::new::(s))?; - let flag_builder = cranelift_codegen::settings::builder(); - isa_builder.finish(cranelift_codegen::settings::Flags::new(flag_builder)) - }; + let engine = api::HostRef::new(api::Engine::new(&config)); + let store = api::HostRef::new(api::Store::new(&engine)); - let mut context = wasmtime_jit::Context::with_isa(isa, wasmtime_jit::CompilationStrategy::Auto) - .with_features(Features { - multi_value: true, - ..Features::default() - }); - context.set_debug_info(generate_debug_info); - let global_exports = context.get_global_exports(); - - for (name, obj) in import_obj.iter() { - context.name_instance( - name.to_string(), - into_instance_from_obj(py, global_exports.clone(), obj)?, - ) - } + let module = api::HostRef::new(api::Module::new(&store, wasm_data).map_err(err2py)?); let data = Rc::new(ModuleData::new(wasm_data).map_err(err2py)?); // If this module expects to be able to use wasi then go ahead and hook // that up into the imported crates. - if let Some(module_name) = data.find_wasi_module_name() { - let wasi_handle = - wasmtime_wasi::instantiate_wasi("", context.get_global_exports(), &[], &[], &[]) - .map_err(|e| err2py(e.into()))?; - context.name_instance(module_name, wasi_handle); + let wasi = if let Some(module_name) = data.find_wasi_module_name() { + let global_exports = store.borrow().global_exports().clone(); + let wasi_handle = wasmtime_wasi::instantiate_wasi("", global_exports, &[], &[], &[]) + .map_err(|e| err2py(e.into()))?; + let instance = + api::Instance::from_handle(&store, wasi_handle).map_err(|e| err2py(e.into()))?; + Some((module_name, instance)) + } else { + None + }; + + let mut imports: Vec = Vec::new(); + for i in module.borrow().imports() { + let module_name = i.module().as_str(); + if let Some(m) = import_obj.get_item(module_name) { + let e = find_export_in(m, &store, i.name().as_str())?; + imports.push(e); + } else if wasi.is_some() && module_name == wasi.as_ref().unwrap().0 { + let e = wasi + .as_ref() + .unwrap() + .1 + .find_export_by_name(i.name().as_str()) + .ok_or_else(|| { + PyErr::new::(format!( + "wasi export {} is not found", + i.name().as_str() + )) + })?; + imports.push(e.clone()); + } else { + return Err(PyErr::new::(format!( + "imported module {} is not found", + module_name + ))); + } } - let instance = context - .instantiate_module(None, wasm_data) - .map_err(|e| err2py(e.into()))?; - - let module = Py::new( - py, - Module { - module: instance.module(), - }, - )?; - - let instance = Py::new( - py, - Instance { - context: Rc::new(RefCell::new(context)), - instance, - data, - }, - )?; + + let instance = api::HostRef::new( + api::Instance::new(&store, &module, &imports) + .map_err(|t| PyErr::new::(format!("instantiated with trap {:?}", t)))?, + ); + + let module = Py::new(py, Module { module })?; + + let instance = Py::new(py, Instance { instance, data })?; Py::new(py, InstantiateResultObject { instance, module }) } diff --git a/crates/misc/py/src/memory.rs b/crates/misc/py/src/memory.rs index 51c8d749e8..8dc161c162 100644 --- a/crates/misc/py/src/memory.rs +++ b/crates/misc/py/src/memory.rs @@ -2,8 +2,6 @@ extern crate alloc; -use alloc::rc::Rc; -use core::cell::RefCell; use core::ptr; use pyo3::class::PyBufferProtocol; use pyo3::exceptions::BufferError; @@ -11,60 +9,18 @@ use pyo3::ffi; use pyo3::prelude::*; use std::ffi::CStr; use std::os::raw::{c_int, c_void}; -use wasmtime_environ::MemoryPlan; -use wasmtime_jit::{Context, InstanceHandle}; -use wasmtime_runtime::{Export, VMMemoryDefinition, VMMemoryImport}; +use wasmtime_api as api; #[pyclass] pub struct Memory { - pub context: Rc>, - pub instance: InstanceHandle, - pub export_name: String, -} - -impl Memory { - fn descriptor(&self) -> *mut VMMemoryDefinition { - let mut instance = self.instance.clone(); - if let Some(Export::Memory { definition, .. }) = instance.lookup(&self.export_name) { - definition - } else { - panic!("memory is expected"); - } - } -} - -impl Memory { - pub fn get_plan(&self) -> MemoryPlan { - let mut instance = self.instance.clone(); - if let Some(Export::Memory { memory, .. }) = instance.lookup(&self.export_name) { - memory - } else { - panic!() - } - } - - pub fn into_import(&self) -> VMMemoryImport { - let mut instance = self.instance.clone(); - if let Some(Export::Memory { - definition, vmctx, .. - }) = instance.lookup(&self.export_name) - { - VMMemoryImport { - from: definition, - vmctx, - } - } else { - panic!() - } - } + pub memory: api::HostRef, } #[pymethods] impl Memory { #[getter(current)] pub fn current(&self) -> u32 { - let current_length = unsafe { (*self.descriptor()).current_length }; - (current_length >> 16) as u32 + self.memory.borrow().size() } pub fn grow(&self, _number: u32) -> u32 { @@ -94,12 +50,10 @@ impl PyBufferProtocol for Memory { 1 }; - let VMMemoryDefinition { - base, - current_length, - } = unsafe { *self.descriptor() }; - unsafe { + let base = self.memory.borrow().data_ptr(); + let current_length = self.memory.borrow().data_size(); + (*view).buf = base as *mut c_void; (*view).len = current_length as isize; (*view).readonly = readonly; diff --git a/crates/misc/py/src/module.rs b/crates/misc/py/src/module.rs index dfe69d21f5..87aa10f6ac 100644 --- a/crates/misc/py/src/module.rs +++ b/crates/misc/py/src/module.rs @@ -2,10 +2,10 @@ extern crate alloc; -use alloc::rc::Rc; use pyo3::prelude::*; +use wasmtime_api as api; #[pyclass] pub struct Module { - pub module: Rc, + pub module: api::HostRef, } diff --git a/crates/misc/py/src/value.rs b/crates/misc/py/src/value.rs index 60ad0074b3..74ab7c61ac 100644 --- a/crates/misc/py/src/value.rs +++ b/crates/misc/py/src/value.rs @@ -1,7 +1,5 @@ //! Utility functions to handle values conversion between abstractions/targets. -use core::ptr; -use cranelift_codegen::ir; use pyo3::exceptions::Exception; use pyo3::prelude::*; use pyo3::types::PyAny; @@ -38,23 +36,3 @@ pub fn value_to_pyobj(py: Python, value: Value) -> PyResult { Value::String(i) => i.into_py(py), }) } - -pub unsafe fn read_value_from(py: Python, ptr: *mut i64, ty: ir::Type) -> PyObject { - match ty { - ir::types::I32 => ptr::read(ptr as *const i32).into_py(py), - ir::types::I64 => ptr::read(ptr as *const i64).into_py(py), - ir::types::F32 => ptr::read(ptr as *const f32).into_py(py), - ir::types::F64 => ptr::read(ptr as *const f64).into_py(py), - _ => panic!("TODO add PyResult to read_value_from"), - } -} - -pub unsafe fn write_value_to(py: Python, ptr: *mut i64, ty: ir::Type, val: PyObject) { - match ty { - ir::types::I32 => ptr::write(ptr as *mut i32, val.extract::(py).expect("i32")), - ir::types::I64 => ptr::write(ptr as *mut i64, val.extract::(py).expect("i64")), - ir::types::F32 => ptr::write(ptr as *mut f32, val.extract::(py).expect("f32")), - ir::types::F64 => ptr::write(ptr as *mut f64, val.extract::(py).expect("f64")), - _ => panic!("TODO add PyResult to write_value_to"), - } -}