From a6d3e3efbfb7240234023773fead49f24f0d007b Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Fri, 18 Nov 2022 15:09:07 -0600 Subject: [PATCH] Remove `wit-bindgen-gen-host-wasmtime-py` (#436) This commit removes the host Python generation from this project. The support here has all moved upstream to https://github.com/bytecodealliance/wasmtime-py and is now supported through: python -m wasmtime.bindgen my-component.wasm --out-dir ./my-component Some documentation is in the upstream `README.md` about how to use. Necessary tests have been mirrored to the upstream repository to the best of my ability, and this is intended to represent an inflection point where the growth of the Python generator is no longer tied up with all the other languages in this repository. This is a step further along the path of #395. --- .github/workflows/main.yml | 4 - Cargo.lock | 15 - Cargo.toml | 2 - crates/gen-host-wasmtime-py/Cargo.toml | 16 - crates/gen-host-wasmtime-py/mypy.ini | 13 - crates/gen-host-wasmtime-py/src/imports.rs | 96 - crates/gen-host-wasmtime-py/src/lib.rs | 2379 ------------------ crates/gen-host-wasmtime-py/src/source.rs | 171 -- crates/gen-host-wasmtime-py/tests/codegen.rs | 38 - crates/gen-host-wasmtime-py/tests/helpers.py | 8 - crates/gen-host-wasmtime-py/tests/runtime.rs | 42 - crates/wit-bindgen-demo/Cargo.toml | 1 - crates/wit-bindgen-demo/demo.wit | 1 - crates/wit-bindgen-demo/main.ts | 1 - crates/wit-bindgen-demo/src/lib.rs | 4 - src/bin/wit-bindgen.rs | 13 +- tests/runtime/flavorful/host.py | 76 - tests/runtime/invalid/host.py | 103 - tests/runtime/lists/host.py | 99 - tests/runtime/many_arguments/host.py | 48 - tests/runtime/numbers/host.py | 94 - tests/runtime/records/host.py | 61 - tests/runtime/smoke/host.py | 19 - tests/runtime/unions/host.py | 228 -- tests/runtime/variants/host.py | 104 - 25 files changed, 1 insertion(+), 3635 deletions(-) delete mode 100644 crates/gen-host-wasmtime-py/Cargo.toml delete mode 100644 crates/gen-host-wasmtime-py/mypy.ini delete mode 100644 crates/gen-host-wasmtime-py/src/imports.rs delete mode 100644 crates/gen-host-wasmtime-py/src/lib.rs delete mode 100644 crates/gen-host-wasmtime-py/src/source.rs delete mode 100644 crates/gen-host-wasmtime-py/tests/codegen.rs delete mode 100644 crates/gen-host-wasmtime-py/tests/helpers.py delete mode 100644 crates/gen-host-wasmtime-py/tests/runtime.rs delete mode 100644 tests/runtime/flavorful/host.py delete mode 100644 tests/runtime/invalid/host.py delete mode 100644 tests/runtime/lists/host.py delete mode 100644 tests/runtime/many_arguments/host.py delete mode 100644 tests/runtime/numbers/host.py delete mode 100644 tests/runtime/records/host.py delete mode 100644 tests/runtime/smoke/host.py delete mode 100644 tests/runtime/unions/host.py delete mode 100644 tests/runtime/variants/host.py 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()