Add initial support for WebAssembly Interface Types (#282)

This commit adds initial support for [WebAssembly Interface
Types][proposal] to wasmtime. This is all intended to be quite
experimental, so experimental in fact that even the name of the
[proposal] is still in flux. (this has otherwise been known as "host
bindings" or "webidl bindings" or "wasm bindings").

The goal of this commit is to start adding support the wasmtime set of
crates for WebAssembly Interface Types. A new `wasmtime-interface-types`
crate has been added with very basic support for dynamically invoking
and inspecting the various bindings of a module. This is in turn powered
by the `wasm-webidl-bindings` crate which is shared with the
`wasm-bindgen` CLI tool as a producer of this section.

Currently the only integration in `wasmtime`-the-binary itself is that
when passed the `--invoke` argument the CLI will now attempt to invoke
the target function with arguments as parsed from the command line
itself. For example if you export a function like:

    fn render(&str) -> String

Then passing `--invoke render` will require one argument on the command
line, which is the first argument as a string, and the return value is
printed to the console. This differs from today's interpretation of
`--invoke` where it is a failure if the invoked function takes more than
one argument and the return values are currently ignored.

This is intended to also be the basis of embedding wasmtime in other
contexts which also want to consume WebAssembly interface types. A
Python extension is also added to this repository which implements the
`wasmtime` package on PyPI. This Python extension is intended to make it
as easy as `pip3 install wasmtime` to load a WebAssembly file with
WebAssembly Interface Types into Python. Extensions for other languages
is of course possible as well!

One of the major missing pieces from this is handling imported functions
with interface bindings. Currently the embedding support doesn't have
much ability to support handling imports ergonomically, so it's intended
that this will be included in a follow-up patch.

[proposal]: https://github.com/webassembly/webidl-bindings

Co-authored-by: Yury Delendik <ydelendik@mozilla.com>
This commit is contained in:
Alex Crichton
2019-08-19 06:32:13 -05:00
committed by Till Schneidereit
parent 7009c8dd73
commit af2b4e4946
38 changed files with 2277 additions and 84 deletions

View File

@@ -0,0 +1,83 @@
//! Memory management for executable code.
// Copy of wasmtime's wasmtime-jit/src/code_memory.rs
use core::{cmp, mem};
use region;
use std::string::String;
use std::vec::Vec;
use wasmtime_runtime::{Mmap, VMFunctionBody};
/// Memory manager for executable code.
pub(crate) struct CodeMemory {
current: Mmap,
mmaps: Vec<Mmap>,
position: usize,
published: usize,
}
impl CodeMemory {
/// Create a new `CodeMemory` instance.
pub fn new() -> Self {
Self {
current: Mmap::new(),
mmaps: Vec::new(),
position: 0,
published: 0,
}
}
/// Allocate `size` bytes of memory which can be made executable later by
/// calling `publish()`. Note that we allocate the memory as writeable so
/// that it can be written to and patched, though we make it readonly before
/// actually executing from it.
///
/// TODO: Add an alignment flag.
fn allocate(&mut self, size: usize) -> Result<&mut [u8], String> {
if self.current.len() - self.position < size {
self.mmaps.push(mem::replace(
&mut self.current,
Mmap::with_at_least(cmp::max(0x10000, size))?,
));
self.position = 0;
}
let old_position = self.position;
self.position += size;
Ok(&mut self.current.as_mut_slice()[old_position..self.position])
}
/// Convert mut a slice from u8 to VMFunctionBody.
fn view_as_mut_vmfunc_slice(slice: &mut [u8]) -> &mut [VMFunctionBody] {
let byte_ptr: *mut [u8] = slice;
let body_ptr = byte_ptr as *mut [VMFunctionBody];
unsafe { &mut *body_ptr }
}
/// Allocate enough memory to hold a copy of `slice` and copy the data into it.
/// TODO: Reorganize the code that calls this to emit code directly into the
/// mmap region rather than into a Vec that we need to copy in.
pub fn allocate_copy_of_byte_slice(
&mut self,
slice: &[u8],
) -> Result<&mut [VMFunctionBody], String> {
let new = self.allocate(slice.len())?;
new.copy_from_slice(slice);
Ok(Self::view_as_mut_vmfunc_slice(new))
}
/// Make all allocated memory executable.
pub fn publish(&mut self) {
self.mmaps
.push(mem::replace(&mut self.current, Mmap::new()));
self.position = 0;
for m in &mut self.mmaps[self.published..] {
if m.len() != 0 {
unsafe {
region::protect(m.as_mut_ptr(), m.len(), region::Protection::ReadExecute)
}
.expect("unable to make memory readonly and executable");
}
}
self.published = self.mmaps.len();
}
}

View File

@@ -0,0 +1,66 @@
//! Support for a calling of a bounds (exported) function.
use pyo3::prelude::*;
use pyo3::types::PyTuple;
use crate::value::{pyobj_to_value, value_to_pyobj};
use std::cell::RefCell;
use std::rc::Rc;
use cranelift_codegen::ir;
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<RefCell<Context>>,
pub instance: InstanceHandle,
pub export_name: String,
pub args_types: Vec<ir::Type>,
pub data: Rc<ModuleData>,
}
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!()
}
}
}
#[pymethods]
impl Function {
#[__call__]
#[args(args = "*")]
fn call(&self, py: Python, args: &PyTuple) -> PyResult<PyObject> {
let mut runtime_args = Vec::new();
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,
)
.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))
}
}
}

View File

@@ -0,0 +1,357 @@
//! Support for a calling of an imported function.
use pyo3::prelude::*;
use pyo3::types::{PyAny, PyDict, PyTuple};
use crate::code_memory::CodeMemory;
use crate::function::Function;
use crate::memory::Memory;
use crate::value::{read_value_from, write_value_to};
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 target_lexicon::HOST;
use wasmtime_environ::{Export, Module};
use wasmtime_runtime::{Imports, InstanceHandle, VMContext, VMFunctionBody};
use core::cmp;
use std::cell::RefCell;
use std::collections::{HashMap, HashSet};
use std::rc::Rc;
struct BoundPyFunction {
name: String,
obj: PyObject,
}
struct ImportObjState {
calls: Vec<BoundPyFunction>,
#[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::<ImportObjState>()
.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_object(py) // FIXME default ???
} else {
if i > 0 {
panic!("multiple returns unsupported");
}
result.clone_ref(py)
};
write_value_to(
py,
values_vec.offset(i as isize),
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<u8> = Vec::new();
let mut reloc_sink = RelocSink {};
let mut trap_sink = binemit::NullTrapSink {};
context
.compile_and_emit(isa, &mut code_buf, &mut reloc_sink, &mut trap_sink)
.expect("compile_and_emit");
code_memory
.allocate_copy_of_byte_slice(&code_buf)
.expect("allocate_copy_of_byte_slice")
.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<ir::Signature> {
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<RefCell<HashMap<String, Option<wasmtime_runtime::Export>>>>,
obj: &PyAny,
) -> PyResult<InstanceHandle> {
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<DefinedFuncIndex, *const VMFunctionBody> =
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::<PyDict>()?;
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::<Function>()? {
// TODO faster calls?
let wasm_fn = item.cast_as::<Function>()?;
dependencies.insert(wasm_fn.instance.clone());
wasm_fn.get_signature()
} else if item.hasattr("__annotations__")? {
let annot = item.getattr("__annotations__")?.cast_as::<PyDict>()?;
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_object(py),
});
} else if item.get_type().is_subclass::<Memory>()? {
let wasm_mem = item.cast_as::<Memory>()?;
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_jt(
&mut self,
_offset: binemit::CodeOffset,
_reloc: binemit::Reloc,
_jt: ir::JumpTable,
) {
panic!("trampoline compilation should not produce jump table relocs");
}
}

View File

@@ -0,0 +1,106 @@
//! WebAssembly Instance API object.
use pyo3::prelude::*;
use pyo3::types::PyDict;
use crate::function::Function;
use crate::memory::Memory;
use std::cell::RefCell;
use std::rc::Rc;
use cranelift_codegen::ir;
use cranelift_codegen::ir::types;
use wasmtime_environ::Export;
use wasmtime_interface_types::ModuleData;
use wasmtime_jit::{Context, InstanceHandle};
use wasmtime_runtime::Export as RuntimeExport;
#[pyclass]
pub struct Instance {
pub context: Rc<RefCell<Context>>,
pub instance: InstanceHandle,
pub data: Rc<ModuleData>,
}
fn get_type_annot(ty: ir::Type) -> &'static str {
match ty {
types::I32 => "i32",
types::I64 => "i64",
types::F32 => "f32",
types::F64 => "f64",
_ => panic!("unknown type"),
}
}
#[pymethods]
impl Instance {
#[getter(exports)]
fn get_exports(&mut self) -> PyResult<PyObject> {
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()),
_ => {
// 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 annot = PyDict::new(py);
let mut args_types = Vec::new();
for index in 1..signature.params.len() {
let ty = signature.params[index].value_type;
args_types.push(ty);
annot.set_item(format!("param{}", index - 1), get_type_annot(ty))?;
}
match signature.returns.len() {
0 => (),
1 => {
annot
.set_item("return", get_type_annot(signature.returns[0].value_type))?;
}
_ => panic!("multi-return"),
}
let f = Py::new(
py,
Function {
context: self.context.clone(),
instance: self.instance.clone(),
data: self.data.clone(),
export_name: name.clone(),
args_types,
},
)?;
// FIXME set the f object the `__annotations__` attribute somehow?
let _ = annot;
exports.set_item(name, f)?;
} else {
panic!("function");
}
}
Ok(exports.to_object(py))
}
}

145
misc/wasmtime-py/src/lib.rs Normal file
View File

@@ -0,0 +1,145 @@
use pyo3::exceptions::Exception;
use pyo3::prelude::*;
use pyo3::types::{PyBytes, PyDict, PySet};
use pyo3::wrap_pyfunction;
use crate::import::into_instance_from_obj;
use crate::instance::Instance;
use crate::memory::Memory;
use crate::module::Module;
use std::cell::RefCell;
use std::rc::Rc;
use wasmtime_interface_types::ModuleData;
mod code_memory;
mod function;
mod import;
mod instance;
mod memory;
mod module;
mod value;
fn err2py(err: failure::Error) -> PyErr {
let mut desc = err.to_string();
for cause in err.iter_causes() {
desc.push_str("\n");
desc.push_str(" caused by: ");
desc.push_str(&cause.to_string());
}
PyErr::new::<Exception, _>(desc)
}
#[pyclass]
pub struct InstantiateResultObject {
instance: Py<Instance>,
module: Py<Module>,
}
#[pymethods]
impl InstantiateResultObject {
#[getter(instance)]
fn get_instance(&self) -> PyResult<Py<Instance>> {
let gil = Python::acquire_gil();
let py = gil.python();
Ok(self.instance.clone_ref(py))
}
#[getter(module)]
fn get_module(&self) -> PyResult<Py<Module>> {
let gil = Python::acquire_gil();
let py = gil.python();
Ok(self.module.clone_ref(py))
}
}
/// WebAssembly instantiate API method.
#[pyfunction]
pub fn instantiate(
py: Python,
buffer_source: &PyBytes,
import_obj: &PyDict,
) -> PyResult<Py<InstantiateResultObject>> {
let wasm_data = buffer_source.as_bytes();
let generate_debug_info = false;
let isa = {
let isa_builder = cranelift_native::builder().map_err(|s| PyErr::new::<Exception, _>(s))?;
let flag_builder = cranelift_codegen::settings::builder();
isa_builder.finish(cranelift_codegen::settings::Flags::new(flag_builder))
};
let mut context = wasmtime_jit::Context::with_isa(isa);
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 data = Rc::new(ModuleData::new(wasm_data).map_err(err2py)?);
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,
},
)?;
Py::new(py, InstantiateResultObject { instance, module })
}
#[pyfunction]
pub fn imported_modules<'p>(py: Python<'p>, buffer_source: &PyBytes) -> PyResult<&'p PyDict> {
let wasm_data = buffer_source.as_bytes();
let dict = PyDict::new(py);
// TODO: error handling
let mut parser = wasmparser::ModuleReader::new(wasm_data).unwrap();
while !parser.eof() {
let section = parser.read().unwrap();
match section.code {
wasmparser::SectionCode::Import => {}
_ => continue,
};
let reader = section.get_import_section_reader().unwrap();
for import in reader {
let import = import.unwrap();
let set = match dict.get_item(import.module) {
Some(set) => set.downcast_ref::<PySet>().unwrap(),
None => {
let set = PySet::new::<PyObject>(py, &[])?;
dict.set_item(import.module, set)?;
set
}
};
set.add(import.field)?;
}
}
Ok(dict)
}
#[pymodule]
fn lib_wasmtime(_: Python, m: &PyModule) -> PyResult<()> {
m.add_class::<Instance>()?;
m.add_class::<Memory>()?;
m.add_class::<Module>()?;
m.add_class::<InstantiateResultObject>()?;
m.add_wrapped(wrap_pyfunction!(instantiate))?;
m.add_wrapped(wrap_pyfunction!(imported_modules))?;
Ok(())
}

View File

@@ -0,0 +1,131 @@
//! WebAssembly Memory API object.
use pyo3::class::PyBufferProtocol;
use pyo3::exceptions::BufferError;
use pyo3::ffi;
use pyo3::prelude::*;
use std::cell::RefCell;
use std::ffi::CStr;
use std::os::raw::{c_int, c_void};
use std::ptr;
use std::rc::Rc;
use wasmtime_environ::MemoryPlan;
use wasmtime_jit::{Context, InstanceHandle};
use wasmtime_runtime::{Export, VMMemoryDefinition, VMMemoryImport};
#[pyclass]
pub struct Memory {
pub context: Rc<RefCell<Context>>,
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!()
}
}
}
#[pymethods]
impl Memory {
#[getter(current)]
pub fn current(&self) -> u32 {
let current_length = unsafe { (*self.descriptor()).current_length };
(current_length >> 16) as u32
}
pub fn grow(&self, _number: u32) -> u32 {
(-1i32) as u32
}
}
#[pyproto]
impl PyBufferProtocol for Memory {
fn bf_getbuffer(&self, view: *mut ffi::Py_buffer, flags: c_int) -> PyResult<()> {
if view.is_null() {
return Err(BufferError::py_err("View is null"));
}
unsafe {
/*
As a special case, for temporary buffers that are wrapped by
PyMemoryView_FromBuffer() or PyBuffer_FillInfo() this field is NULL.
In general, exporting objects MUST NOT use this scheme.
*/
(*view).obj = ptr::null_mut();
}
let readonly = if (flags & ffi::PyBUF_WRITABLE) == ffi::PyBUF_WRITABLE {
0
} else {
1
};
let VMMemoryDefinition {
base,
current_length,
} = unsafe { *self.descriptor() };
unsafe {
(*view).buf = base as *mut c_void;
(*view).len = current_length as isize;
(*view).readonly = readonly;
(*view).itemsize = 1;
(*view).format = ptr::null_mut();
if (flags & ffi::PyBUF_FORMAT) == ffi::PyBUF_FORMAT {
let msg = CStr::from_bytes_with_nul(b"B\0").unwrap();
(*view).format = msg.as_ptr() as *mut _;
}
(*view).ndim = 1;
(*view).shape = ptr::null_mut();
if (flags & ffi::PyBUF_ND) == ffi::PyBUF_ND {
(*view).shape = (&((*view).len)) as *const _ as *mut _;
}
(*view).strides = ptr::null_mut();
if (flags & ffi::PyBUF_STRIDES) == ffi::PyBUF_STRIDES {
(*view).strides = &((*view).itemsize) as *const _ as *mut _;
}
(*view).suboffsets = ptr::null_mut();
(*view).internal = ptr::null_mut();
}
Ok(())
}
}

View File

@@ -0,0 +1,10 @@
//! WebAssembly Module API object.
use pyo3::prelude::*;
use std::rc::Rc;
#[pyclass]
pub struct Module {
pub module: Rc<wasmtime_environ::Module>,
}

View File

@@ -0,0 +1,61 @@
//! Utility functions to handle values conversion between abstractions/targets.
use pyo3::exceptions::Exception;
use pyo3::prelude::*;
use pyo3::types::PyAny;
use cranelift_codegen::ir;
use std::ptr;
use wasmtime_interface_types::Value;
pub fn pyobj_to_value(_: Python, p: &PyAny) -> PyResult<Value> {
if let Ok(n) = p.extract() {
Ok(Value::I32(n))
} else if let Ok(n) = p.extract() {
Ok(Value::U32(n))
} else if let Ok(n) = p.extract() {
Ok(Value::I64(n))
} else if let Ok(n) = p.extract() {
Ok(Value::U64(n))
} else if let Ok(n) = p.extract() {
Ok(Value::F64(n))
} else if let Ok(n) = p.extract() {
Ok(Value::F32(n))
} else if let Ok(s) = p.extract() {
Ok(Value::String(s))
} else {
Err(PyErr::new::<Exception, _>("unsupported value type"))
}
}
pub fn value_to_pyobj(py: Python, value: Value) -> PyResult<PyObject> {
Ok(match value {
Value::I32(i) => i.into_object(py),
Value::U32(i) => i.into_object(py),
Value::I64(i) => i.into_object(py),
Value::U64(i) => i.into_object(py),
Value::F32(i) => i.into_object(py),
Value::F64(i) => i.into_object(py),
Value::String(i) => i.into_object(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_object(py),
ir::types::I64 => ptr::read(ptr as *const i64).into_object(py),
ir::types::F32 => ptr::read(ptr as *const f32).into_object(py),
ir::types::F64 => ptr::read(ptr as *const f64).into_object(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::<i32>(py).expect("i32")),
ir::types::I64 => ptr::write(ptr as *mut i64, val.extract::<i64>(py).expect("i64")),
ir::types::F32 => ptr::write(ptr as *mut f32, val.extract::<f32>(py).expect("f32")),
ir::types::F64 => ptr::write(ptr as *mut f64, val.extract::<f64>(py).expect("f64")),
_ => panic!("TODO add PyResult to write_value_to"),
}
}