diff --git a/.cspell.dict/python-more.txt b/.cspell.dict/python-more.txt index 0404428324..14181ac814 100644 --- a/.cspell.dict/python-more.txt +++ b/.cspell.dict/python-more.txt @@ -149,6 +149,7 @@ nlocals NOARGS nonbytes Nonprintable +obmalloc origname ospath pendingcr diff --git a/.cspell.json b/.cspell.json index 98a03180fe..31af41ddb3 100644 --- a/.cspell.json +++ b/.cspell.json @@ -53,6 +53,7 @@ "baseclass", "boxvec", "Bytecode", + "ccomplex", "cfgs", "codegen", "coro", diff --git a/Cargo.lock b/Cargo.lock index 87cfb55109..ec562c5836 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -115,9 +115,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.97" +version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f" +checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" [[package]] name = "approx" @@ -213,9 +213,9 @@ dependencies = [ [[package]] name = "bstr" -version = "1.11.3" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "531a9155a481e2ee699d4f98f43c0ca4ff8ee1bfd55c31e9e98fb29d2b176fe0" +checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4" dependencies = [ "memchr", "regex-automata", @@ -283,9 +283,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.18" +version = "1.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "525046617d8376e3db1deffb079e91cef90a89fc3ca5c185bbf8c9ecdd15cd5c" +checksum = "8e3a13707ac958681c13b39b458c073d0d9bc8a22cb1b2f4c8e55eb72c13f362" dependencies = [ "shlex", ] @@ -1037,9 +1037,9 @@ checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" [[package]] name = "half" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7db2ff139bba50379da6aa0766b52fdcb62cb5b263009b09ed58ba604e14bbd1" +checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" dependencies = [ "cfg-if", "crunchy", @@ -1219,9 +1219,9 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "jiff" -version = "0.2.5" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c102670231191d07d37a35af3eb77f1f0dbf7a71be51a962dcd57ea607be7260" +checksum = "e5ad87c89110f55e4cd4dc2893a9790820206729eaf221555f742d540b0724a0" dependencies = [ "jiff-static", "log", @@ -1232,9 +1232,9 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.5" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cdde31a9d349f1b1f51a0b3714a5940ac022976f4b49485fc04be052b183b4c" +checksum = "d076d5b64a7e2fe6f0743f02c43ca4a6725c0f904203bfe276a5b3e793103605" dependencies = [ "proc-macro2", "quote", @@ -1272,9 +1272,9 @@ dependencies = [ [[package]] name = "lambert_w" -version = "1.2.9" +version = "1.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd4d9b9fa6582f5d77f954729c91c32a7c85834332e470b014d12e1678fd1793" +checksum = "913e1e36ca541d75f384593fa70bf5a5e9f001f2996bfa7926550d921f83baf6" dependencies = [ "num-complex", "num-traits", @@ -1324,9 +1324,9 @@ checksum = "9fa0e2a1fcbe2f6be6c42e342259976206b383122fc152e872795338b5a3f3a7" [[package]] name = "libc" -version = "0.2.171" +version = "0.2.172" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" +checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" [[package]] name = "libffi" @@ -1401,9 +1401,9 @@ checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" [[package]] name = "linux-raw-sys" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe7db12097d22ec582439daf8618b8fdd1a7bef6270e9af3b1ebcd30893cf413" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" [[package]] name = "lock_api" @@ -1551,9 +1551,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.8.7" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff70ce3e48ae43fa075863cef62e8b43b71a4f2382229920e0df362592919430" +checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" dependencies = [ "adler2", ] @@ -1702,9 +1702,9 @@ checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "openssl-src" -version = "300.4.2+3.4.1" +version = "300.5.0+3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "168ce4e058f975fe43e89d9ccf78ca668601887ae736090aacc23ae353c298e2" +checksum = "e8ce546f549326b0e6052b649198487d91320875da901e7bd11a06d1ee3f9c2f" dependencies = [ "cc", ] @@ -1906,9 +1906,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.94" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" dependencies = [ "unicode-ident", ] @@ -2033,13 +2033,12 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" +checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.3", - "zerocopy 0.8.24", ] [[package]] @@ -2297,6 +2296,7 @@ dependencies = [ "log", "pyo3", "ruff_python_parser", + "rustpython-capi", "rustpython-compiler", "rustpython-pylib", "rustpython-stdlib", @@ -2304,6 +2304,16 @@ dependencies = [ "rustyline", ] +[[package]] +name = "rustpython-capi" +version = "0.4.0" +dependencies = [ + "malachite-bigint", + "num-complex", + "rustpython-common", + "rustpython-vm", +] + [[package]] name = "rustpython-codegen" version = "0.4.0" @@ -2362,7 +2372,7 @@ dependencies = [ name = "rustpython-compiler" version = "0.4.0" dependencies = [ - "rand 0.9.0", + "rand 0.9.1", "ruff_python_ast", "ruff_python_parser", "ruff_source_file", @@ -2451,7 +2461,7 @@ dependencies = [ "is-macro", "lexical-parse-float", "num-traits", - "rand 0.9.0", + "rand 0.9.1", "rustpython-wtf8", "unic-ucd-category", ] @@ -3705,9 +3715,9 @@ dependencies = [ [[package]] name = "xml-rs" -version = "0.8.25" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5b940ebc25896e71dd073bad2dbaa2abfe97b0a391415e22ad1326d9c54e3c4" +checksum = "a62ce76d9b56901b19a74f19431b0d8b3bc7ca4ad685a746dfd78ca8f4fc6bda" [[package]] name = "zerocopy" diff --git a/Cargo.toml b/Cargo.toml index 21fb9de990..bcbb0a7047 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,8 +23,10 @@ sqlite = ["rustpython-stdlib/sqlite"] ssl = ["rustpython-stdlib/ssl"] ssl-vendor = ["ssl", "rustpython-stdlib/ssl-vendor"] tkinter = ["rustpython-stdlib/tkinter"] +capi = ["rustpython-capi"] [dependencies] +rustpython-capi = { workspace = true, optional = true } rustpython-compiler = { workspace = true } rustpython-pylib = { workspace = true, optional = true } rustpython-stdlib = { workspace = true, optional = true, features = ["compiler"] } @@ -114,7 +116,7 @@ template = "installer-config/installer.wxs" [workspace] resolver = "2" members = [ - "compiler", "compiler/core", "compiler/codegen", "compiler/literal", "compiler/source", + "capi", "compiler", "compiler/core", "compiler/codegen", "compiler/literal", "compiler/source", ".", "common", "derive", "jit", "vm", "vm/sre_engine", "pylib", "stdlib", "derive-impl", "wtf8", "wasm/lib", ] @@ -129,6 +131,7 @@ license = "MIT" [workspace.dependencies] rustpython-compiler-source = { path = "compiler/source" } +rustpython-capi = { path = "capi", version = "0.4.0" } rustpython-compiler-core = { path = "compiler/core", version = "0.4.0" } rustpython-compiler = { path = "compiler", version = "0.4.0" } rustpython-codegen = { path = "compiler/codegen", version = "0.4.0" } diff --git a/capi/Cargo.toml b/capi/Cargo.toml new file mode 100644 index 0000000000..0a65b29b4b --- /dev/null +++ b/capi/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "rustpython-capi" +description = "CAPI for RustPython" +version.workspace = true +authors.workspace = true +edition.workspace = true +rust-version.workspace = true +repository.workspace = true +license.workspace = true + +[lib] +crate-type = ["cdylib"] + +[features] + +[dependencies] +malachite-bigint = { workspace = true } +num-complex = { workspace = true } + +rustpython-common = { workspace = true } +rustpython-vm = { workspace = true } + +[lints.rust] +unsafe_code = "allow" +unsafe_op_in_unsafe_fn = "deny" +elided_lifetimes_in_paths = "warn" + +[lints.clippy] +perf = "warn" +style = "warn" +complexity = "warn" +suspicious = "warn" +correctness = "warn" +missing_safety_doc = "allow" diff --git a/capi/src/bool.rs b/capi/src/bool.rs new file mode 100644 index 0000000000..2459face79 --- /dev/null +++ b/capi/src/bool.rs @@ -0,0 +1,15 @@ +// https://docs.python.org/3/c-api/bool.html + +use std::ffi; + +use rustpython_vm::{PyObject, PyObjectRef}; + +// TODO: Everything else + +#[unsafe(export_name = "PyBool_FromLong")] +pub unsafe extern "C" fn bool_from_long(value: ffi::c_long) -> *mut PyObject { + let vm = crate::get_vm(); + Into::::into(vm.ctx.new_bool(value != 0)) + .into_raw() + .as_ptr() +} diff --git a/capi/src/complex.rs b/capi/src/complex.rs new file mode 100644 index 0000000000..8907cd8f02 --- /dev/null +++ b/capi/src/complex.rs @@ -0,0 +1,130 @@ +// https://docs.python.org/3/c-api/complex.html + +use std::ffi; + +use rustpython_vm::{PyObject, PyObjectRef, builtins::PyComplex}; + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct CPyComplex { + pub real: ffi::c_double, + pub imag: ffi::c_double, +} + +impl From for PyComplex { + fn from(value: CPyComplex) -> Self { + PyComplex::new(num_complex::Complex64::new(value.real, value.imag)) + } +} + +impl From for num_complex::Complex64 { + fn from(value: CPyComplex) -> Self { + num_complex::Complex64::new(value.real, value.imag) + } +} + +impl From for CPyComplex { + fn from(value: PyComplex) -> Self { + let complex = value.to_complex(); + CPyComplex { + real: complex.re, + imag: complex.im, + } + } +} + +impl From for CPyComplex { + fn from(value: num_complex::Complex64) -> Self { + CPyComplex { + real: value.re, + imag: value.im, + } + } +} + +// Associated functions for CPyComplex +// Always convert to PyComplex to do operations + +#[unsafe(export_name = "_Py_c_sum")] +pub unsafe extern "C" fn c_sum(a: *const CPyComplex, b: *const CPyComplex) -> CPyComplex { + let a: PyComplex = unsafe { *a }.into(); + let b: PyComplex = unsafe { *b }.into(); + (a.to_complex() + b.to_complex()).into() +} + +#[unsafe(export_name = "_Py_c_diff")] +pub unsafe extern "C" fn c_diff(a: *const CPyComplex, b: *const CPyComplex) -> CPyComplex { + let a: PyComplex = unsafe { *a }.into(); + let b: PyComplex = unsafe { *b }.into(); + (a.to_complex() - b.to_complex()).into() +} + +#[unsafe(export_name = "_Py_c_neg")] +pub unsafe extern "C" fn c_neg(a: *const CPyComplex) -> CPyComplex { + let a: PyComplex = unsafe { *a }.into(); + (-a.to_complex()).into() +} + +#[unsafe(export_name = "_Py_c_prod")] +pub unsafe extern "C" fn c_prod(a: *const CPyComplex, b: *const CPyComplex) -> CPyComplex { + let a: PyComplex = unsafe { *a }.into(); + let b: PyComplex = unsafe { *b }.into(); + (a.to_complex() * b.to_complex()).into() +} + +#[unsafe(export_name = "_Py_c_quot")] +pub unsafe extern "C" fn c_quot(a: *const CPyComplex, b: *const CPyComplex) -> CPyComplex { + let a: PyComplex = unsafe { *a }.into(); + let b: PyComplex = unsafe { *b }.into(); + (a.to_complex() / b.to_complex()).into() +} + +#[unsafe(export_name = "_Py_c_pow")] +pub unsafe extern "C" fn c_pow(a: *const CPyComplex, b: *const CPyComplex) -> CPyComplex { + let a: PyComplex = unsafe { *a }.into(); + let b: PyComplex = unsafe { *b }.into(); + (a.to_complex() * b.to_complex()).into() +} + +#[unsafe(export_name = "PyComplex_FromCComplex")] +pub unsafe extern "C" fn complex_from_ccomplex(value: CPyComplex) -> *mut PyObject { + let vm = crate::get_vm(); + Into::::into(vm.ctx.new_complex(value.into())) + .into_raw() + .as_ptr() +} + +#[unsafe(export_name = "PyComplex_FromDoubles")] +pub unsafe extern "C" fn complex_from_doubles( + real: ffi::c_double, + imag: ffi::c_double, +) -> *mut PyObject { + let vm = crate::get_vm(); + Into::::into(vm.ctx.new_complex(num_complex::Complex64::new(real, imag))) + .into_raw() + .as_ptr() +} + +#[unsafe(export_name = "PyComplex_RealAsDouble")] +pub unsafe extern "C" fn complex_real_as_double(value: *mut PyObject) -> ffi::c_double { + let vm = crate::get_vm(); + let value = crate::cast_obj_ptr(value).unwrap(); + let (complex, _) = value.try_complex(&vm).unwrap().unwrap(); + complex.re +} + +#[unsafe(export_name = "PyComplex_ImagAsDouble")] +pub unsafe extern "C" fn complex_imag_as_double(value: *mut PyObject) -> ffi::c_double { + let vm = crate::get_vm(); + let value = crate::cast_obj_ptr(value).unwrap(); + let (complex, _) = value.try_complex(&vm).unwrap().unwrap(); + complex.im +} + +#[unsafe(export_name = "PyComplex_AsCComplex")] +pub unsafe extern "C" fn complex_as_ccomplex(value: *mut PyObject) -> CPyComplex { + let vm = crate::get_vm(); + let value = crate::cast_obj_ptr(value).unwrap(); + let (complex, _) = value.try_complex(&vm).unwrap().unwrap(); + complex.into() +} diff --git a/capi/src/error.rs b/capi/src/error.rs new file mode 100644 index 0000000000..8aa788e60e --- /dev/null +++ b/capi/src/error.rs @@ -0,0 +1,29 @@ +use std::{ffi, ptr}; + +use rustpython_vm::{PyObject, PyObjectRef}; + +#[unsafe(export_name = "PyErr_NewException")] +pub unsafe extern "C" fn err_new_exception( + name: *const ffi::c_char, + _base: *mut ffi::c_void, + _dict: *mut ffi::c_void, +) -> *mut PyObject { + let vm = crate::get_vm(); + let name_str = unsafe { std::ffi::CStr::from_ptr(name).to_str().unwrap() }; + let last_dot = match name_str.rfind('.') { + Some(x) => x, + None => { + // TODO: Set error + return ptr::null_mut(); + } + }; + let module = &name_str[..last_dot]; + let name = &name_str[last_dot + 1..]; + Into::::into(vm.ctx.new_exception_type( + module, + name, + Some(vec![vm.ctx.exceptions.exception_type.to_owned()]), + )) + .into_raw() + .as_ptr() +} diff --git a/capi/src/float.rs b/capi/src/float.rs new file mode 100644 index 0000000000..34b4df6cdf --- /dev/null +++ b/capi/src/float.rs @@ -0,0 +1,26 @@ +// https://docs.python.org/3/c-api/float.html + +use std::ffi; + +use rustpython_vm::{PyObject, PyObjectRef}; + +/// Returns null if the string is not a valid float. +#[unsafe(export_name = "PyFloat_FromString")] +pub unsafe extern "C" fn float_from_string(value: *const ffi::c_char) -> *mut PyObject { + let vm = crate::get_vm(); + let value_str = unsafe { std::ffi::CStr::from_ptr(value).to_str().unwrap() }; + match value_str.parse::() { + Ok(value) => Into::::into(vm.ctx.new_float(value)) + .into_raw() + .as_ptr(), + Err(_) => std::ptr::null_mut(), + } +} + +#[unsafe(export_name = "PyFloat_FromDouble")] +pub unsafe extern "C" fn float_from_double(value: ffi::c_double) -> *mut PyObject { + let vm = crate::get_vm(); + Into::::into(vm.ctx.new_float(value)) + .into_raw() + .as_ptr() +} diff --git a/capi/src/int.rs b/capi/src/int.rs new file mode 100644 index 0000000000..4a07d80d26 --- /dev/null +++ b/capi/src/int.rs @@ -0,0 +1,48 @@ +// https://docs.python.org/3/c-api/long.html + +use std::ffi; + +use rustpython_vm::{PyObject, PyObjectRef}; + +#[unsafe(export_name = "PyLong_FromLong")] +pub unsafe extern "C" fn long_from_long(value: ffi::c_long) -> *mut PyObject { + let vm = crate::get_vm(); + Into::::into(vm.ctx.new_int(value)) + .into_raw() + .as_ptr() +} + +#[unsafe(export_name = "PyLong_FromUnsignedLong")] +pub unsafe extern "C" fn long_from_unsigned_long(value: ffi::c_ulong) -> *mut PyObject { + let vm = crate::get_vm(); + Into::::into(vm.ctx.new_int(value)) + .into_raw() + .as_ptr() +} + +// TODO: PyLong_FromSsize_t +// TODO: PyLong_FromSize_t +#[unsafe(export_name = "PyLong_FromLongLong")] +pub unsafe extern "C" fn long_from_long_long(value: ffi::c_longlong) -> *mut PyObject { + let vm = crate::get_vm(); + Into::::into(vm.ctx.new_int(value)) + .into_raw() + .as_ptr() +} + +#[unsafe(export_name = "PyLong_FromUnsignedLongLong")] +pub unsafe extern "C" fn long_from_unsigned_long_long(value: ffi::c_ulonglong) -> *mut PyObject { + let vm = crate::get_vm(); + Into::::into(vm.ctx.new_int(value)) + .into_raw() + .as_ptr() +} + +#[unsafe(export_name = "PyLong_FromDouble")] +pub unsafe extern "C" fn long_from_double(value: ffi::c_double) -> *mut PyObject { + let vm = crate::get_vm(); + let value = value as i64; + Into::::into(vm.ctx.new_int(value)) + .into_raw() + .as_ptr() +} diff --git a/capi/src/lib.rs b/capi/src/lib.rs new file mode 100644 index 0000000000..6f8f33757c --- /dev/null +++ b/capi/src/lib.rs @@ -0,0 +1,98 @@ +use std::{cell::RefCell, ffi, sync::Arc}; + +use rustpython_vm::{self as vm, PyObject, PyObjectRef}; + +pub mod bool; +pub mod complex; +pub mod error; +pub mod float; +pub mod int; +pub mod tuple; + +thread_local! { + pub static VM: RefCell>> = const { RefCell::new(None) }; +} + +fn get_vm() -> Arc { + VM.with(|vm| vm.borrow().as_ref().unwrap().clone()) +} + +fn cast_obj_ptr(obj: *mut PyObject) -> Option { + Some(unsafe { PyObjectRef::from_raw(std::ptr::NonNull::new(obj)?) }) +} + +#[repr(C)] +pub enum PyStatusType { + PyStatusTypeOk = 0, + PyStatusTypeError = 1, + PyStatusTypeExit = 2, +} + +#[repr(C)] +pub struct PyStatus { + _type: PyStatusType, + pub func: *const ffi::c_char, + pub err_msg: *const ffi::c_char, + pub exitcode: ffi::c_int, +} + +/// # Safety +/// err_msg and func are null. +#[unsafe(export_name = "PyStatus_Ok")] +pub unsafe extern "C" fn status_ok() -> PyStatus { + PyStatus { + _type: PyStatusType::PyStatusTypeOk, + exitcode: 0, + err_msg: std::ptr::null(), + func: std::ptr::null(), + } +} + +#[repr(C)] +pub struct PyInterpreterConfig { + use_main_obmalloc: i32, + allow_fork: i32, + allow_exec: i32, + allow_threads: i32, + allow_daemon_threads: i32, + check_multi_interp_extensions: i32, + gil: i32, +} + +thread_local! { + pub static INTERP: RefCell> = const { RefCell::new(None) }; +} + +#[unsafe(export_name = "Py_Initialize")] +pub extern "C" fn initialize() { + // TODO: This sort of reimplemented what has already been done in the bin/lib crate, try reusing that. + let settings = vm::Settings::default(); + #[allow(clippy::type_complexity)] + let init_hooks: Vec> = vec![]; + let interp = vm::Interpreter::with_init(settings, |vm| { + for hook in init_hooks { + hook(vm); + } + }); + VM.with(|vm_ref| { + *vm_ref.borrow_mut() = Some(interp.vm.clone()); + }); + INTERP.with(|interp_ref| { + *interp_ref.borrow_mut() = Some(interp); + }); +} + +#[unsafe(export_name = "Py_IsInitialized")] +pub extern "C" fn is_initialized() -> i32 { + VM.with(|vm_ref| vm_ref.borrow().is_some() as i32) +} + +#[unsafe(export_name = "Py_Finalize")] +pub extern "C" fn finalize() { + VM.with(|vm_ref| { + *vm_ref.borrow_mut() = None; + }); + INTERP.with(|interp_ref| { + *interp_ref.borrow_mut() = None; + }); +} diff --git a/capi/src/tuple.rs b/capi/src/tuple.rs new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/capi/src/tuple.rs @@ -0,0 +1 @@ + diff --git a/capi_tests/simple/my_module.c b/capi_tests/simple/my_module.c new file mode 100644 index 0000000000..5826169f4a --- /dev/null +++ b/capi_tests/simple/my_module.c @@ -0,0 +1,37 @@ +#include + +// Function to be called from Python +static PyObject* my_function(PyObject* self, PyObject* args) { + int num; + + // Parse arguments from Python + if (!PyArg_ParseTuple(args, "i", &num)) { + return NULL; // Return NULL if arguments are invalid + } + + // Perform some operation + int result = num * 2; + + // Return the result as a Python object + return Py_BuildValue("i", result); +} + +// Method definition table +static PyMethodDef MyModuleMethods[] = { + {"my_function", my_function, METH_VARARGS, "Doubles the input number."}, + {NULL, NULL, 0, NULL} // Sentinel value ending the table +}; + +// Module definition structure +static struct PyModuleDef mymodule = { + PyModuleDef_HEAD_INIT, + "my_module", // Module name + NULL, // Module documentation (optional) + -1, // Module state size, -1 for modules that don't maintain state + MyModuleMethods +}; + +// Module initialization function +PyMODINIT_FUNC PyInit_my_module(void) { + return PyModule_Create(&mymodule); +} \ No newline at end of file diff --git a/capi_tests/simple/setup.py b/capi_tests/simple/setup.py new file mode 100644 index 0000000000..8120eb1be0 --- /dev/null +++ b/capi_tests/simple/setup.py @@ -0,0 +1,7 @@ +from setuptools import setup, Extension + +setup( + name='my_module', + version='1.0.0', + ext_modules=[Extension('my_module', sources=['my_module.c'])], +) diff --git a/src/lib.rs b/src/lib.rs index e9c9c91294..19d981c8ea 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -99,6 +99,12 @@ pub fn run(init: impl FnOnce(&mut VirtualMachine) + 'static) -> ExitCode { config = config.init_hook(Box::new(init)); let interp = config.interpreter(); + #[cfg(feature = "capi")] + { + rustpython_capi::VM.with(|vm| { + *vm.borrow_mut() = Some(interp.vm.clone()); + }); + } let exitcode = interp.run(move |vm| run_rustpython(vm, run_mode)); ExitCode::from(exitcode) diff --git a/vm/src/builtins/complex.rs b/vm/src/builtins/complex.rs index 02324704b3..029d99ad22 100644 --- a/vm/src/builtins/complex.rs +++ b/vm/src/builtins/complex.rs @@ -28,6 +28,10 @@ pub struct PyComplex { } impl PyComplex { + pub fn new(value: Complex64) -> Self { + PyComplex { value } + } + pub fn to_complex64(self) -> Complex64 { self.value } diff --git a/vm/src/c_module/mod.rs b/vm/src/c_module/mod.rs new file mode 100644 index 0000000000..102525e708 --- /dev/null +++ b/vm/src/c_module/mod.rs @@ -0,0 +1,7 @@ +use std::borrow::Cow; +use std::collections::HashMap; +use crate::builtins::PyModule; +use crate::{PyRef, VirtualMachine}; + +pub type CModuleInitFunc = Box PyRef)>; +pub type CModuleMap = HashMap, CModuleInitFunc, ahash::RandomState>; diff --git a/vm/src/import.rs b/vm/src/import.rs index 90aadbdbf2..8b1b7e4612 100644 --- a/vm/src/import.rs +++ b/vm/src/import.rs @@ -88,7 +88,7 @@ pub fn import_frozen(vm: &VirtualMachine, module_name: &str) -> PyResult { } pub fn import_builtin(vm: &VirtualMachine, module_name: &str) -> PyResult { - let make_module_func = vm.state.module_inits.get(module_name).ok_or_else(|| { + let make_module_func = vm.state.stdlib_module_inits.get(module_name).ok_or_else(|| { vm.new_import_error( format!("Cannot import builtin module {module_name}"), vm.ctx.new_str(module_name), diff --git a/vm/src/lib.rs b/vm/src/lib.rs index de0042c619..04d549a030 100644 --- a/vm/src/lib.rs +++ b/vm/src/lib.rs @@ -45,6 +45,7 @@ pub mod buffer; pub mod builtins; pub mod byte; mod bytes_inner; +pub mod c_module; pub mod cformat; pub mod class; mod codecs; diff --git a/vm/src/stdlib/imp.rs b/vm/src/stdlib/imp.rs index 5c3f4bf61d..7f693cb2a7 100644 --- a/vm/src/stdlib/imp.rs +++ b/vm/src/stdlib/imp.rs @@ -101,7 +101,7 @@ mod _imp { #[pyfunction] fn is_builtin(name: PyStrRef, vm: &VirtualMachine) -> bool { - vm.state.module_inits.contains_key(name.as_str()) + vm.state.stdlib_module_inits.contains_key(name.as_str()) } #[pyfunction] @@ -116,7 +116,9 @@ mod _imp { let module = if let Ok(module) = sys_modules.get_item(&*name, vm) { module - } else if let Some(make_module_func) = vm.state.module_inits.get(name.as_str()) { + } else if let Some(make_module_func) = vm.state.stdlib_module_inits.get(name.as_str()) { + make_module_func(vm).into() + } else if let Some(make_module_func) = vm.state.c_module_inits.get(name.as_str()) { make_module_func(vm).into() } else { vm.ctx.none() diff --git a/vm/src/stdlib/sys.rs b/vm/src/stdlib/sys.rs index 3a66f7f80d..8d8ccaa1db 100644 --- a/vm/src/stdlib/sys.rs +++ b/vm/src/stdlib/sys.rs @@ -134,7 +134,7 @@ mod sys { #[pyattr] fn builtin_module_names(vm: &VirtualMachine) -> PyTupleRef { - let mut module_names: Vec<_> = vm.state.module_inits.keys().cloned().collect(); + let mut module_names: Vec<_> = vm.state.stdlib_module_inits.keys().cloned().collect(); module_names.push("sys".into()); module_names.push("builtins".into()); module_names.sort(); diff --git a/vm/src/vm/interpreter.rs b/vm/src/vm/interpreter.rs index 02c71bf136..1340328aea 100644 --- a/vm/src/vm/interpreter.rs +++ b/vm/src/vm/interpreter.rs @@ -1,6 +1,6 @@ use super::{Context, VirtualMachine, setting::Settings, thread}; use crate::{PyResult, stdlib::atexit, vm::PyBaseExceptionRef}; -use std::sync::atomic::Ordering; +use std::sync::{Arc, atomic::Ordering}; /// The general interface for the VM /// @@ -21,7 +21,7 @@ use std::sync::atomic::Ordering; /// }); /// ``` pub struct Interpreter { - vm: VirtualMachine, + pub vm: Arc, } impl Interpreter { @@ -53,6 +53,9 @@ impl Interpreter { let mut vm = VirtualMachine::new(settings, ctx.clone()); init(&mut vm); vm.initialize(); + // This is kept in-case the c-api vm stops being thread-local, for any reason. + #[allow(clippy::arc_with_non_send_sync)] + let vm = Arc::new(vm); Self { vm } } diff --git a/vm/src/vm/mod.rs b/vm/src/vm/mod.rs index 7baaae7770..f4e1a17140 100644 --- a/vm/src/vm/mod.rs +++ b/vm/src/vm/mod.rs @@ -23,6 +23,7 @@ use crate::{ pystr::AsPyStr, tuple::{PyTuple, PyTupleTyped}, }, + c_module, codecs::CodecsRegistry, common::{hash::HashSecret, lock::PyMutex, rc::PyRc}, convert::ToPyObject, @@ -90,7 +91,8 @@ struct ExceptionStack { pub struct PyGlobalState { pub settings: Settings, - pub module_inits: stdlib::StdlibMap, + pub c_module_inits: c_module::CModuleMap, + pub stdlib_module_inits: stdlib::StdlibMap, pub frozen: HashMap<&'static str, FrozenModule, ahash::RandomState>, pub stacksize: AtomicCell, pub thread_count: AtomicCell, @@ -173,7 +175,7 @@ impl VirtualMachine { repr_guards: RefCell::default(), state: PyRc::new(PyGlobalState { settings, - module_inits, + stdlib_module_inits: module_inits, frozen: HashMap::default(), stacksize: AtomicCell::new(0), thread_count: AtomicCell::new(0), @@ -414,14 +416,27 @@ impl VirtualMachine { where S: Into>, { - self.state_mut().module_inits.insert(name.into(), module); + self.state_mut().stdlib_module_inits.insert(name.into(), module); } pub fn add_native_modules(&mut self, iter: I) where I: IntoIterator, stdlib::StdlibInitFunc)>, { - self.state_mut().module_inits.extend(iter); + self.state_mut().stdlib_module_inits.extend(iter); + } + + pub fn add_c_module(&mut self, name: S, module: c_module::CModuleInitFunc) + where + S: Into> { + self.state_mut().c_module_inits.insert(name.into(), module); + } + + pub fn add_c_modules(&mut self, iter: I) + where + I: IntoIterator, c_module::CModuleInitFunc)>, + { + self.state_mut().c_module_inits.extend(iter); } /// Can only be used in the initialization closure passed to [`Interpreter::with_init`]