From 54156f638f7f6c3599751c6db9c06ecde8ee13c6 Mon Sep 17 00:00:00 2001 From: Bas Schoenmaeckers Date: Sat, 4 Apr 2026 17:21:44 +0200 Subject: [PATCH 01/12] export c api --- Cargo.lock | 27 ++++-- crates/capi/Cargo.toml | 18 ++++ crates/capi/src/bytesobject.rs | 16 ++++ crates/capi/src/import.rs | 9 ++ crates/capi/src/lib.rs | 33 +++++++ crates/capi/src/longobject.rs | 40 ++++++++ crates/capi/src/object.rs | 94 +++++++++++++++++++ crates/capi/src/pyerrors.rs | 58 ++++++++++++ crates/capi/src/pylifecycle.rs | 49 ++++++++++ crates/capi/src/pystate.rs | 28 ++++++ crates/capi/src/refcount.rs | 35 +++++++ crates/capi/src/traceback.rs | 9 ++ crates/capi/src/tupleobject.rs | 15 +++ crates/capi/src/unicodeobject.rs | 34 +++++++ .../pyo3_embed/.cargo/config.toml | 2 + example_projects/pyo3_embed/Cargo.toml | 12 +++ example_projects/pyo3_embed/README.md | 11 +++ example_projects/pyo3_embed/build.rs | 6 ++ .../pyo3_embed/pyo3-rustpython.config | 6 ++ example_projects/pyo3_embed/src/main.rs | 10 ++ 20 files changed, 502 insertions(+), 10 deletions(-) create mode 100644 crates/capi/Cargo.toml create mode 100644 crates/capi/src/bytesobject.rs create mode 100644 crates/capi/src/import.rs create mode 100644 crates/capi/src/lib.rs create mode 100644 crates/capi/src/longobject.rs create mode 100644 crates/capi/src/object.rs create mode 100644 crates/capi/src/pyerrors.rs create mode 100644 crates/capi/src/pylifecycle.rs create mode 100644 crates/capi/src/pystate.rs create mode 100644 crates/capi/src/refcount.rs create mode 100644 crates/capi/src/traceback.rs create mode 100644 crates/capi/src/tupleobject.rs create mode 100644 crates/capi/src/unicodeobject.rs create mode 100644 example_projects/pyo3_embed/.cargo/config.toml create mode 100644 example_projects/pyo3_embed/Cargo.toml create mode 100644 example_projects/pyo3_embed/README.md create mode 100644 example_projects/pyo3_embed/build.rs create mode 100644 example_projects/pyo3_embed/pyo3-rustpython.config create mode 100644 example_projects/pyo3_embed/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index 5fd981fd135..1eca00b9fbb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2643,9 +2643,9 @@ dependencies = [ [[package]] name = "pyo3" -version = "0.28.2" +version = "0.28.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf85e27e86080aafd5a22eae58a162e133a589551542b3e5cee4beb27e54f8e1" +checksum = "91fd8e38a3b50ed1167fb981cd6fd60147e091784c427b8f7183a7ee32c31c12" dependencies = [ "libc", "once_cell", @@ -2657,18 +2657,18 @@ dependencies = [ [[package]] name = "pyo3-build-config" -version = "0.28.2" +version = "0.28.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bf94ee265674bf76c09fa430b0e99c26e319c945d96ca0d5a8215f31bf81cf7" +checksum = "e368e7ddfdeb98c9bca7f8383be1648fd84ab466bf2bc015e94008db6d35611e" dependencies = [ "target-lexicon", ] [[package]] name = "pyo3-ffi" -version = "0.28.2" +version = "0.28.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "491aa5fc66d8059dd44a75f4580a2962c1862a1c2945359db36f6c2818b748dc" +checksum = "7f29e10af80b1f7ccaf7f69eace800a03ecd13e883acfacc1e5d0988605f651e" dependencies = [ "libc", "pyo3-build-config", @@ -2676,9 +2676,9 @@ dependencies = [ [[package]] name = "pyo3-macros" -version = "0.28.2" +version = "0.28.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5d671734e9d7a43449f8480f8b38115df67bef8d21f76837fa75ee7aaa5e52e" +checksum = "df6e520eff47c45997d2fc7dd8214b25dd1310918bbb2642156ef66a67f29813" dependencies = [ "proc-macro2", "pyo3-macros-backend", @@ -2688,9 +2688,9 @@ dependencies = [ [[package]] name = "pyo3-macros-backend" -version = "0.28.2" +version = "0.28.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22faaa1ce6c430a1f71658760497291065e6450d7b5dc2bcf254d49f66ee700a" +checksum = "c4cdc218d835738f81c2338f822078af45b4afdf8b2e33cbb5916f108b813acb" dependencies = [ "heck", "proc-macro2", @@ -3104,6 +3104,13 @@ dependencies = [ "winresource", ] +[[package]] +name = "rustpython-capi" +version = "0.5.0" +dependencies = [ + "rustpython-vm", +] + [[package]] name = "rustpython-codegen" version = "0.5.0" diff --git a/crates/capi/Cargo.toml b/crates/capi/Cargo.toml new file mode 100644 index 00000000000..bf3bc8ca285 --- /dev/null +++ b/crates/capi/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "rustpython-capi" +description = "Minimal CPython C-API compatibility exports for RustPython" +version.workspace = true +authors.workspace = true +edition.workspace = true +rust-version.workspace = true +repository.workspace = true +license.workspace = true + +[lib] +crate-type = ["staticlib"] + +[dependencies] +rustpython-vm = { workspace = true } + +[lints] +workspace = true diff --git a/crates/capi/src/bytesobject.rs b/crates/capi/src/bytesobject.rs new file mode 100644 index 00000000000..a5164bc8487 --- /dev/null +++ b/crates/capi/src/bytesobject.rs @@ -0,0 +1,16 @@ +use core::ffi::c_char; +use core::ptr; + +use crate::PyObject; + +#[unsafe(no_mangle)] +pub extern "C" fn PyBytes_Size(_bytes: *mut PyObject) -> isize { + crate::log_stub("PyBytes_Size"); + 0 +} + +#[unsafe(no_mangle)] +pub extern "C" fn PyBytes_AsString(_bytes: *mut PyObject) -> *mut c_char { + crate::log_stub("PyBytes_AsString"); + ptr::null_mut() +} diff --git a/crates/capi/src/import.rs b/crates/capi/src/import.rs new file mode 100644 index 00000000000..29ea7996687 --- /dev/null +++ b/crates/capi/src/import.rs @@ -0,0 +1,9 @@ +use core::ptr; + +use crate::PyObject; + +#[unsafe(no_mangle)] +pub extern "C" fn PyImport_Import(_name: *mut PyObject) -> *mut PyObject { + crate::log_stub("PyImport_Import"); + ptr::null_mut() +} diff --git a/crates/capi/src/lib.rs b/crates/capi/src/lib.rs new file mode 100644 index 00000000000..6da5d0e04fc --- /dev/null +++ b/crates/capi/src/lib.rs @@ -0,0 +1,33 @@ +use core::ffi::c_long; +pub use rustpython_vm::{PyObject}; + +extern crate alloc; + +pub mod bytesobject; +pub mod import; +pub mod longobject; +pub mod object; +pub mod pyerrors; +pub mod pylifecycle; +pub mod pystate; +pub mod refcount; +pub mod traceback; +pub mod tupleobject; +pub mod unicodeobject; + + +#[repr(C)] +pub struct PyThreadState { + _private: [u8; 0], +} + +#[repr(C)] +pub struct PyLongObject { + ob_base: PyObject, + value: c_long, +} + +#[inline] +pub(crate) fn log_stub(name: &str) { + eprintln!("[rustpython-capi stub] {name} called"); +} diff --git a/crates/capi/src/longobject.rs b/crates/capi/src/longobject.rs new file mode 100644 index 00000000000..586b8c4497a --- /dev/null +++ b/crates/capi/src/longobject.rs @@ -0,0 +1,40 @@ +use core::ffi::{c_long, c_longlong, c_ulong, c_ulonglong}; +use core::ptr; + +use crate::PyObject; + +#[unsafe(no_mangle)] +pub extern "C" fn PyLong_FromLong(_value: c_long) -> *mut PyObject { + crate::log_stub("PyLong_FromLong"); + ptr::null_mut() +} + +#[unsafe(no_mangle)] +pub extern "C" fn PyLong_FromLongLong(_value: c_longlong) -> *mut PyObject { + crate::log_stub("PyLong_FromLongLong"); + ptr::null_mut() +} + +#[unsafe(no_mangle)] +pub extern "C" fn PyLong_FromSsize_t(_value: isize) -> *mut PyObject { + crate::log_stub("PyLong_FromSsize_t"); + ptr::null_mut() +} + +#[unsafe(no_mangle)] +pub extern "C" fn PyLong_FromSize_t(_value: usize) -> *mut PyObject { + crate::log_stub("PyLong_FromSize_t"); + ptr::null_mut() +} + +#[unsafe(no_mangle)] +pub extern "C" fn PyLong_FromUnsignedLong(_value: c_ulong) -> *mut PyObject { + crate::log_stub("PyLong_FromUnsignedLong"); + ptr::null_mut() +} + +#[unsafe(no_mangle)] +pub extern "C" fn PyLong_FromUnsignedLongLong(_value: c_ulonglong) -> *mut PyObject { + crate::log_stub("PyLong_FromUnsignedLongLong"); + ptr::null_mut() +} diff --git a/crates/capi/src/object.rs b/crates/capi/src/object.rs new file mode 100644 index 00000000000..f457606bac2 --- /dev/null +++ b/crates/capi/src/object.rs @@ -0,0 +1,94 @@ +use std::mem::{transmute, MaybeUninit}; +use std::ptr::NonNull; +use rustpython_vm::{PyObjectRef, VirtualMachine, Context}; +use rustpython_vm::builtins::PyType; +use crate::{PyObject}; + +type PyTypeObject = MaybeUninit<&'static PyType>; + +#[unsafe(no_mangle)] +pub static mut PyType_Type: PyTypeObject = MaybeUninit::uninit(); + +#[unsafe(no_mangle)] +pub static mut PyLong_Type: PyTypeObject = MaybeUninit::uninit(); + +#[unsafe(no_mangle)] +pub static mut PyTuple_Type: PyTypeObject = MaybeUninit::uninit(); + +#[unsafe(no_mangle)] +pub static mut PyUnicode_Type: PyTypeObject = MaybeUninit::uninit(); + +unsafe fn setup_type_pointers(ctx: &Context) { + let zoo = &ctx.types; + + unsafe { + PyType_Type.write(zoo.type_type.payload()); + PyLong_Type.write(zoo.int_type.payload()); + PyTuple_Type.write(zoo.tuple_type.payload()); + PyUnicode_Type.write(zoo.str_type.payload()); + } +} + +#[unsafe(no_mangle)] +#[allow(clippy::not_unsafe_ptr_arg_deref)] +pub extern "C" fn _Py_TYPE(op: *mut PyObject) -> *mut PyTypeObject { + if op.is_null() { + return std::ptr::null_mut(); + } + + // SAFETY: op is non-null and expected to be a valid pointer for this shim. + unsafe { transmute((*op).class()) } +} + +#[unsafe(no_mangle)] +pub extern "C" fn Py_TYPE(op: *mut PyObject) -> *mut PyTypeObject { + _Py_TYPE(op) +} + +#[unsafe(no_mangle)] +pub extern "C" fn PyType_GetFlags(_ty: *mut PyTypeObject) -> usize { + crate::log_stub("PyType_GetFlags"); + 0 +} + +#[unsafe(no_mangle)] +pub extern "C" fn PyType_GetName(_ty: *mut PyTypeObject) -> *mut PyObject { + crate::log_stub("PyType_GetName"); + std::ptr::null_mut() +} + +#[unsafe(no_mangle)] +pub extern "C" fn PyType_GetQualName(_ty: *mut PyTypeObject) -> *mut PyObject { + crate::log_stub("PyType_GetQualName"); + std::ptr::null_mut() +} + +#[unsafe(no_mangle)] +pub extern "C" fn PyObject_CallNoArgs(_callable: *mut PyObject) -> *mut PyObject { + crate::log_stub("PyObject_CallNoArgs"); + std::ptr::null_mut() +} + +#[unsafe(no_mangle)] +pub extern "C" fn PyObject_GetAttr(_obj: *mut PyObject, _name: *mut PyObject) -> *mut PyObject { + crate::log_stub("PyObject_GetAttr"); + std::ptr::null_mut() +} + +#[unsafe(no_mangle)] +pub extern "C" fn PyObject_Repr(_obj: *mut PyObject) -> *mut PyObject { + crate::log_stub("PyObject_Repr"); + std::ptr::null_mut() +} + +#[unsafe(no_mangle)] +pub extern "C" fn PyObject_Str(_obj: *mut PyObject) -> *mut PyObject { + crate::log_stub("PyObject_Str"); + std::ptr::null_mut() +} + +#[unsafe(no_mangle)] +pub extern "C" fn Py_GetConstantBorrowed(_constant_id: core::ffi::c_uint) -> *mut PyObject { + crate::log_stub("Py_GetConstantBorrowed"); + std::ptr::null_mut() +} diff --git a/crates/capi/src/pyerrors.rs b/crates/capi/src/pyerrors.rs new file mode 100644 index 00000000000..fba1e4a14b4 --- /dev/null +++ b/crates/capi/src/pyerrors.rs @@ -0,0 +1,58 @@ +use core::ffi::{c_char, c_int}; +use core::ptr; + +use crate::PyObject; + +#[unsafe(no_mangle)] +pub static mut PyExc_BaseException: *mut PyObject = ptr::null_mut(); + +#[unsafe(no_mangle)] +pub static mut PyExc_TypeError: *mut PyObject = ptr::null_mut(); + +#[unsafe(no_mangle)] +pub extern "C" fn PyErr_GetRaisedException() -> *mut PyObject { + crate::log_stub("PyErr_GetRaisedException"); + ptr::null_mut() +} + +#[unsafe(no_mangle)] +pub extern "C" fn PyErr_SetRaisedException(_exc: *mut PyObject) { + crate::log_stub("PyErr_SetRaisedException"); +} + +#[unsafe(no_mangle)] +pub extern "C" fn PyErr_SetObject(_exception: *mut PyObject, _value: *mut PyObject) { + crate::log_stub("PyErr_SetObject"); +} + +#[unsafe(no_mangle)] +pub extern "C" fn PyErr_SetString(_exception: *mut PyObject, _message: *const c_char) { + crate::log_stub("PyErr_SetString"); +} + +#[unsafe(no_mangle)] +pub extern "C" fn PyErr_PrintEx(_set_sys_last_vars: c_int) { + crate::log_stub("PyErr_PrintEx"); +} + +#[unsafe(no_mangle)] +pub extern "C" fn PyErr_WriteUnraisable(_obj: *mut PyObject) { + crate::log_stub("PyErr_WriteUnraisable"); +} + +#[unsafe(no_mangle)] +pub extern "C" fn PyErr_NewExceptionWithDoc( + _name: *const c_char, + _doc: *const c_char, + _base: *mut PyObject, + _dict: *mut PyObject, +) -> *mut PyObject { + crate::log_stub("PyErr_NewExceptionWithDoc"); + ptr::null_mut() +} + +#[unsafe(no_mangle)] +pub extern "C" fn PyException_GetTraceback(_exc: *mut PyObject) -> *mut PyObject { + crate::log_stub("PyException_GetTraceback"); + ptr::null_mut() +} diff --git a/crates/capi/src/pylifecycle.rs b/crates/capi/src/pylifecycle.rs new file mode 100644 index 00000000000..96fbc7b81d5 --- /dev/null +++ b/crates/capi/src/pylifecycle.rs @@ -0,0 +1,49 @@ +use core::ffi::c_int; +use std::cell::RefCell; +use std::mem::ManuallyDrop; +use rustpython_vm::Interpreter; + +thread_local! { + pub static INTERP: RefCell>> = const { RefCell::new(None) }; +} + +#[unsafe(no_mangle)] +pub extern "C-unwind" fn Py_IsInitialized() -> c_int { + INTERP.with(|interp| interp.borrow().is_some() as c_int) +} + +#[unsafe(no_mangle)] +pub extern "C-unwind" fn Py_Initialize() { + Py_InitializeEx(0); +} + +#[unsafe(no_mangle)] +pub extern "C-unwind" fn Py_InitializeEx(_initsigs: c_int) { + if INTERP.with(|interp| interp.borrow().is_none()) { + let interp = Interpreter::with_init(Default::default(), |_vm| { + }); + + INTERP.with(|interp_ref| { + *interp_ref.borrow_mut() = Some(ManuallyDrop::new(interp)); + }); + } +} + +#[unsafe(no_mangle)] +pub extern "C-unwind" fn Py_Finalize() { + let _ = Py_FinalizeEx(); +} + +#[unsafe(no_mangle)] +pub extern "C-unwind" fn Py_FinalizeEx() -> c_int { + INTERP.with(|interp_ref| { + let interp = ManuallyDrop::into_inner(interp_ref.borrow_mut().take() + .expect("Py_FinalizeEx called without an active interpreter")); + interp.finalize(None) + }) as _ +} + +#[unsafe(no_mangle)] +pub extern "C-unwind" fn Py_IsFinalizing() -> c_int { + 0 +} diff --git a/crates/capi/src/pystate.rs b/crates/capi/src/pystate.rs new file mode 100644 index 00000000000..24ce27f6f43 --- /dev/null +++ b/crates/capi/src/pystate.rs @@ -0,0 +1,28 @@ +use crate::{PyThreadState, log_stub}; +use core::ffi::c_int; +use core::ptr; + +#[allow(non_camel_case_types)] +type PyGILState_STATE = c_int; + +#[unsafe(no_mangle)] +pub extern "C" fn PyGILState_Ensure() -> PyGILState_STATE { + log_stub("PyGILState_Ensure"); + 0 +} + +#[unsafe(no_mangle)] +pub extern "C" fn PyGILState_Release(_state: PyGILState_STATE) { + log_stub("PyGILState_Release"); +} + +#[unsafe(no_mangle)] +pub extern "C" fn PyEval_SaveThread() -> *mut PyThreadState { + log_stub("PyEval_SaveThread"); + ptr::null_mut() +} + +#[unsafe(no_mangle)] +pub extern "C" fn PyEval_RestoreThread(_tstate: *mut PyThreadState) { + log_stub("PyEval_RestoreThread"); +} diff --git a/crates/capi/src/refcount.rs b/crates/capi/src/refcount.rs new file mode 100644 index 00000000000..1f26304b871 --- /dev/null +++ b/crates/capi/src/refcount.rs @@ -0,0 +1,35 @@ +use alloc::boxed::Box; +use std::ptr::NonNull; +use rustpython_vm::PyObjectRef; +use crate::object::PyLong_Type; +use crate::{PyLongObject, PyObject}; + +#[unsafe(no_mangle)] +#[allow(clippy::not_unsafe_ptr_arg_deref)] +pub extern "C" fn _Py_DecRef(op: *mut PyObject) { + let Some(ptr) = NonNull::new(op) else { + return; + }; + + let owned = unsafe { + PyObjectRef::from_raw(ptr) + }; + + // Dropping so we decrement the refcount + drop(owned); +} + +#[unsafe(no_mangle)] +#[allow(clippy::not_unsafe_ptr_arg_deref)] +pub extern "C" fn _Py_IncRef(op: *mut PyObject) { + if op.is_null() { + return; + } + + // SAFETY: op is non-null and expected to be a valid pointer for this shim. + let owned = unsafe { + (*op).to_owned() + }; + + std::mem::forget(owned); +} diff --git a/crates/capi/src/traceback.rs b/crates/capi/src/traceback.rs new file mode 100644 index 00000000000..c73c952d5e7 --- /dev/null +++ b/crates/capi/src/traceback.rs @@ -0,0 +1,9 @@ +use core::ffi::c_int; + +use crate::PyObject; + +#[unsafe(no_mangle)] +pub extern "C" fn PyTraceBack_Print(_tb: *mut PyObject, _file: *mut PyObject) -> c_int { + crate::log_stub("PyTraceBack_Print"); + -1 +} diff --git a/crates/capi/src/tupleobject.rs b/crates/capi/src/tupleobject.rs new file mode 100644 index 00000000000..996a835bb78 --- /dev/null +++ b/crates/capi/src/tupleobject.rs @@ -0,0 +1,15 @@ +use core::ptr; + +use crate::PyObject; + +#[unsafe(no_mangle)] +pub extern "C" fn PyTuple_Size(_tuple: *mut PyObject) -> isize { + crate::log_stub("PyTuple_Size"); + 0 +} + +#[unsafe(no_mangle)] +pub extern "C" fn PyTuple_GetItem(_tuple: *mut PyObject, _pos: isize) -> *mut PyObject { + crate::log_stub("PyTuple_GetItem"); + ptr::null_mut() +} diff --git a/crates/capi/src/unicodeobject.rs b/crates/capi/src/unicodeobject.rs new file mode 100644 index 00000000000..2b4fb081d66 --- /dev/null +++ b/crates/capi/src/unicodeobject.rs @@ -0,0 +1,34 @@ +use core::ffi::c_char; +use core::ptr; + +use crate::PyObject; + +#[unsafe(no_mangle)] +pub extern "C" fn PyUnicode_FromStringAndSize(_s: *const c_char, _len: isize) -> *mut PyObject { + crate::log_stub("PyUnicode_FromStringAndSize"); + ptr::null_mut() +} + +#[unsafe(no_mangle)] +pub extern "C" fn PyUnicode_AsUTF8AndSize( + _unicode: *mut PyObject, + _size: *mut isize, +) -> *const c_char { + crate::log_stub("PyUnicode_AsUTF8AndSize"); + ptr::null() +} + +#[unsafe(no_mangle)] +pub extern "C" fn PyUnicode_AsEncodedString( + _unicode: *mut PyObject, + _encoding: *const c_char, + _errors: *const c_char, +) -> *mut PyObject { + crate::log_stub("PyUnicode_AsEncodedString"); + ptr::null_mut() +} + +#[unsafe(no_mangle)] +pub extern "C" fn PyUnicode_InternInPlace(_string: *mut *mut PyObject) { + crate::log_stub("PyUnicode_InternInPlace"); +} diff --git a/example_projects/pyo3_embed/.cargo/config.toml b/example_projects/pyo3_embed/.cargo/config.toml new file mode 100644 index 00000000000..54a884e2458 --- /dev/null +++ b/example_projects/pyo3_embed/.cargo/config.toml @@ -0,0 +1,2 @@ +[env] +PYO3_CONFIG_FILE = { value = "pyo3-rustpython.config", relative = true } diff --git a/example_projects/pyo3_embed/Cargo.toml b/example_projects/pyo3_embed/Cargo.toml new file mode 100644 index 00000000000..6174170c143 --- /dev/null +++ b/example_projects/pyo3_embed/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "example_pyo3_embed" +version = "0.1.0" +edition = "2021" + +[dependencies] +pyo3 = { path = "../../../pyo3", default-features = false, features = ["abi3-py314"] } +rustpython-capi = { path = "../../crates/capi" } + +[workspace] + +[patch.crates-io] diff --git a/example_projects/pyo3_embed/README.md b/example_projects/pyo3_embed/README.md new file mode 100644 index 00000000000..7d9cc2cf98b --- /dev/null +++ b/example_projects/pyo3_embed/README.md @@ -0,0 +1,11 @@ +# PyO3 embed against RustPython C-API + +This example demonstrates linking `pyo3` against RustPython's minimal C-API shim (`rustpython-capi`) instead of a system CPython library. + +From this directory, run: + +```shell +cargo run +``` + +The local `.cargo/config.toml` sets `PYO3_CONFIG_FILE` automatically. diff --git a/example_projects/pyo3_embed/build.rs b/example_projects/pyo3_embed/build.rs new file mode 100644 index 00000000000..8d379be42c0 --- /dev/null +++ b/example_projects/pyo3_embed/build.rs @@ -0,0 +1,6 @@ +fn main() { + println!( + "cargo:rustc-link-arg=/Users/basschoenmaeckers/repo/RustPython/target/debug/librustpython_capi.a" + ); + println!("cargo:rustc-link-lib=framework=CoreFoundation"); +} diff --git a/example_projects/pyo3_embed/pyo3-rustpython.config b/example_projects/pyo3_embed/pyo3-rustpython.config new file mode 100644 index 00000000000..2cf06cd886c --- /dev/null +++ b/example_projects/pyo3_embed/pyo3-rustpython.config @@ -0,0 +1,6 @@ +implementation=CPython +version=3.14 +shared=false +abi3=true +build_flags= +suppress_build_script_link_lines=true diff --git a/example_projects/pyo3_embed/src/main.rs b/example_projects/pyo3_embed/src/main.rs new file mode 100644 index 00000000000..72ba168a907 --- /dev/null +++ b/example_projects/pyo3_embed/src/main.rs @@ -0,0 +1,10 @@ +use pyo3::prelude::*; +use pyo3::types::PyInt; + +fn main() { + Python::initialize(); + + Python::attach(|py| { + // let _x = PyInt::new(py, 123); + }); +} From 2250a1c50923847f737eb15df8b73737616f5c48 Mon Sep 17 00:00:00 2001 From: Bas Schoenmaeckers Date: Sun, 5 Apr 2026 10:36:24 +0200 Subject: [PATCH 02/12] type mapping v1 --- crates/capi/src/object.rs | 51 +++++++++++++++++++++++---------------- 1 file changed, 30 insertions(+), 21 deletions(-) diff --git a/crates/capi/src/object.rs b/crates/capi/src/object.rs index f457606bac2..6a382bb7fa2 100644 --- a/crates/capi/src/object.rs +++ b/crates/capi/src/object.rs @@ -1,47 +1,56 @@ use std::mem::{transmute, MaybeUninit}; use std::ptr::NonNull; -use rustpython_vm::{PyObjectRef, VirtualMachine, Context}; +use rustpython_vm::{PyObjectRef, VirtualMachine, Context, AsObject}; use rustpython_vm::builtins::PyType; use crate::{PyObject}; +use crate::object::PyTypeObject::*; -type PyTypeObject = MaybeUninit<&'static PyType>; -#[unsafe(no_mangle)] -pub static mut PyType_Type: PyTypeObject = MaybeUninit::uninit(); -#[unsafe(no_mangle)] -pub static mut PyLong_Type: PyTypeObject = MaybeUninit::uninit(); +pub enum PyTypeObject { + Type, + Long, + Tuple, + Unicode, +} #[unsafe(no_mangle)] -pub static mut PyTuple_Type: PyTypeObject = MaybeUninit::uninit(); +pub static mut PyType_Type: PyTypeObject = Type; #[unsafe(no_mangle)] -pub static mut PyUnicode_Type: PyTypeObject = MaybeUninit::uninit(); +pub static mut PyLong_Type: PyTypeObject = Long; -unsafe fn setup_type_pointers(ctx: &Context) { - let zoo = &ctx.types; +#[unsafe(no_mangle)] +pub static mut PyTuple_Type: PyTypeObject = Tuple; - unsafe { - PyType_Type.write(zoo.type_type.payload()); - PyLong_Type.write(zoo.int_type.payload()); - PyTuple_Type.write(zoo.tuple_type.payload()); - PyUnicode_Type.write(zoo.str_type.payload()); - } -} +#[unsafe(no_mangle)] +pub static mut PyUnicode_Type: PyTypeObject = Unicode; #[unsafe(no_mangle)] #[allow(clippy::not_unsafe_ptr_arg_deref)] -pub extern "C" fn _Py_TYPE(op: *mut PyObject) -> *mut PyTypeObject { +pub extern "C-unwind" fn _Py_TYPE(op: *mut PyObject) -> *mut PyTypeObject { if op.is_null() { return std::ptr::null_mut(); } - // SAFETY: op is non-null and expected to be a valid pointer for this shim. - unsafe { transmute((*op).class()) } + let zoo = &Context::genesis().types; + let ty = unsafe { (*op).class()}; + + if ty.is(zoo.type_type) { + unsafe { &raw mut PyType_Type } + } else if ty.is(zoo.int_type){ + unsafe { &raw mut PyLong_Type } + } else if ty.is(zoo.tuple_type){ + unsafe { &raw mut PyTuple_Type } + } else if ty.is(zoo.str_type){ + unsafe { &raw mut PyUnicode_Type } + } else { + todo!("Unsupported type: {:?}", ty.name()); + } } #[unsafe(no_mangle)] -pub extern "C" fn Py_TYPE(op: *mut PyObject) -> *mut PyTypeObject { +pub extern "C-unwind" fn Py_TYPE(op: *mut PyObject) -> *mut PyTypeObject { _Py_TYPE(op) } From bc96211cab0abeead51ba565ed11cd6951788154 Mon Sep 17 00:00:00 2001 From: Bas Schoenmaeckers Date: Sun, 5 Apr 2026 10:59:08 +0200 Subject: [PATCH 03/12] Type mapping v2 --- crates/capi/src/object.rs | 69 +++++++++++++++++++++++-------------- crates/capi/src/refcount.rs | 4 +-- 2 files changed, 44 insertions(+), 29 deletions(-) diff --git a/crates/capi/src/object.rs b/crates/capi/src/object.rs index 6a382bb7fa2..7f60a8753aa 100644 --- a/crates/capi/src/object.rs +++ b/crates/capi/src/object.rs @@ -1,30 +1,46 @@ -use std::mem::{transmute, MaybeUninit}; -use std::ptr::NonNull; -use rustpython_vm::{PyObjectRef, VirtualMachine, Context, AsObject}; +use std::sync::LazyLock; +use rustpython_vm::{Context, AsObject, Py}; use rustpython_vm::builtins::PyType; use crate::{PyObject}; -use crate::object::PyTypeObject::*; -pub enum PyTypeObject { - Type, - Long, - Tuple, - Unicode, +pub struct PyTypeObject { + ty: LazyLock<&'static Py>, +} + +impl PyTypeObject { + const fn new(f: fn() -> &'static Py) -> PyTypeObject + { + PyTypeObject { + ty: LazyLock::new(f), + } + } } #[unsafe(no_mangle)] -pub static mut PyType_Type: PyTypeObject = Type; +pub static mut PyType_Type: PyTypeObject = PyTypeObject::new(|| { + let zoo = &Context::genesis().types; + zoo.type_type +}); #[unsafe(no_mangle)] -pub static mut PyLong_Type: PyTypeObject = Long; +pub static mut PyLong_Type: PyTypeObject = PyTypeObject::new(|| { + let zoo = &Context::genesis().types; + zoo.int_type +}); #[unsafe(no_mangle)] -pub static mut PyTuple_Type: PyTypeObject = Tuple; +pub static mut PyTuple_Type: PyTypeObject = PyTypeObject::new(|| { + let zoo = &Context::genesis().types; + zoo.tuple_type +}); #[unsafe(no_mangle)] -pub static mut PyUnicode_Type: PyTypeObject = Unicode; +pub static mut PyUnicode_Type: PyTypeObject = PyTypeObject::new(|| { + let zoo = &Context::genesis().types; + zoo.union_type +}); #[unsafe(no_mangle)] #[allow(clippy::not_unsafe_ptr_arg_deref)] @@ -33,20 +49,21 @@ pub extern "C-unwind" fn _Py_TYPE(op: *mut PyObject) -> *mut PyTypeObject { return std::ptr::null_mut(); } - let zoo = &Context::genesis().types; - let ty = unsafe { (*op).class()}; - - if ty.is(zoo.type_type) { - unsafe { &raw mut PyType_Type } - } else if ty.is(zoo.int_type){ - unsafe { &raw mut PyLong_Type } - } else if ty.is(zoo.tuple_type){ - unsafe { &raw mut PyTuple_Type } - } else if ty.is(zoo.str_type){ - unsafe { &raw mut PyUnicode_Type } - } else { - todo!("Unsupported type: {:?}", ty.name()); + unsafe { + let ty = (*op).class(); + if ty.is(*PyTuple_Type.ty) { + &raw mut PyType_Type + } else if ty.is(*PyTuple_Type.ty){ + &raw mut PyLong_Type + } else if ty.is(*PyTuple_Type.ty){ + &raw mut PyTuple_Type + } else if ty.is(*PyTuple_Type.ty){ + &raw mut PyUnicode_Type + } else { + todo!("Unsupported type: {:?}", ty.name()); + } } + } #[unsafe(no_mangle)] diff --git a/crates/capi/src/refcount.rs b/crates/capi/src/refcount.rs index 1f26304b871..76652175617 100644 --- a/crates/capi/src/refcount.rs +++ b/crates/capi/src/refcount.rs @@ -1,8 +1,6 @@ -use alloc::boxed::Box; use std::ptr::NonNull; use rustpython_vm::PyObjectRef; -use crate::object::PyLong_Type; -use crate::{PyLongObject, PyObject}; +use crate::{PyObject}; #[unsafe(no_mangle)] #[allow(clippy::not_unsafe_ptr_arg_deref)] From 46307f6cebc83d6b1cdf850e551b9f6f8ae4d161 Mon Sep 17 00:00:00 2001 From: Bas Schoenmaeckers Date: Sun, 5 Apr 2026 11:18:11 +0200 Subject: [PATCH 04/12] Implement type flags --- crates/capi/src/longobject.rs | 17 ++++- crates/capi/src/object.rs | 97 ++++++++++++++++--------- example_projects/pyo3_embed/src/main.rs | 5 +- 3 files changed, 81 insertions(+), 38 deletions(-) diff --git a/crates/capi/src/longobject.rs b/crates/capi/src/longobject.rs index 586b8c4497a..a7d620908b7 100644 --- a/crates/capi/src/longobject.rs +++ b/crates/capi/src/longobject.rs @@ -2,11 +2,22 @@ use core::ffi::{c_long, c_longlong, c_ulong, c_ulonglong}; use core::ptr; use crate::PyObject; +use crate::pylifecycle::INTERP; +use rustpython_vm::PyObjectRef; #[unsafe(no_mangle)] -pub extern "C" fn PyLong_FromLong(_value: c_long) -> *mut PyObject { - crate::log_stub("PyLong_FromLong"); - ptr::null_mut() +pub extern "C" fn PyLong_FromLong(value: c_long) -> *mut PyObject { + INTERP.with(|interp_ref| { + let interp = interp_ref.borrow(); + let interp = interp + .as_ref() + .expect("PyLong_FromLong called before Py_InitializeEx"); + + interp.enter(|vm| { + let obj: PyObjectRef = vm.ctx.new_int(value).into(); + obj.into_raw().as_ptr() + }) + }) } #[unsafe(no_mangle)] diff --git a/crates/capi/src/object.rs b/crates/capi/src/object.rs index 7f60a8753aa..1e14f4c3f1b 100644 --- a/crates/capi/src/object.rs +++ b/crates/capi/src/object.rs @@ -1,46 +1,73 @@ -use std::sync::LazyLock; -use rustpython_vm::{Context, AsObject, Py}; -use rustpython_vm::builtins::PyType; -use crate::{PyObject}; - +use core::ffi::c_ulong; +use crate::PyObject; +use rustpython_vm::builtins::PyType; +use rustpython_vm::{AsObject, Context, Py}; +use std::sync::LazyLock; -pub struct PyTypeObject { +pub struct PyTypeObject { ty: LazyLock<&'static Py>, + flags: c_ulong, } impl PyTypeObject { - const fn new(f: fn() -> &'static Py) -> PyTypeObject - { + const fn new(f: fn() -> &'static Py, flags: c_ulong) -> PyTypeObject { PyTypeObject { ty: LazyLock::new(f), + flags, } } } +const PY_TPFLAGS_HAVE_STACKLESS_EXTENSION: c_ulong = 0; +const PY_TPFLAGS_HAVE_VERSION_TAG: c_ulong = 1 << 18; +const PY_TPFLAGS_DEFAULT: c_ulong = + PY_TPFLAGS_HAVE_STACKLESS_EXTENSION | PY_TPFLAGS_HAVE_VERSION_TAG; +const PY_TPFLAGS_IMMUTABLETYPE: c_ulong = 1 << 8; +const PY_TPFLAGS_BASETYPE: c_ulong = 1 << 10; +const PY_TPFLAGS_LONG_SUBCLASS: c_ulong = 1 << 24; +const PY_TPFLAGS_TUPLE_SUBCLASS: c_ulong = 1 << 26; +const PY_TPFLAGS_UNICODE_SUBCLASS: c_ulong = 1 << 28; +const PY_TPFLAGS_TYPE_SUBCLASS: c_ulong = 1 << 31; + #[unsafe(no_mangle)] -pub static mut PyType_Type: PyTypeObject = PyTypeObject::new(|| { - let zoo = &Context::genesis().types; - zoo.type_type -}); +pub static mut PyType_Type: PyTypeObject = PyTypeObject::new( + || { + let zoo = &Context::genesis().types; + zoo.type_type + }, + PY_TPFLAGS_DEFAULT | PY_TPFLAGS_IMMUTABLETYPE | PY_TPFLAGS_BASETYPE | PY_TPFLAGS_TYPE_SUBCLASS, +); #[unsafe(no_mangle)] -pub static mut PyLong_Type: PyTypeObject = PyTypeObject::new(|| { - let zoo = &Context::genesis().types; - zoo.int_type -}); +pub static mut PyLong_Type: PyTypeObject = PyTypeObject::new( + || { + let zoo = &Context::genesis().types; + zoo.int_type + }, + PY_TPFLAGS_DEFAULT | PY_TPFLAGS_IMMUTABLETYPE | PY_TPFLAGS_BASETYPE | PY_TPFLAGS_LONG_SUBCLASS, +); #[unsafe(no_mangle)] -pub static mut PyTuple_Type: PyTypeObject = PyTypeObject::new(|| { - let zoo = &Context::genesis().types; - zoo.tuple_type -}); +pub static mut PyTuple_Type: PyTypeObject = PyTypeObject::new( + || { + let zoo = &Context::genesis().types; + zoo.tuple_type + }, + PY_TPFLAGS_DEFAULT | PY_TPFLAGS_IMMUTABLETYPE | PY_TPFLAGS_BASETYPE | PY_TPFLAGS_TUPLE_SUBCLASS, +); #[unsafe(no_mangle)] -pub static mut PyUnicode_Type: PyTypeObject = PyTypeObject::new(|| { - let zoo = &Context::genesis().types; - zoo.union_type -}); +pub static mut PyUnicode_Type: PyTypeObject = PyTypeObject::new( + || { + let zoo = &Context::genesis().types; + zoo.str_type + }, + PY_TPFLAGS_DEFAULT + | PY_TPFLAGS_IMMUTABLETYPE + | PY_TPFLAGS_BASETYPE + | PY_TPFLAGS_UNICODE_SUBCLASS, +); #[unsafe(no_mangle)] #[allow(clippy::not_unsafe_ptr_arg_deref)] @@ -51,19 +78,18 @@ pub extern "C-unwind" fn _Py_TYPE(op: *mut PyObject) -> *mut PyTypeObject { unsafe { let ty = (*op).class(); - if ty.is(*PyTuple_Type.ty) { - &raw mut PyType_Type - } else if ty.is(*PyTuple_Type.ty){ + if ty.is(*PyType_Type.ty) { + &raw mut PyType_Type + } else if ty.is(*PyLong_Type.ty) { &raw mut PyLong_Type - } else if ty.is(*PyTuple_Type.ty){ + } else if ty.is(*PyTuple_Type.ty) { &raw mut PyTuple_Type - } else if ty.is(*PyTuple_Type.ty){ + } else if ty.is(*PyUnicode_Type.ty) { &raw mut PyUnicode_Type } else { todo!("Unsupported type: {:?}", ty.name()); } } - } #[unsafe(no_mangle)] @@ -72,9 +98,14 @@ pub extern "C-unwind" fn Py_TYPE(op: *mut PyObject) -> *mut PyTypeObject { } #[unsafe(no_mangle)] -pub extern "C" fn PyType_GetFlags(_ty: *mut PyTypeObject) -> usize { - crate::log_stub("PyType_GetFlags"); - 0 +#[allow(clippy::not_unsafe_ptr_arg_deref)] +pub extern "C-unwind" fn PyType_GetFlags(ty: *mut PyTypeObject) -> c_ulong { + if ty.is_null() { + panic!("PyType_GetFlags called with null type pointer"); + } + + // SAFETY: caller guarantees this is a valid exported type object pointer. + unsafe { (*ty).flags } } #[unsafe(no_mangle)] diff --git a/example_projects/pyo3_embed/src/main.rs b/example_projects/pyo3_embed/src/main.rs index 72ba168a907..8c24df57817 100644 --- a/example_projects/pyo3_embed/src/main.rs +++ b/example_projects/pyo3_embed/src/main.rs @@ -1,10 +1,11 @@ use pyo3::prelude::*; -use pyo3::types::PyInt; +use pyo3::types::{PyInt, PyString}; fn main() { Python::initialize(); Python::attach(|py| { - // let _x = PyInt::new(py, 123); + let number = PyInt::new(py, 123); + assert!(number.is_instance_of::()); }); } From b08d41ab2703917e9a67b7240a0d6fc5f42413bf Mon Sep 17 00:00:00 2001 From: Bas Schoenmaeckers Date: Sun, 5 Apr 2026 11:22:46 +0200 Subject: [PATCH 05/12] Implement `PyUnicode_FromStringAndSize` --- crates/capi/src/unicodeobject.rs | 31 ++++++++++++++++++++++--- example_projects/pyo3_embed/src/main.rs | 3 +++ 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/crates/capi/src/unicodeobject.rs b/crates/capi/src/unicodeobject.rs index 2b4fb081d66..0d61b4da465 100644 --- a/crates/capi/src/unicodeobject.rs +++ b/crates/capi/src/unicodeobject.rs @@ -1,12 +1,37 @@ use core::ffi::c_char; use core::ptr; +use core::slice; +use core::str; use crate::PyObject; +use crate::pylifecycle::INTERP; +use rustpython_vm::PyObjectRef; #[unsafe(no_mangle)] -pub extern "C" fn PyUnicode_FromStringAndSize(_s: *const c_char, _len: isize) -> *mut PyObject { - crate::log_stub("PyUnicode_FromStringAndSize"); - ptr::null_mut() +pub extern "C" fn PyUnicode_FromStringAndSize(s: *const c_char, len: isize) -> *mut PyObject { + let len = usize::try_from(len).expect("PyUnicode_FromStringAndSize called with negative len"); + let text = if s.is_null() { + if len != 0 { + panic!("PyUnicode_FromStringAndSize called with null data and non-zero len"); + } + "" + } else { + // SAFETY: caller passes a valid C buffer of length `len`. + let bytes = unsafe { slice::from_raw_parts(s.cast::(), len) }; + str::from_utf8(bytes).expect("PyUnicode_FromStringAndSize got non-UTF8 data") + }; + + INTERP.with(|interp_ref| { + let interp = interp_ref.borrow(); + let interp = interp + .as_ref() + .expect("PyUnicode_FromStringAndSize called before Py_InitializeEx"); + + interp.enter(|vm| { + let obj: PyObjectRef = vm.ctx.new_str(text).into(); + obj.into_raw().as_ptr() + }) + }) } #[unsafe(no_mangle)] diff --git a/example_projects/pyo3_embed/src/main.rs b/example_projects/pyo3_embed/src/main.rs index 8c24df57817..892ea65b94e 100644 --- a/example_projects/pyo3_embed/src/main.rs +++ b/example_projects/pyo3_embed/src/main.rs @@ -7,5 +7,8 @@ fn main() { Python::attach(|py| { let number = PyInt::new(py, 123); assert!(number.is_instance_of::()); + + let string = PyString::new(py, "Hello, World!"); + assert!(string.is_instance_of::()); }); } From 34c13f496a134128f153ca5bb60316231e8537ae Mon Sep 17 00:00:00 2001 From: Bas Schoenmaeckers Date: Sun, 5 Apr 2026 11:26:02 +0200 Subject: [PATCH 06/12] Do not use `C-unwind` --- crates/capi/src/object.rs | 6 +++--- crates/capi/src/pylifecycle.rs | 25 ++++++++++++++----------- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/crates/capi/src/object.rs b/crates/capi/src/object.rs index 1e14f4c3f1b..e09ecdcd74f 100644 --- a/crates/capi/src/object.rs +++ b/crates/capi/src/object.rs @@ -71,7 +71,7 @@ pub static mut PyUnicode_Type: PyTypeObject = PyTypeObject::new( #[unsafe(no_mangle)] #[allow(clippy::not_unsafe_ptr_arg_deref)] -pub extern "C-unwind" fn _Py_TYPE(op: *mut PyObject) -> *mut PyTypeObject { +pub extern "C" fn _Py_TYPE(op: *mut PyObject) -> *mut PyTypeObject { if op.is_null() { return std::ptr::null_mut(); } @@ -93,13 +93,13 @@ pub extern "C-unwind" fn _Py_TYPE(op: *mut PyObject) -> *mut PyTypeObject { } #[unsafe(no_mangle)] -pub extern "C-unwind" fn Py_TYPE(op: *mut PyObject) -> *mut PyTypeObject { +pub extern "C" fn Py_TYPE(op: *mut PyObject) -> *mut PyTypeObject { _Py_TYPE(op) } #[unsafe(no_mangle)] #[allow(clippy::not_unsafe_ptr_arg_deref)] -pub extern "C-unwind" fn PyType_GetFlags(ty: *mut PyTypeObject) -> c_ulong { +pub extern "C" fn PyType_GetFlags(ty: *mut PyTypeObject) -> c_ulong { if ty.is_null() { panic!("PyType_GetFlags called with null type pointer"); } diff --git a/crates/capi/src/pylifecycle.rs b/crates/capi/src/pylifecycle.rs index 96fbc7b81d5..b70fe05fba9 100644 --- a/crates/capi/src/pylifecycle.rs +++ b/crates/capi/src/pylifecycle.rs @@ -1,27 +1,26 @@ use core::ffi::c_int; +use rustpython_vm::Interpreter; use std::cell::RefCell; use std::mem::ManuallyDrop; -use rustpython_vm::Interpreter; thread_local! { pub static INTERP: RefCell>> = const { RefCell::new(None) }; } #[unsafe(no_mangle)] -pub extern "C-unwind" fn Py_IsInitialized() -> c_int { +pub extern "C" fn Py_IsInitialized() -> c_int { INTERP.with(|interp| interp.borrow().is_some() as c_int) } #[unsafe(no_mangle)] -pub extern "C-unwind" fn Py_Initialize() { +pub extern "C" fn Py_Initialize() { Py_InitializeEx(0); } #[unsafe(no_mangle)] -pub extern "C-unwind" fn Py_InitializeEx(_initsigs: c_int) { +pub extern "C" fn Py_InitializeEx(_initsigs: c_int) { if INTERP.with(|interp| interp.borrow().is_none()) { - let interp = Interpreter::with_init(Default::default(), |_vm| { - }); + let interp = Interpreter::with_init(Default::default(), |_vm| {}); INTERP.with(|interp_ref| { *interp_ref.borrow_mut() = Some(ManuallyDrop::new(interp)); @@ -30,20 +29,24 @@ pub extern "C-unwind" fn Py_InitializeEx(_initsigs: c_int) { } #[unsafe(no_mangle)] -pub extern "C-unwind" fn Py_Finalize() { +pub extern "C" fn Py_Finalize() { let _ = Py_FinalizeEx(); } #[unsafe(no_mangle)] -pub extern "C-unwind" fn Py_FinalizeEx() -> c_int { +pub extern "C" fn Py_FinalizeEx() -> c_int { INTERP.with(|interp_ref| { - let interp = ManuallyDrop::into_inner(interp_ref.borrow_mut().take() - .expect("Py_FinalizeEx called without an active interpreter")); + let interp = ManuallyDrop::into_inner( + interp_ref + .borrow_mut() + .take() + .expect("Py_FinalizeEx called without an active interpreter"), + ); interp.finalize(None) }) as _ } #[unsafe(no_mangle)] -pub extern "C-unwind" fn Py_IsFinalizing() -> c_int { +pub extern "C" fn Py_IsFinalizing() -> c_int { 0 } From 24b2c6384b82aa3ad5ac54154f854c7330e953c2 Mon Sep 17 00:00:00 2001 From: Bas Schoenmaeckers Date: Sun, 5 Apr 2026 11:41:57 +0200 Subject: [PATCH 07/12] implement `PyLong_AsLong` --- Cargo.lock | 1 + crates/capi/Cargo.toml | 1 + crates/capi/src/longobject.rs | 28 +++++++++++++++++++++++++ crates/capi/src/object.rs | 8 +------ crates/capi/src/pyerrors.rs | 3 +++ example_projects/pyo3_embed/src/main.rs | 1 + 6 files changed, 35 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1eca00b9fbb..5d534fb6651 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3108,6 +3108,7 @@ dependencies = [ name = "rustpython-capi" version = "0.5.0" dependencies = [ + "num-traits", "rustpython-vm", ] diff --git a/crates/capi/Cargo.toml b/crates/capi/Cargo.toml index bf3bc8ca285..422b47aaa31 100644 --- a/crates/capi/Cargo.toml +++ b/crates/capi/Cargo.toml @@ -12,6 +12,7 @@ license.workspace = true crate-type = ["staticlib"] [dependencies] +num-traits = { workspace = true } rustpython-vm = { workspace = true } [lints] diff --git a/crates/capi/src/longobject.rs b/crates/capi/src/longobject.rs index a7d620908b7..8c4cb3df1fb 100644 --- a/crates/capi/src/longobject.rs +++ b/crates/capi/src/longobject.rs @@ -4,6 +4,7 @@ use core::ptr; use crate::PyObject; use crate::pylifecycle::INTERP; use rustpython_vm::PyObjectRef; +use rustpython_vm::builtins::PyInt; #[unsafe(no_mangle)] pub extern "C" fn PyLong_FromLong(value: c_long) -> *mut PyObject { @@ -49,3 +50,30 @@ pub extern "C" fn PyLong_FromUnsignedLongLong(_value: c_ulonglong) -> *mut PyObj crate::log_stub("PyLong_FromUnsignedLongLong"); ptr::null_mut() } + +#[unsafe(no_mangle)] +pub extern "C" fn PyLong_AsLong(obj: *mut PyObject) -> c_long { + if obj.is_null() { + panic!("PyLong_AsLong called with null object"); + } + + INTERP.with(|interp_ref| { + let interp = interp_ref.borrow(); + let interp = interp + .as_ref() + .expect("PyLong_AsLong called before Py_InitializeEx"); + + interp.enter(|_vm| { + // SAFETY: non-null checked above; caller promises a valid PyObject pointer. + let obj_ref = unsafe { &*obj }; + let int_obj = obj_ref + .downcast_ref::() + .expect("PyLong_AsLong currently only accepts int instances"); + + int_obj + .as_bigint() + .try_into() + .expect("PyLong_AsLong: value out of range for c_long") + }) + }) +} diff --git a/crates/capi/src/object.rs b/crates/capi/src/object.rs index e09ecdcd74f..f041d2774e3 100644 --- a/crates/capi/src/object.rs +++ b/crates/capi/src/object.rs @@ -70,8 +70,7 @@ pub static mut PyUnicode_Type: PyTypeObject = PyTypeObject::new( ); #[unsafe(no_mangle)] -#[allow(clippy::not_unsafe_ptr_arg_deref)] -pub extern "C" fn _Py_TYPE(op: *mut PyObject) -> *mut PyTypeObject { +pub extern "C" fn Py_TYPE(op: *mut PyObject) -> *mut PyTypeObject { if op.is_null() { return std::ptr::null_mut(); } @@ -92,11 +91,6 @@ pub extern "C" fn _Py_TYPE(op: *mut PyObject) -> *mut PyTypeObject { } } -#[unsafe(no_mangle)] -pub extern "C" fn Py_TYPE(op: *mut PyObject) -> *mut PyTypeObject { - _Py_TYPE(op) -} - #[unsafe(no_mangle)] #[allow(clippy::not_unsafe_ptr_arg_deref)] pub extern "C" fn PyType_GetFlags(ty: *mut PyTypeObject) -> c_ulong { diff --git a/crates/capi/src/pyerrors.rs b/crates/capi/src/pyerrors.rs index fba1e4a14b4..3a567bcfece 100644 --- a/crates/capi/src/pyerrors.rs +++ b/crates/capi/src/pyerrors.rs @@ -9,6 +9,9 @@ pub static mut PyExc_BaseException: *mut PyObject = ptr::null_mut(); #[unsafe(no_mangle)] pub static mut PyExc_TypeError: *mut PyObject = ptr::null_mut(); +#[unsafe(no_mangle)] +pub static mut PyExc_OverflowError: *mut PyObject = ptr::null_mut(); + #[unsafe(no_mangle)] pub extern "C" fn PyErr_GetRaisedException() -> *mut PyObject { crate::log_stub("PyErr_GetRaisedException"); diff --git a/example_projects/pyo3_embed/src/main.rs b/example_projects/pyo3_embed/src/main.rs index 892ea65b94e..ca79a80340a 100644 --- a/example_projects/pyo3_embed/src/main.rs +++ b/example_projects/pyo3_embed/src/main.rs @@ -7,6 +7,7 @@ fn main() { Python::attach(|py| { let number = PyInt::new(py, 123); assert!(number.is_instance_of::()); + assert_eq!(number.extract::().unwrap(), 123); let string = PyString::new(py, "Hello, World!"); assert!(string.is_instance_of::()); From 384b378a63d30c360c6b19efafd77b3c7ec8928a Mon Sep 17 00:00:00 2001 From: Bas Schoenmaeckers Date: Sun, 5 Apr 2026 11:48:52 +0200 Subject: [PATCH 08/12] Add `with_vm` helper --- crates/capi/src/longobject.rs | 42 +++++++++++--------------------- crates/capi/src/pylifecycle.rs | 9 +++++++ crates/capi/src/unicodeobject.rs | 15 +++--------- 3 files changed, 27 insertions(+), 39 deletions(-) diff --git a/crates/capi/src/longobject.rs b/crates/capi/src/longobject.rs index 8c4cb3df1fb..8af4ed9e51e 100644 --- a/crates/capi/src/longobject.rs +++ b/crates/capi/src/longobject.rs @@ -2,22 +2,15 @@ use core::ffi::{c_long, c_longlong, c_ulong, c_ulonglong}; use core::ptr; use crate::PyObject; -use crate::pylifecycle::INTERP; +use crate::pylifecycle::with_vm; use rustpython_vm::PyObjectRef; use rustpython_vm::builtins::PyInt; #[unsafe(no_mangle)] pub extern "C" fn PyLong_FromLong(value: c_long) -> *mut PyObject { - INTERP.with(|interp_ref| { - let interp = interp_ref.borrow(); - let interp = interp - .as_ref() - .expect("PyLong_FromLong called before Py_InitializeEx"); - - interp.enter(|vm| { - let obj: PyObjectRef = vm.ctx.new_int(value).into(); - obj.into_raw().as_ptr() - }) + with_vm(|vm| { + let obj: PyObjectRef = vm.ctx.new_int(value).into(); + obj.into_raw().as_ptr() }) } @@ -57,23 +50,16 @@ pub extern "C" fn PyLong_AsLong(obj: *mut PyObject) -> c_long { panic!("PyLong_AsLong called with null object"); } - INTERP.with(|interp_ref| { - let interp = interp_ref.borrow(); - let interp = interp - .as_ref() - .expect("PyLong_AsLong called before Py_InitializeEx"); - - interp.enter(|_vm| { - // SAFETY: non-null checked above; caller promises a valid PyObject pointer. - let obj_ref = unsafe { &*obj }; - let int_obj = obj_ref - .downcast_ref::() - .expect("PyLong_AsLong currently only accepts int instances"); + with_vm(|_vm| { + // SAFETY: non-null checked above; caller promises a valid PyObject pointer. + let obj_ref = unsafe { &*obj }; + let int_obj = obj_ref + .downcast_ref::() + .expect("PyLong_AsLong currently only accepts int instances"); - int_obj - .as_bigint() - .try_into() - .expect("PyLong_AsLong: value out of range for c_long") - }) + int_obj + .as_bigint() + .try_into() + .expect("PyLong_AsLong: value out of range for c_long") }) } diff --git a/crates/capi/src/pylifecycle.rs b/crates/capi/src/pylifecycle.rs index b70fe05fba9..d1a27ec4381 100644 --- a/crates/capi/src/pylifecycle.rs +++ b/crates/capi/src/pylifecycle.rs @@ -1,5 +1,6 @@ use core::ffi::c_int; use rustpython_vm::Interpreter; +use rustpython_vm::VirtualMachine; use std::cell::RefCell; use std::mem::ManuallyDrop; @@ -7,6 +8,14 @@ thread_local! { pub static INTERP: RefCell>> = const { RefCell::new(None) }; } +pub(crate) fn with_vm(f: impl FnOnce(&VirtualMachine) -> R) -> R { + INTERP.with(|interp_ref| { + let interp = interp_ref.borrow(); + let interp = interp.as_ref().expect("VM access before Py_InitializeEx"); + interp.enter(f) + }) +} + #[unsafe(no_mangle)] pub extern "C" fn Py_IsInitialized() -> c_int { INTERP.with(|interp| interp.borrow().is_some() as c_int) diff --git a/crates/capi/src/unicodeobject.rs b/crates/capi/src/unicodeobject.rs index 0d61b4da465..4a241f6c91f 100644 --- a/crates/capi/src/unicodeobject.rs +++ b/crates/capi/src/unicodeobject.rs @@ -4,7 +4,7 @@ use core::slice; use core::str; use crate::PyObject; -use crate::pylifecycle::INTERP; +use crate::pylifecycle::with_vm; use rustpython_vm::PyObjectRef; #[unsafe(no_mangle)] @@ -21,16 +21,9 @@ pub extern "C" fn PyUnicode_FromStringAndSize(s: *const c_char, len: isize) -> * str::from_utf8(bytes).expect("PyUnicode_FromStringAndSize got non-UTF8 data") }; - INTERP.with(|interp_ref| { - let interp = interp_ref.borrow(); - let interp = interp - .as_ref() - .expect("PyUnicode_FromStringAndSize called before Py_InitializeEx"); - - interp.enter(|vm| { - let obj: PyObjectRef = vm.ctx.new_str(text).into(); - obj.into_raw().as_ptr() - }) + with_vm(|vm| { + let obj: PyObjectRef = vm.ctx.new_str(text).into(); + obj.into_raw().as_ptr() }) } From f49e8592f9c1c415be54d62b7054b61f8852d8f8 Mon Sep 17 00:00:00 2001 From: Bas Schoenmaeckers Date: Sun, 5 Apr 2026 12:03:44 +0200 Subject: [PATCH 09/12] Move `PyThreadState` to `pystate.rs` --- crates/capi/src/lib.rs | 15 +-------------- crates/capi/src/pystate.rs | 7 ++++++- crates/capi/src/refcount.rs | 12 ++++-------- 3 files changed, 11 insertions(+), 23 deletions(-) diff --git a/crates/capi/src/lib.rs b/crates/capi/src/lib.rs index 6da5d0e04fc..7080bdbae49 100644 --- a/crates/capi/src/lib.rs +++ b/crates/capi/src/lib.rs @@ -1,5 +1,4 @@ -use core::ffi::c_long; -pub use rustpython_vm::{PyObject}; +pub use rustpython_vm::PyObject; extern crate alloc; @@ -15,18 +14,6 @@ pub mod traceback; pub mod tupleobject; pub mod unicodeobject; - -#[repr(C)] -pub struct PyThreadState { - _private: [u8; 0], -} - -#[repr(C)] -pub struct PyLongObject { - ob_base: PyObject, - value: c_long, -} - #[inline] pub(crate) fn log_stub(name: &str) { eprintln!("[rustpython-capi stub] {name} called"); diff --git a/crates/capi/src/pystate.rs b/crates/capi/src/pystate.rs index 24ce27f6f43..9879348f8a7 100644 --- a/crates/capi/src/pystate.rs +++ b/crates/capi/src/pystate.rs @@ -1,10 +1,15 @@ -use crate::{PyThreadState, log_stub}; +use crate::log_stub; use core::ffi::c_int; use core::ptr; #[allow(non_camel_case_types)] type PyGILState_STATE = c_int; +#[repr(C)] +pub struct PyThreadState { + _interp: *mut std::ffi::c_void, +} + #[unsafe(no_mangle)] pub extern "C" fn PyGILState_Ensure() -> PyGILState_STATE { log_stub("PyGILState_Ensure"); diff --git a/crates/capi/src/refcount.rs b/crates/capi/src/refcount.rs index 76652175617..5e6f55f0460 100644 --- a/crates/capi/src/refcount.rs +++ b/crates/capi/src/refcount.rs @@ -1,6 +1,6 @@ -use std::ptr::NonNull; +use crate::PyObject; use rustpython_vm::PyObjectRef; -use crate::{PyObject}; +use std::ptr::NonNull; #[unsafe(no_mangle)] #[allow(clippy::not_unsafe_ptr_arg_deref)] @@ -9,9 +9,7 @@ pub extern "C" fn _Py_DecRef(op: *mut PyObject) { return; }; - let owned = unsafe { - PyObjectRef::from_raw(ptr) - }; + let owned = unsafe { PyObjectRef::from_raw(ptr) }; // Dropping so we decrement the refcount drop(owned); @@ -25,9 +23,7 @@ pub extern "C" fn _Py_IncRef(op: *mut PyObject) { } // SAFETY: op is non-null and expected to be a valid pointer for this shim. - let owned = unsafe { - (*op).to_owned() - }; + let owned = unsafe { (*op).to_owned() }; std::mem::forget(owned); } From e18eef7684987995c8fa0b5991de3cb4f7f9233b Mon Sep 17 00:00:00 2001 From: Bas Schoenmaeckers Date: Sun, 5 Apr 2026 13:33:05 +0200 Subject: [PATCH 10/12] Basic multi-threading support --- crates/capi/Cargo.toml | 2 +- crates/capi/src/longobject.rs | 2 +- crates/capi/src/pylifecycle.rs | 61 ++++++++++++++----------- crates/capi/src/pystate.rs | 28 ++++++++++-- crates/capi/src/unicodeobject.rs | 2 +- example_projects/pyo3_embed/src/main.rs | 8 ++++ 6 files changed, 68 insertions(+), 35 deletions(-) diff --git a/crates/capi/Cargo.toml b/crates/capi/Cargo.toml index 422b47aaa31..5a55536902b 100644 --- a/crates/capi/Cargo.toml +++ b/crates/capi/Cargo.toml @@ -13,7 +13,7 @@ crate-type = ["staticlib"] [dependencies] num-traits = { workspace = true } -rustpython-vm = { workspace = true } +rustpython-vm = { workspace = true, features = ["threading"]} [lints] workspace = true diff --git a/crates/capi/src/longobject.rs b/crates/capi/src/longobject.rs index 8af4ed9e51e..efaa0fc2cfc 100644 --- a/crates/capi/src/longobject.rs +++ b/crates/capi/src/longobject.rs @@ -2,7 +2,7 @@ use core::ffi::{c_long, c_longlong, c_ulong, c_ulonglong}; use core::ptr; use crate::PyObject; -use crate::pylifecycle::with_vm; +use crate::pystate::with_vm; use rustpython_vm::PyObjectRef; use rustpython_vm::builtins::PyInt; diff --git a/crates/capi/src/pylifecycle.rs b/crates/capi/src/pylifecycle.rs index d1a27ec4381..2a6277ef95a 100644 --- a/crates/capi/src/pylifecycle.rs +++ b/crates/capi/src/pylifecycle.rs @@ -1,24 +1,26 @@ +use crate::log_stub; use core::ffi::c_int; use rustpython_vm::Interpreter; -use rustpython_vm::VirtualMachine; -use std::cell::RefCell; -use std::mem::ManuallyDrop; +use rustpython_vm::vm::thread::ThreadedVirtualMachine; +use std::sync::{Once, OnceLock, mpsc}; -thread_local! { - pub static INTERP: RefCell>> = const { RefCell::new(None) }; -} +static VM_REQUEST_TX: OnceLock>> = + OnceLock::new(); +static INITIALIZED: Once = Once::new(); -pub(crate) fn with_vm(f: impl FnOnce(&VirtualMachine) -> R) -> R { - INTERP.with(|interp_ref| { - let interp = interp_ref.borrow(); - let interp = interp.as_ref().expect("VM access before Py_InitializeEx"); - interp.enter(f) - }) +/// Request a vm from the main interpreter +pub(crate) fn request_vm_from_interpreter() -> ThreadedVirtualMachine { + let tx = VM_REQUEST_TX + .get() + .expect("VM request channel not initialized"); + let (response_tx, response_rx) = mpsc::channel(); + tx.send(response_tx).expect("Failed to send VM request"); + response_rx.recv().expect("Failed to receive VM response") } #[unsafe(no_mangle)] pub extern "C" fn Py_IsInitialized() -> c_int { - INTERP.with(|interp| interp.borrow().is_some() as c_int) + INITIALIZED.is_completed() as _ } #[unsafe(no_mangle)] @@ -28,13 +30,25 @@ pub extern "C" fn Py_Initialize() { #[unsafe(no_mangle)] pub extern "C" fn Py_InitializeEx(_initsigs: c_int) { - if INTERP.with(|interp| interp.borrow().is_none()) { - let interp = Interpreter::with_init(Default::default(), |_vm| {}); + if INITIALIZED.is_completed() { + panic!("Initialize called multiple times"); + } + + INITIALIZED.call_once(|| { + let (tx, rx) = mpsc::channel(); + VM_REQUEST_TX.set(tx).expect("VM request channel was already initialized"); - INTERP.with(|interp_ref| { - *interp_ref.borrow_mut() = Some(ManuallyDrop::new(interp)); + std::thread::spawn(move || { + let interp = Interpreter::with_init(Default::default(), |_vm| {}); + interp.enter(|vm| { + while let Ok(request) = rx.recv() { + request + .send(vm.new_thread()) + .expect("Failed to send VM response"); + } + }) }); - } + }); } #[unsafe(no_mangle)] @@ -44,15 +58,8 @@ pub extern "C" fn Py_Finalize() { #[unsafe(no_mangle)] pub extern "C" fn Py_FinalizeEx() -> c_int { - INTERP.with(|interp_ref| { - let interp = ManuallyDrop::into_inner( - interp_ref - .borrow_mut() - .take() - .expect("Py_FinalizeEx called without an active interpreter"), - ); - interp.finalize(None) - }) as _ + log_stub("Py_FinalizeEx"); + 0 } #[unsafe(no_mangle)] diff --git a/crates/capi/src/pystate.rs b/crates/capi/src/pystate.rs index 9879348f8a7..bedcc610dd8 100644 --- a/crates/capi/src/pystate.rs +++ b/crates/capi/src/pystate.rs @@ -1,6 +1,23 @@ -use crate::log_stub; +use crate::pylifecycle::request_vm_from_interpreter; use core::ffi::c_int; use core::ptr; +use rustpython_vm::VirtualMachine; +use rustpython_vm::vm::thread::ThreadedVirtualMachine; +use std::cell::RefCell; + +thread_local! { + static VM: RefCell> = const { RefCell::new(None) }; +} + +pub fn with_vm(f: impl FnOnce(&VirtualMachine) -> R) -> R { + VM.with(|vm_ref| { + let vm = vm_ref.borrow(); + let vm = vm + .as_ref() + .expect("Thread was not attached to an interpreter"); + vm.run(f) + }) +} #[allow(non_camel_case_types)] type PyGILState_STATE = c_int; @@ -12,22 +29,23 @@ pub struct PyThreadState { #[unsafe(no_mangle)] pub extern "C" fn PyGILState_Ensure() -> PyGILState_STATE { - log_stub("PyGILState_Ensure"); + VM.with(|vm| { + vm.borrow_mut() + .get_or_insert_with(|| request_vm_from_interpreter()); + }); + 0 } #[unsafe(no_mangle)] pub extern "C" fn PyGILState_Release(_state: PyGILState_STATE) { - log_stub("PyGILState_Release"); } #[unsafe(no_mangle)] pub extern "C" fn PyEval_SaveThread() -> *mut PyThreadState { - log_stub("PyEval_SaveThread"); ptr::null_mut() } #[unsafe(no_mangle)] pub extern "C" fn PyEval_RestoreThread(_tstate: *mut PyThreadState) { - log_stub("PyEval_RestoreThread"); } diff --git a/crates/capi/src/unicodeobject.rs b/crates/capi/src/unicodeobject.rs index 4a241f6c91f..27240521110 100644 --- a/crates/capi/src/unicodeobject.rs +++ b/crates/capi/src/unicodeobject.rs @@ -4,7 +4,7 @@ use core::slice; use core::str; use crate::PyObject; -use crate::pylifecycle::with_vm; +use crate::pystate::with_vm; use rustpython_vm::PyObjectRef; #[unsafe(no_mangle)] diff --git a/example_projects/pyo3_embed/src/main.rs b/example_projects/pyo3_embed/src/main.rs index ca79a80340a..3ba2c549411 100644 --- a/example_projects/pyo3_embed/src/main.rs +++ b/example_projects/pyo3_embed/src/main.rs @@ -11,5 +11,13 @@ fn main() { let string = PyString::new(py, "Hello, World!"); assert!(string.is_instance_of::()); + + let number = number.unbind(); + std::thread::spawn(move || { + Python::attach(|py| { + let number = number.bind(py); + assert!(number.is_instance_of::()); + }); + }).join().unwrap(); }); } From 565bf5ebfbc8c56ad5a450832fdbd85b997c41ac Mon Sep 17 00:00:00 2001 From: Bas Schoenmaeckers Date: Sun, 5 Apr 2026 14:07:16 +0200 Subject: [PATCH 11/12] Cleanup --- Cargo.lock | 1 - crates/capi/Cargo.toml | 1 - example_projects/pyo3_embed/Cargo.toml | 2 +- example_projects/pyo3_embed/build.rs | 6 ------ example_projects/pyo3_embed/pyo3-rustpython.config | 3 +++ 5 files changed, 4 insertions(+), 9 deletions(-) delete mode 100644 example_projects/pyo3_embed/build.rs diff --git a/Cargo.lock b/Cargo.lock index 5d534fb6651..1eca00b9fbb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3108,7 +3108,6 @@ dependencies = [ name = "rustpython-capi" version = "0.5.0" dependencies = [ - "num-traits", "rustpython-vm", ] diff --git a/crates/capi/Cargo.toml b/crates/capi/Cargo.toml index 5a55536902b..964a996143a 100644 --- a/crates/capi/Cargo.toml +++ b/crates/capi/Cargo.toml @@ -12,7 +12,6 @@ license.workspace = true crate-type = ["staticlib"] [dependencies] -num-traits = { workspace = true } rustpython-vm = { workspace = true, features = ["threading"]} [lints] diff --git a/example_projects/pyo3_embed/Cargo.toml b/example_projects/pyo3_embed/Cargo.toml index 6174170c143..d424a486184 100644 --- a/example_projects/pyo3_embed/Cargo.toml +++ b/example_projects/pyo3_embed/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" edition = "2021" [dependencies] -pyo3 = { path = "../../../pyo3", default-features = false, features = ["abi3-py314"] } +pyo3 = { version = "0.28.3", features = ["abi3-py314"] } rustpython-capi = { path = "../../crates/capi" } [workspace] diff --git a/example_projects/pyo3_embed/build.rs b/example_projects/pyo3_embed/build.rs deleted file mode 100644 index 8d379be42c0..00000000000 --- a/example_projects/pyo3_embed/build.rs +++ /dev/null @@ -1,6 +0,0 @@ -fn main() { - println!( - "cargo:rustc-link-arg=/Users/basschoenmaeckers/repo/RustPython/target/debug/librustpython_capi.a" - ); - println!("cargo:rustc-link-lib=framework=CoreFoundation"); -} diff --git a/example_projects/pyo3_embed/pyo3-rustpython.config b/example_projects/pyo3_embed/pyo3-rustpython.config index 2cf06cd886c..2b097508e4f 100644 --- a/example_projects/pyo3_embed/pyo3-rustpython.config +++ b/example_projects/pyo3_embed/pyo3-rustpython.config @@ -4,3 +4,6 @@ shared=false abi3=true build_flags= suppress_build_script_link_lines=true +extra_build_script_line=cargo:rustc-link-search=native=/Users/basschoenmaeckers/repo/RustPython/target/debug +extra_build_script_line=cargo:rustc-link-lib=static=rustpython_capi +extra_build_script_line=cargo:rustc-link-lib=framework=CoreFoundation From 85b292811971ee6bbda46049d219ff4b374d99d5 Mon Sep 17 00:00:00 2001 From: Bas Schoenmaeckers Date: Sun, 5 Apr 2026 14:53:57 +0200 Subject: [PATCH 12/12] Attach thread in `Py_InitializeEx` --- crates/capi/src/pylifecycle.rs | 7 ++++++- crates/capi/src/pystate.rs | 14 ++++++++------ 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/crates/capi/src/pylifecycle.rs b/crates/capi/src/pylifecycle.rs index 2a6277ef95a..d8b3fc2ae41 100644 --- a/crates/capi/src/pylifecycle.rs +++ b/crates/capi/src/pylifecycle.rs @@ -1,4 +1,5 @@ use crate::log_stub; +use crate::pystate::attach_vm_to_thread; use core::ffi::c_int; use rustpython_vm::Interpreter; use rustpython_vm::vm::thread::ThreadedVirtualMachine; @@ -36,7 +37,9 @@ pub extern "C" fn Py_InitializeEx(_initsigs: c_int) { INITIALIZED.call_once(|| { let (tx, rx) = mpsc::channel(); - VM_REQUEST_TX.set(tx).expect("VM request channel was already initialized"); + VM_REQUEST_TX + .set(tx) + .expect("VM request channel was already initialized"); std::thread::spawn(move || { let interp = Interpreter::with_init(Default::default(), |_vm| {}); @@ -49,6 +52,8 @@ pub extern "C" fn Py_InitializeEx(_initsigs: c_int) { }) }); }); + + attach_vm_to_thread(); } #[unsafe(no_mangle)] diff --git a/crates/capi/src/pystate.rs b/crates/capi/src/pystate.rs index bedcc610dd8..de25dba790a 100644 --- a/crates/capi/src/pystate.rs +++ b/crates/capi/src/pystate.rs @@ -27,19 +27,22 @@ pub struct PyThreadState { _interp: *mut std::ffi::c_void, } -#[unsafe(no_mangle)] -pub extern "C" fn PyGILState_Ensure() -> PyGILState_STATE { +pub(crate) fn attach_vm_to_thread() { VM.with(|vm| { vm.borrow_mut() .get_or_insert_with(|| request_vm_from_interpreter()); }); +} + +#[unsafe(no_mangle)] +pub extern "C" fn PyGILState_Ensure() -> PyGILState_STATE { + attach_vm_to_thread(); 0 } #[unsafe(no_mangle)] -pub extern "C" fn PyGILState_Release(_state: PyGILState_STATE) { -} +pub extern "C" fn PyGILState_Release(_state: PyGILState_STATE) {} #[unsafe(no_mangle)] pub extern "C" fn PyEval_SaveThread() -> *mut PyThreadState { @@ -47,5 +50,4 @@ pub extern "C" fn PyEval_SaveThread() -> *mut PyThreadState { } #[unsafe(no_mangle)] -pub extern "C" fn PyEval_RestoreThread(_tstate: *mut PyThreadState) { -} +pub extern "C" fn PyEval_RestoreThread(_tstate: *mut PyThreadState) {}