diff --git a/Cargo.lock b/Cargo.lock index bb11b15..16ff7d1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -482,7 +482,7 @@ dependencies = [ [[package]] name = "componentize-py" -version = "0.17.0" +version = "0.17.1" dependencies = [ "anyhow", "assert_cmd", @@ -632,7 +632,7 @@ dependencies = [ "rustc-hash", "serde", "smallvec", - "target-lexicon 0.13.2", + "target-lexicon", ] [[package]] @@ -681,7 +681,7 @@ dependencies = [ "cranelift-codegen", "log", "smallvec", - "target-lexicon 0.13.2", + "target-lexicon", ] [[package]] @@ -698,7 +698,7 @@ checksum = "7617f13f392ebb63c5126258aca8b8eca739636ca7e4eeee301d3eff68489a6a" dependencies = [ "cranelift-codegen", "libc", - "target-lexicon 0.13.2", + "target-lexicon", ] [[package]] @@ -1849,11 +1849,9 @@ dependencies = [ [[package]] name = "pyo3" -version = "0.22.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d922163ba1f79c04bc49073ba7b32fd5a8d3b76a87c955921234b8e77333c51" +version = "0.25.0" +source = "git+https://github.com/dicej/pyo3?branch=v0.25.0-no-wasm32-unwind#d0cf051de41c3cefeee95c9524aa460cde7e849d" dependencies = [ - "cfg-if", "indoc", "libc", "memoffset", @@ -1868,19 +1866,17 @@ dependencies = [ [[package]] name = "pyo3-build-config" -version = "0.22.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc38c5feeb496c8321091edf3d63e9a6829eab4b863b4a6a65f26f3e9cc6b179" +version = "0.25.0" +source = "git+https://github.com/dicej/pyo3?branch=v0.25.0-no-wasm32-unwind#d0cf051de41c3cefeee95c9524aa460cde7e849d" dependencies = [ "once_cell", - "target-lexicon 0.12.16", + "target-lexicon", ] [[package]] name = "pyo3-ffi" -version = "0.22.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94845622d88ae274d2729fcefc850e63d7a3ddff5e3ce11bd88486db9f1d357d" +version = "0.25.0" +source = "git+https://github.com/dicej/pyo3?branch=v0.25.0-no-wasm32-unwind#d0cf051de41c3cefeee95c9524aa460cde7e849d" dependencies = [ "libc", "pyo3-build-config", @@ -1888,9 +1884,8 @@ dependencies = [ [[package]] name = "pyo3-macros" -version = "0.22.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e655aad15e09b94ffdb3ce3d217acf652e26bbc37697ef012f5e5e348c716e5e" +version = "0.25.0" +source = "git+https://github.com/dicej/pyo3?branch=v0.25.0-no-wasm32-unwind#d0cf051de41c3cefeee95c9524aa460cde7e849d" dependencies = [ "proc-macro2", "pyo3-macros-backend", @@ -1900,9 +1895,8 @@ dependencies = [ [[package]] name = "pyo3-macros-backend" -version = "0.22.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae1e3f09eecd94618f60a455a23def79f79eba4dc561a97324bf9ac8c6df30ce" +version = "0.25.0" +source = "git+https://github.com/dicej/pyo3?branch=v0.25.0-no-wasm32-unwind#d0cf051de41c3cefeee95c9524aa460cde7e849d" dependencies = [ "heck", "proc-macro2", @@ -2467,12 +2461,6 @@ dependencies = [ "xattr", ] -[[package]] -name = "target-lexicon" -version = "0.12.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" - [[package]] name = "target-lexicon" version = "0.13.2" @@ -3062,7 +3050,7 @@ dependencies = [ "serde_json", "smallvec", "sptr", - "target-lexicon 0.13.2", + "target-lexicon", "trait-variant", "wasm-encoder 0.224.1", "wasmparser 0.224.1", @@ -3152,7 +3140,7 @@ dependencies = [ "object", "pulley-interpreter", "smallvec", - "target-lexicon 0.13.2", + "target-lexicon", "thiserror", "wasmparser 0.224.1", "wasmtime-environ", @@ -3179,7 +3167,7 @@ dependencies = [ "serde", "serde_derive", "smallvec", - "target-lexicon 0.13.2", + "target-lexicon", "wasm-encoder 0.224.1", "wasmparser 0.224.1", "wasmprinter", @@ -3305,7 +3293,7 @@ dependencies = [ "cranelift-codegen", "gimli", "object", - "target-lexicon 0.13.2", + "target-lexicon", "wasmparser 0.224.1", "wasmtime-cranelift", "wasmtime-environ", @@ -3449,7 +3437,7 @@ dependencies = [ "gimli", "regalloc2", "smallvec", - "target-lexicon 0.13.2", + "target-lexicon", "thiserror", "wasmparser 0.224.1", "wasmtime-cranelift", diff --git a/Cargo.toml b/Cargo.toml index c2f8e02..dc2b894 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "componentize-py" -version = "0.17.0" +version = "0.17.1" edition = "2021" exclude = ["cpython"] @@ -22,7 +22,9 @@ wasmparser = "0.227.0" indexmap = "2.6.0" bincode = "1.3.3" heck = "0.5.0" -pyo3 = { version = "0.22.5", features = [ +# TODO: switch back to upstream once we've updated to Python 3.14, at which +# point the following patch will no longer be needed: +pyo3 = { git = "https://github.com/dicej/pyo3", branch = "v0.25.0-no-wasm32-unwind", features = [ "abi3-py39", "extension-module", ], optional = true } diff --git a/build.rs b/build.rs index e7148cb..fb91dc1 100644 --- a/build.rs +++ b/build.rs @@ -124,9 +124,12 @@ fn package_all_the_things(out_dir: &Path) -> Result<()> { } } - cmd.env("RUSTFLAGS", "-C relocation-model=pic") - .env("CARGO_TARGET_DIR", out_dir) - .env("PYO3_CONFIG_FILE", out_dir.join("pyo3-config.txt")); + cmd.env( + "RUSTFLAGS", + "-C relocation-model=pic --cfg pyo3_disable_reference_pool", + ) + .env("CARGO_TARGET_DIR", out_dir) + .env("PYO3_CONFIG_FILE", out_dir.join("pyo3-config.txt")); let status = cmd.status()?; assert!(status.success()); diff --git a/examples/cli/README.md b/examples/cli/README.md index 9a23c2e..a9d83a9 100644 --- a/examples/cli/README.md +++ b/examples/cli/README.md @@ -10,7 +10,7 @@ run a Python-based component targetting the [wasi-cli] `command` world. ## Prerequisites * `Wasmtime` 26.0.0 or later -* `componentize-py` 0.17.0 +* `componentize-py` 0.17.1 Below, we use [Rust](https://rustup.rs/)'s `cargo` to install `Wasmtime`. If you don't have `cargo`, you can download and install from @@ -18,7 +18,7 @@ https://github.com/bytecodealliance/wasmtime/releases/tag/v26.0.0. ``` cargo install --version 26.0.0 wasmtime-cli -pip install componentize-py==0.17.0 +pip install componentize-py==0.17.1 ``` ## Running the demo diff --git a/examples/http/README.md b/examples/http/README.md index b97dc10..ab5f673 100644 --- a/examples/http/README.md +++ b/examples/http/README.md @@ -10,7 +10,7 @@ run a Python-based component targetting the [wasi-http] `proxy` world. ## Prerequisites * `Wasmtime` 26.0.0 or later -* `componentize-py` 0.17.0 +* `componentize-py` 0.17.1 Below, we use [Rust](https://rustup.rs/)'s `cargo` to install `Wasmtime`. If you don't have `cargo`, you can download and install from @@ -18,7 +18,7 @@ https://github.com/bytecodealliance/wasmtime/releases/tag/v26.0.0. ``` cargo install --version 26.0.0 wasmtime-cli -pip install componentize-py==0.17.0 +pip install componentize-py==0.17.1 ``` ## Running the demo diff --git a/examples/matrix-math/README.md b/examples/matrix-math/README.md index 869ad02..41c8306 100644 --- a/examples/matrix-math/README.md +++ b/examples/matrix-math/README.md @@ -11,7 +11,7 @@ within a guest component. ## Prerequisites * `wasmtime` 26.0.0 or later -* `componentize-py` 0.17.0 +* `componentize-py` 0.17.1 * `NumPy`, built for WASI Note that we use an unofficial build of NumPy since the upstream project does @@ -23,7 +23,7 @@ https://github.com/bytecodealliance/wasmtime/releases/tag/v26.0.0. ``` cargo install --version 26.0.0 wasmtime-cli -pip install componentize-py==0.17.0 +pip install componentize-py==0.17.1 curl -OL https://github.com/dicej/wasi-wheels/releases/download/v0.0.1/numpy-wasi.tar.gz tar xf numpy-wasi.tar.gz ``` diff --git a/examples/sandbox/README.md b/examples/sandbox/README.md index f8919b8..e4a34f4 100644 --- a/examples/sandbox/README.md +++ b/examples/sandbox/README.md @@ -8,10 +8,10 @@ sandboxed Python code snippets from within a Python app. ## Prerequisites * `wasmtime-py` 25.0.0 or later -* `componentize-py` 0.17.0 +* `componentize-py` 0.17.1 ``` -pip install componentize-py==0.17.0 wasmtime==25.0.0 +pip install componentize-py==0.17.1 wasmtime==25.0.0 ``` ## Running the demo diff --git a/examples/tcp/README.md b/examples/tcp/README.md index c5636f7..7368262 100644 --- a/examples/tcp/README.md +++ b/examples/tcp/README.md @@ -11,7 +11,7 @@ making an outbound TCP request using `wasi-sockets`. ## Prerequisites * `Wasmtime` 26.0.0 or later -* `componentize-py` 0.17.0 +* `componentize-py` 0.17.1 Below, we use [Rust](https://rustup.rs/)'s `cargo` to install `Wasmtime`. If you don't have `cargo`, you can download and install from @@ -19,7 +19,7 @@ https://github.com/bytecodealliance/wasmtime/releases/tag/v26.0.0. ``` cargo install --version 26.0.0 wasmtime-cli -pip install componentize-py==0.17.0 +pip install componentize-py==0.17.1 ``` ## Running the demo diff --git a/pyproject.toml b/pyproject.toml index bb63671..c41c577 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ features = ["pyo3/extension-module"] [project] name = "componentize-py" -version = "0.17.0" +version = "0.17.1" description = "Tool to package Python applications as WebAssembly components" readme = "README.md" license = { file = "LICENSE" } diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index 9c69317..c99929c 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -9,7 +9,9 @@ crate-type = ["staticlib"] [dependencies] anyhow = "1.0.91" once_cell = "1.20.2" -pyo3 = { version = "0.22.5", features = ["abi3-py312", "num-bigint"] } +# TODO: switch back to upstream once we've updated to Python 3.14, at which +# point the following patch will no longer be needed: +pyo3 = { git = "https://github.com/dicej/pyo3", branch = "v0.25.0-no-wasm32-unwind", 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"] } diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 1a8d966..359fc34 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -21,16 +21,18 @@ use { once_cell::sync::OnceCell, pyo3::{ exceptions::PyAssertionError, - intern, + ffi, intern, types::{ - PyAnyMethods, PyBool, PyBytes, PyBytesMethods, PyDict, PyList, PyListMethods, - PyMapping, PyMappingMethods, PyModule, PyModuleMethods, PyString, PyTuple, + PyAnyMethods, PyBool, PyBytes, PyBytesMethods, PyDict, PyFloat, PyInt, PyList, + PyListMethods, PyMapping, PyMappingMethods, PyModule, PyModuleMethods, PyString, + PyTuple, }, - AsPyPointer, Borrowed, Bound, Py, PyAny, PyErr, PyObject, PyResult, Python, ToPyObject, + Borrowed, Bound, IntoPyObject, Py, PyAny, PyErr, PyObject, PyResult, Python, }, std::{ alloc::{self, Layout}, ffi::c_void, + iter, mem::{self, MaybeUninit}, ops::DerefMut, ptr, slice, str, @@ -152,8 +154,10 @@ fn call_import<'a>( index: u32, params: Vec>, result_count: usize, -) -> PyResult> { - let mut results = vec![MaybeUninit::<&PyAny>::uninit(); result_count]; +) -> PyResult>> { + let mut results = iter::repeat_with(MaybeUninit::>::uninit) + .take(result_count) + .collect::>(); unsafe { componentize_py_call_indirect( &module.py() as *const _ as _, @@ -163,9 +167,10 @@ fn call_import<'a>( ); // todo: is this sound, or do we need to `.into_iter().map(MaybeUninit::assume_init).collect()` instead? - Ok(mem::transmute::>, Vec<&PyAny>>( - results, - )) + Ok(mem::transmute::< + Vec>>, + Vec>, + >(results)) } } @@ -197,7 +202,7 @@ fn do_init(app_name: String, symbols: Symbols, stub_wasi: bool) -> Result<()> { pyo3::prepare_freethreaded_python(); Python::with_gil(|py| { - let app = match py.import_bound(app_name.as_str()) { + let app = match py.import(app_name.as_str()) { Ok(app) => app, Err(e) => { e.print(py); @@ -219,37 +224,37 @@ fn do_init(app_name: String, symbols: Symbols, stub_wasi: bool) -> Result<()> { protocol, name, }) => Export::Freestanding { - name: PyString::intern_bound(py, name).into(), + name: PyString::intern(py, name).into(), instance: py - .import_bound(module.as_str())? + .import(module.as_str())? .getattr(protocol.as_str())? .call0()? .into(), }, FunctionExport::Freestanding(Function { protocol, name }) => { Export::Freestanding { - name: PyString::intern_bound(py, name).into(), + name: PyString::intern(py, name).into(), instance: app.getattr(protocol.as_str())?.call0()?.into(), } } FunctionExport::Constructor(Constructor { module, protocol }) => { Export::Constructor( - py.import_bound(module.as_str())? + py.import(module.as_str())? .getattr(protocol.as_str())? .into(), ) } FunctionExport::Method(name) => { - Export::Method(PyString::intern_bound(py, name).into()) + Export::Method(PyString::intern(py, name).into()) } FunctionExport::Static(Static { module, protocol, name, }) => Export::Static { - name: PyString::intern_bound(py, name).into(), + name: PyString::intern(py, name).into(), class: py - .import_bound(module.as_str())? + .import(module.as_str())? .getattr(protocol.as_str())? .into(), }, @@ -273,13 +278,13 @@ fn do_init(app_name: String, symbols: Symbols, stub_wasi: bool) -> Result<()> { }) => match kind { OwnedKind::Record(fields) => Type::Record { constructor: py - .import_bound(package.as_str())? + .import(package.as_str())? .getattr(name.as_str())? .into(), fields, }, OwnedKind::Variant(cases) => { - let package = py.import_bound(package.as_str())?; + let package = py.import(package.as_str())?; let cases = cases .iter() @@ -293,7 +298,7 @@ fn do_init(app_name: String, symbols: Symbols, stub_wasi: bool) -> Result<()> { }) .collect::>>()?; - let types_to_discriminants = PyDict::new_bound(py); + let types_to_discriminants = PyDict::new(py); for (index, case) in cases.iter().enumerate() { types_to_discriminants .set_item(&case.constructor, index)?; @@ -306,21 +311,21 @@ fn do_init(app_name: String, symbols: Symbols, stub_wasi: bool) -> Result<()> { } OwnedKind::Enum(count) => Type::Enum { constructor: py - .import_bound(package.as_str())? + .import(package.as_str())? .getattr(name.as_str())? .into(), count: count.try_into().unwrap(), }, OwnedKind::Flags(u32_count) => Type::Flags { constructor: py - .import_bound(package.as_str())? + .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_bound(package.as_str())? + .import(package.as_str())? .getattr(name.as_str())? .into(), local, @@ -338,43 +343,43 @@ fn do_init(app_name: String, symbols: Symbols, stub_wasi: bool) -> Result<()> { ) .unwrap(); - let types = py.import_bound(symbols.types_package.as_str())?; + let types = py.import(symbols.types_package.as_str())?; SOME_CONSTRUCTOR.set(types.getattr("Some")?.into()).unwrap(); OK_CONSTRUCTOR.set(types.getattr("Ok")?.into()).unwrap(); ERR_CONSTRUCTOR.set(types.getattr("Err")?.into()).unwrap(); let environ = py - .import_bound("os")? + .import("os")? .getattr("environ")? .downcast_into::() .unwrap(); let keys = environ.keys()?; - for i in 0..keys.len()? { + for i in 0..keys.len() { environ.del_item(keys.get_item(i)?)?; } ENVIRON.set(environ.into()).unwrap(); FINALIZE - .set(py.import_bound("weakref")?.getattr("finalize")?.into()) + .set(py.import("weakref")?.getattr("finalize")?.into()) .unwrap(); DROP_RESOURCE .set( - py.import_bound("componentize_py_runtime")? + py.import("componentize_py_runtime")? .getattr("drop_resource")? .into(), ) .unwrap(); - SEED.set(py.import_bound("random")?.getattr("seed")?.into()) + SEED.set(py.import("random")?.getattr("seed")?.into()) .unwrap(); let argv = py - .import_bound("sys")? + .import("sys")? .getattr("argv")? .downcast_into::() .unwrap(); @@ -433,7 +438,7 @@ pub unsafe extern "C" fn componentize_py_dispatch( results_canon: *mut c_void, ) { Python::with_gil(|py| { - let mut params_py = vec![MaybeUninit::<&PyAny>::uninit(); param_count.try_into().unwrap()]; + let mut params_py = vec![ptr::null_mut::(); param_count.try_into().unwrap()]; componentize_py_call_indirect( &py as *const _ as _, @@ -442,10 +447,9 @@ pub unsafe extern "C" fn componentize_py_dispatch( from_canon, ); - // todo: is this sound, or do we need to `.into_iter().map(MaybeUninit::assume_init).collect()` instead? - let mut params_py = mem::transmute::>, Vec<&PyAny>>(params_py) + let mut params_py = params_py .into_iter() - .map(|p| Bound::from_borrowed_ptr(py, p.as_ptr())); + .map(|p| Bound::from_borrowed_ptr(py, p)); if !*STUB_WASI.get().unwrap() { static ONCE: Once = Once::new(); @@ -471,18 +475,18 @@ pub unsafe extern "C" fn componentize_py_dispatch( let export = &EXPORTS.get().unwrap()[export]; let result = match export { Export::Freestanding { instance, name } => { - instance.call_method1(py, name, PyTuple::new_bound(py, params_py)) + instance.call_method1(py, name, PyTuple::new(py, params_py).unwrap()) } - Export::Constructor(class) => class.call1(py, PyTuple::new_bound(py, params_py)), + 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_bound(py, params_py)) + .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_bound(py, params_py))), + .and_then(|function| function.call1(py, PyTuple::new(py, params_py).unwrap())), }; let result = match return_style { @@ -500,10 +504,10 @@ pub unsafe extern "C" fn componentize_py_dispatch( .get() .unwrap() .bind(py) - .eq(result.get_type_bound(py)) + .eq(result.get_type(py)) .unwrap() { - result.to_object(py) + result.into_value(py).into_any() } else { result.print(py); panic!("Python function threw an unexpected exception") @@ -658,19 +662,24 @@ pub extern "C" fn componentize_py_get_field<'a>( .nth(field) .unwrap_or(0); - u32::cast_signed(value).to_object(*py).into_bound(*py) + 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() { 0 } else { 1 } - .to_object(*py) - .into_bound(*py), + DISCRIMINANT_FIELD_INDEX => if value.is_none() { 0u32 } else { 1 } + .into_pyobject(*py) + .unwrap() + .into_any(), PAYLOAD_FIELD_INDEX => value.to_owned(), _ => unreachable!(), }, Type::NestingOption => match i32::try_from(field).unwrap() { - DISCRIMINANT_FIELD_INDEX => if value.is_none() { 0 } else { 1 } - .to_object(*py) - .into_bound(*py), + DISCRIMINANT_FIELD_INDEX => if value.is_none() { 0u32 } else { 1 } + .into_pyobject(*py) + .unwrap() + .into_any(), PAYLOAD_FIELD_INDEX => { if value.is_none() { value.to_owned() @@ -700,8 +709,9 @@ pub extern "C" fn componentize_py_get_field<'a>( } else { unreachable!() } - .to_object(*py) - .into_bound(*py), + .into_pyobject(*py) + .unwrap() + .into_any(), PAYLOAD_FIELD_INDEX => value.getattr("value").unwrap(), _ => unreachable!(), }, @@ -740,42 +750,67 @@ pub extern "C" fn componentize_py_from_canon_bool<'a>( py: &Python<'a>, value: u32, ) -> Bound<'a, PyBool> { - PyBool::new_bound(*py, value != 0).to_owned() + PyBool::new(*py, value != 0).to_owned() } #[export_name = "componentize-py#FromCanonI32"] -pub extern "C" fn componentize_py_from_canon_i32(py: &Python, value: i32) -> Py { - value.to_object(*py) +pub extern "C" fn componentize_py_from_canon_i32<'a>( + py: &Python<'a>, + value: i32, +) -> Bound<'a, PyInt> { + value.into_pyobject(*py).unwrap() } #[export_name = "componentize-py#FromCanonU32"] -pub extern "C" fn componentize_py_from_canon_u32(py: &Python, value: u32) -> Py { - value.to_object(*py) +pub extern "C" fn componentize_py_from_canon_u32<'a>( + py: &Python<'a>, + value: u32, +) -> Bound<'a, PyInt> { + value.into_pyobject(*py).unwrap() } #[export_name = "componentize-py#FromCanonI64"] -pub extern "C" fn componentize_py_from_canon_i64(py: &Python, value: i64) -> Py { - value.to_object(*py) +pub extern "C" fn componentize_py_from_canon_i64<'a>( + py: &Python<'a>, + value: i64, +) -> Bound<'a, PyInt> { + value.into_pyobject(*py).unwrap() } #[export_name = "componentize-py#FromCanonU64"] -pub extern "C" fn componentize_py_from_canon_u64(py: &Python, value: u64) -> Py { - value.to_object(*py) +pub extern "C" fn componentize_py_from_canon_u64<'a>( + py: &Python<'a>, + value: u64, +) -> Bound<'a, PyInt> { + value.into_pyobject(*py).unwrap() } #[export_name = "componentize-py#FromCanonF32"] -pub extern "C" fn componentize_py_from_canon_f32(py: &Python, value: f32) -> Py { - value.to_object(*py) +pub extern "C" fn componentize_py_from_canon_f32<'a>( + py: &Python<'a>, + value: f32, +) -> Bound<'a, PyFloat> { + value.into_pyobject(*py).unwrap() } #[export_name = "componentize-py#FromCanonF64"] -pub extern "C" fn componentize_py_from_canon_f64(py: &Python, value: f64) -> Py { - value.to_object(*py) +pub extern "C" fn componentize_py_from_canon_f64<'a>( + py: &Python<'a>, + value: f64, +) -> Bound<'a, PyFloat> { + value.into_pyobject(*py).unwrap() } #[export_name = "componentize-py#FromCanonChar"] -pub extern "C" fn componentize_py_from_canon_char(py: &Python, value: u32) -> Py { - char::from_u32(value).unwrap().to_string().to_object(*py) +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() } /// # Safety @@ -786,7 +821,7 @@ pub unsafe extern "C" fn componentize_py_from_canon_string<'a>( data: *const u8, len: usize, ) -> Bound<'a, PyString> { - PyString::new_bound(*py, unsafe { + PyString::new(*py, unsafe { str::from_utf8_unchecked(slice::from_raw_parts(data, len)) }) } @@ -797,24 +832,24 @@ pub unsafe extern "C" fn componentize_py_from_canon_string<'a>( pub unsafe extern "C" fn componentize_py_init<'a>( py: &Python<'a>, ty: usize, - data: *const &'a PyAny, + 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_borrowed_ptr(*py, e.as_ptr())); + .map(|e| Bound::from_owned_ptr(*py, *e)); constructor - .call1(*py, PyTuple::new_bound(*py, elements)) + .call1(*py, PyTuple::new(*py, elements).unwrap()) .unwrap() .into_bound(*py) } Type::Variant { cases, .. } => { assert!(len == 2); - let discriminant = Bound::from_borrowed_ptr( + let discriminant = Bound::from_owned_ptr( *py, - ptr::read(data.offset(isize::try_from(DISCRIMINANT_FIELD_INDEX).unwrap())).as_ptr(), + ptr::read(data.offset(isize::try_from(DISCRIMINANT_FIELD_INDEX).unwrap())), ) .extract::() .unwrap(); @@ -822,8 +857,9 @@ pub unsafe extern "C" fn componentize_py_init<'a>( if case.has_payload { case.constructor.call1( *py, - (ptr::read( - data.offset(isize::try_from(PAYLOAD_FIELD_INDEX).unwrap()), + (Bound::from_owned_ptr( + *py, + ptr::read(data.offset(isize::try_from(PAYLOAD_FIELD_INDEX).unwrap())), ),), ) } else { @@ -834,20 +870,15 @@ pub unsafe extern "C" fn componentize_py_init<'a>( } Type::Enum { constructor, count } => { assert!(len == 2); - let discriminant = Bound::from_borrowed_ptr( + let discriminant = Bound::from_owned_ptr( *py, - ptr::read(data.offset(isize::try_from(DISCRIMINANT_FIELD_INDEX).unwrap())).as_ptr(), + ptr::read(data.offset(isize::try_from(DISCRIMINANT_FIELD_INDEX).unwrap())), ) .extract::() .unwrap(); assert!(discriminant < *count); constructor - .call1( - *py, - (ptr::read(data.offset( - isize::try_from(DISCRIMINANT_FIELD_INDEX).unwrap(), - )),), - ) + .call1(*py, (discriminant,)) .unwrap() .into_bound(*py) } @@ -864,7 +895,7 @@ pub unsafe extern "C" fn componentize_py_init<'a>( .iter() .map(|v| { i32::cast_unsigned( - Bound::from_borrowed_ptr(*py, v.as_ptr()).extract().unwrap(), + Bound::from_owned_ptr(*py, *v).extract().unwrap(), ) }) .collect(), @@ -875,26 +906,26 @@ pub unsafe extern "C" fn componentize_py_init<'a>( } Type::Option => { assert!(len == 2); - let discriminant = Bound::from_borrowed_ptr( + let discriminant = Bound::from_owned_ptr( *py, - ptr::read(data.offset(isize::try_from(DISCRIMINANT_FIELD_INDEX).unwrap())).as_ptr(), + ptr::read(data.offset(isize::try_from(DISCRIMINANT_FIELD_INDEX).unwrap())), ) .extract::() .unwrap(); match discriminant { 0 => py.None().into_bound(*py), - 1 => Bound::from_borrowed_ptr( + 1 => Bound::from_owned_ptr( *py, - ptr::read(data.offset(isize::try_from(PAYLOAD_FIELD_INDEX).unwrap())).as_ptr(), + ptr::read(data.offset(isize::try_from(PAYLOAD_FIELD_INDEX).unwrap())), ), _ => unreachable!(), } } Type::NestingOption => { assert!(len == 2); - let discriminant = Bound::from_borrowed_ptr( + let discriminant = Bound::from_owned_ptr( *py, - ptr::read(data.offset(isize::try_from(DISCRIMINANT_FIELD_INDEX).unwrap())).as_ptr(), + ptr::read(data.offset(isize::try_from(DISCRIMINANT_FIELD_INDEX).unwrap())), ) .extract::() .unwrap(); @@ -907,8 +938,9 @@ pub unsafe extern "C" fn componentize_py_init<'a>( .unwrap() .call1( *py, - (ptr::read( - data.offset(isize::try_from(PAYLOAD_FIELD_INDEX).unwrap()), + (Bound::from_owned_ptr( + *py, + ptr::read(data.offset(isize::try_from(PAYLOAD_FIELD_INDEX).unwrap())), ),), ) .unwrap() @@ -919,9 +951,9 @@ pub unsafe extern "C" fn componentize_py_init<'a>( } Type::Result => { assert!(len == 2); - let discriminant = Bound::from_borrowed_ptr( + let discriminant = Bound::from_owned_ptr( *py, - ptr::read(data.offset(isize::try_from(DISCRIMINANT_FIELD_INDEX).unwrap())).as_ptr(), + ptr::read(data.offset(isize::try_from(DISCRIMINANT_FIELD_INDEX).unwrap())), ) .extract::() .unwrap(); @@ -933,8 +965,9 @@ pub unsafe extern "C" fn componentize_py_init<'a>( } .call1( *py, - (ptr::read( - data.offset(isize::try_from(PAYLOAD_FIELD_INDEX).unwrap()), + (Bound::from_owned_ptr( + *py, + ptr::read(data.offset(isize::try_from(PAYLOAD_FIELD_INDEX).unwrap())), ),), ) .unwrap() @@ -944,8 +977,8 @@ pub unsafe extern "C" fn componentize_py_init<'a>( assert!(*length == len); let elements = slice::from_raw_parts(data, len) .iter() - .map(|e| Bound::from_borrowed_ptr(*py, e.as_ptr())); - PyTuple::new_bound(*py, elements).into_any() + .map(|e| Bound::from_owned_ptr(*py, *e)); + PyTuple::new(*py, elements).unwrap().into_any() } Type::Handle | Type::Resource { .. } => unreachable!(), } @@ -953,7 +986,7 @@ pub unsafe extern "C" fn componentize_py_init<'a>( #[export_name = "componentize-py#MakeList"] pub extern "C" fn componentize_py_make_list<'a>(py: &Python<'a>) -> Bound<'a, PyList> { - PyList::empty_bound(*py) + PyList::empty(*py) } #[export_name = "componentize-py#ListAppend"] @@ -991,11 +1024,7 @@ pub unsafe extern "C" fn componentize_py_make_bytes<'a>( src: *const u8, len: usize, ) -> Bound<'a, PyBytes> { - PyBytes::new_bound_with(*py, len, |dst| { - dst.copy_from_slice(slice::from_raw_parts(src, len)); - Ok(()) - }) - .unwrap() + PyBytes::new(*py, slice::from_raw_parts(src, len)) } #[export_name = "componentize-py#FromCanonHandle"] @@ -1068,14 +1097,14 @@ pub extern "C" fn componentize_py_from_canon_handle<'a>( .call_method1( *py, intern!(*py, "__new__"), - PyTuple::new_bound(*py, [constructor]), + PyTuple::new(*py, [constructor]).unwrap(), ) .unwrap(); - let handle = value.to_object(*py); + let handle = value.into_pyobject(*py).unwrap(); instance - .setattr(*py, intern!(*py, "handle"), handle.clone_ref(*py)) + .setattr(*py, intern!(*py, "handle"), handle.as_borrowed()) .unwrap(); let finalizer = FINALIZE @@ -1086,7 +1115,7 @@ pub extern "C" fn componentize_py_from_canon_handle<'a>( ( instance.clone_ref(*py), DROP_RESOURCE.get().unwrap(), - drop.to_object(*py), + drop.into_pyobject(*py).unwrap(), handle, ), ) @@ -1139,7 +1168,9 @@ pub extern "C" fn componentize_py_to_canon_handle( let instance = unsafe { PyObject::from_borrowed_ptr(*py, rep) }; - instance.setattr(*py, name, handle.to_object(*py)).unwrap(); + instance + .setattr(*py, name, handle.into_pyobject(*py).unwrap()) + .unwrap(); let finalizer = FINALIZE .get() @@ -1149,7 +1180,7 @@ pub extern "C" fn componentize_py_to_canon_handle( ( instance.clone_ref(*py), DROP_RESOURCE.get().unwrap(), - drop.to_object(*py), + drop.into_pyobject(*py).unwrap(), handle, ), ) diff --git a/src/bindgen.rs b/src/bindgen.rs index b953375..ba1db42 100644 --- a/src/bindgen.rs +++ b/src/bindgen.rs @@ -333,6 +333,7 @@ impl<'a> FunctionBindgen<'a> { .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); @@ -344,6 +345,7 @@ impl<'a> FunctionBindgen<'a> { 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); diff --git a/src/python.rs b/src/python.rs index 2c49e1e..bf97cb8 100644 --- a/src/python.rs +++ b/src/python.rs @@ -98,7 +98,7 @@ fn python_generate_bindings( #[pyo3(name = "script")] fn python_script(py: Python) -> PyResult<()> { crate::command::run( - py.import_bound("sys")? + py.import("sys")? .getattr("argv")? .extract::>()?, ) diff --git a/src/test/python_source/app.py b/src/test/python_source/app.py index 5f0b269..364477b 100644 --- a/src/test/python_source/app.py +++ b/src/test/python_source/app.py @@ -134,6 +134,12 @@ def read_file(self, path: str) -> bytes: return f.read() except: raise Err(traceback.format_exc()) + + def test_refcounts(self): + # Retrieve 5GiB in chunks of 1MiB, which should _not_ lead to a + # `MemoryError` if we're handling refcounts correctly in the runtime. + for _ in range(5 * 1024): + chunk = tests.get_bytes(1024 * 1024) class FooInterface(foo_exports.FooInterface): def test(self, s: str) -> str: diff --git a/src/test/tests.rs b/src/test/tests.rs index daf0d09..ad62663 100644 --- a/src/test/tests.rs +++ b/src/test/tests.rs @@ -60,6 +60,10 @@ impl TestsImports for Ctx { async fn output(&mut self, _: Frame) -> Result<()> { unreachable!() } + + async fn get_bytes(&mut self, count: u32) -> Result> { + Ok(vec![42u8; usize::try_from(count).unwrap()]) + } } macro_rules! load_guest_code { @@ -250,6 +254,11 @@ fn resource_import_and_export() -> Result<()> { }) } +#[test] +fn refcounts() -> Result<()> { + TESTER.test(|world, store, runtime| runtime.block_on(world.call_test_refcounts(store))) +} + #[test] fn resource_borrow_import() -> Result<()> { use componentize_py::test::resource_borrow_import::{Host, HostThing}; diff --git a/src/test/wit/tests.wit b/src/test/wit/tests.wit index acc58c7..48aa3cf 100644 --- a/src/test/wit/tests.wit +++ b/src/test/wit/tests.wit @@ -180,9 +180,13 @@ world tests { export read-file: func(path: string) -> result, string>; + export test-refcounts: func(); + record frame { id: s32, } import output: func(frame: frame); + + import get-bytes: func(count: u32) -> list; }