diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ad6e1b47e..2dc8beeb6 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -52,14 +52,10 @@ jobs: - name: Install NPM packages run: npm install working-directory: crates/gen-host-js - - uses: actions/setup-python@v1 - with: - python-version: 3.9 - uses: actions/setup-java@v3 with: java-version: '18' distribution: 'adopt' - - run: pip install mypy wasmtime - if: matrix.mode == 'release' name: Test release build run: cargo test --workspace --release diff --git a/Cargo.lock b/Cargo.lock index 97c0d4dc1..1cf495204 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1907,7 +1907,6 @@ dependencies = [ "wit-bindgen-gen-guest-rust", "wit-bindgen-gen-guest-teavm-java", "wit-bindgen-gen-host-js", - "wit-bindgen-gen-host-wasmtime-py", "wit-bindgen-gen-host-wasmtime-rust", "wit-bindgen-gen-markdown", "wit-component", @@ -1935,7 +1934,6 @@ dependencies = [ "wit-bindgen-gen-guest-rust", "wit-bindgen-gen-guest-teavm-java", "wit-bindgen-gen-host-js", - "wit-bindgen-gen-host-wasmtime-py", "wit-bindgen-gen-host-wasmtime-rust", "wit-bindgen-gen-markdown", "wit-bindgen-guest-rust", @@ -1994,19 +1992,6 @@ dependencies = [ "wit-component", ] -[[package]] -name = "wit-bindgen-gen-host-wasmtime-py" -version = "0.3.0" -dependencies = [ - "clap", - "heck", - "indexmap", - "test-helpers", - "wasmtime-environ", - "wit-bindgen-core", - "wit-component", -] - [[package]] name = "wit-bindgen-gen-host-wasmtime-rust" version = "0.3.0" diff --git a/Cargo.toml b/Cargo.toml index 4a6b84eab..61edb0005 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,7 +37,6 @@ wit-bindgen-gen-guest-c = { path = 'crates/gen-guest-c', version = '0.3.0' } wit-bindgen-gen-guest-rust = { path = "crates/gen-guest-rust", version = "0.3.0" } wit-bindgen-gen-guest-teavm-java = { path = 'crates/gen-guest-teavm-java', version = '0.3.0' } wit-bindgen-gen-host-js = { path = 'crates/gen-host-js', version = '0.3.0' } -wit-bindgen-gen-host-wasmtime-py = { path = 'crates/gen-host-wasmtime-py', version = '0.3.0' } wit-bindgen-gen-host-wasmtime-rust = { path = 'crates/gen-host-wasmtime-rust', version = '0.3.0' } wit-bindgen-gen-markdown = { path = 'crates/gen-markdown', version = '0.3.0' } wit-bindgen-gen-rust-lib = { path = 'crates/gen-rust-lib', version = '0.3.0' } @@ -57,7 +56,6 @@ clap = { workspace = true } wit-bindgen-core = { path = 'crates/bindgen-core' } wit-bindgen-gen-guest-rust = { path = 'crates/gen-guest-rust', features = ['clap'] } wit-bindgen-gen-host-wasmtime-rust = { path = 'crates/gen-host-wasmtime-rust', features = ['clap'] } -wit-bindgen-gen-host-wasmtime-py = { path = 'crates/gen-host-wasmtime-py', features = ['clap'] } wit-bindgen-gen-host-js = { path = 'crates/gen-host-js', features = ['clap'] } wit-bindgen-gen-guest-c = { path = 'crates/gen-guest-c', features = ['clap'] } wit-bindgen-gen-markdown = { path = 'crates/gen-markdown', features = ['clap'] } diff --git a/crates/gen-host-wasmtime-py/Cargo.toml b/crates/gen-host-wasmtime-py/Cargo.toml deleted file mode 100644 index 786e8fb95..000000000 --- a/crates/gen-host-wasmtime-py/Cargo.toml +++ /dev/null @@ -1,16 +0,0 @@ -[package] -name = "wit-bindgen-gen-host-wasmtime-py" -authors = ["Alex Crichton "] -version.workspace = true -edition.workspace = true - -[dependencies] -wit-bindgen-core = { workspace = true, features = ['component-generator'] } -heck = { workspace = true } -clap = { workspace = true, optional = true } -wit-component = { workspace = true } -indexmap = "1.0" -wasmtime-environ = { workspace = true, features = ['component-model'] } - -[dev-dependencies] -test-helpers = { path = '../test-helpers' } diff --git a/crates/gen-host-wasmtime-py/mypy.ini b/crates/gen-host-wasmtime-py/mypy.ini deleted file mode 100644 index 49f970ace..000000000 --- a/crates/gen-host-wasmtime-py/mypy.ini +++ /dev/null @@ -1,13 +0,0 @@ -[mypy] - -disallow_any_unimported = True - -disallow_untyped_calls = True -disallow_untyped_defs = True -disallow_incomplete_defs = True -check_untyped_defs = True -disallow_untyped_decorators = True -strict_optional = True - -warn_return_any = True -warn_unused_configs = True diff --git a/crates/gen-host-wasmtime-py/src/imports.rs b/crates/gen-host-wasmtime-py/src/imports.rs deleted file mode 100644 index 0a634047a..000000000 --- a/crates/gen-host-wasmtime-py/src/imports.rs +++ /dev/null @@ -1,96 +0,0 @@ -use std::collections::{BTreeMap, BTreeSet}; -use std::fmt::Write; -use wit_bindgen_core::uwriteln; - -/// Tracks all of the import and intrinsics that a given codegen -/// requires and how to generate them when needed. -#[derive(Default)] -pub struct PyImports { - pyimports: BTreeMap>>, -} - -impl PyImports { - /// Record that a Python import is required - pub fn pyimport<'a>(&mut self, module: &str, name: impl Into>) { - let name = name.into(); - let list = self - .pyimports - .entry(module.to_string()) - .or_insert(match name { - Some(_) => Some(BTreeSet::new()), - None => None, - }); - match name { - Some(name) => { - assert!(list.is_some()); - list.as_mut().unwrap().insert(name.to_string()); - } - None => assert!(list.is_none()), - } - } - - pub fn is_empty(&self) -> bool { - self.pyimports.is_empty() - } - - pub fn finish(&self) -> String { - let mut result = String::new(); - for (k, list) in self.pyimports.iter() { - match list { - Some(list) => { - let list = list.iter().cloned().collect::>().join(", "); - uwriteln!(result, "from {k} import {list}"); - } - None => uwriteln!(result, "import {k}"), - } - } - - if !self.pyimports.is_empty() { - result.push_str("\n"); - } - - result - } -} - -#[cfg(test)] -mod test { - use std::collections::{BTreeMap, BTreeSet}; - - use super::PyImports; - - #[test] - fn test_pyimport_only_contents() { - let mut deps = PyImports::default(); - deps.pyimport("typing", None); - deps.pyimport("typing", None); - assert_eq!(deps.pyimports, BTreeMap::from([("typing".into(), None)])); - } - - #[test] - fn test_pyimport_only_module() { - let mut deps = PyImports::default(); - deps.pyimport("typing", "Union"); - deps.pyimport("typing", "List"); - deps.pyimport("typing", "NamedTuple"); - assert_eq!( - deps.pyimports, - BTreeMap::from([( - "typing".into(), - Some(BTreeSet::from([ - "Union".into(), - "List".into(), - "NamedTuple".into() - ])) - )]) - ); - } - - #[test] - #[should_panic] - fn test_pyimport_conflicting() { - let mut deps = PyImports::default(); - deps.pyimport("typing", "NamedTuple"); - deps.pyimport("typing", None); - } -} diff --git a/crates/gen-host-wasmtime-py/src/lib.rs b/crates/gen-host-wasmtime-py/src/lib.rs deleted file mode 100644 index 28eb4d99d..000000000 --- a/crates/gen-host-wasmtime-py/src/lib.rs +++ /dev/null @@ -1,2379 +0,0 @@ -//! Code generator for the `wasmtime` PyPI package. -//! -//! This crate will generate bindings for a single component, like JS, for -//! Python source code. Component-model types are translated to Python and the -//! component is executed using the `wasmtime` PyPI package which is bindings to -//! the `wasmtime` C API which is built on the `wasmtime` Rust API. -//! -//! The generated structure of the bindings looks like follows: -//! -//! ```ignore -//! out_dir/ -//! __init__.py -//! types.py # types shared by all imports/exports -//! imports/ # only present if interfaces are imported -//! __init__.py # reexports `Foo` protocols for each interface -//! foo.py # types and definitions specific to interface `foo` -//! .. -//! exports/ # only present with exported interfaces -//! __init__.py # empty file -//! bar.py # contains `Bar` as the exported interface -//! .. -//! ``` -//! -//! The top-level `__init__.py` contains a `class Foo` where `Foo` is the name -//! fo the component. It contains top-level functions for all top-level exports -//! and exported instances are modeled as a method which returns a struct from -//! `exports/*.py`. - -use heck::*; -use indexmap::IndexMap; -use std::collections::{BTreeMap, BTreeSet, HashSet}; -use std::fmt::Write; -use std::mem; -use wasmtime_environ::component::{ - CanonicalOptions, Component, CoreDef, CoreExport, Export, ExportItem, GlobalInitializer, - InstantiateModule, LowerImport, RuntimeInstanceIndex, StaticModuleIndex, StringEncoding, -}; -use wasmtime_environ::{EntityIndex, ModuleTranslation, PrimaryMap}; -use wit_bindgen_core::component::ComponentGenerator; -use wit_bindgen_core::wit_parser::abi::{ - AbiVariant, Bindgen, Bitcast, Instruction, LiftLower, WasmType, -}; -use wit_bindgen_core::{ - uwrite, uwriteln, wit_parser::*, Files, InterfaceGenerator as _, Ns, WorldGenerator, -}; -use wit_component::ComponentInterfaces; - -mod imports; -mod source; - -use source::Source; - -#[derive(Default)] -struct WasmtimePy { - opts: Opts, - - // `$out_dir/__init__.py` - init: Source, - // `$out_dir/types.py` - types: Source, - // `$out_dir/intrinsics.py` - intrinsics: Source, - // `$out_dir/imports/__init__.py` - imports_init: Source, - // `$out_dir/exports/$name.py` - exports: BTreeMap, - - /// Known imported interfaces to have as an argument to construction of the - /// main component. - imports: Vec, - - /// All intrinsics emitted to `self.intrinsics` so far. - all_intrinsics: BTreeSet<&'static str>, -} - -#[derive(Default, Debug, Clone)] -#[cfg_attr(feature = "clap", derive(clap::Args))] -pub struct Opts { - // ... -} - -impl Opts { - pub fn build(self) -> Box { - let mut r = WasmtimePy::default(); - r.opts = self; - Box::new(r) - } -} - -impl WasmtimePy { - fn interface<'a>(&'a mut self, iface: &'a Interface, at_root: bool) -> InterfaceGenerator<'a> { - InterfaceGenerator { - gen: self, - iface, - src: Source::default(), - at_root, - self_module_path: "", - } - } - - fn print_result(&mut self) { - if !self.all_intrinsics.insert("result_type") { - return; - } - - self.types.pyimport("dataclasses", "dataclass"); - self.types.pyimport("typing", "TypeVar"); - self.types.pyimport("typing", "Generic"); - self.types.pyimport("typing", "Union"); - self.types.push_str( - " - T = TypeVar('T') - @dataclass - class Ok(Generic[T]): - value: T - E = TypeVar('E') - @dataclass - class Err(Generic[E]): - value: E - - Result = Union[Ok[T], Err[E]] - ", - ); - } -} - -fn array_ty(iface: &Interface, ty: &Type) -> Option<&'static str> { - match ty { - Type::Bool => None, - Type::U8 => Some("c_uint8"), - Type::S8 => Some("c_int8"), - Type::U16 => Some("c_uint16"), - Type::S16 => Some("c_int16"), - Type::U32 => Some("c_uint32"), - Type::S32 => Some("c_int32"), - Type::U64 => Some("c_uint64"), - Type::S64 => Some("c_int64"), - Type::Float32 => Some("c_float"), - Type::Float64 => Some("c_double"), - Type::Char => None, - Type::String => None, - Type::Id(id) => match &iface.types[*id].kind { - TypeDefKind::Type(t) => array_ty(iface, t), - _ => None, - }, - } -} - -impl ComponentGenerator for WasmtimePy { - fn instantiate( - &mut self, - name: &str, - component: &Component, - modules: &PrimaryMap>, - interfaces: &ComponentInterfaces, - ) { - self.init.pyimport("wasmtime", None); - - let camel = name.to_upper_camel_case(); - let imports = if !component.import_types.is_empty() { - self.init - .pyimport(".imports", format!("{camel}Imports").as_str()); - format!(", import_object: {camel}Imports") - } else { - String::new() - }; - - uwriteln!(self.init, "class {camel}:"); - self.init.indent(); - - self.init.push_str("\n"); - - uwriteln!( - self.init, - "def __init__(self, store: wasmtime.Store{imports}):" - ); - self.init.indent(); - let mut i = Instantiator { - name, - gen: self, - modules, - component, - interfaces, - instances: PrimaryMap::default(), - lifts: 0, - }; - for init in component.initializers.iter() { - i.global_initializer(init); - } - let (lifts, nested) = i.exports(&component.exports, interfaces.default.as_ref()); - i.gen.init.dedent(); - - i.generate_lifts(&camel, None, &lifts); - for (name, lifts) in nested { - i.generate_lifts(&camel, Some(name), &lifts); - } - i.gen.init.dedent(); - } - - fn finish_component(&mut self, _name: &str, files: &mut Files) { - if !self.imports_init.is_empty() { - files.push("imports/__init__.py", self.imports_init.finish().as_bytes()); - } - if !self.types.is_empty() { - files.push("types.py", self.types.finish().as_bytes()); - } - if !self.intrinsics.is_empty() { - files.push("intrinsics.py", self.intrinsics.finish().as_bytes()); - } - - for (name, src) in self.exports.iter() { - let snake = name.to_snake_case(); - files.push(&format!("exports/{snake}.py"), src.finish().as_bytes()); - } - if !self.exports.is_empty() { - files.push("exports/__init__.py", b""); - } - - files.push("__init__.py", self.init.finish().as_bytes()); - } -} - -struct Instantiator<'a> { - name: &'a str, - gen: &'a mut WasmtimePy, - modules: &'a PrimaryMap>, - instances: PrimaryMap, - interfaces: &'a ComponentInterfaces, - component: &'a Component, - lifts: usize, -} - -struct Lift<'a> { - callee: String, - opts: &'a CanonicalOptions, - iface: &'a Interface, - func: &'a Function, -} - -impl<'a> Instantiator<'a> { - fn global_initializer(&mut self, init: &GlobalInitializer) { - match init { - GlobalInitializer::InstantiateModule(m) => match m { - InstantiateModule::Static(idx, args) => self.instantiate_static_module(*idx, args), - - // This is only needed when instantiating an imported core wasm - // module which while easy to implement here is not possible to - // test at this time so it's left unimplemented. - InstantiateModule::Import(..) => unimplemented!(), - }, - - GlobalInitializer::LowerImport(i) => self.lower_import(i), - - GlobalInitializer::ExtractMemory(m) => { - let def = self.core_export(&m.export); - let i = m.index.as_u32(); - uwriteln!(self.gen.init, "core_memory{i} = {def}"); - uwriteln!( - self.gen.init, - "assert(isinstance(core_memory{i}, wasmtime.Memory))" - ); - uwriteln!(self.gen.init, "self._core_memory{i} = core_memory{i}",); - } - GlobalInitializer::ExtractRealloc(r) => { - let def = self.core_def(&r.def); - let i = r.index.as_u32(); - uwriteln!(self.gen.init, "realloc{i} = {def}"); - uwriteln!( - self.gen.init, - "assert(isinstance(realloc{i}, wasmtime.Func))" - ); - uwriteln!(self.gen.init, "self._realloc{i} = realloc{i}",); - } - GlobalInitializer::ExtractPostReturn(p) => { - let def = self.core_def(&p.def); - let i = p.index.as_u32(); - uwriteln!(self.gen.init, "post_return{i} = {def}"); - uwriteln!( - self.gen.init, - "assert(isinstance(post_return{i}, wasmtime.Func))" - ); - uwriteln!(self.gen.init, "self._post_return{i} = post_return{i}",); - } - - // This is only used for a "degenerate component" which internally - // has a function that always traps. While this should be trivial to - // implement (generate a JS function that always throws) there's no - // way to test this at this time so leave this unimplemented. - GlobalInitializer::AlwaysTrap(_) => unimplemented!(), - - // This is only used when the component exports core wasm modules, - // but that's not possible to test right now so leave these as - // unimplemented. - GlobalInitializer::SaveStaticModule(_) => unimplemented!(), - GlobalInitializer::SaveModuleImport(_) => unimplemented!(), - - // This is required when strings pass between components within a - // component and may change encodings. This is left unimplemented - // for now since it can't be tested and additionally JS doesn't - // support multi-memory which transcoders rely on anyway. - GlobalInitializer::Transcoder(_) => unimplemented!(), - } - } - - fn instantiate_static_module(&mut self, idx: StaticModuleIndex, args: &[CoreDef]) { - let i = self.instances.push(idx); - let core_file_name = self.gen.core_file_name(&self.name, idx.as_u32()); - self.gen.init.pyimport("os", None); - - uwriteln!( - self.gen.init, - "path = os.path.join(os.path.dirname(os.path.abspath(__file__)), '{}')", - core_file_name, - ); - uwriteln!( - self.gen.init, - "module = wasmtime.Module.from_file(store.engine, path)" - ); - uwrite!( - self.gen.init, - "instance{} = wasmtime.Instance(store, module, [", - i.as_u32() - ); - if !args.is_empty() { - self.gen.init.push_str("\n"); - self.gen.init.indent(); - for arg in args { - let def = self.core_def(arg); - uwriteln!(self.gen.init, "{def},"); - } - self.gen.init.dedent(); - } - uwriteln!(self.gen.init, "]).exports(store)"); - } - - fn lower_import(&mut self, import: &LowerImport) { - // Determine the `Interface` that this import corresponds to. At this - // time `wit-component` only supports root-level imports of instances - // where instances export functions. - let (import_index, path) = &self.component.imports[import.import]; - let (import_name, _import_ty) = &self.component.import_types[*import_index]; - assert_eq!(path.len(), 1); - let iface = &self.interfaces.imports[import_name.as_str()]; - let func = iface.functions.iter().find(|f| f.name == path[0]).unwrap(); - - let index = import.index.as_u32(); - let callee = format!( - "import_object.{}.{}", - import_name.to_snake_case(), - func.name.to_snake_case() - ); - - // Generate an inline function "closure" which will capture the - // `imports` argument provided to the constructor of this class and have - // the core wasm signature for this function. Using prior local - // variables the function here will perform all liftings/lowerings. - uwrite!( - self.gen.init, - "def lowering{index}_callee(caller: wasmtime.Caller" - ); - let sig = iface.wasm_signature(AbiVariant::GuestImport, func); - let mut params = Vec::new(); - for (i, param_ty) in sig.params.iter().enumerate() { - self.gen.init.push_str(", "); - let param = format!("arg{i}"); - uwrite!(self.gen.init, "{param}: {}", wasm_ty_typing(*param_ty)); - params.push(param); - } - self.gen.init.push_str(") -> "); - match sig.results.len() { - 0 => self.gen.init.push_str("None"), - 1 => self.gen.init.push_str(wasm_ty_typing(sig.results[0])), - _ => unimplemented!(), - } - self.gen.init.push_str(":\n"); - self.gen.init.indent(); - - let iface_snake = iface.name.to_snake_case(); - self.gen.init.pyimport(".imports", iface_snake.as_str()); - let self_module_path = format!("{iface_snake}."); - - self.bindgen( - params, - callee, - &import.options, - iface, - func, - AbiVariant::GuestImport, - "self", - self_module_path, - true, - ); - self.gen.init.dedent(); - - // Use the `wasmtime` package's embedder methods of creating a wasm - // function to finish the construction here. - uwrite!(self.gen.init, "lowering{index}_ty = wasmtime.FuncType(["); - for param in sig.params.iter() { - self.gen.init.push_str(wasm_ty_ctor(*param)); - self.gen.init.push_str(", "); - } - self.gen.init.push_str("], ["); - for param in sig.results.iter() { - self.gen.init.push_str(wasm_ty_ctor(*param)); - self.gen.init.push_str(", "); - } - self.gen.init.push_str("])\n"); - uwriteln!( - self.gen.init, - "lowering{index} = wasmtime.Func(store, lowering{index}_ty, lowering{index}_callee, access_caller = True)" - ); - } - - fn bindgen( - &mut self, - params: Vec, - callee: String, - opts: &CanonicalOptions, - iface: &Interface, - func: &Function, - abi: AbiVariant, - this: &str, - self_module_path: String, - at_root: bool, - ) { - // Technically it wouldn't be the hardest thing in the world to support - // other string encodings, but for now the code generator was originally - // written to support utf-8 so let's just leave it at that for now. In - // the future when it's easier to produce components with non-utf-8 this - // can be plumbed through to string lifting/lowering below. - assert_eq!(opts.string_encoding, StringEncoding::Utf8); - - let memory = match opts.memory { - Some(idx) => Some(format!("{this}._core_memory{}", idx.as_u32())), - None => None, - }; - let realloc = match opts.realloc { - Some(idx) => Some(format!("{this}._realloc{}", idx.as_u32())), - None => None, - }; - let post_return = match opts.post_return { - Some(idx) => Some(format!("{this}._post_return{}", idx.as_u32())), - None => None, - }; - - let mut sizes = SizeAlign::default(); - sizes.fill(iface); - let mut locals = Ns::default(); - locals.insert("len").unwrap(); // python built-in - locals.insert("base").unwrap(); // may be used as loop var - locals.insert("i").unwrap(); // may be used as loop var - let mut f = FunctionBindgen { - locals, - payloads: Vec::new(), - sizes, - // Generate source directly onto `init` - src: mem::take(&mut self.gen.init), - gen: self.gen, - block_storage: Vec::new(), - blocks: Vec::new(), - callee, - memory, - realloc, - params, - post_return, - iface, - self_module_path, - at_root, - }; - iface.call( - abi, - match abi { - AbiVariant::GuestImport => LiftLower::LiftArgsLowerResults, - AbiVariant::GuestExport => LiftLower::LowerArgsLiftResults, - }, - func, - &mut f, - ); - - // Swap the printed source back into the destination of our `init`, and - // at this time `f.src` should be empty. - mem::swap(&mut f.src, &mut f.gen.init); - assert!(f.src.is_empty()); - } - - fn core_def(&self, def: &CoreDef) -> String { - match def { - CoreDef::Export(e) => self.core_export(e), - CoreDef::Lowered(i) => format!("lowering{}", i.as_u32()), - CoreDef::AlwaysTrap(_) => unimplemented!(), - CoreDef::InstanceFlags(_) => unimplemented!(), - CoreDef::Transcoder(_) => unimplemented!(), - } - } - - fn core_export(&self, export: &CoreExport) -> String - where - T: Into + Copy, - { - let name = match &export.item { - ExportItem::Index(idx) => { - let module = &self.modules[self.instances[export.instance]].module; - let idx = (*idx).into(); - module - .exports - .iter() - .filter_map(|(name, i)| if *i == idx { Some(name) } else { None }) - .next() - .unwrap() - } - ExportItem::Name(s) => s, - }; - let i = export.instance.as_u32() as usize; - format!("instance{i}[\"{name}\"]") - } - - /// Extract the `LiftedFunction` exports to a format that's easier to - /// process for this generator. For now all lifted functions are either - /// "root" lifted functions or one-level-nested for an exported interface. - /// - /// As worlds shape up and more of a component's structure is expressible in - /// `*.wit` this method will likely need to change. - fn exports( - &mut self, - exports: &'a IndexMap, - iface: Option<&'a Interface>, - ) -> (Vec>, BTreeMap<&'a str, Vec>>) { - let mut toplevel = Vec::new(); - let mut nested = BTreeMap::new(); - for (name, export) in exports { - let name = name.as_str(); - match export { - Export::LiftedFunction { - ty: _, - func, - options, - } => { - // For each lifted function the callee `wasmtime.Func` is - // saved into a per-instance field which is then referenced - // as the callee when the relevant function is invoked. - let def = self.core_def(func); - let callee = format!("lift_callee{}", self.lifts); - self.lifts += 1; - uwriteln!(self.gen.init, "{callee} = {def}"); - uwriteln!(self.gen.init, "assert(isinstance({callee}, wasmtime.Func))"); - uwriteln!(self.gen.init, "self.{callee} = {callee}"); - let iface = iface.unwrap(); - let func = iface.functions.iter().find(|f| f.name == *name).unwrap(); - toplevel.push(Lift { - callee, - opts: options, - iface, - func, - }); - } - - Export::Instance(exports) => { - let iface = &self.interfaces.exports[name]; - let (my_toplevel, my_nested) = self.exports(exports, Some(iface)); - // More than one level of nesting not supported at this - // time. - assert!(my_nested.is_empty()); - - let prev = nested.insert(name, my_toplevel); - assert!(prev.is_none()); - } - - // ignore type exports for now - Export::Type(_) => {} - - // This can't be tested at this time so leave it unimplemented - Export::Module(_) => unimplemented!(), - } - } - (toplevel, nested) - } - - fn generate_lifts(&mut self, camel_component: &str, ns: Option<&str>, lifts: &[Lift<'_>]) { - let mut this = "self".to_string(); - - // If these exports are going into a non-default interface then a new - // `class` is generated in the corresponding file which will be - // constructed with the "root" class. Generate the class here, its one - // field of the root class, and then an associated constructor for the - // root class to have. Finally the root class grows a method here as - // well to return the nested instance. - if let Some(ns) = ns { - let src = self.gen.exports.get_mut(ns).unwrap(); - let camel = ns.to_upper_camel_case(); - let snake = ns.to_snake_case(); - uwriteln!(src, "class {camel}:"); - src.indent(); - src.pyimport("..", camel_component); - uwriteln!(src, "component: {camel_component}\n"); - uwriteln!( - src, - "def __init__(self, component: {camel_component}) -> None:" - ); - src.indent(); - uwriteln!(src, "self.component = component"); - src.dedent(); - - this.push_str(".component"); - - self.gen.init.pyimport(".exports", snake.as_str()); - uwriteln!(self.gen.init, "def {snake}(self) -> {snake}.{camel}:"); - self.gen.init.indent(); - uwriteln!(self.gen.init, "return {snake}.{camel}(self)"); - self.gen.init.dedent(); - - // Swap the two sources so the generation into `init` will go into - // the right place - mem::swap(&mut self.gen.init, src); - } - - for lift in lifts { - // Go through some small gymnastics to print the function signature - // here. - let mut src = mem::take(&mut self.gen.init); - let params = with_igen(&mut src, self.gen, lift.iface, ns.is_none(), "", |gen| { - gen.print_sig(lift.func, false) - }); - self.gen.init = src; - self.gen.init.push_str(":\n"); - - // Defer to `self.bindgen` for the body of the function. - self.gen.init.indent(); - self.gen.init.docstring(&lift.func.docs); - self.bindgen( - params, - format!("{this}.{}", lift.callee), - lift.opts, - lift.iface, - lift.func, - AbiVariant::GuestExport, - &this, - String::new(), - ns.is_none(), - ); - self.gen.init.dedent(); - } - - // Undo the swap done above. - if let Some(ns) = ns { - self.gen.init.dedent(); - mem::swap(&mut self.gen.init, self.gen.exports.get_mut(ns).unwrap()); - } - } -} - -impl WorldGenerator for WasmtimePy { - fn import(&mut self, name: &str, iface: &Interface, files: &mut Files) { - let mut gen = self.interface(iface, false); - gen.types(); - - // Generate a "protocol" class which I'm led to believe is the rough - // equivalent of a Rust trait in Python for this imported interface. - // This will be referenced in the constructor for the main component. - let camel = name.to_upper_camel_case(); - let snake = name.to_snake_case(); - gen.src.pyimport("typing", "Protocol"); - uwriteln!(gen.src, "class {camel}(Protocol):"); - gen.src.indent(); - for func in iface.functions.iter() { - gen.src.pyimport("abc", "abstractmethod"); - gen.src.push_str("@abstractmethod\n"); - gen.print_sig(func, true); - gen.src.push_str(":\n"); - gen.src.indent(); - gen.src.push_str("raise NotImplementedError\n"); - gen.src.dedent(); - } - gen.src.dedent(); - gen.src.push_str("\n"); - - let src = gen.src.finish(); - files.push(&format!("imports/{snake}.py"), src.as_bytes()); - self.imports.push(name.to_string()); - } - - fn export(&mut self, name: &str, iface: &Interface, _files: &mut Files) { - let mut gen = self.interface(iface, false); - gen.types(); - - // Only generate types for exports and this will get finished later on - // as lifted functions need to be inserted into these files as they're - // discovered. - let src = gen.src; - self.exports.insert(name.to_string(), src); - } - - fn export_default(&mut self, _name: &str, iface: &Interface, _files: &mut Files) { - let mut gen = self.interface(iface, true); - - // Generate types and imports directly into `__init__.py` for the - // default export, and exported functions (lifted functions) will get - // generate later. - mem::swap(&mut gen.src, &mut gen.gen.init); - gen.types(); - mem::swap(&mut gen.src, &mut gen.gen.init); - } - - fn finish(&mut self, name: &str, _interfaces: &ComponentInterfaces, _files: &mut Files) { - if !self.imports.is_empty() { - let camel = name.to_upper_camel_case(); - self.imports_init.pyimport("dataclasses", "dataclass"); - uwriteln!(self.imports_init, "@dataclass"); - uwriteln!(self.imports_init, "class {camel}Imports:"); - self.imports_init.indent(); - for import in self.imports.iter() { - let snake = import.to_snake_case(); - let camel = import.to_upper_camel_case(); - self.imports_init - .pyimport(&format!(".{snake}"), camel.as_str()); - uwriteln!(self.imports_init, "{snake}: {camel}"); - } - self.imports_init.dedent(); - } - } -} - -struct InterfaceGenerator<'a> { - src: Source, - gen: &'a mut WasmtimePy, - iface: &'a Interface, - at_root: bool, - self_module_path: &'a str, -} - -#[derive(Debug, Clone, Copy)] -enum PyUnionRepresentation { - /// A union whose inner types are used directly - Raw, - /// A union whose inner types have been wrapped in dataclasses - Wrapped, -} - -impl InterfaceGenerator<'_> { - fn import_result_type(&mut self) { - self.gen.print_result(); - let path = if self.at_root { ".types" } else { "..types" }; - self.src.pyimport(path, "Result"); - } - - fn print_ty(&mut self, ty: &Type, forward_ref: bool) { - match ty { - Type::Bool => self.src.push_str("bool"), - Type::U8 - | Type::S8 - | Type::U16 - | Type::S16 - | Type::U32 - | Type::S32 - | Type::U64 - | Type::S64 => self.src.push_str("int"), - Type::Float32 | Type::Float64 => self.src.push_str("float"), - Type::Char => self.src.push_str("str"), - Type::String => self.src.push_str("str"), - Type::Id(id) => { - let ty = &self.iface.types[*id]; - if let Some(name) = &ty.name { - self.src.push_str(self.self_module_path); - self.src.push_str(&name.to_upper_camel_case()); - return; - } - match &ty.kind { - TypeDefKind::Type(t) => self.print_ty(t, forward_ref), - TypeDefKind::Tuple(t) => self.print_tuple(t), - TypeDefKind::Record(_) - | TypeDefKind::Flags(_) - | TypeDefKind::Enum(_) - | TypeDefKind::Variant(_) - | TypeDefKind::Union(_) => { - unreachable!() - } - TypeDefKind::Option(t) => { - self.src.pyimport("typing", "Optional"); - self.src.push_str("Optional["); - self.print_ty(t, true); - self.src.push_str("]"); - } - TypeDefKind::Result(r) => { - self.import_result_type(); - self.src.push_str("Result["); - self.print_optional_ty(r.ok.as_ref(), true); - self.src.push_str(", "); - self.print_optional_ty(r.err.as_ref(), true); - self.src.push_str("]"); - } - TypeDefKind::List(t) => self.print_list(t), - TypeDefKind::Future(t) => { - self.src.push_str("Future["); - self.print_optional_ty(t.as_ref(), true); - self.src.push_str("]"); - } - TypeDefKind::Stream(s) => { - self.src.push_str("Stream["); - self.print_optional_ty(s.element.as_ref(), true); - self.src.push_str(", "); - self.print_optional_ty(s.end.as_ref(), true); - self.src.push_str("]"); - } - } - } - } - } - - fn print_optional_ty(&mut self, ty: Option<&Type>, forward_ref: bool) { - match ty { - Some(ty) => self.print_ty(ty, forward_ref), - None => self.src.push_str("None"), - } - } - - fn print_tuple(&mut self, tuple: &Tuple) { - if tuple.types.is_empty() { - return self.src.push_str("None"); - } - self.src.pyimport("typing", "Tuple"); - self.src.push_str("Tuple["); - for (i, t) in tuple.types.iter().enumerate() { - if i > 0 { - self.src.push_str(", "); - } - self.print_ty(t, true); - } - self.src.push_str("]"); - } - - fn print_list(&mut self, element: &Type) { - match element { - Type::U8 => self.src.push_str("bytes"), - t => { - self.src.pyimport("typing", "List"); - self.src.push_str("List["); - self.print_ty(t, true); - self.src.push_str("]"); - } - } - } - - fn print_sig(&mut self, func: &Function, in_import: bool) -> Vec { - self.src.push_str("def "); - self.src.push_str(&func.name.to_snake_case()); - if in_import { - self.src.push_str("(self"); - } else { - self.src.pyimport("wasmtime", None); - self.src.push_str("(self, caller: wasmtime.Store"); - } - let mut params = Vec::new(); - for (param, ty) in func.params.iter() { - self.src.push_str(", "); - self.src.push_str(¶m.to_snake_case()); - params.push(param.to_snake_case()); - self.src.push_str(": "); - self.print_ty(ty, true); - } - self.src.push_str(") -> "); - match func.results.len() { - 0 => self.src.push_str("None"), - 1 => self.print_ty(func.results.iter_types().next().unwrap(), true), - _ => { - self.src.pyimport("typing", "Tuple"); - self.src.push_str("Tuple["); - for (i, ty) in func.results.iter_types().enumerate() { - if i > 0 { - self.src.push_str(", "); - } - self.print_ty(ty, true); - } - self.src.push_str("]"); - } - } - params - } - - /// Print a wrapped union definition. - /// e.g. - /// ```py - /// @dataclass - /// class Foo0: - /// value: int - /// - /// @dataclass - /// class Foo1: - /// value: int - /// - /// Foo = Union[Foo0, Foo1] - /// ``` - pub fn print_union_wrapped(&mut self, name: &str, union: &Union, docs: &Docs) { - self.src.pyimport("dataclasses", "dataclass"); - let mut cases = Vec::new(); - let name = name.to_upper_camel_case(); - for (i, case) in union.cases.iter().enumerate() { - self.src.push_str("@dataclass\n"); - let name = format!("{name}{i}"); - self.src.push_str(&format!("class {name}:\n")); - self.src.indent(); - self.src.docstring(&case.docs); - self.src.push_str("value: "); - self.print_ty(&case.ty, true); - self.src.newline(); - self.src.dedent(); - self.src.newline(); - cases.push(name); - } - - self.src.pyimport("typing", "Union"); - self.src.comment(docs); - self.src - .push_str(&format!("{name} = Union[{}]\n", cases.join(", "))); - self.src.newline(); - } - - pub fn print_union_raw(&mut self, name: &str, union: &Union, docs: &Docs) { - self.src.pyimport("typing", "Union"); - self.src.comment(docs); - for case in union.cases.iter() { - self.src.comment(&case.docs); - } - self.src.push_str(&name.to_upper_camel_case()); - self.src.push_str(" = Union["); - let mut first = true; - for case in union.cases.iter() { - if !first { - self.src.push_str(","); - } - self.print_ty(&case.ty, true); - first = false; - } - self.src.push_str("]\n\n"); - } -} - -impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for InterfaceGenerator<'a> { - fn iface(&self) -> &'a Interface { - self.iface - } - - fn type_record(&mut self, _id: TypeId, name: &str, record: &Record, docs: &Docs) { - self.src.pyimport("dataclasses", "dataclass"); - self.src.push_str("@dataclass\n"); - self.src - .push_str(&format!("class {}:\n", name.to_upper_camel_case())); - self.src.indent(); - self.src.docstring(docs); - for field in record.fields.iter() { - self.src.comment(&field.docs); - let field_name = field.name.to_snake_case(); - self.src.push_str(&format!("{field_name}: ")); - self.print_ty(&field.ty, true); - self.src.push_str("\n"); - } - if record.fields.is_empty() { - self.src.push_str("pass\n"); - } - self.src.dedent(); - self.src.push_str("\n"); - } - - fn type_tuple(&mut self, _id: TypeId, name: &str, tuple: &Tuple, docs: &Docs) { - self.src.comment(docs); - self.src - .push_str(&format!("{} = ", name.to_upper_camel_case())); - self.print_tuple(tuple); - self.src.push_str("\n"); - } - - fn type_flags(&mut self, _id: TypeId, name: &str, flags: &Flags, docs: &Docs) { - self.src.pyimport("enum", "Flag"); - self.src.pyimport("enum", "auto"); - self.src - .push_str(&format!("class {}(Flag):\n", name.to_upper_camel_case())); - self.src.indent(); - self.src.docstring(docs); - for flag in flags.flags.iter() { - let flag_name = flag.name.to_shouty_snake_case(); - self.src.comment(&flag.docs); - self.src.push_str(&format!("{flag_name} = auto()\n")); - } - if flags.flags.is_empty() { - self.src.push_str("pass\n"); - } - self.src.dedent(); - self.src.push_str("\n"); - } - - fn type_variant(&mut self, _id: TypeId, name: &str, variant: &Variant, docs: &Docs) { - self.src.pyimport("dataclasses", "dataclass"); - let mut cases = Vec::new(); - for case in variant.cases.iter() { - self.src.docstring(&case.docs); - self.src.push_str("@dataclass\n"); - let case_name = format!( - "{}{}", - name.to_upper_camel_case(), - case.name.to_upper_camel_case() - ); - self.src.push_str(&format!("class {case_name}:\n")); - self.src.indent(); - match &case.ty { - Some(ty) => { - self.src.push_str("value: "); - self.print_ty(ty, true); - } - None => self.src.push_str("pass"), - } - self.src.push_str("\n"); - self.src.dedent(); - self.src.push_str("\n"); - cases.push(case_name); - } - - self.src.pyimport("typing", "Union"); - self.src.comment(docs); - self.src.push_str(&format!( - "{} = Union[{}]\n", - name.to_upper_camel_case(), - cases.join(", "), - )); - self.src.push_str("\n"); - } - - /// Appends a Python definition for the provided Union to the current `Source`. - /// e.g. `MyUnion = Union[float, str, int]` - fn type_union(&mut self, _id: TypeId, name: &str, union: &Union, docs: &Docs) { - match classify_union(union.cases.iter().map(|t| t.ty)) { - PyUnionRepresentation::Wrapped => { - self.print_union_wrapped(name, union, docs); - } - PyUnionRepresentation::Raw => { - self.print_union_raw(name, union, docs); - } - } - } - - fn type_option(&mut self, _id: TypeId, name: &str, payload: &Type, docs: &Docs) { - self.src.pyimport("typing", "Optional"); - self.src.comment(docs); - self.src.push_str(&name.to_upper_camel_case()); - self.src.push_str(" = Optional["); - self.print_ty(payload, true); - self.src.push_str("]\n\n"); - } - - fn type_result(&mut self, _id: TypeId, name: &str, result: &Result_, docs: &Docs) { - self.import_result_type(); - - self.src.comment(docs); - self.src - .push_str(&format!("{} = Result[", name.to_upper_camel_case())); - self.print_optional_ty(result.ok.as_ref(), true); - self.src.push_str(", "); - self.print_optional_ty(result.err.as_ref(), true); - self.src.push_str("]\n\n"); - } - - fn type_enum(&mut self, _id: TypeId, name: &str, enum_: &Enum, docs: &Docs) { - self.src.pyimport("enum", "Enum"); - self.src - .push_str(&format!("class {}(Enum):\n", name.to_upper_camel_case())); - self.src.indent(); - self.src.docstring(docs); - for (i, case) in enum_.cases.iter().enumerate() { - self.src.comment(&case.docs); - - // TODO this handling of digits should be more general and - // shouldn't be here just to fix the one case in wasi where an - // enum variant is "2big" and doesn't generate valid Python. We - // should probably apply this to all generated Python - // identifiers. - let mut name = case.name.to_shouty_snake_case(); - if name.chars().next().unwrap().is_digit(10) { - name = format!("_{}", name); - } - self.src.push_str(&format!("{} = {}\n", name, i)); - } - self.src.dedent(); - self.src.push_str("\n"); - } - - fn type_alias(&mut self, _id: TypeId, name: &str, ty: &Type, docs: &Docs) { - self.src.comment(docs); - self.src - .push_str(&format!("{} = ", name.to_upper_camel_case())); - self.print_ty(ty, false); - self.src.push_str("\n"); - } - - fn type_list(&mut self, _id: TypeId, name: &str, ty: &Type, docs: &Docs) { - self.src.comment(docs); - self.src - .push_str(&format!("{} = ", name.to_upper_camel_case())); - self.print_list(ty); - self.src.push_str("\n"); - } - - fn type_builtin(&mut self, id: TypeId, name: &str, ty: &Type, docs: &Docs) { - self.type_alias(id, name, ty, docs); - } -} - -struct FunctionBindgen<'a> { - gen: &'a mut WasmtimePy, - iface: &'a Interface, - locals: Ns, - src: Source, - block_storage: Vec, - blocks: Vec<(String, Vec)>, - params: Vec, - payloads: Vec, - sizes: SizeAlign, - - memory: Option, - realloc: Option, - post_return: Option, - callee: String, - self_module_path: String, - at_root: bool, -} - -impl FunctionBindgen<'_> { - fn clamp(&mut self, results: &mut Vec, operands: &[String], min: T, max: T) - where - T: std::fmt::Display, - { - let clamp = self.print_clamp(); - results.push(format!("{clamp}({}, {min}, {max})", operands[0])); - } - - fn load(&mut self, ty: &str, offset: i32, operands: &[String], results: &mut Vec) { - let load = self.print_load(); - let memory = self.memory.as_ref().unwrap(); - let tmp = self.locals.tmp("load"); - self.src.pyimport("ctypes", None); - uwriteln!( - self.src, - "{tmp} = {load}(ctypes.{ty}, {memory}, caller, {}, {offset})", - operands[0], - ); - results.push(tmp); - } - - fn store(&mut self, ty: &str, offset: i32, operands: &[String]) { - let store = self.print_store(); - let memory = self.memory.as_ref().unwrap(); - self.src.pyimport("ctypes", None); - uwriteln!( - self.src, - "{store}(ctypes.{ty}, {memory}, caller, {}, {offset}, {})", - operands[1], - operands[0] - ); - } - - fn print_ty(&mut self, ty: &Type) { - with_igen( - &mut self.src, - self.gen, - self.iface, - self.at_root, - &self.self_module_path, - |gen| gen.print_ty(ty, false), - ) - } - - fn print_list(&mut self, element: &Type) { - with_igen( - &mut self.src, - self.gen, - self.iface, - self.at_root, - &self.self_module_path, - |gen| gen.print_list(element), - ) - } - - fn print_intrinsic( - &mut self, - name: &'static str, - gen: impl FnOnce(&str, &mut Source), - ) -> &'static str { - let path = if self.at_root { - ".intrinsics" - } else { - "..intrinsics" - }; - self.src.pyimport(path, name); - if !self.gen.all_intrinsics.insert(name) { - return name; - } - gen(name, &mut self.gen.intrinsics); - return name; - } - - fn print_validate_guest_char(&mut self) -> &'static str { - self.print_intrinsic("_validate_guest_char", |name, src| { - uwriteln!( - src, - " - def {name}(i: int) -> str: - if i > 0x10ffff or (i >= 0xd800 and i <= 0xdfff): - raise TypeError('not a valid char') - return chr(i) - ", - ); - }) - } - - fn print_i32_to_f32(&mut self) -> &'static str { - self.print_i32_to_f32_cvts(); - self.print_intrinsic("_i32_to_f32", |name, src| { - uwriteln!( - src, - " - def {name}(i: int) -> float: - _i32_to_f32_i32[0] = i # type: ignore - return _i32_to_f32_f32[0] # type: ignore - ", - ); - }) - } - - fn print_f32_to_i32(&mut self) -> &'static str { - self.print_i32_to_f32_cvts(); - self.print_intrinsic("_f32_to_i32", |name, src| { - uwriteln!( - src, - " - def {name}(i: float) -> int: - _i32_to_f32_f32[0] = i # type: ignore - return _i32_to_f32_i32[0] # type: ignore - ", - ); - }) - } - - fn print_i32_to_f32_cvts(&mut self) { - if !self.gen.all_intrinsics.insert("i32_to_f32_cvts") { - return; - } - self.gen.intrinsics.pyimport("ctypes", None); - self.gen - .intrinsics - .push_str("_i32_to_f32_i32 = ctypes.pointer(ctypes.c_int32(0))\n"); - self.gen.intrinsics.push_str( - "_i32_to_f32_f32 = ctypes.cast(_i32_to_f32_i32, ctypes.POINTER(ctypes.c_float))\n", - ); - } - - fn print_i64_to_f64(&mut self) -> &'static str { - self.print_i64_to_f64_cvts(); - self.print_intrinsic("_i64_to_f64", |name, src| { - uwriteln!( - src, - " - def {name}(i: int) -> float: - _i64_to_f64_i64[0] = i # type: ignore - return _i64_to_f64_f64[0] # type: ignore - ", - ); - }) - } - - fn print_f64_to_i64(&mut self) -> &'static str { - self.print_i64_to_f64_cvts(); - self.print_intrinsic("_f64_to_i64", |name, src| { - uwriteln!( - src, - " - def {name}(i: float) -> int: - _i64_to_f64_f64[0] = i # type: ignore - return _i64_to_f64_i64[0] # type: ignore - ", - ); - }) - } - - fn print_i64_to_f64_cvts(&mut self) { - if !self.gen.all_intrinsics.insert("i64_to_f64_cvts") { - return; - } - self.gen.intrinsics.pyimport("ctypes", None); - self.gen - .intrinsics - .push_str("_i64_to_f64_i64 = ctypes.pointer(ctypes.c_int64(0))\n"); - self.gen.intrinsics.push_str( - "_i64_to_f64_f64 = ctypes.cast(_i64_to_f64_i64, ctypes.POINTER(ctypes.c_double))\n", - ); - } - - fn print_clamp(&mut self) -> &'static str { - self.print_intrinsic("_clamp", |name, src| { - uwriteln!( - src, - " - def {name}(i: int, min: int, max: int) -> int: - if i < min or i > max: - raise OverflowError(f'must be between {{min}} and {{max}}') - return i - ", - ); - }) - } - - fn print_load(&mut self) -> &'static str { - self.print_intrinsic("_load", |name, src| { - src.pyimport("wasmtime", None); - src.pyimport("ctypes", None); - src.pyimport("typing", "Any"); - uwriteln!( - src, - " - def {name}(ty: Any, mem: wasmtime.Memory, store: wasmtime.Storelike, base: int, offset: int) -> Any: - ptr = (base & 0xffffffff) + offset - if ptr + ctypes.sizeof(ty) > mem.data_len(store): - raise IndexError('out-of-bounds store') - raw_base = mem.data_ptr(store) - c_ptr = ctypes.POINTER(ty)( - ty.from_address(ctypes.addressof(raw_base.contents) + ptr) - ) - return c_ptr[0] - ", - ); - }) - } - - fn print_store(&mut self) -> &'static str { - self.print_intrinsic("_store", |name, src| { - src.pyimport("wasmtime", None); - src.pyimport("ctypes", None); - src.pyimport("typing", "Any"); - uwriteln!( - src, - " - def {name}(ty: Any, mem: wasmtime.Memory, store: wasmtime.Storelike, base: int, offset: int, val: Any) -> None: - ptr = (base & 0xffffffff) + offset - if ptr + ctypes.sizeof(ty) > mem.data_len(store): - raise IndexError('out-of-bounds store') - raw_base = mem.data_ptr(store) - c_ptr = ctypes.POINTER(ty)( - ty.from_address(ctypes.addressof(raw_base.contents) + ptr) - ) - c_ptr[0] = val - ", - ); - }) - } - - fn print_decode_utf8(&mut self) -> &'static str { - self.print_intrinsic("_decode_utf8", |name, src| { - src.pyimport("wasmtime", None); - src.pyimport("ctypes", None); - src.pyimport("typing", "Tuple"); - uwriteln!( - src, - " - def {name}(mem: wasmtime.Memory, store: wasmtime.Storelike, ptr: int, len: int) -> str: - ptr = ptr & 0xffffffff - len = len & 0xffffffff - if ptr + len > mem.data_len(store): - raise IndexError('string out of bounds') - base = mem.data_ptr(store) - base = ctypes.POINTER(ctypes.c_ubyte)( - ctypes.c_ubyte.from_address(ctypes.addressof(base.contents) + ptr) - ) - return ctypes.string_at(base, len).decode('utf-8') - ", - ); - }) - } - - fn print_encode_utf8(&mut self) -> &'static str { - self.print_intrinsic("_encode_utf8", |name, src| { - src.pyimport("wasmtime", None); - src.pyimport("ctypes", None); - src.pyimport("typing", "Tuple"); - uwriteln!( - src, - " - def {name}(val: str, realloc: wasmtime.Func, mem: wasmtime.Memory, store: wasmtime.Storelike) -> Tuple[int, int]: - bytes = val.encode('utf8') - ptr = realloc(store, 0, 0, 1, len(bytes)) - assert(isinstance(ptr, int)) - ptr = ptr & 0xffffffff - if ptr + len(bytes) > mem.data_len(store): - raise IndexError('string out of bounds') - base = mem.data_ptr(store) - base = ctypes.POINTER(ctypes.c_ubyte)( - ctypes.c_ubyte.from_address(ctypes.addressof(base.contents) + ptr) - ) - ctypes.memmove(base, bytes, len(bytes)) - return (ptr, len(bytes)) - ", - ); - }) - } - - fn print_canon_lift(&mut self) -> &'static str { - self.print_intrinsic("_list_canon_lift", |name, src| { - src.pyimport("wasmtime", None); - src.pyimport("ctypes", None); - src.pyimport("typing", "List"); - src.pyimport("typing", "Any"); - // TODO: this is doing a native-endian read, not a little-endian - // read - uwriteln!( - src, - " - def {name}(ptr: int, len: int, size: int, ty: Any, mem: wasmtime.Memory ,store: wasmtime.Storelike) -> Any: - ptr = ptr & 0xffffffff - len = len & 0xffffffff - if ptr + len * size > mem.data_len(store): - raise IndexError('list out of bounds') - raw_base = mem.data_ptr(store) - base = ctypes.POINTER(ty)( - ty.from_address(ctypes.addressof(raw_base.contents) + ptr) - ) - if ty == ctypes.c_uint8: - return ctypes.string_at(base, len) - return base[:len] - ", - ); - }) - } - - fn print_canon_lower(&mut self) -> &'static str { - self.print_intrinsic("_list_canon_lower", |name, src| { - src.pyimport("wasmtime", None); - src.pyimport("ctypes", None); - src.pyimport("typing", "Tuple"); - src.pyimport("typing", "List"); - src.pyimport("typing", "Any"); - // TODO: is there a faster way to memcpy other than iterating over - // the input list? - // TODO: this is doing a native-endian write, not a little-endian - // write - uwriteln!( - src, - " - def {name}(list: Any, ty: Any, size: int, align: int, realloc: wasmtime.Func, mem: wasmtime.Memory, store: wasmtime.Storelike) -> Tuple[int, int]: - total_size = size * len(list) - ptr = realloc(store, 0, 0, align, total_size) - assert(isinstance(ptr, int)) - ptr = ptr & 0xffffffff - if ptr + total_size > mem.data_len(store): - raise IndexError('list realloc return of bounds') - raw_base = mem.data_ptr(store) - base = ctypes.POINTER(ty)( - ty.from_address(ctypes.addressof(raw_base.contents) + ptr) - ) - for i, val in enumerate(list): - base[i] = val - return (ptr, len(list)) - ", - ); - }) - } -} - -impl Bindgen for FunctionBindgen<'_> { - type Operand = String; - - fn sizes(&self) -> &SizeAlign { - &self.sizes - } - - fn push_block(&mut self) { - self.block_storage.push(self.src.take_body()); - } - - fn finish_block(&mut self, operands: &mut Vec) { - let to_restore = self.block_storage.pop().unwrap(); - let src = self.src.replace_body(to_restore); - self.blocks.push((src, mem::take(operands))); - } - - fn return_pointer(&mut self, _iface: &Interface, _size: usize, _align: usize) -> String { - unimplemented!() - } - - fn is_list_canonical(&self, iface: &Interface, ty: &Type) -> bool { - array_ty(iface, ty).is_some() - } - - fn emit( - &mut self, - iface: &Interface, - inst: &Instruction<'_>, - operands: &mut Vec, - results: &mut Vec, - ) { - match inst { - Instruction::GetArg { nth } => results.push(self.params[*nth].clone()), - Instruction::I32Const { val } => results.push(val.to_string()), - Instruction::ConstZero { tys } => { - for t in tys.iter() { - match t { - WasmType::I32 | WasmType::I64 => results.push("0".to_string()), - WasmType::F32 | WasmType::F64 => results.push("0.0".to_string()), - } - } - } - - // The representation of i32 in Python is a number, so 8/16-bit - // values get further clamped to ensure that the upper bits aren't - // set when we pass the value, ensuring that only the right number - // of bits are transferred. - Instruction::U8FromI32 => self.clamp(results, operands, u8::MIN, u8::MAX), - Instruction::S8FromI32 => self.clamp(results, operands, i8::MIN, i8::MAX), - Instruction::U16FromI32 => self.clamp(results, operands, u16::MIN, u16::MAX), - Instruction::S16FromI32 => self.clamp(results, operands, i16::MIN, i16::MAX), - // Ensure the bits of the number are treated as unsigned. - Instruction::U32FromI32 => { - results.push(format!("{} & 0xffffffff", operands[0])); - } - // All bigints coming from wasm are treated as signed, so convert - // it to ensure it's treated as unsigned. - Instruction::U64FromI64 => { - results.push(format!("{} & 0xffffffffffffffff", operands[0])); - } - // Nothing to do signed->signed where the representations are the - // same. - Instruction::S32FromI32 | Instruction::S64FromI64 => { - results.push(operands.pop().unwrap()) - } - - // All values coming from the host and going to wasm need to have - // their ranges validated, since the host could give us any value. - Instruction::I32FromU8 => self.clamp(results, operands, u8::MIN, u8::MAX), - Instruction::I32FromS8 => self.clamp(results, operands, i8::MIN, i8::MAX), - Instruction::I32FromU16 => self.clamp(results, operands, u16::MIN, u16::MAX), - Instruction::I32FromS16 => self.clamp(results, operands, i16::MIN, i16::MAX), - // TODO: need to do something to get this to be represented as signed? - Instruction::I32FromU32 => { - self.clamp(results, operands, u32::MIN, u32::MAX); - } - Instruction::I32FromS32 => self.clamp(results, operands, i32::MIN, i32::MAX), - // TODO: need to do something to get this to be represented as signed? - Instruction::I64FromU64 => self.clamp(results, operands, u64::MIN, u64::MAX), - Instruction::I64FromS64 => self.clamp(results, operands, i64::MIN, i64::MAX), - - // Python uses `float` for f32/f64, so everything is equivalent - // here. - Instruction::Float32FromF32 - | Instruction::Float64FromF64 - | Instruction::F32FromFloat32 - | Instruction::F64FromFloat64 => results.push(operands.pop().unwrap()), - - // Validate that i32 values coming from wasm are indeed valid code - // points. - Instruction::CharFromI32 => { - let validate = self.print_validate_guest_char(); - results.push(format!("{validate}({})", operands[0])); - } - - Instruction::I32FromChar => { - results.push(format!("ord({})", operands[0])); - } - - Instruction::Bitcasts { casts } => { - for (cast, op) in casts.iter().zip(operands) { - match cast { - Bitcast::I32ToF32 => { - let cvt = self.print_i32_to_f32(); - results.push(format!("{cvt}({})", op)); - } - Bitcast::F32ToI32 => { - let cvt = self.print_f32_to_i32(); - results.push(format!("{cvt}({})", op)); - } - Bitcast::I64ToF64 => { - let cvt = self.print_i64_to_f64(); - results.push(format!("{cvt}({})", op)); - } - Bitcast::F64ToI64 => { - let cvt = self.print_f64_to_i64(); - results.push(format!("{cvt}({})", op)); - } - Bitcast::I64ToF32 => { - let cvt = self.print_i32_to_f32(); - results.push(format!("{cvt}(({}) & 0xffffffff)", op)); - } - Bitcast::F32ToI64 => { - let cvt = self.print_f32_to_i32(); - results.push(format!("{cvt}({})", op)); - } - Bitcast::I32ToI64 | Bitcast::I64ToI32 | Bitcast::None => { - results.push(op.clone()) - } - } - } - } - - Instruction::BoolFromI32 => { - let op = self.locals.tmp("operand"); - let ret = self.locals.tmp("boolean"); - - uwriteln!(self.src, "{op} = {}", operands[0]); - uwriteln!(self.src, "if {op} == 0:"); - self.src.indent(); - uwriteln!(self.src, "{ret} = False"); - self.src.dedent(); - uwriteln!(self.src, "elif {op} == 1:"); - self.src.indent(); - uwriteln!(self.src, "{ret} = True"); - self.src.dedent(); - uwriteln!(self.src, "else:"); - self.src.indent(); - uwriteln!( - self.src, - "raise TypeError(\"invalid variant discriminant for bool\")" - ); - self.src.dedent(); - results.push(ret); - } - Instruction::I32FromBool => { - results.push(format!("int({})", operands[0])); - } - - Instruction::RecordLower { record, .. } => { - if record.fields.is_empty() { - return; - } - let tmp = self.locals.tmp("record"); - uwriteln!(self.src, "{tmp} = {}", operands[0]); - for field in record.fields.iter() { - let name = self.locals.tmp("field"); - uwriteln!(self.src, "{name} = {tmp}.{}", field.name.to_snake_case(),); - results.push(name); - } - } - - Instruction::RecordLift { name, .. } => { - results.push(format!( - "{}{}({})", - self.self_module_path, - name.to_upper_camel_case(), - operands.join(", ") - )); - } - Instruction::TupleLower { tuple, .. } => { - if tuple.types.is_empty() { - return; - } - self.src.push_str("("); - for _ in 0..tuple.types.len() { - let name = self.locals.tmp("tuplei"); - uwrite!(self.src, "{name},"); - results.push(name); - } - uwriteln!(self.src, ") = {}", operands[0]); - } - Instruction::TupleLift { .. } => { - if operands.is_empty() { - results.push("None".to_string()); - } else { - results.push(format!("({},)", operands.join(", "))); - } - } - Instruction::FlagsLift { name, .. } => { - let operand = match operands.len() { - 1 => operands[0].clone(), - _ => { - let tmp = self.locals.tmp("bits"); - uwriteln!(self.src, "{tmp} = 0"); - for (i, op) in operands.iter().enumerate() { - let i = 32 * i; - uwriteln!(self.src, "{tmp} |= {op} << {i}\n"); - } - tmp - } - }; - results.push(format!( - "{}{}({operand})", - self.self_module_path, - name.to_upper_camel_case() - )); - } - Instruction::FlagsLower { flags, .. } => match flags.repr().count() { - 1 => results.push(format!("({}).value", operands[0])), - n => { - let tmp = self.locals.tmp("bits"); - self.src - .push_str(&format!("{tmp} = ({}).value\n", operands[0])); - for i in 0..n { - let i = 32 * i; - results.push(format!("({tmp} >> {i}) & 0xffffffff")); - } - } - }, - - Instruction::VariantPayloadName => { - let name = self.locals.tmp("payload"); - results.push(name.clone()); - self.payloads.push(name); - } - - Instruction::VariantLower { - variant, - results: result_types, - name, - .. - } => { - let blocks = self - .blocks - .drain(self.blocks.len() - variant.cases.len()..) - .collect::>(); - let payloads = self - .payloads - .drain(self.payloads.len() - variant.cases.len()..) - .collect::>(); - - for _ in 0..result_types.len() { - results.push(self.locals.tmp("variant")); - } - - for (i, ((case, (block, block_results)), payload)) in - variant.cases.iter().zip(blocks).zip(payloads).enumerate() - { - if i == 0 { - self.src.push_str("if "); - } else { - self.src.push_str("elif "); - } - - uwriteln!( - self.src, - "isinstance({}, {}{}{}):", - operands[0], - self.self_module_path, - name.to_upper_camel_case(), - case.name.to_upper_camel_case() - ); - self.src.indent(); - if case.ty.is_some() { - uwriteln!(self.src, "{payload} = {}.value", operands[0]); - } - self.src.push_str(&block); - - for (i, result) in block_results.iter().enumerate() { - uwriteln!(self.src, "{} = {result}", results[i]); - } - self.src.dedent(); - } - let variant_name = name.to_upper_camel_case(); - self.src.push_str("else:\n"); - self.src.indent(); - uwriteln!( - self.src, - "raise TypeError(\"invalid variant specified for {variant_name}\")", - ); - self.src.dedent(); - } - - Instruction::VariantLift { - variant, name, ty, .. - } => { - let blocks = self - .blocks - .drain(self.blocks.len() - variant.cases.len()..) - .collect::>(); - - let result = self.locals.tmp("variant"); - uwrite!(self.src, "{result}: "); - self.print_ty(&Type::Id(*ty)); - self.src.push_str("\n"); - for (i, (case, (block, block_results))) in - variant.cases.iter().zip(blocks).enumerate() - { - if i == 0 { - self.src.push_str("if "); - } else { - self.src.push_str("elif "); - } - uwriteln!(self.src, "{} == {i}:", operands[0]); - self.src.indent(); - self.src.push_str(&block); - - uwrite!( - self.src, - "{result} = {}{}{}(", - self.self_module_path, - name.to_upper_camel_case(), - case.name.to_upper_camel_case() - ); - if block_results.len() > 0 { - assert!(block_results.len() == 1); - self.src.push_str(&block_results[0]); - } - self.src.push_str(")\n"); - self.src.dedent(); - } - self.src.push_str("else:\n"); - self.src.indent(); - let variant_name = name.to_upper_camel_case(); - uwriteln!( - self.src, - "raise TypeError(\"invalid variant discriminant for {variant_name}\")", - ); - self.src.dedent(); - results.push(result); - } - - Instruction::UnionLower { - union, - results: result_types, - name, - .. - } => { - let blocks = self - .blocks - .drain(self.blocks.len() - union.cases.len()..) - .collect::>(); - let payloads = self - .payloads - .drain(self.payloads.len() - union.cases.len()..) - .collect::>(); - - for _ in 0..result_types.len() { - results.push(self.locals.tmp("variant")); - } - - let union_representation = classify_union(union.cases.iter().map(|c| c.ty)); - let name = name.to_upper_camel_case(); - let op0 = &operands[0]; - for (i, ((case, (block, block_results)), payload)) in - union.cases.iter().zip(blocks).zip(payloads).enumerate() - { - self.src.push_str(if i == 0 { "if " } else { "elif " }); - uwrite!(self.src, "isinstance({op0}, "); - match union_representation { - // Prints the Python type for this union case - PyUnionRepresentation::Raw => self.print_ty(&case.ty), - // Prints the name of this union cases dataclass - PyUnionRepresentation::Wrapped => { - uwrite!(self.src, "{}{name}{i}", self.self_module_path); - } - } - uwriteln!(self.src, "):"); - self.src.indent(); - match union_representation { - // Uses the value directly - PyUnionRepresentation::Raw => { - uwriteln!(self.src, "{payload} = {op0}") - } - // Uses this union case dataclass's inner value - PyUnionRepresentation::Wrapped => { - uwriteln!(self.src, "{payload} = {op0}.value") - } - } - self.src.push_str(&block); - for (i, result) in block_results.iter().enumerate() { - uwriteln!(self.src, "{} = {result}", results[i]); - } - self.src.dedent(); - } - self.src.push_str("else:\n"); - self.src.indent(); - uwriteln!( - self.src, - "raise TypeError(\"invalid variant specified for {name}\")" - ); - self.src.dedent(); - } - - Instruction::UnionLift { - union, name, ty, .. - } => { - let blocks = self - .blocks - .drain(self.blocks.len() - union.cases.len()..) - .collect::>(); - - let result = self.locals.tmp("variant"); - uwrite!(self.src, "{result}: "); - self.print_ty(&Type::Id(*ty)); - self.src.push_str("\n"); - let union_representation = classify_union(union.cases.iter().map(|c| c.ty)); - let name = name.to_upper_camel_case(); - let op0 = &operands[0]; - for (i, (_case, (block, block_results))) in - union.cases.iter().zip(blocks).enumerate() - { - self.src.push_str(if i == 0 { "if " } else { "elif " }); - uwriteln!(self.src, "{op0} == {i}:"); - self.src.indent(); - self.src.push_str(&block); - assert!(block_results.len() == 1); - let block_result = &block_results[0]; - uwrite!(self.src, "{result} = "); - match union_representation { - // Uses the passed value directly - PyUnionRepresentation::Raw => self.src.push_str(block_result), - // Constructs an instance of the union cases dataclass - PyUnionRepresentation::Wrapped => { - uwrite!( - self.src, - "{}{name}{i}({block_result})", - self.self_module_path - ) - } - } - self.src.newline(); - self.src.dedent(); - } - self.src.push_str("else:\n"); - self.src.indent(); - uwriteln!( - self.src, - "raise TypeError(\"invalid variant discriminant for {name}\")\n", - ); - self.src.dedent(); - results.push(result); - } - - Instruction::OptionLower { - results: result_types, - .. - } => { - let (some, some_results) = self.blocks.pop().unwrap(); - let (none, none_results) = self.blocks.pop().unwrap(); - let some_payload = self.payloads.pop().unwrap(); - let _none_payload = self.payloads.pop().unwrap(); - - for _ in 0..result_types.len() { - results.push(self.locals.tmp("variant")); - } - - let op0 = &operands[0]; - uwriteln!(self.src, "if {op0} is None:"); - - self.src.indent(); - self.src.push_str(&none); - for (dst, result) in results.iter().zip(&none_results) { - uwriteln!(self.src, "{dst} = {result}"); - } - self.src.dedent(); - self.src.push_str("else:\n"); - self.src.indent(); - uwriteln!(self.src, "{some_payload} = {op0}"); - self.src.push_str(&some); - for (dst, result) in results.iter().zip(&some_results) { - uwriteln!(self.src, "{dst} = {result}"); - } - self.src.dedent(); - } - - Instruction::OptionLift { ty, .. } => { - let (some, some_results) = self.blocks.pop().unwrap(); - let (none, none_results) = self.blocks.pop().unwrap(); - assert!(none_results.len() == 0); - assert!(some_results.len() == 1); - let some_result = &some_results[0]; - - let result = self.locals.tmp("option"); - uwrite!(self.src, "{result}: "); - self.print_ty(&Type::Id(*ty)); - self.src.push_str("\n"); - - let op0 = &operands[0]; - uwriteln!(self.src, "if {op0} == 0:"); - self.src.indent(); - self.src.push_str(&none); - uwriteln!(self.src, "{result} = None"); - self.src.dedent(); - uwriteln!(self.src, "elif {op0} == 1:"); - self.src.indent(); - self.src.push_str(&some); - uwriteln!(self.src, "{result} = {some_result}"); - self.src.dedent(); - - self.src.push_str("else:\n"); - self.src.indent(); - self.src - .push_str("raise TypeError(\"invalid variant discriminant for option\")\n"); - self.src.dedent(); - - results.push(result); - } - - Instruction::ResultLower { - results: result_types, - .. - } => { - let (err, err_results) = self.blocks.pop().unwrap(); - let (ok, ok_results) = self.blocks.pop().unwrap(); - let err_payload = self.payloads.pop().unwrap(); - let ok_payload = self.payloads.pop().unwrap(); - let path = if self.at_root { ".types" } else { "..types" }; - self.src.pyimport(path, "Ok"); - self.src.pyimport(path, "Err"); - - for _ in 0..result_types.len() { - results.push(self.locals.tmp("variant")); - } - - let op0 = &operands[0]; - uwriteln!(self.src, "if isinstance({op0}, Ok):"); - - self.src.indent(); - uwriteln!(self.src, "{ok_payload} = {op0}.value"); - self.src.push_str(&ok); - for (dst, result) in results.iter().zip(&ok_results) { - uwriteln!(self.src, "{dst} = {result}"); - } - self.src.dedent(); - uwriteln!(self.src, "elif isinstance({op0}, Err):"); - self.src.indent(); - uwriteln!(self.src, "{err_payload} = {op0}.value"); - self.src.push_str(&err); - for (dst, result) in results.iter().zip(&err_results) { - uwriteln!(self.src, "{dst} = {result}"); - } - self.src.dedent(); - self.src.push_str("else:\n"); - self.src.indent(); - self.src.push_str(&format!( - "raise TypeError(\"invalid variant specified for expected\")\n", - )); - self.src.dedent(); - } - - Instruction::ResultLift { ty, .. } => { - let (err, err_results) = self.blocks.pop().unwrap(); - let (ok, ok_results) = self.blocks.pop().unwrap(); - let none = String::from("None"); - let err_result = err_results.get(0).unwrap_or(&none); - let ok_result = ok_results.get(0).unwrap_or(&none); - - let path = if self.at_root { ".types" } else { "..types" }; - self.src.pyimport(path, "Ok"); - self.src.pyimport(path, "Err"); - - let result = self.locals.tmp("expected"); - uwrite!(self.src, "{result}: "); - self.print_ty(&Type::Id(*ty)); - self.src.push_str("\n"); - - let op0 = &operands[0]; - uwriteln!(self.src, "if {op0} == 0:"); - self.src.indent(); - self.src.push_str(&ok); - uwriteln!(self.src, "{result} = Ok({ok_result})"); - self.src.dedent(); - uwriteln!(self.src, "elif {op0} == 1:"); - self.src.indent(); - self.src.push_str(&err); - uwriteln!(self.src, "{result} = Err({err_result})"); - self.src.dedent(); - - self.src.push_str("else:\n"); - self.src.indent(); - self.src - .push_str("raise TypeError(\"invalid variant discriminant for expected\")\n"); - self.src.dedent(); - - results.push(result); - } - - Instruction::EnumLower { .. } => results.push(format!("({}).value", operands[0])), - - Instruction::EnumLift { name, .. } => { - results.push(format!( - "{}{}({})", - self.self_module_path, - name.to_upper_camel_case(), - operands[0] - )); - } - - Instruction::ListCanonLower { element, .. } => { - let lower = self.print_canon_lower(); - let realloc = self.realloc.as_ref().unwrap(); - let memory = self.memory.as_ref().unwrap(); - - let ptr = self.locals.tmp("ptr"); - let len = self.locals.tmp("len"); - let array_ty = array_ty(iface, element).unwrap(); - let size = self.sizes.size(element); - let align = self.sizes.align(element); - uwriteln!( - self.src, - "{ptr}, {len} = {lower}({}, ctypes.{array_ty}, {size}, {align}, {realloc}, {memory}, caller)", - operands[0], - ); - results.push(ptr); - results.push(len); - } - Instruction::ListCanonLift { element, .. } => { - let lift = self.print_canon_lift(); - let memory = self.memory.as_ref().unwrap(); - let ptr = self.locals.tmp("ptr"); - let len = self.locals.tmp("len"); - uwriteln!(self.src, "{ptr} = {}", operands[0]); - uwriteln!(self.src, "{len} = {}", operands[1]); - let array_ty = array_ty(iface, element).unwrap(); - self.src.pyimport("ctypes", None); - let lift = format!( - "{lift}({ptr}, {len}, {}, ctypes.{array_ty}, {memory}, caller)", - self.sizes.size(element), - ); - self.src.pyimport("typing", "cast"); - let list = self.locals.tmp("list"); - uwrite!(self.src, "{list} = cast("); - self.print_list(element); - uwriteln!(self.src, ", {lift})"); - results.push(list); - } - Instruction::StringLower { .. } => { - let encode = self.print_encode_utf8(); - let realloc = self.realloc.as_ref().unwrap(); - let memory = self.memory.as_ref().unwrap(); - - let ptr = self.locals.tmp("ptr"); - let len = self.locals.tmp("len"); - uwriteln!( - self.src, - "{ptr}, {len} = {encode}({}, {realloc}, {memory}, caller)", - operands[0], - ); - results.push(ptr); - results.push(len); - } - Instruction::StringLift => { - let decode = self.print_decode_utf8(); - let memory = self.memory.as_ref().unwrap(); - let ptr = self.locals.tmp("ptr"); - let len = self.locals.tmp("len"); - uwriteln!(self.src, "{ptr} = {}", operands[0]); - uwriteln!(self.src, "{len} = {}", operands[1]); - let list = self.locals.tmp("list"); - uwriteln!( - self.src, - "{list} = {decode}({memory}, caller, {ptr}, {len})" - ); - results.push(list); - } - - Instruction::ListLower { element, .. } => { - let base = self.payloads.pop().unwrap(); - let e = self.payloads.pop().unwrap(); - let realloc = self.realloc.as_ref().unwrap(); - let (body, body_results) = self.blocks.pop().unwrap(); - assert!(body_results.is_empty()); - let vec = self.locals.tmp("vec"); - let result = self.locals.tmp("result"); - let len = self.locals.tmp("len"); - let size = self.sizes.size(element); - let align = self.sizes.align(element); - - // first store our vec-to-lower in a temporary since we'll - // reference it multiple times. - uwriteln!(self.src, "{vec} = {}", operands[0]); - uwriteln!(self.src, "{len} = len({vec})"); - - // ... then realloc space for the result in the guest module - uwriteln!( - self.src, - "{result} = {realloc}(caller, 0, 0, {align}, {len} * {size})", - ); - uwriteln!(self.src, "assert(isinstance({result}, int))"); - - // ... then consume the vector and use the block to lower the - // result. - let i = self.locals.tmp("i"); - uwriteln!(self.src, "for {i} in range(0, {len}):"); - self.src.indent(); - uwriteln!(self.src, "{e} = {vec}[{i}]"); - uwriteln!(self.src, "{base} = {result} + {i} * {size}"); - self.src.push_str(&body); - self.src.dedent(); - - results.push(result); - results.push(len); - } - - Instruction::ListLift { element, .. } => { - let (body, body_results) = self.blocks.pop().unwrap(); - let base = self.payloads.pop().unwrap(); - let size = self.sizes.size(element); - let ptr = self.locals.tmp("ptr"); - let len = self.locals.tmp("len"); - uwriteln!(self.src, "{ptr} = {}", operands[0]); - uwriteln!(self.src, "{len} = {}", operands[1]); - let result = self.locals.tmp("result"); - uwrite!(self.src, "{result}: "); - self.print_list(element); - uwriteln!(self.src, " = []"); - - let i = self.locals.tmp("i"); - assert_eq!(body_results.len(), 1); - let body_result0 = &body_results[0]; - - uwriteln!(self.src, "for {i} in range(0, {len}):"); - self.src.indent(); - uwriteln!(self.src, "{base} = {ptr} + {i} * {size}"); - self.src.push_str(&body); - uwriteln!(self.src, "{result}.append({body_result0})"); - self.src.dedent(); - results.push(result); - } - - Instruction::IterElem { .. } => { - let name = self.locals.tmp("e"); - results.push(name.clone()); - self.payloads.push(name); - } - Instruction::IterBasePointer => { - let name = self.locals.tmp("base"); - results.push(name.clone()); - self.payloads.push(name); - } - Instruction::CallWasm { sig, .. } => { - if sig.results.len() > 0 { - for i in 0..sig.results.len() { - if i > 0 { - self.src.push_str(", "); - } - let ret = self.locals.tmp("ret"); - self.src.push_str(&ret); - results.push(ret); - } - self.src.push_str(" = "); - } - self.src.push_str(&self.callee); - self.src.push_str("(caller"); - if operands.len() > 0 { - self.src.push_str(", "); - } - self.src.push_str(&operands.join(", ")); - self.src.push_str(")\n"); - for (ty, name) in sig.results.iter().zip(results.iter()) { - let ty = match ty { - WasmType::I32 | WasmType::I64 => "int", - WasmType::F32 | WasmType::F64 => "float", - }; - self.src - .push_str(&format!("assert(isinstance({}, {}))\n", name, ty)); - } - } - Instruction::CallInterface { func } => { - for i in 0..func.results.len() { - if i > 0 { - self.src.push_str(", "); - } - let result = self.locals.tmp("ret"); - self.src.push_str(&result); - results.push(result); - } - if func.results.len() > 0 { - self.src.push_str(" = "); - } - match &func.kind { - FunctionKind::Freestanding => { - self.src - .push_str(&format!("{}({})", self.callee, operands.join(", "),)); - } - } - self.src.push_str("\n"); - } - - Instruction::Return { amt, .. } => { - if let Some(s) = &self.post_return { - self.src.push_str(&format!("{s}(caller, ret)\n")); - } - match amt { - 0 => {} - 1 => self.src.push_str(&format!("return {}\n", operands[0])), - _ => { - self.src - .push_str(&format!("return ({})\n", operands.join(", "))); - } - } - } - - Instruction::I32Load { offset } => self.load("c_int32", *offset, operands, results), - Instruction::I64Load { offset } => self.load("c_int64", *offset, operands, results), - Instruction::F32Load { offset } => self.load("c_float", *offset, operands, results), - Instruction::F64Load { offset } => self.load("c_double", *offset, operands, results), - Instruction::I32Load8U { offset } => self.load("c_uint8", *offset, operands, results), - Instruction::I32Load8S { offset } => self.load("c_int8", *offset, operands, results), - Instruction::I32Load16U { offset } => self.load("c_uint16", *offset, operands, results), - Instruction::I32Load16S { offset } => self.load("c_int16", *offset, operands, results), - Instruction::I32Store { offset } => self.store("c_uint32", *offset, operands), - Instruction::I64Store { offset } => self.store("c_uint64", *offset, operands), - Instruction::F32Store { offset } => self.store("c_float", *offset, operands), - Instruction::F64Store { offset } => self.store("c_double", *offset, operands), - Instruction::I32Store8 { offset } => self.store("c_uint8", *offset, operands), - Instruction::I32Store16 { offset } => self.store("c_uint16", *offset, operands), - - Instruction::Malloc { size, align, .. } => { - let realloc = self.realloc.as_ref().unwrap(); - let ptr = self.locals.tmp("ptr"); - uwriteln!(self.src, "{ptr} = {realloc}(caller, 0, 0, {align}, {size})"); - uwriteln!(self.src, "assert(isinstance({ptr}, int))"); - results.push(ptr); - } - - i => unimplemented!("{:?}", i), - } - } -} - -fn classify_union(types: impl Iterator) -> PyUnionRepresentation { - #[derive(Debug, Hash, PartialEq, Eq)] - enum PyTypeClass { - Int, - Str, - Float, - Custom, - } - - let mut py_type_classes = HashSet::new(); - for ty in types { - let class = match ty { - Type::Bool - | Type::U8 - | Type::U16 - | Type::U32 - | Type::U64 - | Type::S8 - | Type::S16 - | Type::S32 - | Type::S64 => PyTypeClass::Int, - Type::Float32 | Type::Float64 => PyTypeClass::Float, - Type::Char | Type::String => PyTypeClass::Str, - Type::Id(_) => PyTypeClass::Custom, - }; - if !py_type_classes.insert(class) { - // Some of the cases are not distinguishable - return PyUnionRepresentation::Wrapped; - } - } - PyUnionRepresentation::Raw -} - -fn wasm_ty_ctor(ty: WasmType) -> &'static str { - match ty { - WasmType::I32 => "wasmtime.ValType.i32()", - WasmType::I64 => "wasmtime.ValType.i64()", - WasmType::F32 => "wasmtime.ValType.f32()", - WasmType::F64 => "wasmtime.ValType.f64()", - } -} - -fn wasm_ty_typing(ty: WasmType) -> &'static str { - match ty { - WasmType::I32 => "int", - WasmType::I64 => "int", - WasmType::F32 => "float", - WasmType::F64 => "float", - } -} - -/// Creates a temporary `InterfaceGenerator` with the given parameters to get -/// access to the various `print_*` methods on it. -fn with_igen( - src: &mut Source, - gen: &mut WasmtimePy, - iface: &Interface, - at_root: bool, - self_module_path: &str, - f: impl FnOnce(&mut InterfaceGenerator<'_>) -> R, -) -> R { - // The `print_ty` method is on `InterfaceGenerator` so jerry-rig one of - // those "quickly" to defer to it. - let mut gen = InterfaceGenerator { - src: mem::take(src), - gen, - iface, - at_root, - self_module_path, - }; - let ret = f(&mut gen); - *src = gen.src; - ret -} diff --git a/crates/gen-host-wasmtime-py/src/source.rs b/crates/gen-host-wasmtime-py/src/source.rs deleted file mode 100644 index a8ef64551..000000000 --- a/crates/gen-host-wasmtime-py/src/source.rs +++ /dev/null @@ -1,171 +0,0 @@ -use crate::imports::PyImports; -use std::fmt::{self, Write}; -use std::mem; -use wit_bindgen_core::wit_parser::*; - -/// A [Source] represents some unit of Python code -/// and keeps track of its indent. -#[derive(Default)] -pub struct Source { - body: Body, - imports: PyImports, -} - -#[derive(Default)] -pub struct Body { - contents: String, - indent: usize, -} - -impl Source { - /// Appends a string slice to this [Source]. - /// - /// Strings without newlines, they are simply appended. - /// Strings with newlines are appended and also new lines - /// are indented based on the current indent level. - pub fn push_str(&mut self, src: &str) { - let lines = src.lines().collect::>(); - let mut trim = None; - for (i, line) in lines.iter().enumerate() { - self.body.contents.push_str(if lines.len() == 1 { - line - } else { - let trim = match trim { - Some(n) => n, - None => { - let val = line.len() - line.trim_start().len(); - if !line.is_empty() { - trim = Some(val); - } - val - } - }; - line.get(trim..).unwrap_or("") - }); - if i != lines.len() - 1 || src.ends_with("\n") { - self.newline(); - } - } - } - - /// Prints the documentation as comments - /// e.g. - /// > \# Line one of docs node - /// > - /// > \# Line two of docs node - pub fn comment(&mut self, docs: &Docs) { - let docs = match &docs.contents { - Some(docs) => docs, - None => return, - }; - for line in docs.lines() { - self.push_str(&format!("# {}\n", line)); - } - } - - /// Prints the documentation as comments - /// e.g. - /// > """ - /// > - /// > Line one of docs node - /// > - /// > Line two of docs node - /// > - /// > """ - pub fn docstring(&mut self, docs: &Docs) { - let docs = match &docs.contents { - Some(docs) => docs, - None => return, - }; - let triple_quote = r#"""""#; - self.push_str(triple_quote); - self.newline(); - for line in docs.lines() { - self.push_str(line); - self.newline(); - } - self.push_str(triple_quote); - self.newline(); - } - - /// Indent the source one level. - pub fn indent(&mut self) { - self.body.indent += 4; - self.body.contents.push_str(" "); - } - - /// Unindent, or in Python terms "dedent", - /// the source one level. - pub fn dedent(&mut self) { - self.body.indent -= 4; - assert!(self.body.contents.ends_with(" ")); - self.body.contents.pop(); - self.body.contents.pop(); - self.body.contents.pop(); - self.body.contents.pop(); - } - - /// Go to the next line and apply any indent. - pub fn newline(&mut self) { - self.body.contents.push_str("\n"); - for _ in 0..self.body.indent { - self.body.contents.push_str(" "); - } - } - - pub fn pyimport<'a>(&mut self, module: &str, name: impl Into>) { - self.imports.pyimport(module, name.into()) - } - - pub fn finish(&self) -> String { - let mut ret = self.imports.finish(); - ret.push_str(&self.body.contents); - return ret; - } - - pub fn is_empty(&self) -> bool { - self.imports.is_empty() && self.body.contents.is_empty() - } - - pub fn take_body(&mut self) -> Body { - mem::take(&mut self.body) - } - - pub fn replace_body(&mut self, body: Body) -> String { - mem::replace(&mut self.body, body).contents - } -} - -impl Write for Source { - fn write_str(&mut self, s: &str) -> fmt::Result { - self.push_str(s); - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn simple_append() { - let mut s = Source::default(); - s.push_str("x"); - assert_eq!(s.body.contents, "x"); - s.push_str("y"); - assert_eq!(s.body.contents, "xy"); - s.push_str("z "); - assert_eq!(s.body.contents, "xyz "); - s.push_str(" a "); - assert_eq!(s.body.contents, "xyz a "); - s.push_str("\na"); - assert_eq!(s.body.contents, "xyz a \na"); - } - - #[test] - fn trim_ws() { - let mut s = Source::default(); - s.push_str("def foo():\n return 1\n"); - assert_eq!(s.body.contents, "def foo():\n return 1\n"); - } -} diff --git a/crates/gen-host-wasmtime-py/tests/codegen.rs b/crates/gen-host-wasmtime-py/tests/codegen.rs deleted file mode 100644 index 5bdd1037e..000000000 --- a/crates/gen-host-wasmtime-py/tests/codegen.rs +++ /dev/null @@ -1,38 +0,0 @@ -use std::path::Path; -use std::process::Command; - -macro_rules! codegen_test { - ($name:ident $test:tt) => { - #[test] - fn $name() { - drop(include_str!($test)); - test_helpers::run_component_codegen_test( - "wasmtime-py", - $test.as_ref(), - |name, component, files| { - wit_bindgen_core::component::generate( - &mut *wit_bindgen_gen_host_wasmtime_py::Opts::default().build(), - name, - component, - files, - ) - .unwrap() - }, - verify, - ) - } - }; -} - -test_helpers::codegen_tests!("*.wit"); - -fn verify(dir: &Path, name: &str) { - test_helpers::run_command( - Command::new("mypy") - .arg(dir) - .arg("--config-file") - .arg("mypy.ini") - .arg("--cache-dir") - .arg(dir.parent().unwrap().join("mypycache").join(name)), - ); -} diff --git a/crates/gen-host-wasmtime-py/tests/helpers.py b/crates/gen-host-wasmtime-py/tests/helpers.py deleted file mode 100644 index 72de98f6e..000000000 --- a/crates/gen-host-wasmtime-py/tests/helpers.py +++ /dev/null @@ -1,8 +0,0 @@ -import sys - -class TestWasi: - def log(self, list: bytes) -> None: - sys.stdout.buffer.write(list) - - def log_err(self, list: bytes) -> None: - sys.stderr.buffer.write(list) diff --git a/crates/gen-host-wasmtime-py/tests/runtime.rs b/crates/gen-host-wasmtime-py/tests/runtime.rs deleted file mode 100644 index f5d6b6ff8..000000000 --- a/crates/gen-host-wasmtime-py/tests/runtime.rs +++ /dev/null @@ -1,42 +0,0 @@ -use std::path::Path; -use std::process::Command; - -test_helpers::runtime_component_tests!("py"); - -fn execute(name: &str, lang: &str, wasm: &Path, py: &Path) { - let dir = test_helpers::test_directory("runtime", "wasmtime-py", &format!("{lang}/{name}")); - let wasm = std::fs::read(wasm).unwrap(); - - println!("OUT_DIR = {:?}", dir); - println!("Generating bindings..."); - let mut files = Default::default(); - wit_bindgen_core::component::generate( - &mut *wit_bindgen_gen_host_wasmtime_py::Opts::default().build(), - name, - &wasm, - &mut files, - ) - .unwrap(); - for (file, contents) in files.iter() { - let dst = dir.join(file); - std::fs::create_dir_all(dst.parent().unwrap()).unwrap(); - std::fs::write(&dst, contents).unwrap(); - } - - let cwd = std::env::current_dir().unwrap(); - println!("Running mypy..."); - let pathdir = std::env::join_paths([ - dir.parent().unwrap().to_str().unwrap(), - cwd.join("tests").to_str().unwrap(), - ]) - .unwrap(); - test_helpers::run_command( - Command::new("mypy") - .env("MYPYPATH", &pathdir) - .arg(py) - .arg("--cache-dir") - .arg(dir.parent().unwrap().join("mypycache").join(name)), - ); - - test_helpers::run_command(Command::new("python3").env("PYTHONPATH", &pathdir).arg(py)); -} diff --git a/crates/wit-bindgen-demo/Cargo.toml b/crates/wit-bindgen-demo/Cargo.toml index a6cdc9610..e8d70fc08 100644 --- a/crates/wit-bindgen-demo/Cargo.toml +++ b/crates/wit-bindgen-demo/Cargo.toml @@ -15,7 +15,6 @@ anyhow = { workspace = true } wit-bindgen-core = { workspace = true } wit-bindgen-gen-guest-rust = { workspace = true } wit-bindgen-gen-host-wasmtime-rust = { workspace = true } -wit-bindgen-gen-host-wasmtime-py = { workspace = true } wit-bindgen-gen-host-js = { workspace = true } wit-bindgen-gen-guest-c = { workspace = true } wit-bindgen-gen-guest-teavm-java = { workspace = true } diff --git a/crates/wit-bindgen-demo/demo.wit b/crates/wit-bindgen-demo/demo.wit index 02934b27d..52415ff54 100644 --- a/crates/wit-bindgen-demo/demo.wit +++ b/crates/wit-bindgen-demo/demo.wit @@ -12,7 +12,6 @@ world demo { rust, java, wasmtime, - wasmtime-py, c, markdown, } diff --git a/crates/wit-bindgen-demo/main.ts b/crates/wit-bindgen-demo/main.ts index fc297714d..fc9cfc8eb 100644 --- a/crates/wit-bindgen-demo/main.ts +++ b/crates/wit-bindgen-demo/main.ts @@ -96,7 +96,6 @@ class Editor { case "rust": case "java": case "wasmtime": - case "wasmtime-py": case "c": case "markdown": lang = this.language.value; diff --git a/crates/wit-bindgen-demo/src/lib.rs b/crates/wit-bindgen-demo/src/lib.rs index 0c7631cb3..6c07aaaab 100644 --- a/crates/wit-bindgen-demo/src/lib.rs +++ b/crates/wit-bindgen-demo/src/lib.rs @@ -88,10 +88,6 @@ fn render(lang: demo::Lang, wit: &str, files: &mut Files, options: &demo::Option opts.tracing = options.wasmtime_tracing; gen_world(opts.build(), files) } - demo::Lang::WasmtimePy => gen_component( - wit_bindgen_gen_host_wasmtime_py::Opts::default().build(), - files, - )?, demo::Lang::C => gen_world(wit_bindgen_gen_guest_c::Opts::default().build(), files), demo::Lang::Markdown => gen_world(wit_bindgen_gen_markdown::Opts::default().build(), files), demo::Lang::Js => { diff --git a/src/bin/wit-bindgen.rs b/src/bin/wit-bindgen.rs index 8e8c2d663..0f9fca6d8 100644 --- a/src/bin/wit-bindgen.rs +++ b/src/bin/wit-bindgen.rs @@ -49,13 +49,6 @@ enum HostGenerator { #[clap(flatten)] world: WorldOpt, }, - /// Generates bindings for Python hosts using the Wasmtime engine. - WasmtimePy { - #[clap(flatten)] - opts: wit_bindgen_gen_host_wasmtime_py::Opts, - #[clap(flatten)] - component: ComponentOpts, - }, /// Generates bindings for JavaScript hosts. Js { #[clap(flatten)] @@ -148,8 +141,7 @@ impl Opt { | Category::Guest(GuestGenerator::TeavmJava { common, .. }) | Category::Host(HostGenerator::WasmtimeRust { common, .. }) | Category::Markdown { common, .. } => common, - Category::Host(HostGenerator::Js { component, .. }) - | Category::Host(HostGenerator::WasmtimePy { component, .. }) => &component.common, + Category::Host(HostGenerator::Js { component, .. }) => &component.common, } } } @@ -163,9 +155,6 @@ fn main() -> Result<()> { Category::Host(HostGenerator::WasmtimeRust { opts, world, .. }) => { gen_world(opts.build(), world, &mut files)?; } - Category::Host(HostGenerator::WasmtimePy { opts, component }) => { - gen_component(opts.build(), component, &mut files)?; - } Category::Host(HostGenerator::Js { opts, component }) => { gen_component(opts.build()?, component, &mut files)?; } diff --git a/tests/runtime/flavorful/host.py b/tests/runtime/flavorful/host.py deleted file mode 100644 index 9134499c1..000000000 --- a/tests/runtime/flavorful/host.py +++ /dev/null @@ -1,76 +0,0 @@ -from typing import Tuple, List -from helpers import TestWasi -from flavorful import Flavorful, FlavorfulImports -import flavorful as e -from flavorful.imports import imports as i -from flavorful.types import Result, Ok, Err -import wasmtime - -class MyImports: - def f_list_in_record1(self, a: i.ListInRecord1) -> None: - pass - - def f_list_in_record2(self) -> i.ListInRecord2: - return i.ListInRecord2('list_in_record2') - - def f_list_in_record3(self, a: i.ListInRecord3) -> i.ListInRecord3: - assert(a.a == 'list_in_record3 input') - return i.ListInRecord3('list_in_record3 output') - - def f_list_in_record4(self, a: i.ListInAlias) -> i.ListInAlias: - assert(a.a == 'input4') - return i.ListInRecord4('result4') - - def f_list_in_variant1(self, a: i.ListInVariant1V1, b: i.ListInVariant1V2, c: i.ListInVariant1V3) -> None: - assert(a == 'foo') - assert(b == Err('bar')) - assert(c == 'baz') - - def f_list_in_variant2(self) -> i.ListInVariant2: - return 'list_in_variant2' - - def f_list_in_variant3(self, a: i.ListInVariant3) -> i.ListInVariant3: - assert(a == 'input3') - return 'output3' - - def errno_result(self) -> Result[None, i.MyErrno]: - return Err(i.MyErrno.B) - - def list_typedefs(self, a: i.ListTypedef, c: i.ListTypedef3) -> Tuple[i.ListTypedef2, i.ListTypedef3]: - assert(a == 'typedef1') - assert(c == ['typedef2']) - return (b'typedef3', ['typedef4']) - - def list_of_variants(self, a: List[bool], b: List[Result[None, None]], c: List[i.MyErrno]) -> Tuple[List[bool], List[Result[None, None]], List[i.MyErrno]]: - assert(a == [True, False]) - assert(b == [Ok(None), Err(None)]) - assert(c == [i.MyErrno.SUCCESS, i.MyErrno.A]) - return ( - [False, True], - [Err(None), Ok(None)], - [i.MyErrno.A, i.MyErrno.B], - ) - -def run() -> None: - store = wasmtime.Store() - wasm = Flavorful(store, FlavorfulImports(MyImports(), TestWasi())) - - wasm.test_imports(store) - wasm.f_list_in_record1(store, e.ListInRecord1("list_in_record1")) - assert(wasm.f_list_in_record2(store) == e.ListInRecord2(a="list_in_record2")) - - assert(wasm.f_list_in_record3(store, e.ListInRecord3("list_in_record3 input")).a == "list_in_record3 output") - assert(wasm.f_list_in_record4(store, e.ListInRecord4("input4")).a == "result4") - - wasm.f_list_in_variant1(store, "foo", e.Err("bar"), 'baz') - assert(wasm.f_list_in_variant2(store) == "list_in_variant2") - assert(wasm.f_list_in_variant3(store, "input3") == "output3") - - assert(isinstance(wasm.errno_result(store), e.Err)) - - r1, r2 = wasm.list_typedefs(store, "typedef1", ["typedef2"]) - assert(r1 == b'typedef3') - assert(r2 == ['typedef4']) - -if __name__ == '__main__': - run() diff --git a/tests/runtime/invalid/host.py b/tests/runtime/invalid/host.py deleted file mode 100644 index d5477c30f..000000000 --- a/tests/runtime/invalid/host.py +++ /dev/null @@ -1,103 +0,0 @@ -from typing import Callable, List, Tuple -import wasmtime -from invalid import Invalid, InvalidImports -from invalid.imports import Imports, imports as i -from helpers import TestWasi - -class MyImports(Imports): - def roundtrip_u8(self, x: int) -> int: - raise Exception('unreachable') - - def roundtrip_s8(self, x: int) -> int: - raise Exception('unreachable') - - def roundtrip_u16(self, x: int) -> int: - raise Exception('unreachable') - - def roundtrip_s16(self, x: int) -> int: - raise Exception('unreachable') - - def roundtrip_bool(self, x: bool) -> bool: - raise Exception('unreachable') - - def roundtrip_char(self, x: str) -> str: - raise Exception('unreachable') - - def roundtrip_enum(self, x: i.E) -> i.E: - raise Exception('unreachable') - - def unaligned1(self, x: List[int]) -> None: - raise Exception('unreachable') - - def unaligned2(self, x: List[int]) -> None: - raise Exception('unreachable') - - def unaligned3(self, x: List[int]) -> None: - raise Exception('unreachable') - - def unaligned4(self, x: List[i.Flag32]) -> None: - raise Exception('unreachable') - - def unaligned5(self, x: List[i.Flag64]) -> None: - raise Exception('unreachable') - - def unaligned6(self, x: List[i.UnalignedRecord]) -> None: - raise Exception('unreachable') - - def unaligned7(self, x: List[float]) -> None: - raise Exception('unreachable') - - def unaligned8(self, x: List[float]) -> None: - raise Exception('unreachable') - - def unaligned9(self, x: List[str]) -> None: - raise Exception('unreachable') - - def unaligned10(self, x: List[bytes]) -> None: - raise Exception('unreachable') - - -def new_wasm() -> Tuple[wasmtime.Store, Invalid]: - store = wasmtime.Store() - wasm = Invalid(store, InvalidImports(MyImports(), TestWasi())) - return (store, wasm) - -def run() -> None: - (store, wasm) = new_wasm() - - def assert_throws(f: Callable, msg: str) -> None: - try: - f() - raise RuntimeError('expected exception') - except TypeError as e: - actual = str(e) - except OverflowError as e: - actual = str(e) - except ValueError as e: - actual = str(e) - except IndexError as e: - actual = str(e) - if not msg in actual: - print(actual) - assert(msg in actual) - - # FIXME(#376) these should succeed - assert_throws(lambda: wasm.invalid_bool(store), 'discriminant for bool') - (store, wasm) = new_wasm() - assert_throws(lambda: wasm.invalid_u8(store), 'must be between') - (store, wasm) = new_wasm() - assert_throws(lambda: wasm.invalid_s8(store), 'must be between') - (store, wasm) = new_wasm() - assert_throws(lambda: wasm.invalid_u16(store), 'must be between') - (store, wasm) = new_wasm() - assert_throws(lambda: wasm.invalid_s16(store), 'must be between') - - (store, wasm) = new_wasm() - assert_throws(lambda: wasm.invalid_char(store), 'not a valid char') - (store, wasm) = new_wasm() - assert_throws(lambda: wasm.invalid_enum(store), 'not a valid E') - - # FIXME(#370) should call `unalignedN` and expect an error - -if __name__ == '__main__': - run() diff --git a/tests/runtime/lists/host.py b/tests/runtime/lists/host.py deleted file mode 100644 index 8e5ea000a..000000000 --- a/tests/runtime/lists/host.py +++ /dev/null @@ -1,99 +0,0 @@ -from typing import Tuple, List -from helpers import TestWasi -from lists import Lists, ListsImports -import sys -import wasmtime - -class MyImports: - def empty_list_param(self, a: bytes) -> None: - assert(a == b'') - - def empty_string_param(self, a: str) -> None: - assert(a == '') - - def empty_list_result(self) -> bytes: - return b'' - - def empty_string_result(self) -> str: - return '' - - def list_param(self, a: bytes) -> None: - assert(a == b'\x01\x02\x03\x04') - - def list_param2(self, a: str) -> None: - assert(a == 'foo') - - def list_param3(self, a: List[str]) -> None: - assert(a == ['foo', 'bar', 'baz']) - - def list_param4(self, a: List[List[str]]) -> None: - assert(a == [['foo', 'bar'], ['baz']]) - - def list_result(self) -> bytes: - return b'\x01\x02\x03\x04\x05' - - def list_result2(self) -> str: - return 'hello!' - - def list_result3(self) -> List[str]: - return ['hello,', 'world!'] - - def list_roundtrip(self, a: bytes) -> bytes: - return a - - def string_roundtrip(self, a: str) -> str: - return a - - def list_minmax8(self, a: bytes, b: List[int]) -> Tuple[bytes, List[int]]: - assert(a == b'\x00\xff') - assert(b == [-(1 << (8 - 1)), (1 << (8 - 1)) - 1]) - return (a, b) - - def list_minmax16(self, a: List[int], b: List[int]) -> Tuple[List[int], List[int]]: - assert(a == [0, (1 << 16) - 1]) - assert(b == [-(1 << (16 - 1)), (1 << (16 - 1)) - 1]) - return (a, b) - - def list_minmax32(self, a: List[int], b: List[int]) -> Tuple[List[int], List[int]]: - assert(a == [0, (1 << 32) - 1]) - assert(b == [-(1 << (32 - 1)), (1 << (32 - 1)) - 1]) - return (a, b) - - def list_minmax64(self, a: List[int], b: List[int]) -> Tuple[List[int], List[int]]: - assert(a == [0, (1 << 64) - 1]) - assert(b == [-(1 << (64 - 1)), (1 << (64 - 1)) - 1]) - return (a, b) - - def list_minmax_float(self, a: List[float], b: List[float]) -> Tuple[List[float], List[float]]: - assert(a == [-3.4028234663852886e+38, 3.4028234663852886e+38, -float('inf'), float('inf')]) - assert(b == [-sys.float_info.max, sys.float_info.max, -float('inf'), float('inf')]) - return (a, b) - -def run() -> None: - store = wasmtime.Store() - wasm = Lists(store, ListsImports(MyImports(), TestWasi())) - - allocated_bytes = wasm.allocated_bytes(store) - wasm.test_imports(store) - wasm.empty_list_param(store, b'') - wasm.empty_string_param(store, '') - assert(wasm.empty_list_result(store) == b'') - assert(wasm.empty_string_result(store) == '') - wasm.list_param(store, b'\x01\x02\x03\x04') - wasm.list_param2(store, "foo") - wasm.list_param3(store, ["foo", "bar", "baz"]) - wasm.list_param4(store, [["foo", "bar"], ["baz"]]) - assert(wasm.list_result(store) == b'\x01\x02\x03\x04\x05') - assert(wasm.list_result2(store) == "hello!") - assert(wasm.list_result3(store) == ["hello,", "world!"]) - - assert(wasm.string_roundtrip(store, "x") == "x") - assert(wasm.string_roundtrip(store, "") == "") - assert(wasm.string_roundtrip(store, "hello ⚑ world") == "hello ⚑ world") - - # Ensure that we properly called `free` everywhere in all the glue that we - # needed to. - assert(allocated_bytes == wasm.allocated_bytes(store)) - -if __name__ == '__main__': - run() diff --git a/tests/runtime/many_arguments/host.py b/tests/runtime/many_arguments/host.py deleted file mode 100644 index b5819f9b3..000000000 --- a/tests/runtime/many_arguments/host.py +++ /dev/null @@ -1,48 +0,0 @@ -import wasmtime -from many_arguments import ManyArguments, ManyArgumentsImports -from helpers import TestWasi - -class MyImports: - def many_arguments(self, - a1: int, - a2: int, - a3: int, - a4: int, - a5: int, - a6: int, - a7: int, - a8: int, - a9: int, - a10: int, - a11: int, - a12: int, - a13: int, - a14: int, - a15: int, - a16: int) -> None: - assert(a1 == 1) - assert(a2 == 2) - assert(a3 == 3) - assert(a4 == 4) - assert(a5 == 5) - assert(a6 == 6) - assert(a7 == 7) - assert(a8 == 8) - assert(a9 == 9) - assert(a10 == 10) - assert(a11 == 11) - assert(a12 == 12) - assert(a13 == 13) - assert(a14 == 14) - assert(a15 == 15) - assert(a16 == 16) - - -def run() -> None: - store = wasmtime.Store() - wasm = ManyArguments(store, ManyArgumentsImports(MyImports(), TestWasi())) - - wasm.many_arguments(store, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,14, 15, 16) - -if __name__ == '__main__': - run() diff --git a/tests/runtime/numbers/host.py b/tests/runtime/numbers/host.py deleted file mode 100644 index 228c97ff0..000000000 --- a/tests/runtime/numbers/host.py +++ /dev/null @@ -1,94 +0,0 @@ -import math -import wasmtime -from numbers import Numbers, NumbersImports -from helpers import TestWasi - -class MyImports: - def roundtrip_u8(self, a: int) -> int: - return a - - def roundtrip_s8(self, a: int) -> int: - return a - - def roundtrip_u16(self, a: int) -> int: - return a - - def roundtrip_s16(self, a: int) -> int: - return a - - def roundtrip_u32(self, a: int) -> int: - return a - - def roundtrip_s32(self, a: int) -> int: - return a - - def roundtrip_u64(self, a: int) -> int: - return a - - def roundtrip_s64(self, a: int) -> int: - return a - - def roundtrip_float32(self, a: float) -> float: - return a - - def roundtrip_float64(self, a: float) -> float: - return a - - def roundtrip_char(self, a: str) -> str: - return a - - def set_scalar(self, a: int) -> None: - self.scalar = a - - def get_scalar(self) -> int: - return self.scalar - -def run() -> None: - store = wasmtime.Store() - wasm = Numbers(store, NumbersImports(MyImports(), TestWasi())) - - wasm.test_imports(store) - assert(wasm.roundtrip_u8(store, 1) == 1) - assert(wasm.roundtrip_u8(store, (1 << 8) - 1) == (1 << 8) - 1) - assert(wasm.roundtrip_u16(store, 1) == 1) - assert(wasm.roundtrip_u16(store, (1 << 16) - 1) == (1 << 16) - 1) - assert(wasm.roundtrip_u32(store, 1) == 1) - assert(wasm.roundtrip_u32(store, (1 << 32) - 1) == (1 << 32) - 1) - assert(wasm.roundtrip_u64(store, 1) == 1) - assert(wasm.roundtrip_u64(store, (1 << 64) - 1) == (1 << 64) - 1) - - assert(wasm.roundtrip_s8(store, 1) == 1) - assert(wasm.roundtrip_s8(store, (1 << (8 - 1) - 1)) == (1 << (8 - 1) - 1)) - assert(wasm.roundtrip_s8(store, -(1 << (8 - 1))) == -(1 << (8 - 1))) - assert(wasm.roundtrip_s16(store, 1) == 1) - assert(wasm.roundtrip_s16(store, (1 << (16 - 1) - 1)) == (1 << (16 - 1) - 1)) - assert(wasm.roundtrip_s16(store, -(1 << (16 - 1))) == -(1 << (16 - 1))) - assert(wasm.roundtrip_s32(store, 1) == 1) - assert(wasm.roundtrip_s32(store, (1 << (32 - 1) - 1)) == (1 << (32 - 1) - 1)) - assert(wasm.roundtrip_s32(store, -(1 << (32 - 1))) == -(1 << (32 - 1))) - assert(wasm.roundtrip_s64(store, 1) == 1) - assert(wasm.roundtrip_s64(store, (1 << (64 - 1) - 1)) == (1 << (64 - 1) - 1)) - assert(wasm.roundtrip_s64(store, -(1 << (64 - 1))) == -(1 << (64 - 1))) - - inf = float('inf') - assert(wasm.roundtrip_float32(store, 1.0) == 1.0) - assert(wasm.roundtrip_float32(store, inf) == inf) - assert(wasm.roundtrip_float32(store, -inf) == -inf) - assert(math.isnan(wasm.roundtrip_float32(store, float('nan')))) - - assert(wasm.roundtrip_float64(store, 1.0) == 1.0) - assert(wasm.roundtrip_float64(store, inf) == inf) - assert(wasm.roundtrip_float64(store, -inf) == -inf) - assert(math.isnan(wasm.roundtrip_float64(store, float('nan')))) - - assert(wasm.roundtrip_char(store, 'a') == 'a') - assert(wasm.roundtrip_char(store, ' ') == ' ') - assert(wasm.roundtrip_char(store, '🚩') == '🚩') - - wasm.set_scalar(store, 2) - assert(wasm.get_scalar(store) == 2) - wasm.set_scalar(store, 4) - assert(wasm.get_scalar(store) == 4) - -if __name__ == '__main__': - run() diff --git a/tests/runtime/records/host.py b/tests/runtime/records/host.py deleted file mode 100644 index 61198223f..000000000 --- a/tests/runtime/records/host.py +++ /dev/null @@ -1,61 +0,0 @@ -from helpers import TestWasi -from records import Records, RecordsImports -from records.imports import imports as i -from typing import Tuple -import records as e -import wasmtime - -class MyImports: - def multiple_results(self) -> Tuple[int, int]: - return (4, 5) - - def swap_tuple(self, a: Tuple[int, int]) -> Tuple[int, int]: - return (a[1], a[0]) - - def roundtrip_flags1(self, a: i.F1) -> i.F1: - return a - - def roundtrip_flags2(self, a: i.F2) -> i.F2: - return a - - def roundtrip_flags3(self, a: i.Flag8, b: i.Flag16, c: i.Flag32, d: i.Flag64) -> Tuple[i.Flag8, i.Flag16, i.Flag32, i.Flag64]: - return (a, b, c, d) - - def roundtrip_record1(self, a: i.R1) -> i.R1: - return a - - def tuple0(self, a: None) -> None: - pass - - def tuple1(self, a: Tuple[int]) -> Tuple[int]: - return (a[0],) - -def run() -> None: - store = wasmtime.Store() - wasm = Records(store, RecordsImports(MyImports(), TestWasi())) - - wasm.test_imports(store) - assert(wasm.multiple_results(store) == (100, 200)) - assert(wasm.swap_tuple(store, (1, 2)) == (2, 1)) - assert(wasm.roundtrip_flags1(store, e.F1.A) == e.F1.A) - assert(wasm.roundtrip_flags1(store, e.F1(0)) == e.F1(0)) - assert(wasm.roundtrip_flags1(store, e.F1.A | e.F1.B) == (e.F1.A | e.F1.B)) - - assert(wasm.roundtrip_flags2(store, e.F2.C) == e.F2.C) - assert(wasm.roundtrip_flags2(store, e.F2(0)) == e.F2(0)) - assert(wasm.roundtrip_flags2(store, e.F2.D) == e.F2.D) - assert(wasm.roundtrip_flags2(store, e.F2.C | e.F2.E) == (e.F2.C | e.F2.E)) - - r = wasm.roundtrip_record1(store, e.R1(8, e.F1(0))) - assert(r.a == 8) - assert(r.b == e.F1(0)) - - r = wasm.roundtrip_record1(store, e.R1(a=0, b=e.F1.A | e.F1.B)) - assert(r.a == 0) - assert(r.b == (e.F1.A | e.F1.B)) - - wasm.tuple0(store, None) - assert(wasm.tuple1(store, (1,)) == (1,)) - -if __name__ == '__main__': - run() diff --git a/tests/runtime/smoke/host.py b/tests/runtime/smoke/host.py deleted file mode 100644 index 4f390775b..000000000 --- a/tests/runtime/smoke/host.py +++ /dev/null @@ -1,19 +0,0 @@ -from smoke import Smoke, SmokeImports -from helpers import TestWasi -import wasmtime - -class MyImports: - def thunk(self) -> None: - self.hit = True - -def run() -> None: - store = wasmtime.Store() - - imports = MyImports() - wasm = Smoke(store, SmokeImports(imports, TestWasi())) - - wasm.thunk(store) - assert(imports.hit) - -if __name__ == '__main__': - run() diff --git a/tests/runtime/unions/host.py b/tests/runtime/unions/host.py deleted file mode 100644 index 08c95ae6a..000000000 --- a/tests/runtime/unions/host.py +++ /dev/null @@ -1,228 +0,0 @@ -from typing import Union -import wasmtime -from helpers import TestWasi -from unions import Unions, UnionsImports -import unions as e -from unions.imports import imports as i - -class MyImports: - # Simple uses of unions whose inner values all have the same Python representation - def add_one_integer(self, num: i.AllIntegers) -> i.AllIntegers: - # Bool - if isinstance(num, i.AllIntegers0): - assert num.value in (True, False) - return i.AllIntegers0(not num.value) - # The unsigned numbers - elif isinstance(num, i.AllIntegers1): - lower_limit = 0 - upper_limit = 2**8 - assert lower_limit <= num.value < upper_limit - return i.AllIntegers1(num.value + 1 % upper_limit) - elif isinstance(num, i.AllIntegers2): - lower_limit = 0 - upper_limit = 2**16 - assert lower_limit <= num.value < upper_limit - return i.AllIntegers2(num.value + 1 % upper_limit) - elif isinstance(num, i.AllIntegers3): - lower_limit = 0 - upper_limit = 2**32 - assert lower_limit <= num.value < upper_limit - return i.AllIntegers3(num.value + 1 % upper_limit) - elif isinstance(num, i.AllIntegers4): - lower_limit = 0 - upper_limit = 2**64 - assert lower_limit <= num.value < upper_limit - return i.AllIntegers4(num.value + 1 % upper_limit) - # The signed numbers - elif isinstance(num, i.AllIntegers5): - lower_limit = -2**7 - upper_limit = 2**7 - assert lower_limit <= num.value < upper_limit - return i.AllIntegers5(num.value + 1 % upper_limit) - elif isinstance(num, i.AllIntegers6): - lower_limit = -2**15 - upper_limit = 2**15 - assert lower_limit <= num.value < upper_limit - return i.AllIntegers6(num.value + 1 % upper_limit) - elif isinstance(num, i.AllIntegers7): - lower_limit = -2**31 - upper_limit = 2**31 - assert lower_limit <= num.value < upper_limit - return i.AllIntegers7(num.value + 1 % upper_limit) - elif isinstance(num, i.AllIntegers8): - lower_limit = -2**63 - upper_limit = 2**63 - assert lower_limit <= num.value < upper_limit - return i.AllIntegers8(num.value + 1 % upper_limit) - else: - raise ValueError("Invalid input value!") - - def add_one_float(self, num: i.AllFloats) -> i.AllFloats: - if isinstance(num, i.AllFloats0): - return i.AllFloats0(num.value + 1) - if isinstance(num, i.AllFloats1): - return i.AllFloats1(num.value + 1) - else: - raise ValueError("Invalid input value!") - - def replace_first_char(self, text: i.AllText, letter: str) -> i.AllText: - if isinstance(text, i.AllText0): - return i.AllText0(letter) - if isinstance(text, i.AllText1): - return i.AllText1(letter + text.value[1:]) - else: - raise ValueError("Invalid input value!") - - # Identify each case of unions whose inner values all have the same Python representation - def identify_integer(self, num: i.AllIntegers) -> int: - # Bool - if isinstance(num, i.AllIntegers0): - return 0 - # The unsigned numbers - elif isinstance(num, i.AllIntegers1): - return 1 - elif isinstance(num, i.AllIntegers2): - return 2 - elif isinstance(num, i.AllIntegers3): - return 3 - elif isinstance(num, i.AllIntegers4): - return 4 - # The signed numbers - elif isinstance(num, i.AllIntegers5): - return 5 - elif isinstance(num, i.AllIntegers6): - return 6 - elif isinstance(num, i.AllIntegers7): - return 7 - elif isinstance(num, i.AllIntegers8): - return 8 - else: - raise ValueError("Invalid input value!") - - def identify_float(self, num: i.AllFloats) -> int: - if isinstance(num, i.AllFloats0): - return 0 - if isinstance(num, i.AllFloats1): - return 1 - else: - raise ValueError("Invalid input value!") - - def identify_text(self, text: i.AllText) -> int: - if isinstance(text, i.AllText0): - return 0 - if isinstance(text, i.AllText1): - return 1 - else: - raise ValueError("Invalid input value!") - - # A simple use of a union which contains multiple entries of the same type - def add_one_duplicated(self, num: i.DuplicatedS32) -> i.DuplicatedS32: - if isinstance(num, i.DuplicatedS320): - return i.DuplicatedS320(num.value + 1) - if isinstance(num, i.DuplicatedS321): - return i.DuplicatedS321(num.value + 1) - if isinstance(num, i.DuplicatedS322): - return i.DuplicatedS322(num.value + 1) - else: - raise ValueError("Invalid input value!") - - # Identify each case of unions which contains multiple entries of the same type - def identify_duplicated(self, num: i.DuplicatedS32) -> int: - if isinstance(num, i.DuplicatedS320): - return 0 - if isinstance(num, i.DuplicatedS321): - return 1 - if isinstance(num, i.DuplicatedS322): - return 2 - else: - raise ValueError("Invalid input value!") - - # A simple use of a union whose cases have distinct Python representations - def add_one_distinguishable_num(self, num: Union[float, int]) -> Union[float, int]: - return num + 1 - - # Identify each case of unions whose cases have distinct Python representations - def identify_distinguishable_num(self, num: i.DistinguishableNum) -> int: - if isinstance(num, float): - return 0 - elif isinstance(num, int): - return 1 - else: - raise ValueError("Invalid input value!") - -def run() -> None: - store = wasmtime.Store() - wasm = Unions(store, UnionsImports(MyImports(), TestWasi())) - - # TODO: should get these tests working ideally but requires some - # bit-fiddling on the host. - # wasm.test_imports(store) - - # All-Integers - # Booleans - assert wasm.add_one_integer(store, e.AllIntegers0(False)) == e.AllIntegers0(True) - assert wasm.add_one_integer(store, e.AllIntegers0(True)) == e.AllIntegers0(False) - # Unsigned integers - assert wasm.add_one_integer(store, e.AllIntegers1(0)) == e.AllIntegers1(1) - assert wasm.add_one_integer(store, e.AllIntegers1(2**8-1)) == e.AllIntegers1(0) - assert wasm.add_one_integer(store, e.AllIntegers2(0)) == e.AllIntegers2(1) - assert wasm.add_one_integer(store, e.AllIntegers2(2**16-1)) == e.AllIntegers2(0) - assert wasm.add_one_integer(store, e.AllIntegers3(0)) == e.AllIntegers3(1) - assert wasm.add_one_integer(store, e.AllIntegers3(2**32-1)) == e.AllIntegers3(0) - assert wasm.add_one_integer(store, e.AllIntegers4(0)) == e.AllIntegers4(1) - assert wasm.add_one_integer(store, e.AllIntegers4(2**64-1)) == e.AllIntegers4(0) - # Signed integers - assert wasm.add_one_integer(store, e.AllIntegers5(0)) == e.AllIntegers5(1) - assert wasm.add_one_integer(store, e.AllIntegers5(2**7-1)) == e.AllIntegers5(-2**7) - assert wasm.add_one_integer(store, e.AllIntegers6(0)) == e.AllIntegers6(1) - assert wasm.add_one_integer(store, e.AllIntegers6(2**15-1)) == e.AllIntegers6(-2**15) - assert wasm.add_one_integer(store, e.AllIntegers7(0)) == e.AllIntegers7(1) - assert wasm.add_one_integer(store, e.AllIntegers7(2**31-1)) == e.AllIntegers7(-2**31) - assert wasm.add_one_integer(store, e.AllIntegers8(0)) == e.AllIntegers8(1) - assert wasm.add_one_integer(store, e.AllIntegers8(2**63-1)) == e.AllIntegers8(-2**63) - - # All-Floats - assert wasm.add_one_float(store, e.AllFloats0(0.0)) == e.AllFloats0(1.0) - assert wasm.add_one_float(store, e.AllFloats1(0.0)) == e.AllFloats1(1.0) - - # All-Text - assert wasm.replace_first_char(store, e.AllText0('a'), 'z') == e.AllText0('z') - assert wasm.replace_first_char(store, e.AllText1('abc'), 'z') == e.AllText1('zbc') - - # All-Integers - assert wasm.identify_integer(store, e.AllIntegers0(True)) == 0 - assert wasm.identify_integer(store, e.AllIntegers1(0)) == 1 - assert wasm.identify_integer(store, e.AllIntegers2(0)) == 2 - assert wasm.identify_integer(store, e.AllIntegers3(0)) == 3 - assert wasm.identify_integer(store, e.AllIntegers4(0)) == 4 - assert wasm.identify_integer(store, e.AllIntegers5(0)) == 5 - assert wasm.identify_integer(store, e.AllIntegers6(0)) == 6 - assert wasm.identify_integer(store, e.AllIntegers7(0)) == 7 - assert wasm.identify_integer(store, e.AllIntegers8(0)) == 8 - - # All-Floats - assert wasm.identify_float(store, e.AllFloats0(0.0)) == 0 - assert wasm.identify_float(store, e.AllFloats1(0.0)) == 1 - - # All-Text - assert wasm.identify_text(store, e.AllText0('a')) == 0 - assert wasm.identify_text(store, e.AllText1('abc')) == 1 - - # Duplicated - assert wasm.add_one_duplicated(store, e.DuplicatedS320(0)) == e.DuplicatedS320(1) - assert wasm.add_one_duplicated(store, e.DuplicatedS321(1)) == e.DuplicatedS321(2) - assert wasm.add_one_duplicated(store, e.DuplicatedS322(2)) == e.DuplicatedS322(3) - - assert wasm.identify_duplicated(store, e.DuplicatedS320(0)) == 0 - assert wasm.identify_duplicated(store, e.DuplicatedS321(0)) == 1 - assert wasm.identify_duplicated(store, e.DuplicatedS322(0)) == 2 - - # Distinguishable - assert wasm.add_one_distinguishable_num(store, 0.0) == 1.0 - assert wasm.add_one_distinguishable_num(store, 0) == 1 - - assert wasm.identify_distinguishable_num(store, 0.0) == 0 - assert wasm.identify_distinguishable_num(store, 1) == 1 - -if __name__ == '__main__': - run() diff --git a/tests/runtime/variants/host.py b/tests/runtime/variants/host.py deleted file mode 100644 index 59b064725..000000000 --- a/tests/runtime/variants/host.py +++ /dev/null @@ -1,104 +0,0 @@ -from typing import Optional, Tuple -import wasmtime -from helpers import TestWasi -from variants.imports import imports as i -from variants import Variants, VariantsImports -import variants as e -from variants.types import Result, Ok, Err - -class MyImports: - def roundtrip_option(self, a: Optional[float]) -> Optional[int]: - if a: - return int(a) - return None - - def roundtrip_result(self, a: Result[int, float]) -> Result[float, int]: - if isinstance(a, Ok): - return Ok(float(a.value)) - return Err(int(a.value)) - - def roundtrip_enum(self, a: i.E1) -> i.E1: - return a - - def invert_bool(self, a: bool) -> bool: - return not a - - def variant_casts(self, a: i.Casts) -> i.Casts: - return a - - def variant_zeros(self, a: i.Zeros) -> i.Zeros: - return a - - def variant_typedefs(self, a: i.OptionTypedef, b: i.BoolTypedef, c: i.ResultTypedef) -> None: - pass - - def variant_enums(self, a: bool, b: Result[None, None], c: i.MyErrno) -> Tuple[bool, Result[None, None], i.MyErrno]: - assert(a) - assert(isinstance(b, Ok)) - assert(c == i.MyErrno.SUCCESS) - return (False, Err(None), i.MyErrno.A) - -def run() -> None: - store = wasmtime.Store() - wasm = Variants(store, VariantsImports(MyImports(), TestWasi())) - - wasm.test_imports(store) - - assert(wasm.roundtrip_option(store, 1.) == 1) - assert(wasm.roundtrip_option(store, None) == None) - assert(wasm.roundtrip_option(store, 2.) == 2) - assert(wasm.roundtrip_result(store, Ok(2)) == Ok(2)) - assert(wasm.roundtrip_result(store, Ok(4)) == Ok(4)) - assert(wasm.roundtrip_result(store, Err(5)) == Err(5)) - - assert(wasm.roundtrip_enum(store, e.E1.A) == e.E1.A) - assert(wasm.roundtrip_enum(store, e.E1.B) == e.E1.B) - - assert(wasm.invert_bool(store, True) == False) - assert(wasm.invert_bool(store, False) == True) - - a1, a2, a3, a4, a5, a6 = wasm.variant_casts(store, ( - e.C1A(1), - e.C2A(2), - e.C3A(3), - e.C4A(4), - e.C5A(5), - e.C6A(6.), - )) - assert(a1 == e.C1A(1)) - assert(a2 == e.C2A(2)) - assert(a3 == e.C3A(3)) - assert(a4 == e.C4A(4)) - assert(a5 == e.C5A(5)) - assert(a6 == e.C6A(6)) - - b1, b2, b3, b4, b5, b6 = wasm.variant_casts(store, ( - e.C1B(1), - e.C2B(2), - e.C3B(3), - e.C4B(4), - e.C5B(5), - e.C6B(6.), - )) - assert(b1 == e.C1B(1)) - assert(b2 == e.C2B(2)) - assert(b3 == e.C3B(3)) - assert(b4 == e.C4B(4)) - assert(b5 == e.C5B(5)) - assert(b6 == e.C6B(6)) - - z1, z2, z3, z4 = wasm.variant_zeros(store, ( - e.Z1A(1), - e.Z2A(2), - e.Z3A(3.), - e.Z4A(4.), - )) - assert(z1 == e.Z1A(1)) - assert(z2 == e.Z2A(2)) - assert(z3 == e.Z3A(3)) - assert(z4 == e.Z4A(4)) - - wasm.variant_typedefs(store, None, False, Err(None)) - -if __name__ == '__main__': - run()