From 4069be194462ae5be22ed1f0fa81df6ecf3593aa Mon Sep 17 00:00:00 2001 From: Joel Dice Date: Thu, 16 Oct 2025 15:36:24 -0600 Subject: [PATCH 1/3] adopt `wit-dylib` This removes the built-in Wasm code generator in favor of `wit-dylib`, which is designed to be reused by interpreters for various languages. It avoids redundant effort and leaves us with less code to maintain overall. This isn't particularly optimized, yet. Given how `wit-dylib[-ffi]` works, we have to acquire and release the CPython GIL a lot more frequently (e.g. for every parameter and result of every import and export call). I haven't measured the overhead in practice, so I don't know if it's significant; if it is, we can make changes to `wit-dylib` to reduce the overhead. Also, `wit-dylib` currently embeds a data section with a lot of metadata that we don't use, so we could slim that down if needed. Otherwise, this should not affect the developer experience in any noticeable way. Signed-off-by: Joel Dice --- Cargo.lock | 114 +- Cargo.toml | 18 +- runtime/Cargo.toml | 3 +- runtime/src/lib.rs | 1776 +++++++++++++++-------------- shared/Cargo.toml | 6 - shared/src/lib.rs | 5 - src/abi.rs | 247 ---- src/bindgen.rs | 2691 -------------------------------------------- src/bindings.rs | 396 ------- src/lib.rs | 84 +- src/summary.rs | 780 +++++++------ wit/init.wit | 92 +- 12 files changed, 1547 insertions(+), 4665 deletions(-) delete mode 100644 shared/Cargo.toml delete mode 100644 shared/src/lib.rs delete mode 100644 src/abi.rs delete mode 100644 src/bindgen.rs delete mode 100644 src/bindings.rs diff --git a/Cargo.lock b/Cargo.lock index dd296d7..adc585a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -474,14 +474,14 @@ checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" [[package]] name = "component-init-transform" version = "0.2.0" -source = "git+https://github.com/dicej/component-init?rev=3800ab6e#3800ab6ef88c4a9817119ccfe5fa9de8ff2afff1" +source = "git+https://github.com/dicej/component-init?rev=3b9680fe#3b9680fe652421d4d80e3d00c93d8b5eecb8acdc" dependencies = [ "anyhow", "async-trait", "futures", - "wasm-encoder 0.238.1", - "wasm-metadata 0.238.1", - "wasmparser 0.238.1", + "wasm-encoder 0.240.0", + "wasm-metadata 0.240.0", + "wasmparser 0.240.0", ] [[package]] @@ -496,7 +496,6 @@ dependencies = [ "cap-std", "clap", "component-init-transform", - "componentize-py-shared", "flate2", "fs_extra", "futures", @@ -517,12 +516,13 @@ dependencies = [ "test-generator", "tokio", "toml", - "wasm-encoder 0.239.0", - "wasmparser 0.239.0", + "wasm-encoder 0.240.0", + "wasmparser 0.240.0", "wasmtime", "wasmtime-wasi", - "wit-component 0.239.0", - "wit-parser 0.239.0", + "wit-component 0.240.0", + "wit-dylib", + "wit-parser 0.240.0", "zstd", ] @@ -531,18 +531,14 @@ name = "componentize-py-runtime" version = "0.1.0" dependencies = [ "anyhow", - "componentize-py-shared", "num-bigint", "once_cell", "pyo3", "wit-bindgen 0.40.0", "wit-bindgen-rt", + "wit-dylib-ffi", ] -[[package]] -name = "componentize-py-shared" -version = "0.1.0" - [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -2911,22 +2907,21 @@ dependencies = [ [[package]] name = "wasm-encoder" -version = "0.238.1" +version = "0.239.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d50d48c31c615f77679b61c607b8151378a5d03159616bf3d17e8e2005afdaf5" +checksum = "5be00faa2b4950c76fe618c409d2c3ea5a3c9422013e079482d78544bb2d184c" dependencies = [ "leb128fmt", - "wasmparser 0.238.1", + "wasmparser 0.239.0", ] [[package]] name = "wasm-encoder" -version = "0.239.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5be00faa2b4950c76fe618c409d2c3ea5a3c9422013e079482d78544bb2d184c" +version = "0.240.0" +source = "git+https://github.com/bytecodealliance/wasm-tools?rev=b1d8ff59#b1d8ff591bcb8c052b54c3c17495bbbef401897c" dependencies = [ "leb128fmt", - "wasmparser 0.239.0", + "wasmparser 0.240.0", ] [[package]] @@ -2950,9 +2945,8 @@ dependencies = [ [[package]] name = "wasm-metadata" -version = "0.238.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00094573b000c92134f2ef0f8afa4f6f892de37e78442988c946243a8c44364e" +version = "0.240.0" +source = "git+https://github.com/bytecodealliance/wasm-tools?rev=b1d8ff59#b1d8ff591bcb8c052b54c3c17495bbbef401897c" dependencies = [ "anyhow", "auditable-serde", @@ -2963,20 +2957,8 @@ dependencies = [ "serde_json", "spdx", "url", - "wasm-encoder 0.238.1", - "wasmparser 0.238.1", -] - -[[package]] -name = "wasm-metadata" -version = "0.239.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20b3ec880a9ac69ccd92fbdbcf46ee833071cf09f82bb005b2327c7ae6025ae2" -dependencies = [ - "anyhow", - "indexmap", - "wasm-encoder 0.239.0", - "wasmparser 0.239.0", + "wasm-encoder 0.240.0", + "wasmparser 0.240.0", ] [[package]] @@ -2993,9 +2975,9 @@ dependencies = [ [[package]] name = "wasmparser" -version = "0.238.1" +version = "0.239.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fa99c8328024423875ae4a55345cfde8f0371327fb2d0f33b0f52a06fc44408" +checksum = "8c9d90bb93e764f6beabf1d02028c70a2156a6583e63ac4218dd07ef733368b0" dependencies = [ "bitflags", "hashbrown", @@ -3006,9 +2988,8 @@ dependencies = [ [[package]] name = "wasmparser" -version = "0.239.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c9d90bb93e764f6beabf1d02028c70a2156a6583e63ac4218dd07ef733368b0" +version = "0.240.0" +source = "git+https://github.com/bytecodealliance/wasm-tools?rev=b1d8ff59#b1d8ff591bcb8c052b54c3c17495bbbef401897c" dependencies = [ "bitflags", "hashbrown", @@ -3042,6 +3023,7 @@ dependencies = [ "cc", "cfg-if", "encoding_rs", + "futures", "fxprof-processed-profile", "gimli 0.32.2", "hashbrown", @@ -3810,9 +3792,8 @@ dependencies = [ [[package]] name = "wit-component" -version = "0.239.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88a866b19dba2c94d706ec58c92a4c62ab63e482b4c935d2a085ac94caecb136" +version = "0.240.0" +source = "git+https://github.com/bytecodealliance/wasm-tools?rev=b1d8ff59#b1d8ff591bcb8c052b54c3c17495bbbef401897c" dependencies = [ "anyhow", "bitflags", @@ -3821,12 +3802,28 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "wasm-encoder 0.239.0", - "wasm-metadata 0.239.0", - "wasmparser 0.239.0", - "wit-parser 0.239.0", + "wasm-encoder 0.240.0", + "wasm-metadata 0.240.0", + "wasmparser 0.240.0", + "wit-parser 0.240.0", ] +[[package]] +name = "wit-dylib" +version = "0.240.0" +source = "git+https://github.com/bytecodealliance/wasm-tools?rev=b1d8ff59#b1d8ff591bcb8c052b54c3c17495bbbef401897c" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder 0.240.0", + "wit-parser 0.240.0", +] + +[[package]] +name = "wit-dylib-ffi" +version = "0.1.0" +source = "git+https://github.com/bytecodealliance/wasm-tools?rev=b1d8ff59#b1d8ff591bcb8c052b54c3c17495bbbef401897c" + [[package]] name = "wit-parser" version = "0.227.1" @@ -3863,6 +3860,23 @@ dependencies = [ "wasmparser 0.239.0", ] +[[package]] +name = "wit-parser" +version = "0.240.0" +source = "git+https://github.com/bytecodealliance/wasm-tools?rev=b1d8ff59#b1d8ff591bcb8c052b54c3c17495bbbef401897c" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser 0.240.0", +] + [[package]] name = "witx" version = "0.9.1" diff --git a/Cargo.toml b/Cargo.toml index 6e5a122..9ff0364 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,11 +14,12 @@ clap = { version = "4.5.20", features = ["derive"] } tar = "0.4.42" tempfile = "3.13.0" zstd = "0.13.2" -componentize-py-shared = { path = "shared" } -wasm-encoder = "0.239.0" -wit-parser = "0.239.0" -wit-component = "0.239.0" -wasmparser = "0.239.0" +# TODO: switch to wasm-tools 1.241.0 when available +wasm-encoder = { git = "https://github.com/bytecodealliance/wasm-tools", rev = "b1d8ff59" } +wit-dylib = { git = "https://github.com/bytecodealliance/wasm-tools", rev = "b1d8ff59" } +wit-parser = { git = "https://github.com/bytecodealliance/wasm-tools", rev = "b1d8ff59" } +wit-component = { git = "https://github.com/bytecodealliance/wasm-tools", rev = "b1d8ff59" } +wasmparser = { git = "https://github.com/bytecodealliance/wasm-tools", rev = "b1d8ff59" } indexmap = "2.6.0" bincode = "1.3.3" heck = "0.5.0" @@ -26,10 +27,10 @@ pyo3 = { version = "0.26.0", features = [ "abi3-py39", "extension-module", ], optional = true } -wasmtime = "37.0.2" +wasmtime = { version = "37.0.2", features = [ "component-model-async" ] } wasmtime-wasi = "37.0.2" once_cell = "1.20.2" -component-init-transform = { version = "0.2", git = "https://github.com/dicej/component-init", rev = "3800ab6e" } +component-init-transform = { git = "https://github.com/dicej/component-init", rev = "3b9680fe" } async-trait = "0.1.83" futures = "0.3.31" tokio = { version = "1.41.0", features = [ @@ -80,5 +81,4 @@ test-generator = { path = "test-generator" } flate2 = "1.1.1" [workspace] -members = ["runtime", "shared", "test-generator"] - +members = ["runtime", "test-generator"] diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index b2920b6..75f6464 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -10,7 +10,8 @@ crate-type = ["staticlib"] anyhow = "1.0.91" once_cell = "1.20.2" pyo3 = { version="0.26.0", features = ["abi3-py312", "num-bigint"] } -componentize-py-shared = { path = "../shared" } num-bigint = "0.4.6" wit-bindgen = { version = "0.40.0", default-features = false, features = ["macros", "realloc"] } wit-bindgen-rt = { version = "0.40.0" } +# TODO: switch to a release when available: +wit-dylib-ffi = { git = "https://github.com/bytecodealliance/wasm-tools", rev = "b1d8ff59", default-features = false } diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 2d1a721..d0d6186 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -3,42 +3,50 @@ clippy::useless_conversion, reason = "some pyo3 macros produce code that does this" )] -#![allow(static_mut_refs, reason = "wit-bindgen produces code that does this")] +#![allow( + static_mut_refs, + reason = "wit-bindgen::generate produces code that does this" +)] #![allow(unknown_lints)] #![allow( unnecessary_transmutes, reason = "nightly warning but not supported on stable" )] +#![allow( + clippy::not_unsafe_ptr_arg_deref, + reason = "wit_dylib_ffi::export produces code that does this" +)] use { anyhow::{Error, Result}, - componentize_py_shared::ReturnStyle, exports::exports::{ - self as exp, Bundled, Constructor, Function, FunctionExport, Guest, LocalResource, - OwnedKind, OwnedType, RemoteResource, Resource, Static, Symbols, + self as exp, Constructor, FunctionExportKind, Guest, OptionKind, ResultRecord, ReturnStyle, + Static, Symbols, }, num_bigint::BigUint, once_cell::sync::OnceCell, pyo3::{ + Bound, IntoPyObject, Py, PyAny, PyErr, PyResult, Python, exceptions::PyAssertionError, - ffi, intern, + intern, types::{ - PyAnyMethods, PyBool, PyBytes, PyBytesMethods, PyDict, PyFloat, PyInt, PyList, - PyListMethods, PyMapping, PyMappingMethods, PyModule, PyModuleMethods, PyString, - PyTuple, + PyAnyMethods, PyBool, PyBytes, PyBytesMethods, PyDict, PyList, PyListMethods, + PyMapping, PyMappingMethods, PyModule, PyModuleMethods, PyString, PyTuple, }, - Borrowed, Bound, IntoPyObject, Py, PyAny, PyErr, PyResult, Python, }, std::{ alloc::{self, Layout}, - ffi::c_void, iter, - mem::{self, MaybeUninit}, + marker::PhantomData, + mem, ops::DerefMut, - ptr, slice, str, + slice, str, sync::{Mutex, Once}, }, wasi::cli::environment, + wit_dylib_ffi::{ + self as wit, Call, ExportFunction, Interpreter, List, Type, Wit, WitOption, WitResult, + }, }; wit_bindgen::generate!({ @@ -49,9 +57,17 @@ wit_bindgen::generate!({ export!(MyExports); +static WIT: OnceCell = OnceCell::new(); static STUB_WASI: OnceCell = OnceCell::new(); static EXPORTS: OnceCell> = OnceCell::new(); -static TYPES: OnceCell> = OnceCell::new(); +static RESOURCES: OnceCell> = OnceCell::new(); +static RECORDS: OnceCell> = OnceCell::new(); +static FLAGS: OnceCell> = OnceCell::new(); +static TUPLES: OnceCell> = OnceCell::new(); +static VARIANTS: OnceCell> = OnceCell::new(); +static ENUMS: OnceCell> = OnceCell::new(); +static OPTIONS: OnceCell> = OnceCell::new(); +static RESULTS: OnceCell> = OnceCell::new(); static ENVIRON: OnceCell> = OnceCell::new(); static SOME_CONSTRUCTOR: OnceCell> = OnceCell::new(); static OK_CONSTRUCTOR: OnceCell> = OnceCell::new(); @@ -62,15 +78,13 @@ static SEED: OnceCell> = OnceCell::new(); static ARGV: OnceCell> = OnceCell::new(); struct Borrow { - handle: i32, - drop: u32, + value: Py, + handle: u32, + drop: unsafe extern "C" fn(u32), } static BORROWS: Mutex> = Mutex::new(Vec::new()); -const DISCRIMINANT_FIELD_INDEX: i32 = 0; -const PAYLOAD_FIELD_INDEX: i32 = 1; - #[derive(Debug)] struct Case { constructor: Py, @@ -78,38 +92,47 @@ struct Case { } #[derive(Debug)] -enum Type { - Record { - constructor: Py, - fields: Vec, - }, - Variant { - types_to_discriminants: Py, - cases: Vec, - }, - Enum { - constructor: Py, - count: usize, - }, - Flags { - constructor: Py, - u32_count: usize, - }, - Option, - NestingOption, - Result, - Tuple(usize), - Handle, - Resource { - constructor: Py, - local: Option, - #[allow(dead_code)] - remote: Option, - }, +struct Resource { + constructor: Py, +} + +#[derive(Debug)] +struct Record { + constructor: Py, + fields: Vec, +} + +#[derive(Debug)] +struct Flags { + constructor: Py, + u32_count: usize, +} + +#[derive(Debug)] +struct Tuple { + count: usize, +} + +#[derive(Debug)] +struct Variant { + types_to_discriminants: Py, + cases: Vec, +} + +#[derive(Debug)] +struct Enum { + constructor: Py, + count: usize, } #[derive(Debug)] -enum Export { +struct Export { + kind: ExportKind, + return_style: ReturnStyle, +} + +#[derive(Debug)] +enum ExportKind { Freestanding { instance: Py, name: Py, @@ -136,55 +159,36 @@ impl From for Anyhow { } } -#[link(wasm_import_module = "env")] -extern "C" { - #[cfg_attr(target_arch = "wasm32", link_name = "componentize-py#CallIndirect")] - fn componentize_py_call_indirect( - context: *const c_void, - input: *const c_void, - output: *mut c_void, - index: u32, - ); -} - #[pyo3::pyfunction] #[pyo3(pass_module)] fn call_import<'a>( module: Bound<'a, PyModule>, index: u32, params: Vec>, - result_count: usize, + _result_count: usize, ) -> PyResult>> { - let mut results = iter::repeat_with(MaybeUninit::>::uninit) - .take(result_count) - .collect::>(); - unsafe { - componentize_py_call_indirect( - &module.py() as *const _ as _, - params.as_ptr() as _, - results.as_mut_ptr() as _, - index, - ); - - // todo: is this sound, or do we need to `.into_iter().map(MaybeUninit::assume_init).collect()` instead? - Ok(mem::transmute::< - Vec>>, - Vec>, - >(results)) + let func = WIT + .get() + .unwrap() + .import_func(usize::try_from(index).unwrap()); + + if func.is_async() { + todo!() + } else { + let mut call = MyCall::new(params.into_iter().rev().map(|v| v.unbind()).collect()); + func.call_import_sync(&mut call); + Ok(mem::take(&mut call.stack) + .into_iter() + .map(|v| v.into_bound(module.py())) + .collect()) } } #[pyo3::pyfunction] #[pyo3(pass_module)] -fn drop_resource(module: &Bound, index: u32, handle: usize) -> PyResult<()> { - let params = [handle]; +fn drop_resource(_module: &Bound, index: usize, handle: u32) -> PyResult<()> { unsafe { - componentize_py_call_indirect( - &module.py() as *const _ as _, - params.as_ptr() as _, - ptr::null_mut(), - index, - ); + mem::transmute::(index)(handle); } Ok(()) } @@ -218,131 +222,167 @@ fn do_init(app_name: String, symbols: Symbols, stub_wasi: bool) -> Result<(), St .exports .iter() .map(|export| { - Ok(match export { - FunctionExport::Bundled(Bundled { - module, - protocol, - name, - }) => Export::Freestanding { - name: PyString::intern(py, name).into(), - instance: py - .import(module.as_str())? - .getattr(protocol.as_str())? - .call0()? - .into(), - }, - FunctionExport::Freestanding(Function { protocol, name }) => { - Export::Freestanding { + Ok(Export { + kind: match &export.kind { + FunctionExportKind::Freestanding(exp::Function { + protocol, + name, + }) => ExportKind::Freestanding { name: PyString::intern(py, name).into(), instance: app.getattr(protocol.as_str())?.call0()?.into(), - } - } - FunctionExport::Constructor(Constructor { module, protocol }) => { - Export::Constructor( + }, + FunctionExportKind::Constructor(Constructor { + module, + protocol, + }) => ExportKind::Constructor( py.import(module.as_str())? .getattr(protocol.as_str())? .into(), - ) - } - FunctionExport::Method(name) => { - Export::Method(PyString::intern(py, name).into()) - } - FunctionExport::Static(Static { - module, - protocol, - name, - }) => Export::Static { - name: PyString::intern(py, name).into(), - class: py - .import(module.as_str())? - .getattr(protocol.as_str())? - .into(), + ), + FunctionExportKind::Method(name) => { + ExportKind::Method(PyString::intern(py, name).into()) + } + FunctionExportKind::Static(Static { + module, + protocol, + name, + }) => ExportKind::Static { + name: PyString::intern(py, name).into(), + class: py + .import(module.as_str())? + .getattr(protocol.as_str())? + .into(), + }, }, + return_style: export.return_style, }) }) .collect::>()?, ) .unwrap(); - TYPES + RESOURCES .set( symbols - .types + .resources .into_iter() .map(|ty| { - Ok(match ty { - exp::Type::Owned(OwnedType { - kind, - package, - name, - }) => match kind { - OwnedKind::Record(fields) => Type::Record { - constructor: py - .import(package.as_str())? - .getattr(name.as_str())? - .into(), - fields, - }, - OwnedKind::Variant(cases) => { - let package = py.import(package.as_str())?; - - let cases = cases - .iter() - .map(|case| { - Ok(Case { - constructor: package - .getattr(case.name.as_str())? - .into(), - has_payload: case.has_payload, - }) - }) - .collect::>>()?; - - let types_to_discriminants = PyDict::new(py); - for (index, case) in cases.iter().enumerate() { - types_to_discriminants - .set_item(&case.constructor, index)?; - } - - Type::Variant { - cases, - types_to_discriminants: types_to_discriminants.into(), - } - } - OwnedKind::Enum(count) => Type::Enum { - constructor: py - .import(package.as_str())? - .getattr(name.as_str())? - .into(), - count: count.try_into().unwrap(), - }, - OwnedKind::Flags(u32_count) => Type::Flags { - constructor: py - .import(package.as_str())? - .getattr(name.as_str())? - .into(), - u32_count: u32_count.try_into().unwrap(), - }, - OwnedKind::Resource(Resource { local, remote }) => Type::Resource { - constructor: py - .import(package.as_str())? - .getattr(name.as_str())? - .into(), - local, - remote, - }, - }, - exp::Type::Option => Type::Option, - exp::Type::NestingOption => Type::NestingOption, - exp::Type::Result => Type::Result, - exp::Type::Tuple(length) => Type::Tuple(length.try_into().unwrap()), - exp::Type::Handle => Type::Handle, + Ok(Resource { + constructor: py + .import(ty.package.as_str())? + .getattr(ty.name.as_str())? + .into(), }) }) .collect::>()?, ) .unwrap(); + RECORDS + .set( + symbols + .records + .into_iter() + .map(|ty| { + Ok(Record { + constructor: py + .import(ty.package.as_str())? + .getattr(ty.name.as_str())? + .into(), + fields: ty.fields, + }) + }) + .collect::>()?, + ) + .unwrap(); + + FLAGS + .set( + symbols + .flags + .into_iter() + .map(|ty| { + Ok(Flags { + constructor: py + .import(ty.package.as_str())? + .getattr(ty.name.as_str())? + .into(), + u32_count: ty.u32_count.try_into().unwrap(), + }) + }) + .collect::>()?, + ) + .unwrap(); + + TUPLES + .set( + symbols + .tuples + .into_iter() + .map(|ty| { + Ok(Tuple { + count: ty.count.try_into().unwrap(), + }) + }) + .collect::>()?, + ) + .unwrap(); + + VARIANTS + .set( + symbols + .variants + .into_iter() + .map(|ty| { + let package = py.import(ty.package.as_str())?; + + let cases = ty + .cases + .iter() + .map(|case| { + Ok(Case { + constructor: package.getattr(case.name.as_str())?.into(), + has_payload: case.has_payload, + }) + }) + .collect::>>()?; + + let types_to_discriminants = PyDict::new(py); + for (index, case) in cases.iter().enumerate() { + types_to_discriminants.set_item(&case.constructor, index)?; + } + + Ok(Variant { + cases, + types_to_discriminants: types_to_discriminants.into(), + }) + }) + .collect::>()?, + ) + .unwrap(); + + ENUMS + .set( + symbols + .enums + .into_iter() + .map(|ty| { + Ok(Enum { + constructor: py + .import(ty.package.as_str())? + .getattr(ty.name.as_str())? + .into(), + count: ty.count.try_into().unwrap(), + }) + }) + .collect::>()?, + ) + .unwrap(); + + OPTIONS.set(symbols.options).unwrap(); + + RESULTS.set(symbols.results).unwrap(); + let types = py.import(symbols.types_package.as_str())?; SOME_CONSTRUCTOR.set(types.getattr("Some")?.into()).unwrap(); @@ -427,797 +467,857 @@ impl Guest for MyExports { } } -/// # Safety -/// TODO -#[export_name = "componentize-py#Dispatch"] -pub unsafe extern "C" fn componentize_py_dispatch( - export: usize, - from_canon: u32, - to_canon: u32, - param_count: u32, - return_style: ReturnStyle, - params_canon: *const c_void, - results_canon: *mut c_void, -) { - Python::attach(|py| { - let mut params_py = vec![ptr::null_mut::(); param_count.try_into().unwrap()]; - - componentize_py_call_indirect( - &py as *const _ as _, - params_canon, - params_py.as_mut_ptr() as _, - from_canon, - ); - - let mut params_py = params_py - .into_iter() - .map(|p| Bound::from_borrowed_ptr(py, p)); - - if !*STUB_WASI.get().unwrap() { - static ONCE: Once = Once::new(); - ONCE.call_once(|| { - // We must call directly into the host to get the runtime environment since libc's version will only - // contain the build-time pre-init snapshot. - let environ = ENVIRON.get().unwrap().bind(py); - for (k, v) in environment::get_environment() { - environ.set_item(k, v).unwrap(); - } +struct MyInterpreter; - // Likewise for CLI arguments. - for arg in environment::get_arguments() { - ARGV.get().unwrap().bind(py).append(arg).unwrap(); - } +impl Interpreter for MyInterpreter { + type CallCx<'a> = MyCall<'a>; - // Call `random.seed()` to ensure we get a fresh seed rather than the one that got baked in during - // pre-init. - SEED.get().unwrap().call0(py).unwrap(); - }); - } + fn initialize(wit: Wit) { + WIT.set(wit).map_err(drop).unwrap(); + } - let export = &EXPORTS.get().unwrap()[export]; - let result = match export { - Export::Freestanding { instance, name } => { - instance.call_method1(py, name, PyTuple::new(py, params_py).unwrap()) + fn export_start<'a>(_: Wit, _: ExportFunction) -> Box> { + Box::new(MyCall::new(Vec::new())) + } + + fn export_call(_: Wit, func: ExportFunction, cx: &mut MyCall<'_>) { + Python::attach(|py| { + if !*STUB_WASI.get().unwrap() { + static ONCE: Once = Once::new(); + ONCE.call_once(|| { + // We must call directly into the host to get the runtime + // environment since libc's version will only contain the + // build-time pre-init snapshot. + let environ = ENVIRON.get().unwrap().bind(py); + for (k, v) in environment::get_environment() { + environ.set_item(k, v).unwrap(); + } + + // Likewise for CLI arguments. + for arg in environment::get_arguments() { + ARGV.get().unwrap().bind(py).append(arg).unwrap(); + } + + // Call `random.seed()` to ensure we get a fresh seed rather + // than the one that got baked in during pre-init. + SEED.get().unwrap().call0(py).unwrap(); + }); } - Export::Constructor(class) => class.call1(py, PyTuple::new(py, params_py).unwrap()), - Export::Method(name) => params_py - // Call method on self with remaining iterator elements - .next() - .unwrap() - .call_method1(name, PyTuple::new(py, params_py).unwrap()) - .map(|r| r.into()), - Export::Static { class, name } => class - .getattr(py, name) - .and_then(|function| function.call1(py, PyTuple::new(py, params_py).unwrap())), - }; - let result = match return_style { - ReturnStyle::Normal => match result { - Ok(result) => result, - Err(error) => { + let mut params_py = mem::take(&mut cx.stack).into_iter(); + let export = &EXPORTS.get().unwrap()[func.index()]; + let result = match &export.kind { + ExportKind::Freestanding { instance, name } => { + instance.call_method1(py, name, PyTuple::new(py, params_py).unwrap()) + } + ExportKind::Constructor(class) => { + class.call1(py, PyTuple::new(py, params_py).unwrap()) + } + ExportKind::Method(name) => params_py + // Call method on self with remaining iterator elements + .next() + .unwrap() + .call_method1(py, name, PyTuple::new(py, params_py).unwrap()) + .map(|r| r.into()), + ExportKind::Static { class, name } => class + .getattr(py, name) + .and_then(|function| function.call1(py, PyTuple::new(py, params_py).unwrap())), + }; + + let result = match (result, export.return_style) { + (Ok(_), ReturnStyle::None) => None, + (Ok(result), ReturnStyle::Normal) => Some(result), + (Ok(result), ReturnStyle::Result) => { + Some(OK_CONSTRUCTOR.get().unwrap().call1(py, (result,)).unwrap()) + } + (Err(error), ReturnStyle::None | ReturnStyle::Normal) => { error.print(py); panic!("Python function threw an unexpected exception") } - }, - ReturnStyle::Result => match result { - Ok(result) => OK_CONSTRUCTOR.get().unwrap().call1(py, (result,)).unwrap(), - Err(result) => { + (Err(error), ReturnStyle::Result) => { if ERR_CONSTRUCTOR .get() .unwrap() .bind(py) - .eq(result.get_type(py)) + .eq(error.get_type(py)) .unwrap() { - result.into_value(py).into_any() + Some(error.into_value(py).into_any()) } else { - result.print(py); + error.print(py); panic!("Python function threw an unexpected exception") } } - }, - }; + }; - let result_array = [result]; + if let Some(result) = result { + cx.stack.push(result); + } - componentize_py_call_indirect( - &py as *const _ as _, - result_array.as_ptr() as *const _ as _, - results_canon, - to_canon, - ); + let borrows = mem::take(BORROWS.lock().unwrap().deref_mut()); + for Borrow { + value, + handle, + drop, + } in borrows + { + let value = value.bind(py); - let borrows = mem::take(BORROWS.lock().unwrap().deref_mut()); - for Borrow { handle, drop } in borrows { - let params = [handle]; - unsafe { - componentize_py_call_indirect( - &py as *const _ as _, - params.as_ptr() as _, - ptr::null_mut(), - drop, - ); + value.delattr(intern!(py, "handle")).unwrap(); + + value + .getattr(intern!(py, "finalizer")) + .unwrap() + .call_method0(intern!(py, "detach")) + .unwrap(); + + unsafe { + drop(handle); + } } - } - }); -} + }); + } -/// # Safety -/// TODO -#[export_name = "componentize-py#Allocate"] -pub unsafe extern "C" fn componentize_py_allocate(size: usize, align: usize) -> *mut u8 { - alloc::alloc(Layout::from_size_align(size, align).unwrap()) + async fn export_call_async(_: Wit, func: ExportFunction, cx: Box>) { + _ = (func, cx); + todo!() + } + + fn resource_dtor(ty: wit::Resource, handle: usize) { + _ = (ty, handle); + todo!() + } } -/// # Safety -/// TODO -#[export_name = "componentize-py#Free"] -pub unsafe extern "C" fn componentize_py_free(ptr: *mut u8, size: usize, align: usize) { - alloc::dealloc(ptr, Layout::from_size_align(size, align).unwrap()) +struct MyCall<'a> { + _phantom: PhantomData<&'a ()>, + iter_stack: Vec, + deferred_deallocations: Vec<(*mut u8, Layout)>, + strings: Vec, + stack: Vec>, } -#[export_name = "componentize-py#ToCanonBool"] -pub extern "C" fn componentize_py_to_canon_bool(_py: &Python, value: Borrowed) -> u32 { - if value.is_truthy().unwrap() { - 1 - } else { - 0 +impl MyCall<'_> { + fn new(stack: Vec>) -> Self { + // TODO: tell py03 to attach (and detach on drop) this thread to the + // interpreter. + Self { + _phantom: PhantomData, + iter_stack: Vec::new(), + deferred_deallocations: Vec::new(), + strings: Vec::new(), + stack, + } } } -#[export_name = "componentize-py#ToCanonI32"] -pub extern "C" fn componentize_py_to_canon_i32(_py: &Python, value: Borrowed) -> i32 { - value.extract().unwrap() +impl Drop for MyCall<'_> { + fn drop(&mut self) { + for &(ptr, layout) in &self.deferred_deallocations { + unsafe { + alloc::dealloc(ptr, layout); + } + } + } } -#[export_name = "componentize-py#ToCanonU32"] -pub extern "C" fn componentize_py_to_canon_u32(_py: &Python, value: Borrowed) -> u32 { - value.extract().unwrap() -} +impl Call for MyCall<'_> { + unsafe fn defer_deallocate(&mut self, ptr: *mut u8, layout: Layout) { + self.deferred_deallocations.push((ptr, layout)); + } -#[export_name = "componentize-py#ToCanonI64"] -pub extern "C" fn componentize_py_to_canon_i64(_py: &Python, value: Borrowed) -> i64 { - value.extract().unwrap() -} + fn pop_u8(&mut self) -> u8 { + Python::attach(|py| self.stack.pop().unwrap().extract(py).unwrap()) + } -#[export_name = "componentize-py#ToCanonU64"] -pub extern "C" fn componentize_py_to_canon_u64(_py: &Python, value: Borrowed) -> u64 { - value.extract().unwrap() -} + fn pop_u16(&mut self) -> u16 { + Python::attach(|py| self.stack.pop().unwrap().extract(py).unwrap()) + } -#[export_name = "componentize-py#ToCanonF32"] -pub extern "C" fn componentize_py_to_canon_f32(_py: &Python, value: Borrowed) -> f32 { - value.extract().unwrap() -} + fn pop_u32(&mut self) -> u32 { + Python::attach(|py| self.stack.pop().unwrap().extract(py).unwrap()) + } -#[export_name = "componentize-py#ToCanonF64"] -pub extern "C" fn componentize_py_to_canon_f64(_py: &Python, value: Borrowed) -> f64 { - value.extract().unwrap() -} + fn pop_u64(&mut self) -> u64 { + Python::attach(|py| self.stack.pop().unwrap().extract(py).unwrap()) + } -#[export_name = "componentize-py#ToCanonChar"] -pub extern "C" fn componentize_py_to_canon_char(_py: &Python, value: Borrowed) -> u32 { - let value = value.extract::().unwrap(); - assert!(value.chars().count() == 1); - value.chars().next().unwrap() as u32 -} + fn pop_s8(&mut self) -> i8 { + Python::attach(|py| self.stack.pop().unwrap().extract(py).unwrap()) + } -/// # Safety -/// TODO -#[export_name = "componentize-py#ToCanonString"] -pub unsafe extern "C" fn componentize_py_to_canon_string( - _py: &Python, - value: Borrowed, - destination: *mut (*const u8, usize), -) { - let value = value.extract::().unwrap().into_bytes(); - unsafe { - let result = alloc::alloc(Layout::from_size_align(value.len(), 1).unwrap()); - ptr::copy_nonoverlapping(value.as_ptr(), result, value.len()); - destination.write((result, value.len())); + fn pop_s16(&mut self) -> i16 { + Python::attach(|py| self.stack.pop().unwrap().extract(py).unwrap()) } -} -#[export_name = "componentize-py#GetField"] -pub extern "C" fn componentize_py_get_field<'a>( - py: &'a Python, - value: Borrowed<'_, 'a, PyAny>, - ty: usize, - field: usize, -) -> Bound<'a, PyAny> { - match &TYPES.get().unwrap()[ty] { - Type::Record { fields, .. } => value.getattr(fields[field].as_str()).unwrap(), - Type::Variant { - types_to_discriminants, - cases, - } => { - let discriminant = types_to_discriminants - .bind(*py) - .get_item(value.get_type()) - .unwrap(); + fn pop_s32(&mut self) -> i32 { + Python::attach(|py| self.stack.pop().unwrap().extract(py).unwrap()) + } - match i32::try_from(field).unwrap() { - DISCRIMINANT_FIELD_INDEX => discriminant, - PAYLOAD_FIELD_INDEX => { - if cases[discriminant.extract::().unwrap()].has_payload { - value.getattr("value").unwrap() - } else { - py.None().into_bound(*py) - } - } - _ => unreachable!(), + fn pop_s64(&mut self) -> i64 { + Python::attach(|py| self.stack.pop().unwrap().extract(py).unwrap()) + } + + fn pop_bool(&mut self) -> bool { + Python::attach(|py| self.stack.pop().unwrap().is_truthy(py).unwrap()) + } + + fn pop_char(&mut self) -> char { + let value = Python::attach(|py| self.stack.pop().unwrap().extract::(py).unwrap()); + assert!(value.chars().count() == 1); + value.chars().next().unwrap() + } + + fn pop_f32(&mut self) -> f32 { + Python::attach(|py| self.stack.pop().unwrap().extract(py).unwrap()) + } + + fn pop_f64(&mut self) -> f64 { + Python::attach(|py| self.stack.pop().unwrap().extract(py).unwrap()) + } + + fn pop_string(&mut self) -> &str { + let value = Python::attach(|py| self.stack.pop().unwrap().extract::(py).unwrap()); + self.strings.push(value); + self.strings.last().unwrap() + } + + fn pop_borrow(&mut self, ty: wit::Resource) -> u32 { + Python::attach(|py| { + let value = self.stack.pop().unwrap(); + if let Some(new) = ty.new() { + // exported resource type + exported_resource_to_canon(py, ty, new, value) + } else { + // imported resource type + imported_resource_to_canon(py, value) } - } - Type::Enum { .. } => match i32::try_from(field).unwrap() { - DISCRIMINANT_FIELD_INDEX => value.getattr("value").unwrap(), - PAYLOAD_FIELD_INDEX => py.None().into_bound(*py), - _ => unreachable!(), - }, - Type::Flags { u32_count, .. } => { - assert!(field < *u32_count); - let value = value - .getattr("value") + }) + } + + fn pop_own(&mut self, ty: wit::Resource) -> u32 { + Python::attach(|py| { + let value = self.stack.pop().unwrap(); + if let Some(new) = ty.new() { + // exported resource type + exported_resource_to_canon(py, ty, new, value) + } else { + // imported resource type + value + .bind(py) + .getattr(intern!(py, "finalizer")) + .unwrap() + .call_method0(intern!(py, "detach")) + .unwrap(); + + imported_resource_to_canon(py, value) + } + }) + } + + fn pop_enum(&mut self, _ty: wit::Enum) -> u32 { + Python::attach(|py| { + let value = self.stack.pop().unwrap(); + let value = value.bind(py); + value + .getattr(intern!(py, "value")) .unwrap() - .extract::() + .extract() .unwrap() - .iter_u32_digits() - .nth(field) - .unwrap_or(0); + }) + } - u32::cast_signed(value) - .into_pyobject(*py) - .unwrap() - .into_any() - } - Type::Option => match i32::try_from(field).unwrap() { - DISCRIMINANT_FIELD_INDEX => if value.is_none() { 0u32 } else { 1 } - .into_pyobject(*py) + fn pop_flags(&mut self, _ty: wit::Flags) -> u32 { + Python::attach(|py| { + let value = self.stack.pop().unwrap(); + let value = value.bind(py); + // See the comment in `Self::push_flags` about using `num-bigint` + // here to represent arbitrary bit lengths. + value + .getattr(intern!(py, "value")) .unwrap() - .into_any(), - PAYLOAD_FIELD_INDEX => unsafe { - // Avoid incrementing `value`'s refcount while returning it: - Bound::from_owned_ptr(*py, value.as_ptr()) - }, - _ => unreachable!(), - }, - Type::NestingOption => match i32::try_from(field).unwrap() { - DISCRIMINANT_FIELD_INDEX => if value.is_none() { 0u32 } else { 1 } - .into_pyobject(*py) + .extract::() .unwrap() - .into_any(), - PAYLOAD_FIELD_INDEX => { - if value.is_none() { - unsafe { - // Avoid incrementing `value`'s refcount while returning - // it: - Bound::from_owned_ptr(*py, value.as_ptr()) + .iter_u32_digits() + .next() + .unwrap_or(0) + }) + } + + fn pop_future(&mut self, ty: wit::Future) -> u32 { + _ = ty; + todo!() + } + + fn pop_stream(&mut self, ty: wit::Stream) -> u32 { + _ = ty; + todo!() + } + + fn pop_option(&mut self, ty: WitOption) -> u32 { + Python::attach(|py| { + if self.stack.last().unwrap().is_none(py) { + self.stack.pop().unwrap(); + 0 + } else { + match &OPTIONS.get().unwrap()[ty.index()] { + OptionKind::NonNesting => { + // Leave value on the stack as-is + } + OptionKind::Nesting => { + let value = self.stack.pop().unwrap(); + self.stack + .push(value.getattr(py, intern!(py, "value")).unwrap()); } - } else { - value.getattr("value").unwrap() } + 1 } - _ => unreachable!(), - }, - Type::Result => match i32::try_from(field).unwrap() { - DISCRIMINANT_FIELD_INDEX => if OK_CONSTRUCTOR + }) + } + + fn pop_result(&mut self, ty: WitResult) -> u32 { + let &ResultRecord { has_ok, has_err } = &RESULTS.get().unwrap()[ty.index()]; + + Python::attach(|py| { + let value = self.stack.pop().unwrap(); + let value = value.bind(py); + + let (discriminant, has_payload) = if OK_CONSTRUCTOR .get() .unwrap() - .bind(*py) + .bind(py) .eq(value.get_type()) .unwrap() { - 0_i32 + (0, has_ok) } else if ERR_CONSTRUCTOR .get() .unwrap() - .bind(*py) + .bind(py) .eq(value.get_type()) .unwrap() { - 1 + (1, has_err) } else { unreachable!() + }; + + if has_payload { + self.stack + .push(value.getattr(intern!(py, "value")).unwrap().unbind()); } - .into_pyobject(*py) - .unwrap() - .into_any(), - PAYLOAD_FIELD_INDEX => value.getattr("value").unwrap(), - _ => unreachable!(), - }, - Type::Tuple(length) => { - assert!(field < *length); - value - .downcast::() - .unwrap() - .get_item(field) + + discriminant + }) + } + + fn pop_variant(&mut self, ty: wit::Variant) -> u32 { + let Variant { + types_to_discriminants, + cases, + } = &VARIANTS.get().unwrap()[ty.index()]; + + Python::attach(|py| { + let value = self.stack.pop().unwrap(); + let value = value.bind(py); + + let discriminant = types_to_discriminants + .bind(py) + .get_item(value.get_type()) .unwrap() + .extract::() + .unwrap(); + + if cases[discriminant].has_payload { + self.stack + .push(value.getattr(intern!(py, "value")).unwrap().unbind()) + } + + u32::try_from(discriminant).unwrap() + }) + } + + fn pop_record(&mut self, ty: wit::Record) { + let Record { fields, .. } = &RECORDS.get().unwrap()[ty.index()]; + + Python::attach(|py| { + let value = self.stack.pop().unwrap(); + self.stack.extend( + fields + .iter() + .rev() + .map(|name| value.getattr(py, name.as_str()).unwrap()), + ); + }); + } + + fn pop_tuple(&mut self, ty: wit::Tuple) { + let &Tuple { count } = &TUPLES.get().unwrap()[ty.index()]; + + Python::attach(|py| { + let value = self.stack.pop().unwrap(); + let value = value.cast_bound::(py).unwrap(); + + self.stack.extend( + (0..count) + .rev() + .map(|index| value.get_item(index).unwrap().unbind()), + ); + }); + } + + unsafe fn maybe_pop_list(&mut self, ty: List) -> Option<(*const u8, usize)> { + if let Type::U8 = ty.ty() { + Python::attach(|py| { + let src = self.stack.pop().unwrap(); + let src = src.cast_bound::(py).unwrap(); + let len = src.len().unwrap(); + let dst = alloc::alloc(Layout::from_size_align(len, 1).unwrap()); + slice::from_raw_parts_mut(dst, len).copy_from_slice(src.as_bytes()); + Some((dst as _, len)) + }) + } else { + None } - Type::Handle | Type::Resource { .. } => unreachable!(), } -} -#[export_name = "componentize-py#GetListLength"] -pub extern "C" fn componentize_py_get_list_length(_py: &Python, value: Borrowed) -> usize { - if let Ok(bytes) = value.downcast::() { - bytes.len().unwrap() - } else { - value.downcast::().unwrap().len() + fn pop_list(&mut self, _ty: List) -> usize { + Python::attach(|py| { + self.iter_stack.push(0); + let value = self.stack.last().unwrap(); + value.cast_bound::(py).unwrap().len() + }) } -} -#[export_name = "componentize-py#GetListElement"] -pub extern "C" fn componentize_py_get_list_element<'a>( - _py: &Python<'a>, - value: Borrowed<'_, 'a, PyAny>, - index: usize, -) -> Bound<'a, PyAny> { - value.downcast::().unwrap().get_item(index).unwrap() -} + fn pop_iter_next(&mut self, _ty: List) { + Python::attach(|py| { + let index = *self.iter_stack.last().unwrap(); + let element = self + .stack + .last() + .unwrap() + .cast_bound::(py) + .unwrap() + .get_item(index) + .unwrap(); + *self.iter_stack.last_mut().unwrap() = index + 1; + self.stack.push(element.into_any().unbind()); + }) + } -#[export_name = "componentize-py#FromCanonBool"] -pub extern "C" fn componentize_py_from_canon_bool<'a>( - py: &Python<'a>, - value: u32, -) -> Bound<'a, PyBool> { - PyBool::new(*py, value != 0).to_owned() -} + fn pop_iter(&mut self, _ty: List) { + self.iter_stack.pop().unwrap(); + Python::attach(|py| { + self.stack.pop().unwrap().drop_ref(py); + }) + } -#[export_name = "componentize-py#FromCanonI32"] -pub extern "C" fn componentize_py_from_canon_i32<'a>( - py: &Python<'a>, - value: i32, -) -> Bound<'a, PyInt> { - value.into_pyobject(*py).unwrap() -} + fn push_bool(&mut self, val: bool) { + self.stack.push(Python::attach(|py| { + PyBool::new(py, val).to_owned().into_any().unbind() + })) + } -#[export_name = "componentize-py#FromCanonU32"] -pub extern "C" fn componentize_py_from_canon_u32<'a>( - py: &Python<'a>, - value: u32, -) -> Bound<'a, PyInt> { - value.into_pyobject(*py).unwrap() -} + fn push_char(&mut self, val: char) { + self.stack.push(Python::attach(|py| { + val.to_string() + .into_pyobject(py) + .unwrap() + .into_any() + .unbind() + })) + } -#[export_name = "componentize-py#FromCanonI64"] -pub extern "C" fn componentize_py_from_canon_i64<'a>( - py: &Python<'a>, - value: i64, -) -> Bound<'a, PyInt> { - value.into_pyobject(*py).unwrap() -} + fn push_u8(&mut self, val: u8) { + self.stack.push(Python::attach(|py| { + val.into_pyobject(py).unwrap().into_any().unbind() + })) + } -#[export_name = "componentize-py#FromCanonU64"] -pub extern "C" fn componentize_py_from_canon_u64<'a>( - py: &Python<'a>, - value: u64, -) -> Bound<'a, PyInt> { - value.into_pyobject(*py).unwrap() -} + fn push_s8(&mut self, val: i8) { + self.stack.push(Python::attach(|py| { + val.into_pyobject(py).unwrap().into_any().unbind() + })) + } -#[export_name = "componentize-py#FromCanonF32"] -pub extern "C" fn componentize_py_from_canon_f32<'a>( - py: &Python<'a>, - value: f32, -) -> Bound<'a, PyFloat> { - value.into_pyobject(*py).unwrap() -} + fn push_u16(&mut self, val: u16) { + self.stack.push(Python::attach(|py| { + val.into_pyobject(py).unwrap().into_any().unbind() + })) + } -#[export_name = "componentize-py#FromCanonF64"] -pub extern "C" fn componentize_py_from_canon_f64<'a>( - py: &Python<'a>, - value: f64, -) -> Bound<'a, PyFloat> { - value.into_pyobject(*py).unwrap() -} + fn push_s16(&mut self, val: i16) { + self.stack.push(Python::attach(|py| { + val.into_pyobject(py).unwrap().into_any().unbind() + })) + } -#[export_name = "componentize-py#FromCanonChar"] -pub extern "C" fn componentize_py_from_canon_char<'a>( - py: &Python<'a>, - value: u32, -) -> Bound<'a, PyString> { - char::from_u32(value) - .unwrap() - .to_string() - .into_pyobject(*py) - .unwrap() -} + fn push_u32(&mut self, val: u32) { + self.stack.push(Python::attach(|py| { + val.into_pyobject(py).unwrap().into_any().unbind() + })); + } -/// # Safety -/// TODO -#[export_name = "componentize-py#FromCanonString"] -pub unsafe extern "C" fn componentize_py_from_canon_string<'a>( - py: &Python<'a>, - data: *const u8, - len: usize, -) -> Bound<'a, PyString> { - PyString::new(*py, unsafe { - str::from_utf8_unchecked(slice::from_raw_parts(data, len)) - }) -} + fn push_s32(&mut self, val: i32) { + self.stack.push(Python::attach(|py| { + val.into_pyobject(py).unwrap().into_any().unbind() + })) + } -/// # Safety -/// TODO -#[export_name = "componentize-py#Init"] -pub unsafe extern "C" fn componentize_py_init<'a>( - py: &Python<'a>, - ty: usize, - data: *const *mut ffi::PyObject, - len: usize, -) -> Bound<'a, PyAny> { - match &TYPES.get().unwrap()[ty] { - Type::Record { constructor, .. } => { - let elements = slice::from_raw_parts(data, len) - .iter() - .map(|e| Bound::from_owned_ptr(*py, *e)); + fn push_u64(&mut self, val: u64) { + self.stack.push(Python::attach(|py| { + val.into_pyobject(py).unwrap().into_any().unbind() + })) + } + + fn push_s64(&mut self, val: i64) { + self.stack.push(Python::attach(|py| { + val.into_pyobject(py).unwrap().into_any().unbind() + })) + } + + fn push_f32(&mut self, val: f32) { + self.stack.push(Python::attach(|py| { + val.into_pyobject(py).unwrap().into_any().unbind() + })) + } + + fn push_f64(&mut self, val: f64) { + self.stack.push(Python::attach(|py| { + val.into_pyobject(py).unwrap().into_any().unbind() + })) + } + + fn push_string(&mut self, val: String) { + self.stack.push(Python::attach(|py| { + val.into_pyobject(py).unwrap().into_any().unbind() + })) + } + + fn push_record(&mut self, ty: wit::Record) { + let Record { + constructor, + fields, + } = &RECORDS.get().unwrap()[ty.index()]; + + let elements = self + .stack + .drain(self.stack.len().checked_sub(fields.len()).unwrap()..); + + let result = Python::attach(|py| { constructor - .call1(*py, PyTuple::new(*py, elements).unwrap()) + .call1(py, PyTuple::new(py, elements).unwrap()) .unwrap() - .into_bound(*py) - } - Type::Variant { cases, .. } => { - assert!(len == 2); - let discriminant = Bound::from_owned_ptr( - *py, - ptr::read(data.offset(isize::try_from(DISCRIMINANT_FIELD_INDEX).unwrap())), - ) - .extract::() - .unwrap(); - let case = &cases[usize::try_from(discriminant).unwrap()]; - if case.has_payload { - case.constructor.call1( - *py, - (Bound::from_owned_ptr( - *py, - ptr::read(data.offset(isize::try_from(PAYLOAD_FIELD_INDEX).unwrap())), - ),), - ) - } else { - case.constructor.call1(*py, ()) - } - .unwrap() - .into_bound(*py) - } - Type::Enum { constructor, count } => { - assert!(len == 2); - let discriminant = Bound::from_owned_ptr( - *py, - ptr::read(data.offset(isize::try_from(DISCRIMINANT_FIELD_INDEX).unwrap())), - ) - .extract::() - .unwrap(); - assert!(discriminant < *count); - constructor - .call1(*py, (discriminant,)) + }); + + self.stack.push(result); + } + + fn push_tuple(&mut self, ty: wit::Tuple) { + let &Tuple { count } = &TUPLES.get().unwrap()[ty.index()]; + + let elements = self + .stack + .drain(self.stack.len().checked_sub(count).unwrap()..); + + let result = Python::attach(|py| { + PyTuple::new(py, elements) .unwrap() - .into_bound(*py) - } - Type::Flags { + .to_owned() + .into_any() + .unbind() + }); + + self.stack.push(result); + } + + fn push_flags(&mut self, ty: wit::Flags, bits: u32) { + let Flags { constructor, u32_count, - } => { - assert!(len == *u32_count); + } = &FLAGS.get().unwrap()[ty.index()]; + + // Note that `componentize-py` was originally written when a component + // model `flags` type could have an arbitrary number of bits. Since + // then, the spec was updated to only allow up to 32 bits, but we still + // support arbitrary sizes here. See + // https://github.com/WebAssembly/component-model/issues/370 for + // details. + // + // TODO: If it's unlikely that the spec will ever support more than 32 + // bits, remove the `num-bigint` dependency and simplify the code here + // and in `summary.rs`. + let result = Python::attach(|py| { constructor .call1( - *py, + py, (BigUint::new( - slice::from_raw_parts(data, len) - .iter() - .map(|v| { - i32::cast_unsigned( - Bound::from_owned_ptr(*py, *v).extract().unwrap(), - ) - }) + iter::once(bits) + .chain(iter::repeat(0)) + .take(*u32_count) .collect(), ),), ) .unwrap() - .into_bound(*py) - } - Type::Option => { - assert!(len == 2); - let discriminant = Bound::from_owned_ptr( - *py, - ptr::read(data.offset(isize::try_from(DISCRIMINANT_FIELD_INDEX).unwrap())), - ) - .extract::() - .unwrap(); - match discriminant { - 0 => py.None().into_bound(*py), - 1 => Bound::from_owned_ptr( - *py, - ptr::read(data.offset(isize::try_from(PAYLOAD_FIELD_INDEX).unwrap())), - ), - _ => unreachable!(), - } - } - Type::NestingOption => { - assert!(len == 2); - let discriminant = Bound::from_owned_ptr( - *py, - ptr::read(data.offset(isize::try_from(DISCRIMINANT_FIELD_INDEX).unwrap())), - ) - .extract::() - .unwrap(); + }); - match discriminant { - 0 => py.None().into_bound(*py), + self.stack.push(result); + } - 1 => SOME_CONSTRUCTOR - .get() - .unwrap() - .call1( - *py, - (Bound::from_owned_ptr( - *py, - ptr::read(data.offset(isize::try_from(PAYLOAD_FIELD_INDEX).unwrap())), - ),), - ) + fn push_enum(&mut self, ty: wit::Enum, discriminant: u32) { + let &Enum { + ref constructor, + count, + } = &ENUMS.get().unwrap()[ty.index()]; + + assert!(usize::try_from(discriminant).unwrap() < count); + + let result = Python::attach(|py| constructor.call1(py, (discriminant,)).unwrap()); + + self.stack.push(result); + } + + fn push_borrow(&mut self, ty: wit::Resource, handle: u32) { + Python::attach(|py| { + self.stack.push(if ty.rep().is_some() { + // exported resource type + unsafe { Py::::from_borrowed_ptr(py, handle as usize as _) } + } else { + // imported resource type + let value = imported_resource_from_canon(py, ty, handle); + + BORROWS.lock().unwrap().push(Borrow { + value: value.clone_ref(py), + handle, + drop: ty.drop(), + }); + + value + }) + }) + } + + fn push_own(&mut self, ty: wit::Resource, handle: u32) { + Python::attach(|py| { + self.stack.push(if let Some(rep) = ty.rep() { + // exported resource type + let rep = unsafe { rep(handle) }; + let value = unsafe { Py::::from_borrowed_ptr(py, rep as _) }.into_bound(py); + + value + .delattr(intern!(py, "__componentize_py_handle")) + .unwrap(); + + value + .getattr(intern!(py, "finalizer")) .unwrap() - .into_bound(*py), + .call_method0(intern!(py, "detach")) + .unwrap(); - _ => unreachable!(), - } - } - Type::Result => { - assert!(len == 2); - let discriminant = Bound::from_owned_ptr( - *py, - ptr::read(data.offset(isize::try_from(DISCRIMINANT_FIELD_INDEX).unwrap())), - ) - .extract::() - .unwrap(); + value.unbind() + } else { + // imported resource type + imported_resource_from_canon(py, ty, handle) + }); + }) + } - match discriminant { - 0 => OK_CONSTRUCTOR.get().unwrap(), - 1 => ERR_CONSTRUCTOR.get().unwrap(), - _ => unreachable!(), - } - .call1( - *py, - (Bound::from_owned_ptr( - *py, - ptr::read(data.offset(isize::try_from(PAYLOAD_FIELD_INDEX).unwrap())), - ),), - ) - .unwrap() - .into_bound(*py) - } - Type::Tuple(length) => { - assert!(*length == len); - let elements = slice::from_raw_parts(data, len) - .iter() - .map(|e| Bound::from_owned_ptr(*py, *e)); - PyTuple::new(*py, elements).unwrap().into_any() - } - Type::Handle | Type::Resource { .. } => unreachable!(), + fn push_future(&mut self, ty: wit::Future, handle: u32) { + _ = (ty, handle); + todo!() } -} -#[export_name = "componentize-py#MakeList"] -pub extern "C" fn componentize_py_make_list<'a>(py: &Python<'a>) -> Bound<'a, PyList> { - PyList::empty(*py) -} + fn push_stream(&mut self, ty: wit::Stream, handle: u32) { + _ = (ty, handle); + todo!() + } -#[export_name = "componentize-py#ListAppend"] -pub extern "C" fn componentize_py_list_append( - _py: &Python, - list: Borrowed, - element: Borrowed, -) { - list.append(element).unwrap(); -} + fn push_variant(&mut self, ty: wit::Variant, discriminant: u32) { + let Variant { cases, .. } = &VARIANTS.get().unwrap()[ty.index()]; -#[export_name = "componentize-py#None"] -pub extern "C" fn componentize_py_none(py: &Python) -> Py { - py.None() -} + let result = Python::attach(|py| { + let case = &cases[usize::try_from(discriminant).unwrap()]; + if case.has_payload { + let payload = self.stack.pop().unwrap(); + case.constructor.call1(py, (payload,)) + } else { + case.constructor.call1(py, ()) + } + .unwrap() + }); -/// # Safety -/// TODO -#[export_name = "componentize-py#GetBytes"] -pub unsafe extern "C" fn componentize_py_get_bytes( - _py: &Python, - src: Borrowed, - dst: *mut u8, - len: usize, -) { - assert_eq!(len, src.len().unwrap()); - slice::from_raw_parts_mut(dst, len).copy_from_slice(src.as_bytes()) -} + self.stack.push(result); + } -/// # Safety -/// TODO -#[export_name = "componentize-py#MakeBytes"] -pub unsafe extern "C" fn componentize_py_make_bytes<'a>( - py: &Python<'a>, - src: *const u8, - len: usize, -) -> Bound<'a, PyBytes> { - PyBytes::new(*py, slice::from_raw_parts(src, len)) -} + fn push_option(&mut self, ty: WitOption, is_some: bool) { + Python::attach(|py| { + if is_some { + match &OPTIONS.get().unwrap()[ty.index()] { + OptionKind::NonNesting => { + // Leave payload on the stack as-is. + } + OptionKind::Nesting => { + let payload = self.stack.pop().unwrap(); + self.stack.push( + SOME_CONSTRUCTOR + .get() + .unwrap() + .call1(py, (payload,)) + .unwrap(), + ); + } + } + } else { + self.stack.push(py.None()); + } + }); + } -#[export_name = "componentize-py#FromCanonHandle"] -pub extern "C" fn componentize_py_from_canon_handle<'a>( - py: &Python<'a>, - value: i32, - borrow: i32, - local: i32, - resource: i32, -) -> Bound<'a, PyAny> { - let ty = &TYPES.get().unwrap()[usize::try_from(resource).unwrap()]; - let Type::Resource { - constructor, - local: resource_local, - remote: resource_remote, - } = ty - else { - panic!("expected resource, found {ty:?}"); - }; + fn push_result(&mut self, ty: WitResult, is_err: bool) { + let &ResultRecord { has_ok, has_err } = &RESULTS.get().unwrap()[ty.index()]; - if local != 0 { - if borrow != 0 { - unsafe { Py::::from_borrowed_ptr(*py, value as usize as _) }.into_bound(*py) - } else { - let Some(LocalResource { rep, .. }) = resource_local else { - panic!("expected local resource, found {ty:?}"); + Python::attach(|py| { + let (constructor, has_payload) = if is_err { + (ERR_CONSTRUCTOR.get().unwrap(), has_err) + } else { + (OK_CONSTRUCTOR.get().unwrap(), has_ok) }; - let rep = { - let params = [value]; - let mut results = [MaybeUninit::::uninit()]; - unsafe { - componentize_py_call_indirect( - py as *const _ as _, - params.as_ptr() as _, - results.as_mut_ptr() as _, - *rep, - ); - results[0].assume_init() - } + let payload = if has_payload { + self.stack.pop().unwrap() + } else { + py.None() }; - let value = unsafe { Py::::from_borrowed_ptr(*py, rep as _) }.into_bound(*py); + self.stack.push(constructor.call1(py, (payload,)).unwrap()) + }); + } - value - .delattr(intern!(*py, "__componentize_py_handle")) - .unwrap(); + unsafe fn push_raw_list(&mut self, ty: List, src: *mut u8, len: usize) -> bool { + if let Type::U8 = ty.ty() { + self.stack.push(Python::attach(|py| { + let value = PyBytes::new(py, slice::from_raw_parts(src, len)) + .to_owned() + .into_any() + .unbind(); + alloc::dealloc(src, Layout::from_size_align(len, 1).unwrap()); + value + })); + true + } else { + false + } + } - value - .getattr(intern!(*py, "finalizer")) + fn push_list(&mut self, _ty: List, _capacity: usize) { + self.stack.push(Python::attach(|py| { + PyList::empty(py).to_owned().into_any().unbind() + })); + } + + fn list_append(&mut self, _ty: List) { + Python::attach(|py| { + let element = self.stack.pop().unwrap(); + self.stack + .last() .unwrap() - .call_method0(intern!(*py, "detach")) - .unwrap(); + .cast_bound::(py) + .unwrap() + .append(element) + .unwrap() + }); + } +} - value - } - } else { - let Some(RemoteResource { drop }) = resource_remote else { - panic!("expected remote resource, found {ty:?}"); - }; +fn imported_resource_from_canon(py: Python<'_>, ty: wit::Resource, handle: u32) -> Py { + let Resource { constructor } = &RESOURCES.get().unwrap()[ty.index()]; - if borrow != 0 { - BORROWS.lock().unwrap().push(Borrow { - handle: value, - drop: *drop, - }); - } + let instance = constructor + .call_method1( + py, + intern!(py, "__new__"), + PyTuple::new(py, [constructor]).unwrap(), + ) + .unwrap(); - let instance = constructor - .call_method1( - *py, - intern!(*py, "__new__"), - PyTuple::new(*py, [constructor]).unwrap(), - ) - .unwrap(); + let handle = handle.into_pyobject(py).unwrap(); + + instance + .setattr(py, intern!(py, "handle"), handle.as_borrowed()) + .unwrap(); + + let finalizer = FINALIZE + .get() + .unwrap() + .call1( + py, + ( + instance.clone_ref(py), + DROP_RESOURCE.get().unwrap(), + (ty.drop() as usize).into_pyobject(py).unwrap(), + handle, + ), + ) + .unwrap(); + + instance + .setattr(py, intern!(py, "finalizer"), finalizer) + .unwrap(); + + instance +} - let handle = value.into_pyobject(*py).unwrap(); +fn exported_resource_to_canon( + py: Python<'_>, + ty: wit::Resource, + new: unsafe extern "C" fn(usize) -> u32, + value: Py, +) -> u32 { + let name = intern!(py, "__componentize_py_handle"); + if value.bind(py).hasattr(name).unwrap() { + value.bind(py).getattr(name).unwrap().extract().unwrap() + } else { + let rep = value.into_ptr(); + let handle = unsafe { new(rep as usize) }; + let instance = unsafe { Py::::from_borrowed_ptr(py, rep) }; instance - .setattr(*py, intern!(*py, "handle"), handle.as_borrowed()) + .setattr(py, name, handle.into_pyobject(py).unwrap()) .unwrap(); let finalizer = FINALIZE .get() .unwrap() .call1( - *py, + py, ( - instance.clone_ref(*py), + instance.clone_ref(py), DROP_RESOURCE.get().unwrap(), - drop.into_pyobject(*py).unwrap(), + (ty.drop() as usize).into_pyobject(py).unwrap(), handle, ), ) .unwrap(); instance - .setattr(*py, intern!(*py, "finalizer"), finalizer) + .setattr(py, intern!(py, "finalizer"), finalizer) .unwrap(); - instance.into_bound(*py) + handle } } -#[export_name = "componentize-py#ToCanonHandle"] -pub extern "C" fn componentize_py_to_canon_handle( - py: &Python, - value: Borrowed, - borrow: i32, - local: i32, - resource: i32, -) -> u32 { - if local != 0 { - let ty = &TYPES.get().unwrap()[usize::try_from(resource).unwrap()]; - let Type::Resource { - local: Some(LocalResource { new, drop, .. }), - .. - } = ty - else { - panic!("expected local resource, found {ty:?}"); - }; - - let name = intern!(*py, "__componentize_py_handle"); - if value.hasattr(name).unwrap() { - value.getattr(name).unwrap().extract().unwrap() - } else { - let rep = Py::::from(value.to_owned()).into_ptr(); - let handle = { - let params = [rep as usize]; - let mut results = [MaybeUninit::::uninit()]; - unsafe { - componentize_py_call_indirect( - py as *const _ as _, - params.as_ptr() as _, - results.as_mut_ptr() as _, - *new, - ); - results[0].assume_init() - } - }; - - let instance = unsafe { Py::::from_borrowed_ptr(*py, rep) }; - - instance - .setattr(*py, name, handle.into_pyobject(*py).unwrap()) - .unwrap(); - - let finalizer = FINALIZE - .get() - .unwrap() - .call1( - *py, - ( - instance.clone_ref(*py), - DROP_RESOURCE.get().unwrap(), - drop.into_pyobject(*py).unwrap(), - handle, - ), - ) - .unwrap(); - - instance - .setattr(*py, intern!(*py, "finalizer"), finalizer) - .unwrap(); - - handle - } - } else { - if borrow == 0 { - value - .getattr(intern!(*py, "finalizer")) - .unwrap() - .call_method0(intern!(*py, "detach")) - .unwrap(); - } - - value - .getattr(intern!(*py, "handle")) - .unwrap() - .extract() - .unwrap() - } +fn imported_resource_to_canon(py: Python<'_>, value: Py) -> u32 { + value + .bind(py) + .getattr(intern!(py, "handle")) + .unwrap() + .extract() + .unwrap() } +wit_dylib_ffi::export!(MyInterpreter); + // As of this writing, recent Rust `nightly` builds include a version of the `libc` crate that expects `wasi-libc` // to define the following global variables, but `wasi-libc` defines them as preprocessor constants which aren't // visible at link time, so we need to define them somewhere. Ideally, we should fix this upstream, but for now we diff --git a/shared/Cargo.toml b/shared/Cargo.toml deleted file mode 100644 index 037f3d0..0000000 --- a/shared/Cargo.toml +++ /dev/null @@ -1,6 +0,0 @@ -[package] -name = "componentize-py-shared" -version = "0.1.0" -edition = "2021" - -[dependencies] diff --git a/shared/src/lib.rs b/shared/src/lib.rs deleted file mode 100644 index f4ef1c2..0000000 --- a/shared/src/lib.rs +++ /dev/null @@ -1,5 +0,0 @@ -#[repr(u8)] -pub enum ReturnStyle { - Normal, - Result, -} diff --git a/src/abi.rs b/src/abi.rs deleted file mode 100644 index 3fb7053..0000000 --- a/src/abi.rs +++ /dev/null @@ -1,247 +0,0 @@ -use { - std::iter, - wasm_encoder::ValType, - wit_parser::{FlagsRepr, Resolve, Type, TypeDefKind}, -}; - -pub const MAX_FLAT_PARAMS: usize = 16; -pub const MAX_FLAT_RESULTS: usize = 1; - -pub fn align(a: usize, b: usize) -> usize { - assert!(b.is_power_of_two()); - (a + (b - 1)) & !(b - 1) -} - -pub struct Abi { - pub size: usize, - pub align: usize, - pub flattened: Vec, -} - -pub fn record_abi(resolve: &Resolve, types: impl IntoIterator) -> Abi { - let mut size = 0_usize; - let mut align_ = 1; - let mut flattened = Vec::new(); - for ty in types { - let abi = abi(resolve, ty); - size = align(size, abi.align); - size += abi.size; - if abi.align > align_ { - align_ = abi.align; - } - flattened.extend(abi.flattened); - } - - Abi { - size: align(size, align_), - align: align_, - flattened, - } -} - -pub fn record_abi_limit( - resolve: &Resolve, - types: impl IntoIterator, - limit: usize, -) -> Abi { - let mut abi = record_abi(resolve, types); - if abi.flattened.len() > limit { - abi.flattened = vec![ValType::I32]; - } - abi -} - -fn join(a: ValType, b: ValType) -> ValType { - if a == b { - a - } else if let (ValType::I32, ValType::F32) | (ValType::F32, ValType::I32) = (a, b) { - ValType::I32 - } else { - ValType::I64 - } -} - -pub fn discriminant_size(count: usize) -> usize { - match count { - 1..=0xFF => 1, - 0x100..=0xFFFF => 2, - 0x1_0000..=0xFFFF_FFFF => 4, - _ => unreachable!(), - } -} - -fn variant_abi(resolve: &Resolve, types: impl IntoIterator>) -> Abi { - let mut size = 0_usize; - let mut align_ = 1; - let mut flattened = Vec::new(); - let mut count = 0; - for ty in types { - count += 1; - if let Some(ty) = ty { - let abi = abi(resolve, ty); - if abi.size > size { - size = abi.size; - } - if abi.align > align_ { - align_ = abi.align; - } - for (index, ty) in abi.flattened.iter().enumerate() { - if index == flattened.len() { - flattened.push(*ty); - } else { - flattened[index] = join(flattened[index], *ty); - } - } - } - } - - let discriminant_size = discriminant_size(count); - let align_ = align_.max(discriminant_size); - let size = align(size + align(discriminant_size, align_), align_); - let flattened = iter::once(ValType::I32).chain(flattened).collect(); - - Abi { - size, - align: align_, - flattened, - } -} - -pub fn has_pointer(resolve: &Resolve, ty: Type) -> bool { - match ty { - Type::Bool - | Type::U8 - | Type::S8 - | Type::U16 - | Type::S16 - | Type::U32 - | Type::S32 - | Type::Char - | Type::U64 - | Type::S64 - | Type::F32 - | Type::F64 - | Type::ErrorContext => false, - Type::String => true, - Type::Id(id) => match &resolve.types[id].kind { - TypeDefKind::Record(record) => record - .fields - .iter() - .any(|field| has_pointer(resolve, field.ty)), - TypeDefKind::Variant(variant) => variant - .cases - .iter() - .any(|case| case.ty.map(|ty| has_pointer(resolve, ty)).unwrap_or(false)), - // TODO: should we be calling destructors for handles in post-return functions? - TypeDefKind::Handle(_) | TypeDefKind::Enum(_) | TypeDefKind::Flags(_) => false, - TypeDefKind::Option(ty) => has_pointer(resolve, *ty), - TypeDefKind::Result(result) => { - result - .ok - .map(|ty| has_pointer(resolve, ty)) - .unwrap_or(false) - || result - .err - .map(|ty| has_pointer(resolve, ty)) - .unwrap_or(false) - } - TypeDefKind::Tuple(tuple) => tuple.types.iter().any(|ty| has_pointer(resolve, *ty)), - TypeDefKind::List(_) => true, - TypeDefKind::Type(ty) => has_pointer(resolve, *ty), - kind => todo!("{kind:?}"), - }, - } -} - -pub fn abi(resolve: &Resolve, ty: Type) -> Abi { - match ty { - Type::Bool | Type::U8 | Type::S8 => Abi { - size: 1, - align: 1, - flattened: vec![ValType::I32], - }, - Type::U16 | Type::S16 => Abi { - size: 2, - align: 2, - flattened: vec![ValType::I32], - }, - Type::U32 | Type::S32 | Type::Char | Type::ErrorContext => Abi { - size: 4, - align: 4, - flattened: vec![ValType::I32], - }, - Type::U64 | Type::S64 => Abi { - size: 8, - align: 8, - flattened: vec![ValType::I64], - }, - Type::F32 => Abi { - size: 4, - align: 4, - flattened: vec![ValType::F32], - }, - Type::F64 => Abi { - size: 8, - align: 8, - flattened: vec![ValType::F64], - }, - Type::String => Abi { - size: 8, - align: 4, - flattened: vec![ValType::I32; 2], - }, - Type::Id(id) => match &resolve.types[id].kind { - TypeDefKind::Record(record) => { - record_abi(resolve, record.fields.iter().map(|field| field.ty)) - } - TypeDefKind::Variant(variant) => { - variant_abi(resolve, variant.cases.iter().map(|case| case.ty)) - } - TypeDefKind::Enum(en) => variant_abi(resolve, en.cases.iter().map(|_| None)), - TypeDefKind::Option(ty) => variant_abi(resolve, [None, Some(*ty)]), - TypeDefKind::Result(result) => variant_abi(resolve, [result.ok, result.err]), - TypeDefKind::Flags(flags) => { - let repr = flags.repr(); - - Abi { - size: match &repr { - FlagsRepr::U8 => 1, - FlagsRepr::U16 => 2, - FlagsRepr::U32(count) => 4 * *count, - }, - align: match &repr { - FlagsRepr::U8 | FlagsRepr::U32(0) => 1, - FlagsRepr::U16 => 2, - FlagsRepr::U32(_) => 4, - }, - flattened: vec![ValType::I32; repr.count()], - } - } - TypeDefKind::Tuple(tuple) => record_abi(resolve, tuple.types.iter().copied()), - TypeDefKind::List(_) => Abi { - size: 8, - align: 4, - flattened: vec![ValType::I32; 2], - }, - TypeDefKind::Type(ty) => abi(resolve, *ty), - TypeDefKind::Handle(_) => Abi { - size: 4, - align: 4, - flattened: vec![ValType::I32], - }, - kind => todo!("{kind:?}"), - }, - } -} - -pub fn is_option(resolve: &Resolve, ty: Type) -> bool { - if let Type::Id(id) = ty { - match &resolve.types[id].kind { - TypeDefKind::Option(_) => true, - TypeDefKind::Type(ty) => is_option(resolve, *ty), - _ => false, - } - } else { - false - } -} diff --git a/src/bindgen.rs b/src/bindgen.rs deleted file mode 100644 index c5c0156..0000000 --- a/src/bindgen.rs +++ /dev/null @@ -1,2691 +0,0 @@ -use { - crate::{ - abi::{self, Abi, MAX_FLAT_PARAMS, MAX_FLAT_RESULTS}, - summary::{Direction, MyFunction, Summary}, - util::Types as _, - }, - componentize_py_shared::ReturnStyle, - indexmap::IndexSet, - once_cell::sync::Lazy, - std::collections::HashMap, - wasm_encoder::{BlockType, Instruction as Ins, MemArg, ValType}, - wit_parser::{Handle, Resolve, Type, TypeDefKind, TypeId}, -}; - -// Assume Wasm32 -// TODO: Wasm64 support -const WORD_SIZE: usize = 4; -const WORD_ALIGN: usize = 2; // as a power of two - -const STACK_ALIGNMENT: usize = 8; - -pub const DISPATCHABLE_CORE_PARAM_COUNT: usize = 3; -pub const DISPATCH_CORE_PARAM_COUNT: usize = DISPATCHABLE_CORE_PARAM_COUNT + 1; - -const DISCRIMINANT_FIELD_INDEX: i32 = 0; -const PAYLOAD_FIELD_INDEX: i32 = 1; - -pub static IMPORT_SIGNATURES: &[(&str, &[ValType], &[ValType])] = &[ - ( - "componentize-py#Dispatch", - &[ValType::I32; 7] as &[_], - &[] as &[_], - ), - ( - "componentize-py#Allocate", - &[ValType::I32; 2], - &[ValType::I32], - ), - ("componentize-py#Free", &[ValType::I32; 3], &[]), - ( - "componentize-py#ToCanonBool", - &[ValType::I32; 2], - &[ValType::I32], - ), - ( - "componentize-py#ToCanonI32", - &[ValType::I32; 2], - &[ValType::I32], - ), - ( - "componentize-py#ToCanonU32", - &[ValType::I32; 2], - &[ValType::I32], - ), - ( - "componentize-py#ToCanonI64", - &[ValType::I32; 2], - &[ValType::I64], - ), - ( - "componentize-py#ToCanonU64", - &[ValType::I32; 2], - &[ValType::I64], - ), - ( - "componentize-py#ToCanonF32", - &[ValType::I32; 2], - &[ValType::F32], - ), - ( - "componentize-py#ToCanonF64", - &[ValType::I32; 2], - &[ValType::F64], - ), - ( - "componentize-py#ToCanonChar", - &[ValType::I32; 2], - &[ValType::I32], - ), - ("componentize-py#ToCanonString", &[ValType::I32; 3], &[]), - ( - "componentize-py#GetField", - &[ValType::I32; 4], - &[ValType::I32], - ), - ( - "componentize-py#GetListLength", - &[ValType::I32; 2], - &[ValType::I32], - ), - ( - "componentize-py#GetListElement", - &[ValType::I32; 3], - &[ValType::I32], - ), - ( - "componentize-py#FromCanonBool", - &[ValType::I32; 2], - &[ValType::I32], - ), - ( - "componentize-py#FromCanonI32", - &[ValType::I32; 2], - &[ValType::I32], - ), - ( - "componentize-py#FromCanonU32", - &[ValType::I32; 2], - &[ValType::I32], - ), - ( - "componentize-py#FromCanonI64", - &[ValType::I32, ValType::I64], - &[ValType::I32], - ), - ( - "componentize-py#FromCanonU64", - &[ValType::I32, ValType::I64], - &[ValType::I32], - ), - ( - "componentize-py#FromCanonF32", - &[ValType::I32, ValType::F32], - &[ValType::I32], - ), - ( - "componentize-py#FromCanonF64", - &[ValType::I32, ValType::F64], - &[ValType::I32], - ), - ( - "componentize-py#FromCanonChar", - &[ValType::I32; 2], - &[ValType::I32], - ), - ( - "componentize-py#FromCanonString", - &[ValType::I32; 3], - &[ValType::I32], - ), - ("componentize-py#MakeList", &[ValType::I32], &[ValType::I32]), - ("componentize-py#ListAppend", &[ValType::I32; 3], &[]), - ("componentize-py#None", &[ValType::I32], &[ValType::I32]), - ("componentize-py#Init", &[ValType::I32; 4], &[ValType::I32]), - ("componentize-py#GetBytes", &[ValType::I32; 4], &[]), - ( - "componentize-py#MakeBytes", - &[ValType::I32; 3], - &[ValType::I32], - ), - ( - "componentize-py#FromCanonHandle", - &[ValType::I32; 5], - &[ValType::I32], - ), - ( - "componentize-py#ToCanonHandle", - &[ValType::I32; 5], - &[ValType::I32], - ), - ("cabi_realloc", &[ValType::I32; 4], &[ValType::I32]), -]; - -pub static IMPORTS: Lazy> = Lazy::new(|| { - IMPORT_SIGNATURES - .iter() - .enumerate() - .map(|(index, (name, ..))| (*name, index.try_into().unwrap())) - .collect() -}); - -pub fn mem_arg(offset: u64, align: u32) -> MemArg { - MemArg { - offset, - align, - memory_index: 0, - } -} - -pub struct FunctionBindgen<'a> { - pub local_types: Vec, - pub instructions: Vec>, - resolve: &'a Resolve, - stack_pointer: u32, - types: &'a IndexSet, - params: &'a [(String, Type)], - result: &'a Option, - params_abi: Abi, - results_abi: Abi, - local_stack: Vec, - param_count: usize, - tuple_types: &'a HashMap, - option_type: Option, - nesting_option_type: Option, - result_type: Option, - resource_directions: Option<&'a im_rc::HashMap>, -} - -#[allow(clippy::wrong_self_convention)] -impl<'a> FunctionBindgen<'a> { - pub fn new(summary: &'a Summary, function: &'a MyFunction, stack_pointer: u32) -> Self { - Self { - resolve: summary.resolve, - stack_pointer, - types: &summary.types, - params: function.params, - result: function.result, - params_abi: abi::record_abi(summary.resolve, function.params.types()), - results_abi: abi::record_abi(summary.resolve, function.result.types()), - local_types: Vec::new(), - local_stack: Vec::new(), - instructions: Vec::new(), - param_count: function.core_export_type(summary.resolve).0.len(), - tuple_types: &summary.tuple_types, - option_type: summary.option_type, - nesting_option_type: summary.nesting_option_type, - result_type: summary.result_type, - resource_directions: function - .interface - .as_ref() - .map(|interface| &interface.resource_directions), - } - } - - pub fn compile_import(&mut self, index: u32) { - // Arg 0: *const Python - let context = 0; - // Arg 1: *const &PyAny - let input = 1; - // Arg 2: *mut &PyAny - let output = 2; - - let locals = if self.params_abi.flattened.len() <= MAX_FLAT_PARAMS { - let locals = self - .params_abi - .flattened - .clone() - .iter() - .map(|ty| self.push_local(*ty)) - .collect::>(); - - let mut from_canon_index = 0; - let mut load_offset = 0; - for ty in self.params.types() { - let abi = abi::abi(self.resolve, ty); - - let value = self.push_local(ValType::I32); - - self.push(Ins::LocalGet(input)); - self.push(Ins::I32Load(mem_arg( - load_offset, - WORD_ALIGN.try_into().unwrap(), - ))); - self.push(Ins::LocalSet(value)); - - self.to_canon(ty, context, value); - - for local in locals[from_canon_index..][..abi.flattened.len()] - .iter() - .rev() - { - self.push(Ins::LocalSet(*local)); - } - - for local in &locals[from_canon_index..][..abi.flattened.len()] { - self.push(Ins::LocalGet(*local)); - } - - from_canon_index += abi.flattened.len(); - load_offset += u64::try_from(WORD_SIZE).unwrap(); - - self.pop_local(value, ValType::I32); - } - - Some(locals) - } else { - self.push_stack(self.params_abi.size); - - let mut store_offset = 0; - let mut load_offset = 0; - for ty in self.params.types() { - let value = self.push_local(ValType::I32); - let destination = self.push_local(ValType::I32); - - let abi = abi::abi(self.resolve, ty); - store_offset = abi::align(store_offset, abi.align); - - self.get_stack(); - self.push(Ins::I32Const(store_offset.try_into().unwrap())); - self.push(Ins::I32Add); - self.push(Ins::LocalSet(destination)); - - self.push(Ins::LocalGet(input)); - self.push(Ins::I32Load(mem_arg( - load_offset, - WORD_ALIGN.try_into().unwrap(), - ))); - self.push(Ins::LocalSet(value)); - - self.store(ty, context, value, destination); - - store_offset += abi.size; - load_offset += u64::try_from(WORD_SIZE).unwrap(); - - self.pop_local(destination, ValType::I32); - self.pop_local(value, ValType::I32); - } - - self.get_stack(); - - None - }; - - if self.results_abi.flattened.len() > MAX_FLAT_RESULTS { - self.push_stack(self.results_abi.size); - - self.get_stack(); - } - - self.push(Ins::Call(index)); - - if self.results_abi.flattened.len() <= MAX_FLAT_RESULTS { - let locals = self - .results_abi - .flattened - .clone() - .iter() - .map(|ty| { - let local = self.push_local(*ty); - self.push(Ins::LocalSet(local)); - local - }) - .collect::>(); - - self.from_canon_record(self.result.types(), context, &locals, output); - self.free_canon_record(self.result.types(), &locals); - - for (local, ty) in locals.iter().zip(&self.results_abi.flattened.clone()).rev() { - self.pop_local(*local, *ty); - } - } else { - let source = self.push_local(ValType::I32); - - self.get_stack(); - self.push(Ins::LocalSet(source)); - - self.load_record(self.result.types(), context, source, output); - self.free_stored_record(self.result.types(), source); - - self.pop_local(source, ValType::I32); - self.pop_stack(self.results_abi.size); - } - - if let Some(locals) = locals { - self.free_canon_record(self.params.types(), &locals); - - for (local, ty) in locals.iter().zip(&self.params_abi.flattened.clone()).rev() { - self.pop_local(*local, *ty); - } - } else { - let value = self.push_local(ValType::I32); - - self.get_stack(); - self.push(Ins::LocalSet(value)); - - self.free_stored_record(self.params.types(), value); - - self.pop_local(value, ValType::I32); - self.pop_stack(self.params_abi.size); - } - } - - pub fn compile_export(&mut self, index: i32, from_canon: i32, to_canon: i32) { - let return_style = match self.result.types().collect::>().as_slice() { - [Type::Id(id)] if matches!(&self.resolve.types[*id].kind, TypeDefKind::Result(_)) => { - ReturnStyle::Result - } - _ => ReturnStyle::Normal, - }; - - self.push(Ins::I32Const(index)); - self.push(Ins::I32Const(from_canon)); - self.push(Ins::I32Const(to_canon)); - self.push(Ins::I32Const( - self.params.types().count().try_into().unwrap(), - )); - self.push(Ins::I32Const(return_style as _)); - - if self.params_abi.flattened.len() <= MAX_FLAT_PARAMS { - self.push_stack(self.params_abi.size); - - let destination = self.push_local(ValType::I32); - self.get_stack(); - self.push(Ins::LocalSet(destination)); - - self.store_copy_record( - self.params.types(), - &(0..self.params_abi.flattened.len().try_into().unwrap()).collect::>(), - destination, - ); - - self.pop_local(destination, ValType::I32); - - self.get_stack(); - } else { - self.push(Ins::LocalGet(0)); - }; - - let result = if self.results_abi.flattened.len() <= MAX_FLAT_RESULTS { - self.push_stack(self.results_abi.size); - self.get_stack(); - - None - } else { - let result = self.push_local(ValType::I32); - self.push(Ins::I32Const(self.results_abi.size.try_into().unwrap())); - self.push(Ins::I32Const(self.results_abi.align.try_into().unwrap())); - self.push(Ins::Call(*IMPORTS.get("componentize-py#Allocate").unwrap())); - self.push(Ins::LocalTee(result)); - - Some(result) - }; - - self.push(Ins::Call(*IMPORTS.get("componentize-py#Dispatch").unwrap())); - - if let Some(result) = result { - self.push(Ins::LocalGet(result)); - self.pop_local(result, ValType::I32); - } else { - let source = self.push_local(ValType::I32); - self.get_stack(); - self.push(Ins::LocalSet(source)); - - self.load_copy_record(self.result.types(), source); - - self.pop_local(source, ValType::I32); - - self.pop_stack(self.results_abi.size); - } - - if self.params_abi.flattened.len() <= MAX_FLAT_PARAMS { - self.pop_stack(self.params_abi.size); - } - } - - pub fn compile_export_from_canon(&mut self) { - // Arg 0: *const Python - let context = 0; - // Arg 1: *const MyParams - let source = 1; - // Arg 2: *mut &PyAny - let destination = 2; - - self.load_record(self.params.types(), context, source, destination); - } - - pub fn compile_export_to_canon(&mut self) { - // Arg 0: *const Python - let context = 0; - // Arg 1: *const &PyAny - let source = 1; - // Arg 2: *mut MyResults - let destination = 2; - - let mut store_offset = 0; - let mut load_offset = 0; - for ty in self.result.types() { - let abi = abi::abi(self.resolve, ty); - store_offset = abi::align(store_offset, abi.align); - - let field_value = self.push_local(ValType::I32); - let field_destination = self.push_local(ValType::I32); - - self.push(Ins::LocalGet(source)); - self.push(Ins::I32Load(mem_arg( - load_offset, - WORD_ALIGN.try_into().unwrap(), - ))); - self.push(Ins::LocalSet(field_value)); - - self.push(Ins::LocalGet(destination)); - self.push(Ins::I32Const(store_offset.try_into().unwrap())); - self.push(Ins::I32Add); - self.push(Ins::LocalSet(field_destination)); - - self.store(ty, context, field_value, field_destination); - - store_offset += abi.size; - load_offset += u64::try_from(WORD_SIZE).unwrap(); - - self.pop_local(field_destination, ValType::I32); - self.pop_local(field_value, ValType::I32); - } - } - - pub fn compile_export_post_return(&mut self) { - if self.results_abi.flattened.len() > MAX_FLAT_RESULTS { - // Arg 0: *mut MyResults - let value = 0; - - self.free_stored_record(self.result.types(), value); - - self.push(Ins::LocalGet(value)); - self.push(Ins::I32Const(self.results_abi.size.try_into().unwrap())); - self.push(Ins::I32Const(self.results_abi.align.try_into().unwrap())); - self.push(Ins::Call(*IMPORTS.get("componentize-py#Free").unwrap())); - } else { - unreachable!() - } - } - - pub fn compile_resource_new(&mut self, index: u32) { - // Arg 0: *const Python - let _ = 0; - // Arg 1: *const usize - let input = 1; - // Arg 2: *mut u32 - let output = 2; - - self.push(Ins::LocalGet(output)); - self.push(Ins::LocalGet(input)); - self.push(Ins::I32Load(mem_arg(0, WORD_ALIGN.try_into().unwrap()))); - self.push(Ins::Call(index)); - self.push(Ins::I32Store(mem_arg(0, 2))); - } - - pub fn compile_resource_rep(&mut self, index: u32) { - // Arg 0: *const Python - let _ = 0; - // Arg 1: *const u32 - let input = 1; - // Arg 2: *mut usize - let output = 2; - - self.push(Ins::LocalGet(output)); - self.push(Ins::LocalGet(input)); - self.push(Ins::I32Load(mem_arg(0, 2))); - self.push(Ins::Call(index)); - self.push(Ins::I32Store(mem_arg(0, WORD_ALIGN.try_into().unwrap()))); - } - - pub fn compile_resource_drop(&mut self, index: u32) { - // Arg 0: *const Python - let _ = 0; - // Arg 1: *const u32 - let input = 1; - // Arg 2: *mut c_void - let _ = 2; - - self.push(Ins::LocalGet(input)); - self.push(Ins::I32Load(mem_arg(0, 2))); - self.push(Ins::Call(index)); - } - - fn push_stack(&mut self, size: usize) { - self.push(Ins::GlobalGet(self.stack_pointer)); - self.push(Ins::I32Const( - abi::align(size, STACK_ALIGNMENT).try_into().unwrap(), - )); - self.push(Ins::I32Sub); - self.push(Ins::GlobalSet(self.stack_pointer)); - } - - fn pop_stack(&mut self, size: usize) { - self.push(Ins::GlobalGet(self.stack_pointer)); - self.push(Ins::I32Const( - abi::align(size, STACK_ALIGNMENT).try_into().unwrap(), - )); - self.push(Ins::I32Add); - self.push(Ins::GlobalSet(self.stack_pointer)); - } - - fn push(&mut self, instruction: Ins<'static>) { - self.instructions.push(instruction) - } - - fn get_stack(&mut self) { - self.push(Ins::GlobalGet(self.stack_pointer)); - } - - fn push_local(&mut self, ty: ValType) -> u32 { - while self.local_types.len() > self.local_stack.len() - && self.local_types[self.local_stack.len()] != ty - { - self.local_stack.push(false); - } - - self.local_stack.push(true); - if self.local_types.len() < self.local_stack.len() { - self.local_types.push(ty); - } - - (self.param_count + self.local_stack.len() - 1) - .try_into() - .unwrap() - } - - fn pop_local(&mut self, index: u32, ty: ValType) { - assert!(index == u32::try_from(self.param_count + self.local_stack.len() - 1).unwrap()); - assert!(ty == self.local_types[self.local_stack.len() - 1]); - - self.local_stack.pop(); - while let Some(false) = self.local_stack.last() { - self.local_stack.pop(); - } - } - - fn to_canon(&mut self, ty: Type, context: u32, value: u32) { - match ty { - Type::Bool => { - self.push(Ins::LocalGet(context)); - self.push(Ins::LocalGet(value)); - self.push(Ins::Call( - *IMPORTS.get("componentize-py#ToCanonBool").unwrap(), - )); - } - Type::S8 | Type::S16 | Type::S32 => { - self.push(Ins::LocalGet(context)); - self.push(Ins::LocalGet(value)); - self.push(Ins::Call( - *IMPORTS.get("componentize-py#ToCanonI32").unwrap(), - )); - } - Type::U8 | Type::U16 | Type::U32 | Type::ErrorContext => { - self.push(Ins::LocalGet(context)); - self.push(Ins::LocalGet(value)); - self.push(Ins::Call( - *IMPORTS.get("componentize-py#ToCanonU32").unwrap(), - )); - } - Type::S64 => { - self.push(Ins::LocalGet(context)); - self.push(Ins::LocalGet(value)); - self.push(Ins::Call( - *IMPORTS.get("componentize-py#ToCanonI64").unwrap(), - )); - } - Type::U64 => { - self.push(Ins::LocalGet(context)); - self.push(Ins::LocalGet(value)); - self.push(Ins::Call( - *IMPORTS.get("componentize-py#ToCanonU64").unwrap(), - )); - } - Type::F32 => { - self.push(Ins::LocalGet(context)); - self.push(Ins::LocalGet(value)); - self.push(Ins::Call( - *IMPORTS.get("componentize-py#ToCanonF32").unwrap(), - )); - } - Type::F64 => { - self.push(Ins::LocalGet(context)); - self.push(Ins::LocalGet(value)); - self.push(Ins::Call( - *IMPORTS.get("componentize-py#ToCanonF64").unwrap(), - )); - } - Type::Char => { - self.push(Ins::LocalGet(context)); - self.push(Ins::LocalGet(value)); - self.push(Ins::Call( - *IMPORTS.get("componentize-py#ToCanonChar").unwrap(), - )); - } - Type::String => { - self.push(Ins::LocalGet(context)); - self.push(Ins::LocalGet(value)); - self.push_stack(WORD_SIZE * 2); - self.get_stack(); - self.push(Ins::Call( - *IMPORTS.get("componentize-py#ToCanonString").unwrap(), - )); - self.get_stack(); - self.push(Ins::I32Load(mem_arg(0, WORD_ALIGN.try_into().unwrap()))); - self.get_stack(); - self.push(Ins::I32Load(mem_arg( - WORD_SIZE.try_into().unwrap(), - WORD_ALIGN.try_into().unwrap(), - ))); - self.pop_stack(WORD_SIZE * 2); - } - Type::Id(id) => match &self.resolve.types[id].kind { - TypeDefKind::Record(record) => { - self.to_canon_record(id, record.fields.iter().map(|f| f.ty), context, value); - } - TypeDefKind::Variant(variant) => { - self.to_canon_variant( - id, - &abi::abi(self.resolve, ty), - variant.cases.iter().map(|c| c.ty), - context, - value, - ); - } - TypeDefKind::Enum(en) => { - self.to_canon_variant( - id, - &abi::abi(self.resolve, ty), - en.cases.iter().map(|_| None), - context, - value, - ); - } - TypeDefKind::Option(some) => { - self.to_canon_variant( - self.get_option_type(*some), - &abi::abi(self.resolve, ty), - [None, Some(*some)], - context, - value, - ); - } - TypeDefKind::Result(result) => { - self.to_canon_variant( - self.result_type.unwrap(), - &abi::abi(self.resolve, ty), - [result.ok, result.err], - context, - value, - ); - } - TypeDefKind::Flags(flags) => { - self.to_canon_record(id, flags.types(), context, value); - } - TypeDefKind::Tuple(tuple) => { - self.to_canon_record( - *self.tuple_types.get(&tuple.types.len()).unwrap(), - tuple.types.iter().copied(), - context, - value, - ); - } - TypeDefKind::List(ty) => { - let abi = abi::abi(self.resolve, *ty); - - let length = self.push_local(ValType::I32); - let destination = self.push_local(ValType::I32); - - self.push(Ins::LocalGet(context)); - self.push(Ins::LocalGet(value)); - self.push(Ins::Call( - *IMPORTS.get("componentize-py#GetListLength").unwrap(), - )); - self.push(Ins::LocalSet(length)); - - self.push(Ins::LocalGet(length)); - self.push(Ins::I32Const(abi.size.try_into().unwrap())); - self.push(Ins::I32Mul); - self.push(Ins::I32Const(abi.align.try_into().unwrap())); - self.push(Ins::Call(*IMPORTS.get("componentize-py#Allocate").unwrap())); - self.push(Ins::LocalSet(destination)); - - if let Type::U8 | Type::S8 = ty { - self.push(Ins::LocalGet(context)); - self.push(Ins::LocalGet(value)); - self.push(Ins::LocalGet(destination)); - self.push(Ins::LocalGet(length)); - self.push(Ins::Call(*IMPORTS.get("componentize-py#GetBytes").unwrap())); - } else { - let index = self.push_local(ValType::I32); - let element_value = self.push_local(ValType::I32); - let element_destination = self.push_local(ValType::I32); - - self.push(Ins::I32Const(0)); - self.push(Ins::LocalSet(index)); - - self.push(Ins::Loop(BlockType::Empty)); - - self.push(Ins::LocalGet(index)); - self.push(Ins::LocalGet(length)); - self.push(Ins::I32Ne); - - self.push(Ins::If(BlockType::Empty)); - - self.push(Ins::LocalGet(context)); - self.push(Ins::LocalGet(value)); - self.push(Ins::LocalGet(index)); - self.push(Ins::Call( - *IMPORTS.get("componentize-py#GetListElement").unwrap(), - )); - self.push(Ins::LocalSet(element_value)); - - self.push(Ins::LocalGet(destination)); - self.push(Ins::LocalGet(index)); - self.push(Ins::I32Const(abi.size.try_into().unwrap())); - self.push(Ins::I32Mul); - self.push(Ins::I32Add); - self.push(Ins::LocalSet(element_destination)); - - self.store(*ty, context, element_value, element_destination); - - self.push(Ins::LocalGet(index)); - self.push(Ins::I32Const(1)); - self.push(Ins::I32Add); - self.push(Ins::LocalSet(index)); - - self.push(Ins::Br(1)); - - self.push(Ins::End); - - self.push(Ins::End); - - self.pop_local(element_destination, ValType::I32); - self.pop_local(element_value, ValType::I32); - self.pop_local(index, ValType::I32); - } - - self.push(Ins::LocalGet(destination)); - self.push(Ins::LocalGet(length)); - - self.pop_local(destination, ValType::I32); - self.pop_local(length, ValType::I32); - } - TypeDefKind::Handle(handle) => { - self.marshal_handle(handle, context, value); - self.push(Ins::Call( - *IMPORTS.get("componentize-py#ToCanonHandle").unwrap(), - )); - } - TypeDefKind::Type(ty) => self.to_canon(*ty, context, value), - kind => todo!("{kind:?}"), - }, - } - } - - fn marshal_handle(&mut self, handle: &Handle, context: u32, value: u32) { - let (borrow, resource) = match handle { - Handle::Own(resource) => (0, resource), - Handle::Borrow(resource) => (1, resource), - }; - let resource = dealias(self.resolve, *resource); - let local = if let Some(Direction::Export) = self - .resource_directions - .and_then(|directions| directions.get(&resource)) - { - 1 - } else { - 0 - }; - self.push(Ins::LocalGet(context)); - self.push(Ins::LocalGet(value)); - self.push(Ins::I32Const(borrow)); - self.push(Ins::I32Const(local)); - self.push(Ins::I32Const( - self.types - .get_index_of(&resource) - .unwrap() - .try_into() - .unwrap(), - )); - } - - fn to_canon_record( - &mut self, - id: TypeId, - types: impl IntoIterator, - context: u32, - value: u32, - ) { - let type_index = self.types.get_index_of(&id).unwrap(); - for (field_index, ty) in types.into_iter().enumerate() { - let field_value = self.push_local(ValType::I32); - - self.push(Ins::LocalGet(context)); - self.push(Ins::LocalGet(value)); - self.push(Ins::I32Const(type_index.try_into().unwrap())); - self.push(Ins::I32Const(field_index.try_into().unwrap())); - self.push(Ins::Call(*IMPORTS.get("componentize-py#GetField").unwrap())); - self.push(Ins::LocalSet(field_value)); - - self.to_canon(ty, context, field_value); - - self.pop_local(field_value, ValType::I32); - } - } - - fn to_canon_variant( - &mut self, - id: TypeId, - abi: &Abi, - types: impl IntoIterator>, - context: u32, - value: u32, - ) { - // TODO: instead of storing to and then loading from memory, write directly to the primary stack (and/or - // locals) - - let destination = self.push_local(ValType::I32); - self.push_stack(abi.size); - self.get_stack(); - self.push(Ins::LocalSet(destination)); - - let types = types.into_iter().collect::>(); - - self.store_variant(id, abi, types.clone(), context, value, destination); - - self.load_copy_variant(abi, types, destination); - - self.pop_stack(abi.size); - self.pop_local(destination, ValType::I32); - } - - fn store(&mut self, ty: Type, context: u32, value: u32, destination: u32) { - match ty { - Type::Bool => { - self.push(Ins::LocalGet(destination)); - self.push(Ins::LocalGet(context)); - self.push(Ins::LocalGet(value)); - self.push(Ins::Call( - *IMPORTS.get("componentize-py#ToCanonBool").unwrap(), - )); - self.push(Ins::I32Store8(mem_arg(0, 0))); - } - Type::U8 | Type::S8 => { - self.push(Ins::LocalGet(destination)); - self.to_canon(ty, context, value); - self.push(Ins::I32Store8(mem_arg(0, 0))); - } - Type::U16 | Type::S16 => { - self.push(Ins::LocalGet(destination)); - self.to_canon(ty, context, value); - self.push(Ins::I32Store16(mem_arg(0, 1))); - } - Type::U32 | Type::S32 | Type::ErrorContext => { - self.push(Ins::LocalGet(destination)); - self.to_canon(ty, context, value); - self.push(Ins::I32Store(mem_arg(0, 2))); - } - Type::U64 | Type::S64 => { - self.push(Ins::LocalGet(destination)); - self.to_canon(ty, context, value); - self.push(Ins::I64Store(mem_arg(0, 3))); - } - Type::F32 => { - self.push(Ins::LocalGet(destination)); - self.to_canon(ty, context, value); - self.push(Ins::F32Store(mem_arg(0, 2))); - } - Type::F64 => { - self.push(Ins::LocalGet(destination)); - self.to_canon(ty, context, value); - self.push(Ins::F64Store(mem_arg(0, 3))); - } - Type::Char => { - self.push(Ins::LocalGet(destination)); - self.push(Ins::LocalGet(context)); - self.push(Ins::LocalGet(value)); - self.push(Ins::Call( - *IMPORTS.get("componentize-py#ToCanonChar").unwrap(), - )); - self.push(Ins::I32Store(mem_arg(0, 2))); - } - Type::String => { - self.push(Ins::LocalGet(context)); - self.push(Ins::LocalGet(value)); - self.push(Ins::LocalGet(destination)); - self.push(Ins::Call( - *IMPORTS.get("componentize-py#ToCanonString").unwrap(), - )); - } - Type::Id(id) => match &self.resolve.types[id].kind { - TypeDefKind::Record(record) => { - self.store_record( - id, - record.fields.iter().map(|f| f.ty), - context, - value, - destination, - ); - } - TypeDefKind::Variant(variant) => { - self.store_variant( - id, - &abi::abi(self.resolve, ty), - variant.cases.iter().map(|c| c.ty), - context, - value, - destination, - ); - } - TypeDefKind::Enum(en) => { - self.store_variant( - id, - &abi::abi(self.resolve, ty), - en.cases.iter().map(|_| None), - context, - value, - destination, - ); - } - TypeDefKind::Option(some) => { - self.store_variant( - self.get_option_type(*some), - &abi::abi(self.resolve, ty), - [None, Some(*some)], - context, - value, - destination, - ); - } - TypeDefKind::Result(result) => { - self.store_variant( - self.result_type.unwrap(), - &abi::abi(self.resolve, ty), - [result.ok, result.err], - context, - value, - destination, - ); - } - TypeDefKind::Flags(flags) => { - self.store_record(id, flags.types(), context, value, destination); - } - TypeDefKind::Tuple(tuple) => { - self.store_record( - *self.tuple_types.get(&tuple.types.len()).unwrap(), - tuple.types.iter().copied(), - context, - value, - destination, - ); - } - TypeDefKind::List(_) => { - let length = self.push_local(ValType::I32); - - self.push(Ins::LocalGet(destination)); - self.to_canon(ty, context, value); - self.push(Ins::LocalSet(length)); - self.push(Ins::I32Store(mem_arg(0, WORD_ALIGN.try_into().unwrap()))); - self.push(Ins::LocalGet(destination)); - self.push(Ins::LocalGet(length)); - self.push(Ins::I32Store(mem_arg( - WORD_SIZE.try_into().unwrap(), - WORD_ALIGN.try_into().unwrap(), - ))); - - self.pop_local(length, ValType::I32); - } - TypeDefKind::Handle(_) => { - self.push(Ins::LocalGet(destination)); - self.to_canon(ty, context, value); - self.push(Ins::I32Store(mem_arg(0, 2))); - } - TypeDefKind::Type(ty) => self.store(*ty, context, value, destination), - kind => todo!("{kind:?}"), - }, - } - } - - fn store_record( - &mut self, - id: TypeId, - types: impl IntoIterator, - context: u32, - value: u32, - destination: u32, - ) { - let type_index = self.types.get_index_of(&id).unwrap(); - let mut store_offset = 0; - for (field_index, ty) in types.into_iter().enumerate() { - let abi = abi::abi(self.resolve, ty); - store_offset = abi::align(store_offset, abi.align); - - let field_value = self.push_local(ValType::I32); - let field_destination = self.push_local(ValType::I32); - - self.push(Ins::LocalGet(context)); - self.push(Ins::LocalGet(value)); - self.push(Ins::I32Const(type_index.try_into().unwrap())); - self.push(Ins::I32Const(field_index.try_into().unwrap())); - self.push(Ins::Call(*IMPORTS.get("componentize-py#GetField").unwrap())); - self.push(Ins::LocalSet(field_value)); - - self.push(Ins::LocalGet(destination)); - self.push(Ins::I32Const(store_offset.try_into().unwrap())); - self.push(Ins::I32Add); - self.push(Ins::LocalSet(field_destination)); - - self.store(ty, context, field_value, field_destination); - - store_offset += abi.size; - - self.pop_local(field_destination, ValType::I32); - self.pop_local(field_value, ValType::I32); - } - } - - fn search_variant( - &mut self, - block_type: BlockType, - base: usize, - types: &[Option], - discriminant: u32, - predicate: impl (Fn(&Self, Option) -> bool) + Copy, - fun: impl Fn(&mut Self, Option) + Copy, - ) { - match types { - [] => unreachable!(), - [ty] => fun(self, *ty), - types => { - if types.iter().any(|ty| predicate(self, *ty)) { - let middle = types.len() / 2; - self.push(Ins::LocalGet(discriminant)); - self.push(Ins::I32Const((base + middle).try_into().unwrap())); - self.push(Ins::I32LtU); - self.push(Ins::If(block_type)); - self.search_variant( - block_type, - base, - &types[..middle], - discriminant, - predicate, - fun, - ); - self.push(Ins::Else); - self.search_variant( - block_type, - base + middle, - &types[middle..], - discriminant, - predicate, - fun, - ); - self.push(Ins::End); - } else { - fun(self, None); - } - } - } - } - - fn store_variant( - &mut self, - id: TypeId, - abi: &Abi, - types: impl IntoIterator>, - context: u32, - value: u32, - destination: u32, - ) { - let type_index = self.types.get_index_of(&id).unwrap(); - let types = types.into_iter().collect::>(); - let discriminant_size = abi::discriminant_size(types.len()); - let discriminant = self.push_local(ValType::I32); - - self.push(Ins::LocalGet(context)); - self.push(Ins::LocalGet(context)); - self.push(Ins::LocalGet(value)); - self.push(Ins::I32Const(type_index.try_into().unwrap())); - self.push(Ins::I32Const(DISCRIMINANT_FIELD_INDEX)); - self.push(Ins::Call(*IMPORTS.get("componentize-py#GetField").unwrap())); - self.push(Ins::Call( - *IMPORTS.get("componentize-py#ToCanonI32").unwrap(), - )); - self.push(Ins::LocalSet(discriminant)); - - self.push(Ins::LocalGet(destination)); - self.push(Ins::LocalGet(discriminant)); - match discriminant_size { - 1 => self.push(Ins::I32Store8(mem_arg(0, 0))), - 2 => self.push(Ins::I32Store16(mem_arg(0, 1))), - 4 => self.push(Ins::I32Store(mem_arg(0, 2))), - _ => unreachable!(), - } - - if types.iter().any(Option::is_some) { - let payload = self.push_local(ValType::I32); - let payload_destination = self.push_local(ValType::I32); - - self.push(Ins::LocalGet(context)); - self.push(Ins::LocalGet(value)); - self.push(Ins::I32Const(type_index.try_into().unwrap())); - self.push(Ins::I32Const(PAYLOAD_FIELD_INDEX)); - self.push(Ins::Call(*IMPORTS.get("componentize-py#GetField").unwrap())); - self.push(Ins::LocalSet(payload)); - - self.push(Ins::LocalGet(destination)); - self.push(Ins::I32Const( - abi::align(discriminant_size, abi.align).try_into().unwrap(), - )); - self.push(Ins::I32Add); - self.push(Ins::LocalSet(payload_destination)); - - self.search_variant( - BlockType::Empty, - 0, - &types, - discriminant, - |_, ty| ty.is_some(), - |this, ty| { - if let Some(ty) = ty { - this.store(ty, context, payload, payload_destination); - } - }, - ); - - self.pop_local(payload_destination, ValType::I32); - self.pop_local(payload, ValType::I32); - } - - self.pop_local(discriminant, ValType::I32); - } - - fn store_copy(&mut self, ty: Type, source: &[u32], destination: u32) { - match ty { - Type::Bool | Type::U8 | Type::S8 => { - self.push(Ins::LocalGet(destination)); - self.push(Ins::LocalGet(source[0])); - self.push(Ins::I32Store8(mem_arg(0, 0))); - } - Type::U16 | Type::S16 => { - self.push(Ins::LocalGet(destination)); - self.push(Ins::LocalGet(source[0])); - self.push(Ins::I32Store16(mem_arg(0, 1))); - } - Type::U32 | Type::S32 | Type::Char | Type::ErrorContext => { - self.push(Ins::LocalGet(destination)); - self.push(Ins::LocalGet(source[0])); - self.push(Ins::I32Store(mem_arg(0, 2))); - } - Type::U64 | Type::S64 => { - self.push(Ins::LocalGet(destination)); - self.push(Ins::LocalGet(source[0])); - self.push(Ins::I64Store(mem_arg(0, 3))); - } - Type::F32 => { - self.push(Ins::LocalGet(destination)); - self.push(Ins::LocalGet(source[0])); - self.push(Ins::F32Store(mem_arg(0, 2))); - } - Type::F64 => { - self.push(Ins::LocalGet(destination)); - self.push(Ins::LocalGet(source[0])); - self.push(Ins::F64Store(mem_arg(0, 3))); - } - Type::String => { - self.push(Ins::LocalGet(destination)); - self.push(Ins::LocalGet(source[0])); - self.push(Ins::I32Store(mem_arg(0, WORD_ALIGN.try_into().unwrap()))); - self.push(Ins::LocalGet(destination)); - self.push(Ins::LocalGet(source[1])); - self.push(Ins::I32Store(mem_arg( - WORD_SIZE.try_into().unwrap(), - WORD_ALIGN.try_into().unwrap(), - ))); - } - Type::Id(id) => match &self.resolve.types[id].kind { - TypeDefKind::Record(record) => { - self.store_copy_record(record.fields.iter().map(|f| f.ty), source, destination); - } - TypeDefKind::Variant(variant) => { - self.store_copy_variant( - &abi::abi(self.resolve, ty), - variant.cases.iter().map(|c| c.ty), - source, - destination, - ); - } - TypeDefKind::Enum(en) => { - self.store_copy_variant( - &abi::abi(self.resolve, ty), - en.cases.iter().map(|_| None), - source, - destination, - ); - } - TypeDefKind::Option(some) => { - self.store_copy_variant( - &abi::abi(self.resolve, ty), - [None, Some(*some)], - source, - destination, - ); - } - TypeDefKind::Result(result) => { - self.store_copy_variant( - &abi::abi(self.resolve, ty), - [result.ok, result.err], - source, - destination, - ); - } - TypeDefKind::Flags(flags) => { - self.store_copy_record(flags.types(), source, destination); - } - TypeDefKind::Tuple(tuple) => { - self.store_copy_record(tuple.types.iter().copied(), source, destination); - } - TypeDefKind::List(_) => { - self.push(Ins::LocalGet(destination)); - self.push(Ins::LocalGet(source[0])); - self.push(Ins::I32Store(mem_arg(0, WORD_ALIGN.try_into().unwrap()))); - self.push(Ins::LocalGet(destination)); - self.push(Ins::LocalGet(source[1])); - self.push(Ins::I32Store(mem_arg( - WORD_SIZE.try_into().unwrap(), - WORD_ALIGN.try_into().unwrap(), - ))); - } - TypeDefKind::Handle(_) => { - self.push(Ins::LocalGet(destination)); - self.push(Ins::LocalGet(source[0])); - self.push(Ins::I32Store(mem_arg(0, 2))); - } - TypeDefKind::Type(ty) => self.store_copy(*ty, source, destination), - kind => todo!("{kind:?}"), - }, - } - } - - fn store_copy_record( - &mut self, - types: impl IntoIterator, - source: &[u32], - destination: u32, - ) { - let mut local_index = 0; - let mut store_offset = 0; - for ty in types { - let abi = abi::abi(self.resolve, ty); - store_offset = abi::align(store_offset, abi.align); - - let field_destination = self.push_local(ValType::I32); - - self.push(Ins::LocalGet(destination)); - self.push(Ins::I32Const(store_offset.try_into().unwrap())); - self.push(Ins::I32Add); - self.push(Ins::LocalSet(field_destination)); - - self.store_copy( - ty, - &source[local_index..][..abi.flattened.len()], - field_destination, - ); - - local_index += abi.flattened.len(); - store_offset += abi.size; - - self.pop_local(field_destination, ValType::I32); - } - } - - fn convert(&mut self, source_type: ValType, destination_type: ValType) { - match (source_type, destination_type) { - (ValType::I32, ValType::I64) => self.push(Ins::I64ExtendI32U), - (ValType::I64, ValType::I32) => self.push(Ins::I32WrapI64), - (ValType::I32, ValType::F32) => self.push(Ins::F32ReinterpretI32), - (ValType::F32, ValType::I32) => self.push(Ins::I32ReinterpretF32), - (ValType::I64, ValType::F64) => self.push(Ins::F64ReinterpretI64), - (ValType::F64, ValType::I64) => self.push(Ins::I64ReinterpretF64), - (ValType::F32, ValType::I64) => { - self.push(Ins::I32ReinterpretF32); - self.push(Ins::I64ExtendI32U); - } - (ValType::I64, ValType::F32) => { - self.push(Ins::I32WrapI64); - self.push(Ins::F32ReinterpretI32); - } - _ => unreachable!("can't convert {source_type:?} to {destination_type:?}"), - } - } - - fn convert_all( - &mut self, - abi: &Abi, - payload_type: Type, - value: &[u32], - ) -> (Vec, Vec<(u32, ValType)>) { - let payload_abi = abi::abi(self.resolve, payload_type); - let mut my_value = Vec::new(); - let locals = payload_abi - .flattened - .iter() - .zip(abi.flattened.iter().skip(1)) - .zip(value) - .filter_map(|((payload_type, joined_type), value)| { - if payload_type == joined_type { - my_value.push(*value); - None - } else { - let local = self.push_local(*payload_type); - self.push(Ins::LocalGet(*value)); - self.convert(*joined_type, *payload_type); - self.push(Ins::LocalSet(local)); - my_value.push(local); - Some((local, *payload_type)) - } - }) - .collect::>(); - - (my_value, locals) - } - - fn store_copy_variant( - &mut self, - abi: &Abi, - types: impl IntoIterator>, - source: &[u32], - destination: u32, - ) { - let types = types.into_iter().collect::>(); - let discriminant_size = abi::discriminant_size(types.len()); - - self.push(Ins::LocalGet(destination)); - self.push(Ins::LocalGet(source[0])); - match discriminant_size { - 1 => self.push(Ins::I32Store8(mem_arg(0, 0))), - 2 => self.push(Ins::I32Store16(mem_arg(0, 1))), - 4 => self.push(Ins::I32Store(mem_arg(0, 2))), - _ => unreachable!(), - } - - if types.iter().any(Option::is_some) { - let payload_destination = self.push_local(ValType::I32); - - self.push(Ins::LocalGet(destination)); - self.push(Ins::I32Const( - abi::align(discriminant_size, abi.align).try_into().unwrap(), - )); - self.push(Ins::I32Add); - self.push(Ins::LocalSet(payload_destination)); - - self.search_variant( - BlockType::Empty, - 0, - &types, - source[0], - |_, ty| ty.is_some(), - |this, ty| { - if let Some(ty) = ty { - let (source, locals) = this.convert_all(abi, ty, &source[1..]); - - this.store_copy(ty, &source, payload_destination); - - for (local, ty) in locals.into_iter().rev() { - this.pop_local(local, ty); - } - } - }, - ); - - self.pop_local(payload_destination, ValType::I32); - } - } - - fn from_canon(&mut self, ty: Type, context: u32, value: &[u32]) { - match ty { - Type::Bool => { - self.push(Ins::LocalGet(context)); - self.push(Ins::LocalGet(value[0])); - self.push(Ins::Call( - *IMPORTS.get("componentize-py#FromCanonBool").unwrap(), - )); - } - Type::S8 | Type::S16 | Type::S32 => { - self.push(Ins::LocalGet(context)); - self.push(Ins::LocalGet(value[0])); - self.push(Ins::Call( - *IMPORTS.get("componentize-py#FromCanonI32").unwrap(), - )); - } - Type::U8 | Type::U16 | Type::U32 | Type::ErrorContext => { - self.push(Ins::LocalGet(context)); - self.push(Ins::LocalGet(value[0])); - self.push(Ins::Call( - *IMPORTS.get("componentize-py#FromCanonU32").unwrap(), - )); - } - Type::S64 => { - self.push(Ins::LocalGet(context)); - self.push(Ins::LocalGet(value[0])); - self.push(Ins::Call( - *IMPORTS.get("componentize-py#FromCanonI64").unwrap(), - )); - } - Type::U64 => { - self.push(Ins::LocalGet(context)); - self.push(Ins::LocalGet(value[0])); - self.push(Ins::Call( - *IMPORTS.get("componentize-py#FromCanonU64").unwrap(), - )); - } - Type::F32 => { - self.push(Ins::LocalGet(context)); - self.push(Ins::LocalGet(value[0])); - self.push(Ins::Call( - *IMPORTS.get("componentize-py#FromCanonF32").unwrap(), - )); - } - Type::F64 => { - self.push(Ins::LocalGet(context)); - self.push(Ins::LocalGet(value[0])); - self.push(Ins::Call( - *IMPORTS.get("componentize-py#FromCanonF64").unwrap(), - )); - } - Type::Char => { - self.push(Ins::LocalGet(context)); - self.push(Ins::LocalGet(value[0])); - self.push(Ins::Call( - *IMPORTS.get("componentize-py#FromCanonChar").unwrap(), - )); - } - Type::String => { - self.push(Ins::LocalGet(context)); - self.push(Ins::LocalGet(value[0])); - self.push(Ins::LocalGet(value[1])); - self.push(Ins::Call( - *IMPORTS.get("componentize-py#FromCanonString").unwrap(), - )); - } - Type::Id(id) => match &self.resolve.types[id].kind { - TypeDefKind::Record(record) => { - self.from_canon_record_onto_stack( - id, - record.fields.iter().map(|f| f.ty), - context, - value, - ); - } - TypeDefKind::Variant(variant) => { - self.from_canon_variant( - id, - &abi::abi(self.resolve, ty), - variant.cases.iter().map(|c| c.ty), - context, - value, - ); - } - TypeDefKind::Enum(en) => { - self.from_canon_variant( - id, - &abi::abi(self.resolve, ty), - en.cases.iter().map(|_| None), - context, - value, - ); - } - TypeDefKind::Option(some) => { - self.from_canon_variant( - self.get_option_type(*some), - &abi::abi(self.resolve, ty), - [None, Some(*some)], - context, - value, - ); - } - TypeDefKind::Result(result) => { - self.from_canon_variant( - self.result_type.unwrap(), - &abi::abi(self.resolve, ty), - [result.ok, result.err], - context, - value, - ); - } - TypeDefKind::Flags(flags) => { - self.from_canon_record_onto_stack( - id, - flags.types().collect::>().into_iter(), - context, - value, - ); - } - TypeDefKind::Tuple(tuple) => { - self.from_canon_record_onto_stack( - *self.tuple_types.get(&tuple.types.len()).unwrap(), - tuple.types.iter().copied(), - context, - value, - ); - } - TypeDefKind::List(ty) => { - let source = value[0]; - let length = value[1]; - - let abi = abi::abi(self.resolve, *ty); - - if let Type::U8 | Type::S8 = ty { - self.push(Ins::LocalGet(context)); - self.push(Ins::LocalGet(source)); - self.push(Ins::LocalGet(length)); - self.push(Ins::Call( - *IMPORTS.get("componentize-py#MakeBytes").unwrap(), - )); - } else { - let index = self.push_local(ValType::I32); - let element_source = self.push_local(ValType::I32); - let destination = self.push_local(ValType::I32); - - self.push(Ins::LocalGet(context)); - self.push(Ins::Call(*IMPORTS.get("componentize-py#MakeList").unwrap())); - self.push(Ins::LocalSet(destination)); - - self.push(Ins::I32Const(0)); - self.push(Ins::LocalSet(index)); - - self.push(Ins::Loop(BlockType::Empty)); - - self.push(Ins::LocalGet(index)); - self.push(Ins::LocalGet(length)); - self.push(Ins::I32Ne); - - self.push(Ins::If(BlockType::Empty)); - - self.push(Ins::LocalGet(source)); - self.push(Ins::LocalGet(index)); - self.push(Ins::I32Const(abi.size.try_into().unwrap())); - self.push(Ins::I32Mul); - self.push(Ins::I32Add); - self.push(Ins::LocalSet(element_source)); - - self.push(Ins::LocalGet(context)); - self.push(Ins::LocalGet(destination)); - - self.load(*ty, context, element_source); - - self.push(Ins::Call( - *IMPORTS.get("componentize-py#ListAppend").unwrap(), - )); - - self.push(Ins::LocalGet(index)); - self.push(Ins::I32Const(1)); - self.push(Ins::I32Add); - self.push(Ins::LocalSet(index)); - - self.push(Ins::Br(1)); - - self.push(Ins::End); - - self.push(Ins::End); - - self.push(Ins::LocalGet(destination)); - - self.pop_local(destination, ValType::I32); - self.pop_local(element_source, ValType::I32); - self.pop_local(index, ValType::I32); - } - } - TypeDefKind::Handle(handle) => { - self.marshal_handle(handle, context, value[0]); - self.push(Ins::Call( - *IMPORTS.get("componentize-py#FromCanonHandle").unwrap(), - )); - } - TypeDefKind::Type(ty) => self.from_canon(*ty, context, value), - kind => todo!("{kind:?}"), - }, - } - } - - fn from_canon_record( - &mut self, - types: impl IntoIterator, - context: u32, - source: &[u32], - destination: u32, - ) { - let mut from_canon_index = 0; - let mut store_offset = 0; - for ty in types { - let flat_count = abi::abi(self.resolve, ty).flattened.len(); - - self.push(Ins::LocalGet(destination)); - self.from_canon(ty, context, &source[from_canon_index..][..flat_count]); - self.push(Ins::I32Store(mem_arg( - store_offset, - WORD_ALIGN.try_into().unwrap(), - ))); - - from_canon_index += flat_count; - store_offset += u64::try_from(WORD_SIZE).unwrap(); - } - } - - fn from_canon_record_onto_stack( - &mut self, - id: TypeId, - types: impl ExactSizeIterator, - context: u32, - source: &[u32], - ) { - let len = types.len(); - self.push_stack(len * WORD_SIZE); - let destination = self.push_local(ValType::I32); - - self.get_stack(); - self.push(Ins::LocalSet(destination)); - - self.from_canon_record(types, context, source, destination); - - self.push(Ins::LocalGet(context)); - self.push(Ins::I32Const( - self.types.get_index_of(&id).unwrap().try_into().unwrap(), - )); - self.get_stack(); - self.push(Ins::I32Const(len.try_into().unwrap())); - self.push(Ins::Call(*IMPORTS.get("componentize-py#Init").unwrap())); - - self.pop_local(destination, ValType::I32); - self.pop_stack(len * WORD_SIZE); - } - - fn from_canon_variant( - &mut self, - id: TypeId, - abi: &Abi, - types: impl IntoIterator>, - context: u32, - source: &[u32], - ) { - self.push_stack(WORD_SIZE * 2); - - let types = types.into_iter().collect::>(); - - self.push(Ins::LocalGet(context)); - self.push(Ins::I32Const( - self.types.get_index_of(&id).unwrap().try_into().unwrap(), - )); - self.get_stack(); - self.push(Ins::I32Const(2)); - - self.get_stack(); - self.push(Ins::LocalGet(context)); - self.push(Ins::LocalGet(source[0])); - self.push(Ins::Call( - *IMPORTS.get("componentize-py#FromCanonI32").unwrap(), - )); - self.push(Ins::I32Store(mem_arg(0, WORD_ALIGN.try_into().unwrap()))); - - self.get_stack(); - self.search_variant( - BlockType::Result(ValType::I32), - 0, - &types, - source[0], - |_, ty| ty.is_some(), - |this, ty| { - if let Some(ty) = ty { - let (source, locals) = this.convert_all(abi, ty, &source[1..]); - - this.from_canon(ty, context, &source); - - for (local, ty) in locals.into_iter().rev() { - this.pop_local(local, ty); - } - } else { - this.push(Ins::LocalGet(context)); - this.push(Ins::Call(*IMPORTS.get("componentize-py#None").unwrap())); - } - }, - ); - self.push(Ins::I32Store(mem_arg( - WORD_SIZE.try_into().unwrap(), - WORD_ALIGN.try_into().unwrap(), - ))); - - self.push(Ins::Call(*IMPORTS.get("componentize-py#Init").unwrap())); - - self.pop_stack(WORD_SIZE * 2); - } - - fn load(&mut self, ty: Type, context: u32, source: u32) { - match ty { - Type::Bool | Type::U8 => { - let value = self.push_local(ValType::I32); - self.push(Ins::LocalGet(source)); - self.push(Ins::I32Load8U(mem_arg(0, 0))); - self.push(Ins::LocalSet(value)); - self.from_canon(ty, context, &[value]); - self.pop_local(value, ValType::I32); - } - Type::S8 => { - let value = self.push_local(ValType::I32); - self.push(Ins::LocalGet(source)); - self.push(Ins::I32Load8S(mem_arg(0, 0))); - self.push(Ins::LocalSet(value)); - self.from_canon(ty, context, &[value]); - self.pop_local(value, ValType::I32); - } - Type::U16 => { - let value = self.push_local(ValType::I32); - self.push(Ins::LocalGet(source)); - self.push(Ins::I32Load16U(mem_arg(0, 1))); - self.push(Ins::LocalSet(value)); - self.from_canon(ty, context, &[value]); - self.pop_local(value, ValType::I32); - } - Type::S16 => { - let value = self.push_local(ValType::I32); - self.push(Ins::LocalGet(source)); - self.push(Ins::I32Load16S(mem_arg(0, 1))); - self.push(Ins::LocalSet(value)); - self.from_canon(ty, context, &[value]); - self.pop_local(value, ValType::I32); - } - Type::U32 | Type::S32 | Type::Char | Type::ErrorContext => { - let value = self.push_local(ValType::I32); - self.push(Ins::LocalGet(source)); - self.push(Ins::I32Load(mem_arg(0, 2))); - self.push(Ins::LocalSet(value)); - self.from_canon(ty, context, &[value]); - self.pop_local(value, ValType::I32); - } - Type::U64 | Type::S64 => { - let value = self.push_local(ValType::I64); - self.push(Ins::LocalGet(source)); - self.push(Ins::I64Load(mem_arg(0, 3))); - self.push(Ins::LocalSet(value)); - self.from_canon(ty, context, &[value]); - self.pop_local(value, ValType::I64); - } - Type::F32 => { - let value = self.push_local(ValType::F32); - self.push(Ins::LocalGet(source)); - self.push(Ins::F32Load(mem_arg(0, 2))); - self.push(Ins::LocalSet(value)); - self.from_canon(ty, context, &[value]); - self.pop_local(value, ValType::F32); - } - Type::F64 => { - let value = self.push_local(ValType::F64); - self.push(Ins::LocalGet(source)); - self.push(Ins::F64Load(mem_arg(0, 3))); - self.push(Ins::LocalSet(value)); - self.from_canon(ty, context, &[value]); - self.pop_local(value, ValType::F64); - } - Type::String => { - self.push(Ins::LocalGet(context)); - self.push(Ins::LocalGet(source)); - self.push(Ins::I32Load(mem_arg(0, WORD_ALIGN.try_into().unwrap()))); - self.push(Ins::LocalGet(source)); - self.push(Ins::I32Load(mem_arg( - WORD_SIZE.try_into().unwrap(), - WORD_ALIGN.try_into().unwrap(), - ))); - self.push(Ins::Call( - *IMPORTS.get("componentize-py#FromCanonString").unwrap(), - )); - } - Type::Id(id) => match &self.resolve.types[id].kind { - TypeDefKind::Record(record) => { - self.load_record_onto_stack( - id, - record.fields.iter().map(|f| f.ty), - context, - source, - ); - } - TypeDefKind::Variant(variant) => { - self.load_variant( - id, - &abi::abi(self.resolve, ty), - variant.cases.iter().map(|c| c.ty), - context, - source, - ); - } - TypeDefKind::Enum(en) => { - self.load_variant( - id, - &abi::abi(self.resolve, ty), - en.cases.iter().map(|_| None), - context, - source, - ); - } - TypeDefKind::Option(some) => { - self.load_variant( - self.get_option_type(*some), - &abi::abi(self.resolve, ty), - [None, Some(*some)], - context, - source, - ); - } - TypeDefKind::Result(result) => { - self.load_variant( - self.result_type.unwrap(), - &abi::abi(self.resolve, ty), - [result.ok, result.err], - context, - source, - ); - } - TypeDefKind::Flags(flags) => { - self.load_record_onto_stack( - id, - flags.types().collect::>().into_iter(), - context, - source, - ); - } - TypeDefKind::Tuple(tuple) => { - self.load_record_onto_stack( - *self.tuple_types.get(&tuple.types.len()).unwrap(), - tuple.types.iter().copied(), - context, - source, - ); - } - TypeDefKind::List(_) => { - let body = self.push_local(ValType::I32); - let length = self.push_local(ValType::I32); - - self.push(Ins::LocalGet(source)); - self.push(Ins::I32Load(mem_arg(0, WORD_ALIGN.try_into().unwrap()))); - self.push(Ins::LocalSet(body)); - - self.push(Ins::LocalGet(source)); - self.push(Ins::I32Load(mem_arg( - WORD_SIZE.try_into().unwrap(), - WORD_ALIGN.try_into().unwrap(), - ))); - self.push(Ins::LocalSet(length)); - - self.from_canon(ty, context, &[body, length]); - - self.pop_local(length, ValType::I32); - self.pop_local(body, ValType::I32); - } - TypeDefKind::Handle(_) => { - let value = self.push_local(ValType::I32); - self.push(Ins::LocalGet(source)); - self.push(Ins::I32Load(mem_arg(0, 2))); - self.push(Ins::LocalSet(value)); - self.from_canon(ty, context, &[value]); - self.pop_local(value, ValType::I32); - } - TypeDefKind::Type(ty) => self.load(*ty, context, source), - kind => todo!("{kind:?}"), - }, - } - } - - fn load_record( - &mut self, - types: impl IntoIterator, - context: u32, - source: u32, - destination: u32, - ) { - let mut load_offset = 0; - let mut store_offset = 0; - for ty in types { - let field_source = self.push_local(ValType::I32); - - let abi = abi::abi(self.resolve, ty); - load_offset = abi::align(load_offset, abi.align); - - self.push(Ins::LocalGet(source)); - self.push(Ins::I32Const(load_offset.try_into().unwrap())); - self.push(Ins::I32Add); - self.push(Ins::LocalSet(field_source)); - self.push(Ins::LocalGet(destination)); - self.load(ty, context, field_source); - self.push(Ins::I32Store(mem_arg( - store_offset, - WORD_ALIGN.try_into().unwrap(), - ))); - - load_offset += abi.size; - store_offset += u64::try_from(WORD_SIZE).unwrap(); - - self.pop_local(field_source, ValType::I32); - } - } - - fn load_record_onto_stack( - &mut self, - id: TypeId, - types: impl ExactSizeIterator, - context: u32, - source: u32, - ) { - let len = types.len(); - self.push_stack(len * WORD_SIZE); - let destination = self.push_local(ValType::I32); - - self.get_stack(); - self.push(Ins::LocalSet(destination)); - - self.load_record(types, context, source, destination); - - self.push(Ins::LocalGet(context)); - self.push(Ins::I32Const( - self.types.get_index_of(&id).unwrap().try_into().unwrap(), - )); - self.get_stack(); - self.push(Ins::I32Const(len.try_into().unwrap())); - self.push(Ins::Call(*IMPORTS.get("componentize-py#Init").unwrap())); - - self.pop_local(destination, ValType::I32); - self.pop_stack(len * WORD_SIZE); - } - - fn load_variant( - &mut self, - id: TypeId, - abi: &Abi, - types: impl IntoIterator>, - context: u32, - source: u32, - ) { - self.push_stack(WORD_SIZE * 2); - - let types = types.into_iter().collect::>(); - let discriminant_size = abi::discriminant_size(types.len()); - let discriminant = self.push_local(ValType::I32); - - self.push(Ins::LocalGet(context)); - self.push(Ins::I32Const( - self.types.get_index_of(&id).unwrap().try_into().unwrap(), - )); - self.get_stack(); - self.push(Ins::I32Const(2)); - - self.get_stack(); - self.push(Ins::LocalGet(context)); - self.push(Ins::LocalGet(source)); - match discriminant_size { - 1 => self.push(Ins::I32Load8U(mem_arg(0, 0))), - 2 => self.push(Ins::I32Load16U(mem_arg(0, 1))), - 4 => self.push(Ins::I32Load(mem_arg(0, 2))), - _ => unreachable!(), - } - self.push(Ins::LocalTee(discriminant)); - self.push(Ins::Call( - *IMPORTS.get("componentize-py#FromCanonI32").unwrap(), - )); - self.push(Ins::I32Store(mem_arg(0, WORD_ALIGN.try_into().unwrap()))); - - self.get_stack(); - if types.iter().any(Option::is_some) { - let payload_source = self.push_local(ValType::I32); - - self.push(Ins::LocalGet(source)); - self.push(Ins::I32Const( - abi::align(discriminant_size, abi.align).try_into().unwrap(), - )); - self.push(Ins::I32Add); - self.push(Ins::LocalSet(payload_source)); - - self.search_variant( - BlockType::Result(ValType::I32), - 0, - &types, - discriminant, - |_, ty| ty.is_some(), - |this, ty| { - if let Some(ty) = ty { - this.load(ty, context, payload_source); - } else { - this.push(Ins::LocalGet(context)); - this.push(Ins::Call(*IMPORTS.get("componentize-py#None").unwrap())); - } - }, - ); - - self.pop_local(payload_source, ValType::I32); - } else { - self.push(Ins::LocalGet(context)); - self.push(Ins::Call(*IMPORTS.get("componentize-py#None").unwrap())); - } - self.push(Ins::I32Store(mem_arg( - WORD_SIZE.try_into().unwrap(), - WORD_ALIGN.try_into().unwrap(), - ))); - - self.push(Ins::Call(*IMPORTS.get("componentize-py#Init").unwrap())); - - self.pop_stack(WORD_SIZE * 2); - self.pop_local(discriminant, ValType::I32); - } - - fn load_copy(&mut self, ty: Type, source: u32) { - match ty { - Type::Bool | Type::U8 => { - self.push(Ins::LocalGet(source)); - self.push(Ins::I32Load8U(mem_arg(0, 0))); - } - Type::S8 => { - self.push(Ins::LocalGet(source)); - self.push(Ins::I32Load8S(mem_arg(0, 0))); - } - Type::U16 => { - self.push(Ins::LocalGet(source)); - self.push(Ins::I32Load16U(mem_arg(0, 1))); - } - Type::S16 => { - self.push(Ins::LocalGet(source)); - self.push(Ins::I32Load16S(mem_arg(0, 1))); - } - Type::U32 | Type::S32 | Type::Char | Type::ErrorContext => { - self.push(Ins::LocalGet(source)); - self.push(Ins::I32Load(mem_arg(0, 2))); - } - Type::U64 | Type::S64 => { - self.push(Ins::LocalGet(source)); - self.push(Ins::I64Load(mem_arg(0, 3))); - } - Type::F32 => { - self.push(Ins::LocalGet(source)); - self.push(Ins::F32Load(mem_arg(0, 2))); - } - Type::F64 => { - self.push(Ins::LocalGet(source)); - self.push(Ins::F64Load(mem_arg(0, 3))); - } - Type::String => { - self.push(Ins::LocalGet(source)); - self.push(Ins::I32Load(mem_arg(0, WORD_ALIGN.try_into().unwrap()))); - self.push(Ins::LocalGet(source)); - self.push(Ins::I32Load(mem_arg( - WORD_SIZE.try_into().unwrap(), - WORD_ALIGN.try_into().unwrap(), - ))); - } - Type::Id(id) => match &self.resolve.types[id].kind { - TypeDefKind::Record(record) => { - self.load_copy_record(record.fields.iter().map(|f| f.ty), source); - } - TypeDefKind::Variant(variant) => { - self.load_copy_variant( - &abi::abi(self.resolve, ty), - variant.cases.iter().map(|c| c.ty), - source, - ); - } - TypeDefKind::Enum(en) => { - self.load_copy_variant( - &abi::abi(self.resolve, ty), - en.cases.iter().map(|_| None), - source, - ); - } - TypeDefKind::Option(some) => { - self.load_copy_variant( - &abi::abi(self.resolve, ty), - [None, Some(*some)], - source, - ); - } - TypeDefKind::Result(result) => { - self.load_copy_variant( - &abi::abi(self.resolve, ty), - [result.ok, result.err], - source, - ); - } - TypeDefKind::Flags(flags) => { - self.load_copy_record(flags.types(), source); - } - TypeDefKind::Tuple(tuple) => { - self.load_copy_record(tuple.types.iter().copied(), source); - } - TypeDefKind::List(_) => { - self.push(Ins::LocalGet(source)); - self.push(Ins::I32Load(mem_arg(0, WORD_ALIGN.try_into().unwrap()))); - self.push(Ins::LocalGet(source)); - self.push(Ins::I32Load(mem_arg( - WORD_SIZE.try_into().unwrap(), - WORD_ALIGN.try_into().unwrap(), - ))); - } - TypeDefKind::Handle(_) => { - self.push(Ins::LocalGet(source)); - self.push(Ins::I32Load(mem_arg(0, 2))); - } - TypeDefKind::Type(ty) => self.load_copy(*ty, source), - kind => todo!("{kind:?}"), - }, - } - } - - fn load_copy_record(&mut self, types: impl IntoIterator, source: u32) { - let mut load_offset = 0; - for ty in types { - let field_source = self.push_local(ValType::I32); - - let abi = abi::abi(self.resolve, ty); - load_offset = abi::align(load_offset, abi.align); - - self.push(Ins::LocalGet(source)); - self.push(Ins::I32Const(load_offset.try_into().unwrap())); - self.push(Ins::I32Add); - self.push(Ins::LocalSet(field_source)); - - self.load_copy(ty, field_source); - - load_offset += abi.size; - - self.pop_local(field_source, ValType::I32); - } - } - - fn zero(&mut self, ty: ValType) { - self.push(match ty { - ValType::I32 => Ins::I32Const(0), - ValType::I64 => Ins::I64Const(0), - ValType::F32 => Ins::F32Const(0.0.into()), - ValType::F64 => Ins::F64Const(0.0.into()), - _ => unreachable!(), - }) - } - - fn load_copy_variant( - &mut self, - abi: &Abi, - types: impl IntoIterator>, - source: u32, - ) { - let types = types.into_iter().collect::>(); - let discriminant_size = abi::discriminant_size(types.len()); - - self.push(Ins::LocalGet(source)); - match discriminant_size { - 1 => self.push(Ins::I32Load8U(mem_arg(0, 0))), - 2 => self.push(Ins::I32Load16U(mem_arg(0, 1))), - 4 => self.push(Ins::I32Load(mem_arg(0, 2))), - _ => unreachable!(), - } - - if types.iter().any(Option::is_some) { - let discriminant = self.push_local(ValType::I32); - let payload_source = self.push_local(ValType::I32); - let destination = abi - .flattened - .iter() - .skip(1) - .map(|&ty| self.push_local(ty)) - .collect::>(); - - self.push(Ins::LocalTee(discriminant)); - - self.push(Ins::LocalGet(source)); - self.push(Ins::I32Const( - abi::align(discriminant_size, abi.align).try_into().unwrap(), - )); - self.push(Ins::I32Add); - self.push(Ins::LocalSet(payload_source)); - - self.search_variant( - BlockType::Empty, - 0, - &types, - discriminant, - |_, ty| ty.is_some(), - |this, ty| { - if let Some(ty) = ty { - this.load_copy(ty, payload_source); - - let payload_abi = abi::abi(this.resolve, ty); - for ((payload_type, joined_type), local) in payload_abi - .flattened - .iter() - .zip(abi.flattened.iter().skip(1)) - .zip(&destination) - .rev() - { - if payload_type != joined_type { - this.convert(*payload_type, *joined_type); - } - this.push(Ins::LocalSet(*local)); - } - - for (joined_type, local) in abi - .flattened - .iter() - .skip(1) - .zip(&destination) - .skip(payload_abi.flattened.len()) - { - this.zero(*joined_type); - this.push(Ins::LocalSet(*local)); - } - } - }, - ); - - for &local in &destination { - self.push(Ins::LocalGet(local)); - } - - for (local, ty) in destination - .into_iter() - .zip(abi.flattened.iter().skip(1)) - .rev() - { - self.pop_local(local, *ty); - } - self.pop_local(payload_source, ValType::I32); - self.pop_local(discriminant, ValType::I32); - } - } - - fn free_canon(&mut self, ty: Type, value: &[u32]) { - match ty { - Type::Bool - | Type::U8 - | Type::U16 - | Type::U32 - | Type::S8 - | Type::S16 - | Type::S32 - | Type::Char - | Type::U64 - | Type::S64 - | Type::F32 - | Type::F64 - | Type::ErrorContext => {} - - Type::String => { - self.push(Ins::LocalGet(value[0])); - self.push(Ins::LocalGet(value[1])); - self.push(Ins::I32Const(1)); - self.push(Ins::Call(*IMPORTS.get("componentize-py#Free").unwrap())); - } - - Type::Id(id) => match &self.resolve.types[id].kind { - TypeDefKind::Record(record) => { - self.free_canon_record(record.fields.iter().map(|f| f.ty), value); - } - TypeDefKind::Variant(variant) => { - self.free_canon_variant( - &abi::abi(self.resolve, ty), - variant.cases.iter().map(|c| c.ty), - value, - ); - } - TypeDefKind::Enum(en) => { - self.free_canon_variant( - &abi::abi(self.resolve, ty), - en.cases.iter().map(|_| None), - value, - ); - } - TypeDefKind::Option(some) => { - self.free_canon_variant( - &abi::abi(self.resolve, ty), - [None, Some(*some)], - value, - ); - } - TypeDefKind::Result(result) => { - self.free_canon_variant( - &abi::abi(self.resolve, ty), - [result.ok, result.err], - value, - ); - } - TypeDefKind::Flags(flags) => { - self.free_canon_record(flags.types(), value); - } - TypeDefKind::Tuple(tuple) => { - self.free_canon_record(tuple.types.iter().copied(), value); - } - TypeDefKind::List(ty) => { - let pointer = value[0]; - let length = value[1]; - - let abi = abi::abi(self.resolve, *ty); - - if abi::has_pointer(self.resolve, *ty) { - let index = self.push_local(ValType::I32); - let element_pointer = self.push_local(ValType::I32); - - self.push(Ins::I32Const(0)); - self.push(Ins::LocalSet(index)); - - self.push(Ins::Loop(BlockType::Empty)); - - self.push(Ins::LocalGet(index)); - self.push(Ins::LocalGet(length)); - self.push(Ins::I32Ne); - - self.push(Ins::If(BlockType::Empty)); - - self.push(Ins::LocalGet(pointer)); - self.push(Ins::LocalGet(index)); - self.push(Ins::I32Const(abi.size.try_into().unwrap())); - self.push(Ins::I32Mul); - self.push(Ins::I32Add); - self.push(Ins::LocalSet(element_pointer)); - - self.free_stored(*ty, element_pointer); - - self.push(Ins::LocalGet(index)); - self.push(Ins::I32Const(1)); - self.push(Ins::I32Add); - self.push(Ins::LocalSet(index)); - - self.push(Ins::Br(1)); - - self.push(Ins::End); - - self.push(Ins::End); - - self.pop_local(element_pointer, ValType::I32); - self.pop_local(index, ValType::I32); - } - - self.push(Ins::LocalGet(pointer)); - self.push(Ins::LocalGet(length)); - self.push(Ins::I32Const(abi.size.try_into().unwrap())); - self.push(Ins::I32Mul); - self.push(Ins::I32Const(abi.align.try_into().unwrap())); - self.push(Ins::Call(*IMPORTS.get("componentize-py#Free").unwrap())); - } - TypeDefKind::Handle(_) => {} - TypeDefKind::Type(ty) => self.free_canon(*ty, value), - kind => todo!("{kind:?}"), - }, - } - } - - fn free_canon_record(&mut self, types: impl IntoIterator, value: &[u32]) { - let mut from_canon_index = 0; - for ty in types { - let flat_count = abi::abi(self.resolve, ty).flattened.len(); - - self.free_canon(ty, &value[from_canon_index..][..flat_count]); - - from_canon_index += flat_count; - } - } - - fn free_canon_variant( - &mut self, - abi: &Abi, - types: impl IntoIterator>, - value: &[u32], - ) { - self.search_variant( - BlockType::Empty, - 0, - &types.into_iter().collect::>(), - value[0], - |this, ty| { - ty.map(|ty| abi::has_pointer(this.resolve, ty)) - .unwrap_or(false) - }, - |this, ty| { - if let Some(ty) = ty { - let (value, locals) = this.convert_all(abi, ty, &value[1..]); - - this.free_canon(ty, &value); - - for (local, ty) in locals.into_iter().rev() { - this.pop_local(local, ty); - } - } - }, - ) - } - - fn free_stored(&mut self, ty: Type, value: u32) { - match ty { - Type::Bool - | Type::U8 - | Type::U16 - | Type::U32 - | Type::S8 - | Type::S16 - | Type::S32 - | Type::Char - | Type::U64 - | Type::S64 - | Type::F32 - | Type::F64 - | Type::ErrorContext => {} - - Type::String => { - self.push(Ins::LocalGet(value)); - self.push(Ins::I32Load(mem_arg(0, WORD_ALIGN.try_into().unwrap()))); - self.push(Ins::LocalGet(value)); - self.push(Ins::I32Load(mem_arg( - WORD_SIZE.try_into().unwrap(), - WORD_ALIGN.try_into().unwrap(), - ))); - self.push(Ins::I32Const(1)); - self.push(Ins::Call(*IMPORTS.get("componentize-py#Free").unwrap())); - } - - Type::Id(id) => match &self.resolve.types[id].kind { - TypeDefKind::Record(record) => { - self.free_stored_record(record.fields.iter().map(|f| f.ty), value); - } - TypeDefKind::Variant(variant) => { - self.free_stored_variant( - &abi::abi(self.resolve, ty), - variant.cases.iter().map(|c| c.ty), - value, - ); - } - TypeDefKind::Enum(en) => { - self.free_stored_variant( - &abi::abi(self.resolve, ty), - en.cases.iter().map(|_| None), - value, - ); - } - TypeDefKind::Option(some) => { - self.free_stored_variant( - &abi::abi(self.resolve, ty), - [None, Some(*some)], - value, - ); - } - TypeDefKind::Result(result) => { - self.free_stored_variant( - &abi::abi(self.resolve, ty), - [result.ok, result.err], - value, - ); - } - TypeDefKind::Flags(flags) => { - self.free_stored_record(flags.types(), value); - } - TypeDefKind::Tuple(tuple) => { - self.free_stored_record(tuple.types.iter().copied(), value); - } - TypeDefKind::List(ty) => { - let abi = abi::abi(self.resolve, *ty); - - let body = self.push_local(ValType::I32); - let length = self.push_local(ValType::I32); - - self.push(Ins::LocalGet(value)); - self.push(Ins::I32Load(mem_arg(0, WORD_ALIGN.try_into().unwrap()))); - self.push(Ins::LocalSet(body)); - - self.push(Ins::LocalGet(value)); - self.push(Ins::I32Load(mem_arg( - WORD_SIZE.try_into().unwrap(), - WORD_ALIGN.try_into().unwrap(), - ))); - self.push(Ins::LocalSet(length)); - - if abi::has_pointer(self.resolve, *ty) { - let index = self.push_local(ValType::I32); - let element_value = self.push_local(ValType::I32); - - self.push(Ins::I32Const(0)); - self.push(Ins::LocalSet(index)); - - self.push(Ins::Loop(BlockType::Empty)); - - self.push(Ins::LocalGet(index)); - self.push(Ins::LocalGet(length)); - self.push(Ins::I32Ne); - - self.push(Ins::If(BlockType::Empty)); - - self.push(Ins::LocalGet(body)); - self.push(Ins::LocalGet(index)); - self.push(Ins::I32Const(abi.size.try_into().unwrap())); - self.push(Ins::I32Mul); - self.push(Ins::I32Add); - self.push(Ins::LocalSet(element_value)); - - self.free_stored(*ty, element_value); - - self.push(Ins::LocalGet(index)); - self.push(Ins::I32Const(1)); - self.push(Ins::I32Add); - self.push(Ins::LocalSet(index)); - - self.push(Ins::Br(1)); - - self.push(Ins::End); - - self.push(Ins::End); - - self.pop_local(element_value, ValType::I32); - self.pop_local(index, ValType::I32); - } - - self.push(Ins::LocalGet(body)); - self.push(Ins::LocalGet(length)); - self.push(Ins::I32Const(abi.size.try_into().unwrap())); - self.push(Ins::I32Mul); - self.push(Ins::I32Const(abi.align.try_into().unwrap())); - self.push(Ins::Call(*IMPORTS.get("componentize-py#Free").unwrap())); - - self.pop_local(length, ValType::I32); - self.pop_local(body, ValType::I32); - } - TypeDefKind::Handle(_) => {} - TypeDefKind::Type(ty) => self.free_stored(*ty, value), - kind => todo!("{kind:?}"), - }, - } - } - - fn free_stored_record(&mut self, types: impl IntoIterator, value: u32) { - let types = types.into_iter().collect::>(); - - let mut load_offset = 0; - for ty in types { - let abi = abi::abi(self.resolve, ty); - load_offset = abi::align(load_offset, abi.align); - - if abi::has_pointer(self.resolve, ty) { - let field_value = self.push_local(ValType::I32); - - self.push(Ins::LocalGet(value)); - self.push(Ins::I32Const(load_offset.try_into().unwrap())); - self.push(Ins::I32Add); - self.push(Ins::LocalSet(field_value)); - - self.free_stored(ty, field_value); - - self.pop_local(field_value, ValType::I32); - } - - load_offset += abi.size; - } - } - - fn free_stored_variant( - &mut self, - abi: &Abi, - types: impl IntoIterator>, - value: u32, - ) { - let types = types.into_iter().collect::>(); - let discriminant_size = abi::discriminant_size(types.len()); - let predicate = |this: &Self, ty: Option| { - ty.map(|ty| abi::has_pointer(this.resolve, ty)) - .unwrap_or(false) - }; - - if types.iter().any(|ty| predicate(self, *ty)) { - let discriminant = self.push_local(ValType::I32); - - self.push(Ins::LocalGet(value)); - match discriminant_size { - 1 => self.push(Ins::I32Load8U(mem_arg(0, 0))), - 2 => self.push(Ins::I32Load16U(mem_arg(0, 1))), - 4 => self.push(Ins::I32Load(mem_arg(0, 2))), - _ => unreachable!(), - } - self.push(Ins::LocalSet(discriminant)); - - let payload_value = self.push_local(ValType::I32); - - self.push(Ins::LocalGet(value)); - self.push(Ins::I32Const( - abi::align(discriminant_size, abi.align).try_into().unwrap(), - )); - self.push(Ins::I32Add); - self.push(Ins::LocalSet(payload_value)); - - self.search_variant( - BlockType::Empty, - 0, - &types, - discriminant, - predicate, - |this, ty| { - if let Some(ty) = ty { - this.free_stored(ty, payload_value); - } - }, - ); - - self.pop_local(payload_value, ValType::I32); - self.pop_local(discriminant, ValType::I32); - } - } - - fn get_option_type(&self, some: Type) -> TypeId { - if abi::is_option(self.resolve, some) { - self.nesting_option_type.unwrap() - } else { - self.option_type.unwrap() - } - } -} - -pub fn dealias(resolve: &Resolve, mut id: TypeId) -> TypeId { - loop { - match &resolve.types[id].kind { - TypeDefKind::Type(Type::Id(that_id)) => id = *that_id, - _ => break id, - } - } -} diff --git a/src/bindings.rs b/src/bindings.rs deleted file mode 100644 index 0fc0918..0000000 --- a/src/bindings.rs +++ /dev/null @@ -1,396 +0,0 @@ -use { - crate::{ - bindgen::{ - DISPATCH_CORE_PARAM_COUNT, DISPATCHABLE_CORE_PARAM_COUNT, FunctionBindgen, - IMPORT_SIGNATURES, IMPORTS, - }, - summary::{FunctionKind, Summary}, - }, - anyhow::Result, - indexmap::IndexSet, - std::borrow::Cow, - wasm_encoder::{ - CodeSection, ConstExpr, CustomSection, ElementSection, Elements, Encode, EntityType, - ExportKind, ExportSection, Function, FunctionSection, GlobalType, ImportSection, - Instruction as Ins, MemoryType, Module, RefType, TableType, TypeSection, ValType, - }, - wit_component::metadata, - wit_parser::{Resolve, WorldId}, -}; - -const WASM_DYLINK_MEM_INFO: u8 = 1; -const WASM_DYLINK_NEEDED: u8 = 2; - -struct MemInfo { - memory_size: u32, - memory_alignment: u32, - table_size: u32, - table_alignment: u32, -} - -pub fn make_bindings( - resolve: &Resolve, - worlds: &IndexSet, - summary: &Summary, -) -> Result> { - // TODO: deduplicate types - let mut types = TypeSection::new(); - let mut imports = ImportSection::new(); - let mut functions = FunctionSection::new(); - let mut exports = ExportSection::new(); - let mut code = CodeSection::new(); - let mut function_names = Vec::new(); - let mut global_names = Vec::new(); - - for (name, params, results) in IMPORT_SIGNATURES { - let offset = types.len(); - types - .ty() - .function(params.iter().copied(), results.iter().copied()); - imports.import("env", name, EntityType::Function(offset)); - function_names.push((offset, (*name).to_owned())); - } - - for function in summary.functions.iter().filter(|f| { - matches!( - f.kind, - FunctionKind::Import - | FunctionKind::ResourceNew - | FunctionKind::ResourceRep - | FunctionKind::ResourceDropLocal - | FunctionKind::ResourceDropRemote - ) - }) { - let module = &function - .interface - .as_ref() - .map(|interface| { - format!( - "{}{}", - if matches!( - function.kind, - FunctionKind::Import | FunctionKind::ResourceDropRemote - ) { - "" - } else { - "[export]" - }, - if let Some(name) = resolve.id_of(interface.id) { - name - } else { - interface.name.to_owned() - } - ) - }) - .unwrap_or_else(|| "$root".to_owned()); - - let name = function.name; - let name = &match function.kind { - FunctionKind::ResourceNew => format!("[resource-new]{name}"), - FunctionKind::ResourceRep => format!("[resource-rep]{name}"), - FunctionKind::ResourceDropLocal | FunctionKind::ResourceDropRemote => { - format!("[resource-drop]{name}") - } - _ => name.to_owned(), - }; - - let (params, results) = function.core_import_type(resolve); - let offset = types.len(); - - types.ty().function(params, results); - imports.import(module, name, EntityType::Function(offset)); - function_names.push(( - offset, - format!("{}-imported", function.internal_name(resolve)), - )); - } - - let import_function_count = imports.len(); - - let table_base = 0; - imports.import( - "env", - "__table_base", - EntityType::Global(GlobalType { - val_type: ValType::I32, - mutable: false, - shared: false, - }), - ); - global_names.push((table_base, "__table_base".to_owned())); - - let stack_pointer = 1; - imports.import( - "env", - "__stack_pointer", - EntityType::Global(GlobalType { - val_type: ValType::I32, - mutable: true, - shared: false, - }), - ); - global_names.push((stack_pointer, "__stack_pointer".to_owned())); - - imports.import( - "env", - "memory", - EntityType::Memory(MemoryType { - minimum: 0, - maximum: None, - memory64: false, - shared: false, - page_size_log2: None, - }), - ); - - imports.import( - "env", - "__indirect_function_table", - EntityType::Table(TableType { - element_type: RefType::FUNCREF, - minimum: summary - .functions - .iter() - .filter(|function| function.is_dispatchable()) - .count() - .try_into() - .unwrap(), - maximum: None, - table64: false, - shared: false, - }), - ); - - let export_set = summary - .functions - .iter() - .filter_map(|f| { - if let FunctionKind::Export = f.kind { - Some((f.interface.as_ref().map(|i| i.name), f.name)) - } else { - None - } - }) - .collect::>(); - - let mut import_index = IMPORT_SIGNATURES.len(); - let mut dispatch_index = 0; - for (index, function) in summary.functions.iter().enumerate() { - let offset = types.len(); - let (params, results) = function.core_export_type(resolve); - types.ty().function(params, results); - functions.function(offset); - function_names.push((offset, function.internal_name(resolve))); - let mut r#gen = FunctionBindgen::new(summary, function, stack_pointer); - - match function.kind { - FunctionKind::Import => { - r#gen.compile_import(import_index.try_into().unwrap()); - import_index += 1; - } - FunctionKind::Export => r#gen.compile_export( - export_set - .get_index_of(&(function.interface.as_ref().map(|i| i.name), function.name)) - .unwrap() - .try_into()?, - // The next two `dispatch_index`es should be the from_canon and to_canon functions (see ordering in - // `Summary::visit_function`): - dispatch_index, - dispatch_index + 1, - ), - FunctionKind::ExportFromCanon => r#gen.compile_export_from_canon(), - FunctionKind::ExportToCanon => r#gen.compile_export_to_canon(), - FunctionKind::ExportPostReturn => r#gen.compile_export_post_return(), - FunctionKind::ResourceNew => { - r#gen.compile_resource_new(import_index.try_into().unwrap()); - import_index += 1; - } - FunctionKind::ResourceRep => { - r#gen.compile_resource_rep(import_index.try_into().unwrap()); - import_index += 1; - } - FunctionKind::ResourceDropLocal | FunctionKind::ResourceDropRemote => { - r#gen.compile_resource_drop(import_index.try_into().unwrap()); - import_index += 1; - } - }; - - let mut func = Function::new_with_locals_types(r#gen.local_types); - for instruction in &r#gen.instructions { - func.instruction(instruction); - } - func.instruction(&Ins::End); - code.function(&func); - - if function.is_dispatchable() { - dispatch_index += 1; - } - - match function.kind { - FunctionKind::Export | FunctionKind::ExportPostReturn => { - exports.export( - &format!( - "{}{}", - if let FunctionKind::ExportPostReturn = function.kind { - "cabi_post_" - } else { - "" - }, - if let Some(interface) = &function.interface { - format!( - "{}#{}", - if let Some(name) = resolve.id_of(interface.id) { - name - } else { - interface.name.to_owned() - }, - function.name - ) - } else { - function.name.to_owned() - } - ), - ExportKind::Func, - import_function_count + u32::try_from(index).unwrap(), - ); - } - - _ => (), - } - } - - { - let dispatch_offset = types.len(); - types - .ty() - .function([ValType::I32; DISPATCH_CORE_PARAM_COUNT], []); - let dispatchable_offset = types.len(); - types - .ty() - .function([ValType::I32; DISPATCHABLE_CORE_PARAM_COUNT], []); - functions.function(dispatch_offset); - let name = "componentize-py#CallIndirect"; - function_names.push((dispatch_offset, name.to_owned())); - let mut dispatch = Function::new([]); - - for local in 0..DISPATCH_CORE_PARAM_COUNT { - dispatch.instruction(&Ins::LocalGet(u32::try_from(local).unwrap())); - } - dispatch.instruction(&Ins::GlobalGet(table_base)); - dispatch.instruction(&Ins::I32Add); - dispatch.instruction(&Ins::CallIndirect { - type_index: dispatchable_offset, - table_index: 0, - }); - dispatch.instruction(&Ins::End); - - code.function(&dispatch); - - exports.export(name, ExportKind::Func, dispatch_offset); - } - - exports.export( - "cabi_import_realloc", - ExportKind::Func, - *IMPORTS.get("cabi_realloc").unwrap(), - ); - - exports.export( - "cabi_export_realloc", - ExportKind::Func, - *IMPORTS.get("cabi_realloc").unwrap(), - ); - - let mut elements = ElementSection::new(); - elements.active( - Some(0), - &ConstExpr::global_get(table_base), - Elements::Functions( - summary - .functions - .iter() - .enumerate() - .filter_map(|(index, function)| { - function - .is_dispatchable() - .then_some(import_function_count + u32::try_from(index).unwrap()) - }) - .collect::>() - .into(), - ), - ); - - let mut names_data = Vec::new(); - for (code, names) in [(0x01_u8, &function_names), (0x07_u8, &global_names)] { - let mut subsection = Vec::new(); - names.len().encode(&mut subsection); - for (index, name) in names { - index.encode(&mut subsection); - name.encode(&mut subsection); - } - names_data.push(code); - subsection.encode(&mut names_data); - } - - let mem_info = MemInfo { - memory_size: 0, - memory_alignment: 0, - table_size: summary - .functions - .iter() - .filter(|function| function.is_dispatchable()) - .count() - .try_into() - .unwrap(), - table_alignment: 0, - }; - - let mut mem_info_subsection = Vec::new(); - mem_info.memory_size.encode(&mut mem_info_subsection); - mem_info.memory_alignment.encode(&mut mem_info_subsection); - mem_info.table_size.encode(&mut mem_info_subsection); - mem_info.table_alignment.encode(&mut mem_info_subsection); - - let mut needed_subsection = Vec::new(); - 1_u32.encode(&mut needed_subsection); - "libcomponentize_py_runtime.so".encode(&mut needed_subsection); - - let mut dylink0 = Vec::new(); - dylink0.push(WASM_DYLINK_MEM_INFO); - mem_info_subsection.encode(&mut dylink0); - dylink0.push(WASM_DYLINK_NEEDED); - needed_subsection.encode(&mut dylink0); - - let mut result = Module::new(); - result.section(&CustomSection { - name: Cow::Borrowed("dylink.0"), - data: Cow::Borrowed(&dylink0), - }); - result.section(&types); - result.section(&imports); - result.section(&functions); - result.section(&exports); - result.section(&elements); - result.section(&code); - result.section(&CustomSection { - name: Cow::Borrowed("name"), - data: Cow::Borrowed(&names_data), - }); - for &world in worlds { - result.section(&CustomSection { - name: Cow::Owned(format!("component-type:{}", resolve.worlds[world].name)), - data: Cow::Owned(metadata::encode( - resolve, - world, - wit_component::StringEncoding::UTF8, - None, - )?), - }); - } - - let result = result.finish(); - - wasmparser::validate(&result)?; - - Ok(result) -} diff --git a/src/lib.rs b/src/lib.rs index 46f97a2..babbf8f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,6 +10,7 @@ use { indexmap::{IndexMap, IndexSet}, serde::Deserialize, std::{ + borrow::Cow, collections::HashMap, fs, iter, ops::Deref, @@ -17,6 +18,7 @@ use { str, }, summary::{Escape, Locations, Summary}, + wasm_encoder::{CustomSection, Section as _}, wasmtime::{ Config, Engine, Store, component::{Component, Instance, Linker, ResourceTable, ResourceType}, @@ -25,12 +27,14 @@ use { DirPerms, FilePerms, WasiCtx, WasiCtxBuilder, WasiCtxView, WasiView, p2::pipe::{MemoryInputPipe, MemoryOutputPipe}, }, - wit_parser::{Resolve, TypeDefKind, UnresolvedPackageGroup, WorldId, WorldItem, WorldKey}, + wit_component::metadata, + wit_dylib::DylibOpts, + wit_parser::{ + CloneMaps, Package, PackageName, Resolve, Stability, TypeDefKind, UnresolvedPackageGroup, + World, WorldId, WorldItem, WorldKey, + }, }; -mod abi; -mod bindgen; -mod bindings; pub mod command; mod link; mod prelink; @@ -50,7 +54,7 @@ static DEFAULT_WORLD_MODULE: &str = "wit_world"; wasmtime::component::bindgen!({ path: "wit", world: "init", - exports: { default: async } + exports: { default: async }, }); pub struct Ctx { @@ -189,11 +193,13 @@ pub fn generate_bindings( // `componentize` function below, since that can affect the bindings we should be generating. let (resolve, world) = parse_wit(wit_path, world, features, all_features)?; + let import_function_indexes = &HashMap::new(); let summary = Summary::try_new( &resolve, &iter::once(world).collect(), import_interface_names, export_interface_names, + import_function_indexes, )?; let world_module = world_module.unwrap_or(DEFAULT_WORLD_MODULE); let world_dir = output_dir.join(world_module.replace('.', "/")); @@ -296,7 +302,7 @@ pub async fn componentize( }) .collect::>>()?; - let resolve = if let Some(resolve) = resolve { + let mut resolve = if let Some(resolve) = resolve { resolve } else { // If no WIT directory was provided as a parameter and none were referenced by Python packages, use @@ -304,7 +310,7 @@ pub async fn componentize( let paths: &[&Path] = &[]; let (my_resolve, world) = parse_wit(paths, world, features, all_features).context( "no WIT files found; please specify the directory or file \ - containing the WIT world you wish to target", + containing the WIT world you wish to target", )?; main_world = Some(world); my_resolve @@ -328,16 +334,75 @@ pub async fn componentize( ); } + let union_package = resolve.packages.alloc(Package { + name: PackageName { + namespace: "componentize-py".into(), + name: "union".into(), + version: None, + }, + docs: Default::default(), + interfaces: Default::default(), + worlds: Default::default(), + }); + + let union_world = resolve.worlds.alloc(World { + name: "union".into(), + imports: Default::default(), + exports: Default::default(), + package: Some(union_package), + docs: Default::default(), + stability: Stability::Unknown, + includes: Default::default(), + include_names: Default::default(), + }); + + resolve.packages[union_package] + .worlds + .insert("union".into(), union_world); + + let mut clone_maps = CloneMaps::default(); + for &world in &worlds { + resolve.merge_worlds(world, union_world, &mut clone_maps)?; + } + + let (mut bindings, metadata) = wit_dylib::create_with_metadata( + &resolve, + union_world, + Some(&mut DylibOpts { + interpreter: Some("libcomponentize_py_runtime.so".into()), + async_: Default::default(), + }), + ); + + CustomSection { + name: Cow::Borrowed("component-type:componentize-py-union"), + data: Cow::Owned(metadata::encode( + &resolve, + union_world, + wit_component::StringEncoding::UTF8, + None, + )?), + } + .append_to(&mut bindings); + + let imported_function_indexes = metadata + .import_funcs + .iter() + .enumerate() + .map(|(index, func)| ((func.interface.as_deref(), func.name.as_str()), index)) + .collect::>(); + let summary = Summary::try_new( &resolve, &worlds, &import_interface_names, &export_interface_names, + &imported_function_indexes, )?; libraries.push(Library { name: "libcomponentize_py_bindings.so".into(), - module: bindings::make_bindings(&resolve, &worlds, &summary)?, + module: bindings, dl_openable: false, }); @@ -362,7 +427,6 @@ pub async fn componentize( .stdout(stdout.clone()) .stderr(stderr.clone()) .env("PYTHONUNBUFFERED", "1") - .env("COMPONENTIZE_PY_APP_NAME", app_name) .env("PYTHONHOME", "/python") .preopened_dir( embedded_python_standard_lib.path(), @@ -479,7 +543,7 @@ pub async fn componentize( // Generate a `Symbols` object containing metadata to be passed to the pre-init function. The runtime library // will use this to look up types and functions that will later be referenced by the generated Wasm code. - let symbols = summary.collect_symbols(&locations); + let symbols = summary.collect_symbols(&locations, &metadata, &clone_maps); // Finally, pre-initialize the component, writing the result to `output_path`. diff --git a/src/summary.rs b/src/summary.rs index dcf6ff8..9aba42b 100644 --- a/src/summary.rs +++ b/src/summary.rs @@ -1,17 +1,14 @@ use { crate::{ - abi::{self, MAX_FLAT_PARAMS, MAX_FLAT_RESULTS}, - bindgen::{self, DISPATCHABLE_CORE_PARAM_COUNT}, exports::exports::{ - self, Case, Constructor, Function, FunctionExport, LocalResource, OwnedKind, OwnedType, - RemoteResource, Resource, Static, Symbols, + self, Case, Constructor, Function, FunctionExport, FunctionExportKind, ReturnStyle, + Static, Symbols, }, util::Types as _, }, anyhow::{Result, bail}, heck::{ToShoutySnakeCase, ToSnakeCase, ToUpperCamelCase}, indexmap::{IndexMap, IndexSet}, - once_cell::sync, semver::Version, std::{ collections::{HashMap, HashSet, hash_map::Entry}, @@ -19,14 +16,13 @@ use { fs::{self, File}, io::Write as _, iter, - ops::Deref, path::Path, str, }, - wasm_encoder::ValType, + wit_dylib::Metadata, wit_parser::{ - Handle, InterfaceId, Resolve, Result_, Type, TypeDefKind, TypeId, TypeOwner, WorldId, - WorldItem, WorldKey, + CloneMaps, Handle, InterfaceId, Resolve, Result_, Type, TypeDef, TypeDefKind, TypeId, + TypeOwner, WorldId, WorldItem, WorldKey, }, }; @@ -40,27 +36,19 @@ pub enum Direction { #[derive(Default, Copy, Clone)] struct ResourceInfo { - local_dispatch_index: Option, - remote_dispatch_index: Option, + local: bool, + remote: bool, } #[derive(Clone)] -struct ResourceState<'a> { +struct ResourceState { direction: Direction, - interface: Option>, } #[derive(Copy, Clone)] pub enum FunctionKind { Import, - ResourceNew, - ResourceRep, - ResourceDropLocal, - ResourceDropRemote, Export, - ExportFromCanon, - ExportToCanon, - ExportPostReturn, } #[derive(Copy, Clone)] @@ -73,9 +61,8 @@ pub struct PackageName<'a> { #[derive(Clone)] pub struct MyInterface<'a> { pub id: InterfaceId, - pub name: &'a str, + pub key: &'a WorldKey, pub docs: Option<&'a str>, - pub resource_directions: im_rc::HashMap, } pub struct MyFunction<'a> { @@ -96,80 +83,6 @@ impl MyFunction<'_> { WorldKey::Name(self.name.into()) } } - - pub fn internal_name(&self, resolve: &Resolve) -> String { - if let Some(interface) = &self.interface { - format!( - "{}#{}{}", - if let Some(name) = resolve.id_of(interface.id) { - name - } else { - interface.name.to_owned() - }, - self.name, - match self.kind { - FunctionKind::Import => "-import", - FunctionKind::ResourceNew => "-resource-new", - FunctionKind::ResourceRep => "-resource-rep", - FunctionKind::ResourceDropLocal => "-resource-drop-local", - FunctionKind::ResourceDropRemote => "-resource-drop-remote", - FunctionKind::Export => "-export", - FunctionKind::ExportFromCanon => "-from-canon", - FunctionKind::ExportToCanon => "-to-canon", - FunctionKind::ExportPostReturn => "-post-return", - } - ) - } else { - self.name.to_owned() - } - } - - pub fn core_import_type(&self, resolve: &Resolve) -> (Vec, Vec) { - let mut params = - abi::record_abi_limit(resolve, self.params.types(), MAX_FLAT_PARAMS).flattened; - - let mut results = abi::record_abi(resolve, self.result.types()).flattened; - - if results.len() > MAX_FLAT_RESULTS { - params.push(ValType::I32); - results = Vec::new(); - }; - - (params, results) - } - - pub fn core_export_type(&self, resolve: &Resolve) -> (Vec, Vec) { - match self.kind { - FunctionKind::Export => ( - abi::record_abi_limit(resolve, self.params.types(), MAX_FLAT_PARAMS).flattened, - abi::record_abi_limit(resolve, self.result.types(), MAX_FLAT_RESULTS).flattened, - ), - FunctionKind::Import - | FunctionKind::ResourceNew - | FunctionKind::ResourceRep - | FunctionKind::ResourceDropLocal - | FunctionKind::ResourceDropRemote - | FunctionKind::ExportFromCanon - | FunctionKind::ExportToCanon => ( - vec![ValType::I32; DISPATCHABLE_CORE_PARAM_COUNT], - Vec::new(), - ), - FunctionKind::ExportPostReturn => (vec![ValType::I32], Vec::new()), - } - } - - pub fn is_dispatchable(&self) -> bool { - match self.kind { - FunctionKind::Import - | FunctionKind::ResourceNew - | FunctionKind::ResourceRep - | FunctionKind::ResourceDropLocal - | FunctionKind::ResourceDropRemote - | FunctionKind::ExportFromCanon - | FunctionKind::ExportToCanon => true, - FunctionKind::Export | FunctionKind::ExportPostReturn => false, - } - } } #[derive(Copy, Clone)] @@ -221,14 +134,14 @@ pub struct Summary<'a> { pub option_type: Option, pub nesting_option_type: Option, pub result_type: Option, - resource_state: Option>, + resource_state: Option, resource_directions: im_rc::HashMap, resource_info: HashMap, - dispatch_count: usize, world_types: HashMap>, world_keys: HashMap>, imported_interface_names: HashMap, exported_interface_names: HashMap, + imported_function_indexes: &'a HashMap<(Option<&'a str>, &'a str), usize>, } impl<'a> Summary<'a> { @@ -237,6 +150,7 @@ impl<'a> Summary<'a> { worlds: &IndexSet, import_interface_names: &HashMap<&str, &str>, export_interface_names: &HashMap<&str, &str>, + imported_function_indexes: &'a HashMap<(Option<&'a str>, &'a str), usize>, ) -> Result { let mut me = Self { resolve, @@ -251,11 +165,11 @@ impl<'a> Summary<'a> { resource_state: None, resource_directions: im_rc::HashMap::new(), resource_info: HashMap::new(), - dispatch_count: 0, world_types: HashMap::new(), world_keys: HashMap::new(), imported_interface_names: HashMap::new(), exported_interface_names: HashMap::new(), + imported_function_indexes, }; let mut import_keys_seen = HashSet::new(); @@ -290,9 +204,6 @@ impl<'a> Summary<'a> { } fn push_function(&mut self, function: MyFunction<'a>) { - if function.is_dispatchable() { - self.dispatch_count += 1; - } self.functions.push(function); } @@ -335,7 +246,7 @@ impl<'a> Summary<'a> { self.types.insert(id); } TypeDefKind::Option(some) => { - if abi::is_option(self.resolve, *some) { + if is_option(self.resolve, *some) { if self.nesting_option_type.is_none() { self.nesting_option_type = Some(id); } @@ -381,73 +292,13 @@ impl<'a> Summary<'a> { self.resource_directions.insert(id, state.direction); let info = self.resource_info.entry(id).or_default(); - let make = |kind, params, result| MyFunction { - kind, - interface: state.interface.clone(), - name: ty.name.as_deref().unwrap(), - docs: None, - params, - result, - wit_kind: wit_parser::FunctionKind::Freestanding, - }; - match state.direction { Direction::Import => { - info.remote_dispatch_index = Some(self.dispatch_count); - - static DROP_PARAMS: sync::Lazy<[(String, Type); 1]> = - sync::Lazy::new(|| [("handle".to_string(), Type::U32)]); - - static DROP_RESULTS: sync::Lazy> = - sync::Lazy::new(|| None); - - self.push_function(make( - FunctionKind::ResourceDropRemote, - DROP_PARAMS.deref(), - &DROP_RESULTS, - )); + info.remote = true; } Direction::Export => { - info.local_dispatch_index = Some(self.dispatch_count); - - // The order these functions are added must match the `LocalResource` field - // initialization order in `summarize_type`. - // TODO: make this less fragile. - - static NEW_PARAMS: sync::Lazy<[(String, Type); 1]> = - sync::Lazy::new(|| [("rep".to_string(), Type::U32)]); - - static NEW_RESULTS: Option = Some(Type::U32); - - self.push_function(make( - FunctionKind::ResourceNew, - NEW_PARAMS.deref(), - &NEW_RESULTS, - )); - - static REP_PARAMS: sync::Lazy<[(String, Type); 1]> = - sync::Lazy::new(|| [("handle".to_string(), Type::U32)]); - - static REP_RESULTS: Option = Some(Type::U32); - - self.push_function(make( - FunctionKind::ResourceRep, - REP_PARAMS.deref(), - &REP_RESULTS, - )); - - static DROP_PARAMS: sync::Lazy<[(String, Type); 1]> = - sync::Lazy::new(|| [("handle".to_string(), Type::U32)]); - - static DROP_RESULTS: sync::Lazy> = - sync::Lazy::new(|| None); - - self.push_function(make( - FunctionKind::ResourceDropLocal, - DROP_PARAMS.deref(), - DROP_RESULTS.deref(), - )); + info.local = true; } } } @@ -494,22 +345,7 @@ impl<'a> Summary<'a> { self.push_function(make(FunctionKind::Import)); } Direction::Export => { - // NB: We rely on this order when compiling, so please don't change it: - // todo: make this less fragile self.push_function(make(FunctionKind::Export)); - self.push_function(make(FunctionKind::ExportFromCanon)); - self.push_function(make(FunctionKind::ExportToCanon)); - if abi::record_abi(self.resolve, result.types()) - .flattened - .len() - > MAX_FLAT_RESULTS - { - self.push_function(make(FunctionKind::ExportPostReturn)); - } else { - // As of this writing, no type involving heap allocation can fit into `MAX_FLAT_RESULTS`, so - // nothing to do. We'll need to revisit this if `MAX_FLAT_RESULTS` changes or if new types are - // added. - } } } } @@ -567,15 +403,7 @@ impl<'a> Summary<'a> { docs: interface.docs.contents.as_deref(), }; - self.resource_state = Some(ResourceState { - direction, - interface: Some(MyInterface { - name: item_name, - id: *id, - docs: interface.docs.contents.as_deref(), - resource_directions: Default::default(), - }), - }); + self.resource_state = Some(ResourceState { direction }); for id in interface.types.values() { self.visit_type(Type::Id(*id), world); } @@ -588,10 +416,9 @@ impl<'a> Summary<'a> { for (func_name, func) in &interface.functions { self.visit_function( Some(MyInterface { - name: item_name, id: *id, + key, docs: interface.docs.contents.as_deref(), - resource_directions: self.resource_directions.clone(), }), func_name, func.docs.contents.as_deref(), @@ -623,91 +450,178 @@ impl<'a> Summary<'a> { Ok(()) } - fn summarize_type(&self, id: TypeId, world_module: &str) -> exports::Type { - let ty = &self.resolve.types[id]; - if let Some(package) = self.package(ty.owner, world_module) { + fn package_and_name( + &self, + id: TypeId, + ty: &TypeDef, + world_module: &str, + reverse_cloned_interfaces: &HashMap, + ) -> Option<(String, String)> { + if let Some(package) = self.package(ty.owner, world_module, reverse_cloned_interfaces) { let name = if let Some(name) = &ty.name { name.to_upper_camel_case().escape() } else { format!("AnonymousType{}", self.types.get_index_of(&id).unwrap()) }; - let kind = match &ty.kind { - TypeDefKind::Record(record) => OwnedKind::Record( - record - .fields - .iter() - .map(|f| f.name.to_snake_case().escape()) - .collect(), - ), - TypeDefKind::Variant(variant) => OwnedKind::Variant( - variant - .cases - .iter() - .map(|c| Case { - name: format!("{name}_{}", c.name.to_upper_camel_case().escape()), - has_payload: c.ty.is_some(), - }) - .collect(), - ), - TypeDefKind::Enum(en) => OwnedKind::Enum(en.cases.len().try_into().unwrap()), - TypeDefKind::Flags(flags) => { - OwnedKind::Flags(flags.repr().count().try_into().unwrap()) - } - TypeDefKind::Tuple(_) | TypeDefKind::Option(_) | TypeDefKind::Result(_) => { - return self.summarize_unowned_type(id); - } - TypeDefKind::Resource => { - let info = &self.resource_info[&id]; - OwnedKind::Resource(Resource { - local: info - .local_dispatch_index - .map(|dispatch_index| LocalResource { - // This must match the order the functions are added in `visit_type`: - new: u32::try_from(dispatch_index).unwrap(), - rep: u32::try_from(dispatch_index + 1).unwrap(), - drop: u32::try_from(dispatch_index + 2).unwrap(), - }), - remote: info - .remote_dispatch_index - .map(|dispatch_index| RemoteResource { - drop: u32::try_from(dispatch_index).unwrap(), - }), - }) - } - kind => todo!("{kind:?}"), - }; - exports::Type::Owned(OwnedType { - package, - name, - kind, - }) + Some((package, name)) } else { - self.summarize_unowned_type(id) + None } } - fn summarize_unowned_type(&self, id: TypeId) -> exports::Type { + fn summarize_resource( + &self, + id: TypeId, + world_module: &str, + reverse_cloned_interfaces: &HashMap, + ) -> exports::Resource { let ty = &self.resolve.types[id]; - match &ty.kind { - TypeDefKind::Tuple(tuple) => { - exports::Type::Tuple(tuple.types.len().try_into().unwrap()) - } - TypeDefKind::Option(some) => { - if abi::is_option(self.resolve, *some) { - exports::Type::NestingOption - } else { - exports::Type::Option - } - } - TypeDefKind::Result(_) => exports::Type::Result, - TypeDefKind::Handle(_) => exports::Type::Handle, - kind => todo!("{kind:?}"), + assert!(matches!(ty.kind, TypeDefKind::Resource)); + let (package, name) = self + .package_and_name(id, ty, world_module, reverse_cloned_interfaces) + .unwrap(); + + exports::Resource { package, name } + } + + fn summarize_record( + &self, + id: TypeId, + world_module: &str, + reverse_cloned_interfaces: &HashMap, + ) -> exports::Record { + let ty = &self.resolve.types[id]; + let TypeDefKind::Record(record) = &ty.kind else { + unreachable!() + }; + let (package, name) = self + .package_and_name(id, ty, world_module, reverse_cloned_interfaces) + .unwrap(); + + exports::Record { + package, + name, + fields: record + .fields + .iter() + .map(|f| f.name.to_snake_case().escape()) + .collect(), + } + } + + fn summarize_flags( + &self, + id: TypeId, + world_module: &str, + reverse_cloned_interfaces: &HashMap, + ) -> exports::Flags { + let ty = &self.resolve.types[id]; + let TypeDefKind::Flags(flags) = &ty.kind else { + unreachable!() + }; + let (package, name) = self + .package_and_name(id, ty, world_module, reverse_cloned_interfaces) + .unwrap(); + + exports::Flags { + package, + name, + u32_count: flags.repr().count().try_into().unwrap(), + } + } + + fn summarize_variant( + &self, + id: TypeId, + world_module: &str, + reverse_cloned_interfaces: &HashMap, + ) -> exports::Variant { + let ty = &self.resolve.types[id]; + let TypeDefKind::Variant(variant) = &ty.kind else { + unreachable!() + }; + let (package, name) = self + .package_and_name(id, ty, world_module, reverse_cloned_interfaces) + .unwrap(); + + let cases = variant + .cases + .iter() + .map(|c| Case { + name: format!("{name}_{}", c.name.to_upper_camel_case().escape()), + has_payload: c.ty.is_some(), + }) + .collect(); + + exports::Variant { + package, + name, + cases, + } + } + + fn summarize_enum( + &self, + id: TypeId, + world_module: &str, + reverse_cloned_interfaces: &HashMap, + ) -> exports::Enum { + let ty = &self.resolve.types[id]; + let TypeDefKind::Enum(enum_) = &ty.kind else { + unreachable!() + }; + let (package, name) = self + .package_and_name(id, ty, world_module, reverse_cloned_interfaces) + .unwrap(); + + exports::Enum { + package, + name, + count: enum_.cases.len().try_into().unwrap(), + } + } + + fn summarize_tuple(&self, id: TypeId) -> exports::Tuple { + let TypeDefKind::Tuple(tuple) = &self.resolve.types[id].kind else { + unreachable!() + }; + + exports::Tuple { + count: tuple.types.len().try_into().unwrap(), + } + } + + fn summarize_option(&self, id: TypeId) -> exports::OptionKind { + let TypeDefKind::Option(some) = &self.resolve.types[id].kind else { + unreachable!() + }; + + if is_option(self.resolve, *some) { + exports::OptionKind::Nesting + } else { + exports::OptionKind::NonNesting } } - pub fn collect_symbols(&self, locations: &Locations) -> Symbols { - let mut exports = Vec::new(); + fn summarize_result(&self, id: TypeId) -> exports::ResultRecord { + let TypeDefKind::Result(result) = &self.resolve.types[id].kind else { + unreachable!() + }; + + exports::ResultRecord { + has_ok: result.ok.is_some(), + has_err: result.err.is_some(), + } + } + + pub fn collect_symbols( + &self, + locations: &Locations, + metadata: &Metadata, + clone_maps: &CloneMaps, + ) -> Symbols { + let mut map = HashMap::new(); for function in &self.functions { if let FunctionKind::Export = function.kind { let scope = if let Some(interface) = &function.interface { @@ -716,54 +630,182 @@ impl<'a> Summary<'a> { locations.keys.get(&function.key()).unwrap() }; - exports.push(match function.wit_kind { - wit_parser::FunctionKind::Freestanding => { - FunctionExport::Freestanding(Function { - protocol: scope.to_upper_camel_case().escape(), - name: self.function_name(function), - }) - } - wit_parser::FunctionKind::Constructor(id) => { - FunctionExport::Constructor(Constructor { - module: scope.to_snake_case().escape(), - protocol: self.resolve.types[id] - .name - .as_deref() - .unwrap() - .to_upper_camel_case() - .escape(), - }) - } - wit_parser::FunctionKind::Method(_) => { - FunctionExport::Method(self.function_name(function)) - } - wit_parser::FunctionKind::Static(id) => FunctionExport::Static(Static { - module: scope.to_snake_case().escape(), - protocol: self.resolve.types[id] - .name - .as_deref() - .unwrap() - .to_upper_camel_case() - .escape(), - name: self.function_name(function), - }), - _ => todo!("handle async functions"), - }); + map.insert( + ( + function + .interface + .as_ref() + .map(|v| self.resolve.name_world_key(v.key)), + function.name, + ), + FunctionExport { + kind: match function.wit_kind { + wit_parser::FunctionKind::Freestanding => { + FunctionExportKind::Freestanding(Function { + protocol: scope.to_upper_camel_case().escape(), + name: self.function_name(function), + }) + } + wit_parser::FunctionKind::Constructor(id) => { + FunctionExportKind::Constructor(Constructor { + module: scope.to_snake_case().escape(), + protocol: self.resolve.types[id] + .name + .as_deref() + .unwrap() + .to_upper_camel_case() + .escape(), + }) + } + wit_parser::FunctionKind::Method(_) => { + FunctionExportKind::Method(self.function_name(function)) + } + wit_parser::FunctionKind::Static(id) => { + FunctionExportKind::Static(Static { + module: scope.to_snake_case().escape(), + protocol: self.resolve.types[id] + .name + .as_deref() + .unwrap() + .to_upper_camel_case() + .escape(), + name: self.function_name(function), + }) + } + _ => todo!("handle async functions"), + }, + return_style: match function.result { + None => ReturnStyle::None, + &Some(Type::Id(id)) + if matches!( + &self.resolve.types[id].kind, + TypeDefKind::Result(_) + ) => + { + ReturnStyle::Result + } + _ => ReturnStyle::Normal, + }, + }, + ); } } - let mut types = Vec::new(); - for ty in &self.types { - types.push(self.summarize_type(*ty, &locations.types.get(ty).unwrap().module)); + let exports = metadata + .export_funcs + .iter() + .map(|function| { + map.remove(&(function.interface.clone(), &function.name)) + .unwrap() + }) + .collect(); + + assert!(map.is_empty()); + + let mut reverse_cloned_types = HashMap::new(); + for (&original, &clone) in clone_maps.types() { + assert!(reverse_cloned_types.insert(clone, original).is_none()); + } + + let original = |ty| { + if let Some(&original) = reverse_cloned_types.get(&ty) { + original + } else { + ty + } + }; + + let module = |ty| &locations.types.get(&ty).unwrap().module; + + let mut reverse_cloned_interfaces = HashMap::new(); + for (&original, &clone) in clone_maps.interfaces() { + assert!(reverse_cloned_interfaces.insert(clone, original).is_none()); } + let resources = metadata + .resources + .iter() + .map(|ty| original(ty.id)) + .map(|ty| self.summarize_resource(ty, module(ty), &reverse_cloned_interfaces)) + .collect(); + + let records = metadata + .records + .iter() + .map(|ty| original(ty.id)) + .map(|ty| self.summarize_record(ty, module(ty), &reverse_cloned_interfaces)) + .collect(); + + let flags = metadata + .flags + .iter() + .map(|ty| original(ty.id)) + .map(|ty| self.summarize_flags(ty, module(ty), &reverse_cloned_interfaces)) + .collect(); + + let tuples = metadata + .tuples + .iter() + .map(|ty| original(ty.id)) + .map(|ty| self.summarize_tuple(ty)) + .collect(); + + let variants = metadata + .variants + .iter() + .map(|ty| original(ty.id)) + .map(|ty| self.summarize_variant(ty, module(ty), &reverse_cloned_interfaces)) + .collect(); + + let enums = metadata + .enums + .iter() + .map(|ty| original(ty.id)) + .map(|ty| self.summarize_enum(ty, module(ty), &reverse_cloned_interfaces)) + .collect(); + + let options = metadata + .options + .iter() + .map(|ty| original(ty.id)) + .map(|ty| self.summarize_option(ty)) + .collect(); + + let results = metadata + .results + .iter() + .map(|ty| original(ty.id)) + .map(|ty| self.summarize_result(ty)) + .collect(); + Symbols { types_package: format!("{}.types", locations.types_module.as_ref().unwrap()), exports, - types, + resources, + records, + flags, + tuples, + variants, + enums, + options, + results, } } + fn imported_function_index(&self, function: &MyFunction) -> usize { + *self + .imported_function_indexes + .get(&( + function + .interface + .as_ref() + .map(|v| self.resolve.name_world_key(v.key)) + .as_deref(), + function.name, + )) + .unwrap() + } + fn function_name(&self, function: &MyFunction) -> String { self.function_name_with(&function.wit_kind, function.name) } @@ -840,7 +882,7 @@ impl<'a> Summary<'a> { TypeNames::new(self, TypeOwner::None).type_name( ty, &if let Type::Id(id) = ty { - Some(bindgen::dealias(self.resolve, id)) + Some(dealias(self.resolve, id)) } else { None } @@ -1021,13 +1063,7 @@ impl<'a> Summary<'a> { let empty = &ResourceInfo::default(); - if self - .resource_info - .get(&id) - .unwrap_or(empty) - .remote_dispatch_index - .is_some() - { + if self.resource_info.get(&id).unwrap_or(empty).remote { for function in &self.functions { if matches_resource(function, id, Direction::Import) { sort(function, sorted, visited); @@ -1035,13 +1071,7 @@ impl<'a> Summary<'a> { } } - if self - .resource_info - .get(&id) - .unwrap_or(empty) - .local_dispatch_index - .is_some() - { + if self.resource_info.get(&id).unwrap_or(empty).local { for function in &self.functions { if matches_resource(function, id, Direction::Export) { sort(function, sorted, visited); @@ -1353,14 +1383,8 @@ class {camel}(Flag): let empty = &ResourceInfo::default(); - let import = if self - .resource_info - .get(&id) - .unwrap_or(empty) - .remote_dispatch_index - .is_some() - { - let method = |(index, function)| { + let import = if self.resource_info.get(&id).unwrap_or(empty).remote { + let method = |function| { let FunctionCode { snake, params, @@ -1392,6 +1416,7 @@ class {camel}(Flag): " ) } else { + let index = self.imported_function_index(function); format!( " def {snake}({params}){return_type}: @@ -1409,6 +1434,7 @@ class {camel}(Flag): {docs}{NOT_IMPLEMENTED}" ) } else { + let index = self.imported_function_index(function); format!( "{class_method} def {snake}({params}){return_type}: @@ -1419,53 +1445,41 @@ class {camel}(Flag): }; let methods = self - .functions - .iter() - .filter_map({ - let mut index = 0; - move |function| { - let result = matches_resource(function, id, Direction::Import) - .then_some((index, function)); - - if function.is_dispatchable() { - index += 1; - } - - result - } - }) - .map(method) - .chain(iter::once({ - let newline = '\n'; - let indent = " "; - let doc = "Release this resource."; - let docs = - format!(r#""""{newline}{indent}{doc}{newline}{indent}"""{newline}{indent}"#); - let enter = r#" + .functions + .iter() + .filter(move |function| matches_resource(function, id, Direction::Import)) + .map(method) + .chain(iter::once({ + let newline = '\n'; + let indent = " "; + let doc = "Release this resource."; + let docs = + format!(r#""""{newline}{indent}{doc}{newline}{indent}"""{newline}{indent}"#); + let enter = r#" def __enter__(self) -> Self: """Returns self""" return self "#; - if stub_runtime_calls { - format!( - "{enter} + if stub_runtime_calls { + format!( + "{enter} def __exit__(self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None) -> bool | None: {docs}{NOT_IMPLEMENTED} " - ) - } else { - format!( - "{enter} + ) + } else { + format!( + "{enter} def __exit__(self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None) -> bool | None: {docs}(_, func, args, _) = self.finalizer.detach() self.handle = None func(args[0], args[1]) " - ) - } - })) - .collect::>() - .concat(); + ) + } + })) + .collect::>() + .concat(); Some(format!( " @@ -1477,13 +1491,7 @@ class {camel}: None }; - let export = if self - .resource_info - .get(&id) - .unwrap_or(empty) - .local_dispatch_index - .is_some() - { + let export = if self.resource_info.get(&id).unwrap_or(empty).local { let method = |function| { let FunctionCode { snake, @@ -1640,7 +1648,6 @@ class {camel}(Protocol): seen.insert(id); } - let mut index = 0; for function in &self.functions { let key = function.key(); let direction = if let FunctionKind::Import = &function.kind { @@ -1704,6 +1711,7 @@ def {snake}({params}){return_type}: " ) } else { + let index = self.imported_function_index(function); format!( " def {snake}({params}){return_type}: @@ -1772,15 +1780,10 @@ def {snake}({params}){return_type}: definitions.alias_module = Some(module.clone()); } } - _ => unreachable!(), } } _ => (), } - - if function.is_dispatchable() { - index += 1; - } } let python_imports = @@ -2019,9 +2022,17 @@ from .types import Result, Ok, Err, Some } } - fn package(&self, owner: TypeOwner, world_module: &str) -> Option { + fn package( + &self, + owner: TypeOwner, + world_module: &str, + reverse_cloned_interfaces: &HashMap, + ) -> Option { match owner { - TypeOwner::Interface(interface) => { + TypeOwner::Interface(mut interface) => { + if let Some(&original) = reverse_cloned_interfaces.get(&interface) { + interface = original; + } let (module, package) = self.interface_package(interface); Some(format!("{world_module}.{module}.{package}")) } @@ -2080,7 +2091,7 @@ from .types import Result, Ok, Err, Some TypeDefKind::Resource => { let empty = &ResourceInfo::default(); let info = self.resource_info.get(&id).unwrap_or(empty); - info.local_dispatch_index.is_some() && info.remote_dispatch_index.is_some() + info.local && info.remote } kind => todo!("{kind:?}"), }, @@ -2156,7 +2167,7 @@ impl<'a> TypeNames<'a> { } } TypeDefKind::Option(some) => { - if abi::is_option(self.summary.resolve, *some) { + if is_option(self.summary.resolve, *some) { format!("Optional[Some[{}]]", self.type_name(*some, seen, resource)) } else { format!("Optional[{}]", self.type_name(*some, seen, resource)) @@ -2280,3 +2291,24 @@ fn docstring( String::new() } } + +fn dealias(resolve: &Resolve, mut id: TypeId) -> TypeId { + loop { + match &resolve.types[id].kind { + TypeDefKind::Type(Type::Id(that_id)) => id = *that_id, + _ => break id, + } + } +} + +fn is_option(resolve: &Resolve, ty: Type) -> bool { + if let Type::Id(id) = ty { + match &resolve.types[id].kind { + TypeDefKind::Option(_) => true, + TypeDefKind::Type(ty) => is_option(resolve, *ty), + _ => false, + } + } else { + false + } +} diff --git a/wit/init.wit b/wit/init.wit index ef3ec33..35f8826 100644 --- a/wit/init.wit +++ b/wit/init.wit @@ -2,14 +2,8 @@ package componentize-py:init; world init { import wasi:cli/environment@0.2.0; - + export exports: interface { - record bundled { - module: string, - protocol: string, - name: string - } - record function { protocol: string, name: string @@ -26,61 +20,83 @@ world init { name: string } - variant function-export { - bundled(bundled), + variant function-export-kind { freestanding(function), %constructor(%constructor), %method(string), %static(%static) } - record case { + variant return-style { + none, + normal, + %result, + } + + record function-export { + kind: function-export-kind, + return-style: return-style + } + + record %resource { + %package: string, + name: string + } + + record %record { + %package: string, name: string, - has-payload: bool, + fields: list } - record local-resource { - new: u32, - rep: u32, - drop: u32 + record %flags { + %package: string, + name: string, + u32-count: u32 } - record remote-resource { - drop: u32 + record %tuple { + count: u32 } - - record %resource { - local: option, - remote: option + + record case { + name: string, + has-payload: bool } - variant owned-kind { - %record(list), - %variant(list), - %enum(u32), - %flags(u32), - %resource(%resource), + record %variant { + %package: string, + name: string, + cases: list } - record owned-type { - kind: owned-kind, + record %enum { %package: string, - name: string + name: string, + count: u32 } - variant %type { - owned(owned-type), - %option, - nesting-option, - %result, - %tuple(u32), - handle + enum option-kind { + non-nesting, + nesting + } + + record result-record { + has-ok: bool, + has-err: bool, } record symbols { types-package: string, exports: list, - types: list<%type> + resources: list<%resource>, + records: list<%record>, + %flags: list<%flags>, + tuples: list<%tuple>, + variants: list<%variant>, + enums: list<%enum>, + options: list, + results: list, } init: func(app-name: string, symbols: symbols, stub-wasi: bool) -> result<_, string>; From 3ea7ce0e6703352ad658bb8827326a6ba356a330 Mon Sep 17 00:00:00 2001 From: Joel Dice Date: Mon, 27 Oct 2025 09:36:09 -0600 Subject: [PATCH 2/3] run `cargo fmt` Signed-off-by: Joel Dice --- runtime/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index d0d6186..9a02b7d 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -26,13 +26,13 @@ use { num_bigint::BigUint, once_cell::sync::OnceCell, pyo3::{ - Bound, IntoPyObject, Py, PyAny, PyErr, PyResult, Python, exceptions::PyAssertionError, intern, types::{ PyAnyMethods, PyBool, PyBytes, PyBytesMethods, PyDict, PyList, PyListMethods, PyMapping, PyMappingMethods, PyModule, PyModuleMethods, PyString, PyTuple, }, + Bound, IntoPyObject, Py, PyAny, PyErr, PyResult, Python, }, std::{ alloc::{self, Layout}, From f1179798479aa04693eaa0a4f7480d28904b7bd0 Mon Sep 17 00:00:00 2001 From: Joel Dice Date: Mon, 27 Oct 2025 09:47:15 -0600 Subject: [PATCH 3/3] update README.md files to reference `wasmtime` release that actually exists Signed-off-by: Joel Dice --- examples/cli/README.md | 6 +++--- examples/http/README.md | 6 +++--- examples/matrix-math/README.md | 6 +++--- examples/sandbox/README.md | 4 ++-- examples/tcp/README.md | 6 +++--- 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/examples/cli/README.md b/examples/cli/README.md index 389e990..7d52f2d 100644 --- a/examples/cli/README.md +++ b/examples/cli/README.md @@ -9,15 +9,15 @@ run a Python-based component targetting the [wasi-cli] `command` world. ## Prerequisites -* `Wasmtime` 37.0.1 or later +* `Wasmtime` 37.0.0 or later * `componentize-py` 0.18.0 Below, we use [Rust](https://rustup.rs/)'s `cargo` to install `Wasmtime`. If you don't have `cargo`, you can download and install from -https://github.com/bytecodealliance/wasmtime/releases/tag/v37.0.1. +https://github.com/bytecodealliance/wasmtime/releases/tag/v37.0.0. ``` -cargo install --version 37.0.1 wasmtime-cli +cargo install --version 37.0.0 wasmtime-cli pip install componentize-py==0.18.0 ``` diff --git a/examples/http/README.md b/examples/http/README.md index cc50efd..9b849ba 100644 --- a/examples/http/README.md +++ b/examples/http/README.md @@ -9,15 +9,15 @@ run a Python-based component targetting the [wasi-http] `proxy` world. ## Prerequisites -* `Wasmtime` 37.0.1 or later +* `Wasmtime` 37.0.0 or later * `componentize-py` 0.18.0 Below, we use [Rust](https://rustup.rs/)'s `cargo` to install `Wasmtime`. If you don't have `cargo`, you can download and install from -https://github.com/bytecodealliance/wasmtime/releases/tag/v37.0.1. +https://github.com/bytecodealliance/wasmtime/releases/tag/v37.0.0. ``` -cargo install --version 37.0.1 wasmtime-cli +cargo install --version 37.0.0 wasmtime-cli pip install componentize-py==0.18.0 ``` diff --git a/examples/matrix-math/README.md b/examples/matrix-math/README.md index 41da95b..f3eea06 100644 --- a/examples/matrix-math/README.md +++ b/examples/matrix-math/README.md @@ -10,7 +10,7 @@ within a guest component. ## Prerequisites -* `wasmtime` 37.0.1 or later +* `wasmtime` 37.0.0 or later * `componentize-py` 0.18.0 * `NumPy`, built for WASI @@ -19,10 +19,10 @@ not yet publish WASI builds. Below, we use [Rust](https://rustup.rs/)'s `cargo` to install `Wasmtime`. If you don't have `cargo`, you can download and install from -https://github.com/bytecodealliance/wasmtime/releases/tag/v37.0.1. +https://github.com/bytecodealliance/wasmtime/releases/tag/v37.0.0. ``` -cargo install --version 37.0.1 wasmtime-cli +cargo install --version 37.0.0 wasmtime-cli pip install componentize-py==0.18.0 curl -OL https://github.com/dicej/wasi-wheels/releases/download/v0.0.2/numpy-wasi.tar.gz tar xf numpy-wasi.tar.gz diff --git a/examples/sandbox/README.md b/examples/sandbox/README.md index 39d6ca0..3cf0303 100644 --- a/examples/sandbox/README.md +++ b/examples/sandbox/README.md @@ -7,11 +7,11 @@ sandboxed Python code snippets from within a Python app. ## Prerequisites -* `wasmtime-py` 37.0.1 or later +* `wasmtime-py` 37.0.0 or later * `componentize-py` 0.18.0 ``` -pip install componentize-py==0.18.0 wasmtime==37.0.1 +pip install componentize-py==0.18.0 wasmtime==37.0.0 ``` ## Running the demo diff --git a/examples/tcp/README.md b/examples/tcp/README.md index 9e5312d..9f179cc 100644 --- a/examples/tcp/README.md +++ b/examples/tcp/README.md @@ -10,15 +10,15 @@ making an outbound TCP request using `wasi-sockets`. ## Prerequisites -* `Wasmtime` 37.0.1 or later +* `Wasmtime` 37.0.0 or later * `componentize-py` 0.18.0 Below, we use [Rust](https://rustup.rs/)'s `cargo` to install `Wasmtime`. If you don't have `cargo`, you can download and install from -https://github.com/bytecodealliance/wasmtime/releases/tag/v37.0.1. +https://github.com/bytecodealliance/wasmtime/releases/tag/v37.0.0. ``` -cargo install --version 37.0.1 wasmtime-cli +cargo install --version 37.0.0 wasmtime-cli pip install componentize-py==0.18.0 ```