Implement Py_mod_create slot support in multi-phase init

This commit is contained in:
Jeong YunWon committed 2026-01-22 11:21:42 +09:00
1 parent bc02b2318c
commit 20376451eb
62 files changed
+964 -906

No files matched your search

+3 -4
View File
@@ -24,10 +24,9 @@ fn bench_rustpython_code(b: &mut Bencher, name: &str, source: &str) {
settings.path_list.push("Lib/".to_string());
settings.write_bytecode = false;
settings.user_site_directory = false;
Interpreter::with_init(settings, |vm| {
vm.add_native_modules(rustpython_stdlib::get_module_inits());
})
.enter(|vm| {
let builder = Interpreter::builder(settings);
let defs = rustpython_stdlib::stdlib_module_defs(&builder.ctx);
builder.add_native_modules(&defs).build().enter(|vm| {
// Note: bench_cpython is both compiling and executing the code.
// As such we compile the code in the benchmark loop as well.
b.iter(|| {
+4 -4
View File
@@ -113,10 +113,10 @@ fn bench_rustpython_code(group: &mut BenchmarkGroup<WallTime>, bench: &MicroBenc
settings.write_bytecode = false;
settings.user_site_directory = false;
Interpreter::with_init(settings, |vm| {
vm.add_native_module_defs(rustpython_stdlib::get_module_defs());
})
.enter(|vm| {
let builder = Interpreter::builder(settings);
let defs = rustpython_stdlib::stdlib_module_defs(&builder.ctx);
let interp = builder.add_native_modules(&defs).build();
interp.enter(|vm| {
let setup_code = vm
.compile(&bench.setup, Mode::Exec, bench.name.to_owned())
.expect("Error compiling setup code");
+3 -1
View File
@@ -5,8 +5,10 @@ use alloc::fmt;
use core::cmp;
use std::{ffi, io};
#[cfg(unix)]
use std::os::fd::AsFd;
#[cfg(not(windows))]
use std::os::fd::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd};
use std::os::fd::{AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd};
#[cfg(windows)]
use std::os::windows::io::BorrowedHandle;
-13
View File
@@ -144,18 +144,6 @@ pub fn impl_pymodule(attr: PunctuatedNestedMeta, module_item: Item) -> Result<To
})
}
},
parse_quote! {
#[allow(dead_code)]
pub(crate) fn make_module(
vm: &::rustpython_vm::VirtualMachine
) -> ::rustpython_vm::PyRef<::rustpython_vm::builtins::PyModule> {
use ::rustpython_vm::PyPayload;
let module = ::rustpython_vm::builtins::PyModule::from_def(module_def(&vm.ctx)).into_ref(&vm.ctx);
__init_dict(vm, &module);
module_exec(vm, &module).unwrap();
module
}
},
]);
}
if !is_submodule && !context.has_module_exec {
@@ -202,7 +190,6 @@ pub fn impl_pymodule(attr: PunctuatedNestedMeta, module_item: Item) -> Result<To
vm: &::rustpython_vm::VirtualMachine,
module: &::rustpython_vm::Py<::rustpython_vm::builtins::PyModule>,
) {
module.__init_methods(vm).unwrap();
__init_attributes(vm, module);
}
},
+1 -1
View File
@@ -144,7 +144,7 @@ pub fn pyexception(attr: TokenStream, item: TokenStream) -> TokenStream {
/// This attribute must be applied to an inline module.
/// It defines a Python module in the form of a `module_def` function in the module;
/// this has to be used in a `get_module_defs` to properly register the module.
/// this has to be used in a `add_native_module` to properly register the module.
/// Additionally, this macro defines 'MODULE_NAME' and 'DOC' in the module.
/// # Arguments
/// - `name`: the name of the python module,
@@ -1,7 +1,7 @@
pub(crate) use opcode::module_def;
pub(crate) use _opcode::module_def;
#[pymodule]
mod opcode {
mod _opcode {
use crate::vm::{
AsObject, PyObjectRef, PyResult, VirtualMachine,
builtins::{PyInt, PyIntRef},
@@ -8,10 +8,10 @@
// spell-checker:ignore cantlock commithook foreignkey notnull primarykey gettemppath autoindex convpath
// spell-checker:ignore dbmoved vnode nbytes
pub(crate) use _sqlite::module_def;
pub(crate) use _sqlite3::module_def;
#[pymodule]
mod _sqlite {
mod _sqlite3 {
use libsqlite3_sys::{
SQLITE_BLOB, SQLITE_DETERMINISTIC, SQLITE_FLOAT, SQLITE_INTEGER, SQLITE_NULL,
SQLITE_OPEN_CREATE, SQLITE_OPEN_READWRITE, SQLITE_OPEN_URI, SQLITE_TEXT, SQLITE_TRACE_STMT,
+81 -125
View File
@@ -38,10 +38,10 @@ mod json;
#[cfg(not(any(target_os = "ios", target_arch = "wasm32")))]
mod locale;
mod _opcode;
mod math;
#[cfg(any(unix, windows))]
mod mmap;
mod opcode;
mod pyexpat;
mod pystruct;
mod random;
@@ -65,6 +65,11 @@ mod posixshmem;
#[cfg(unix)]
mod posixsubprocess;
// libc is missing constants on redox
#[cfg(all(
feature = "sqlite",
not(any(target_os = "android", target_arch = "wasm32"))
))]
mod _sqlite3;
#[cfg(all(unix, not(any(target_os = "android", target_os = "redox"))))]
mod grp;
#[cfg(windows)]
@@ -75,11 +80,6 @@ mod resource;
mod scproxy;
#[cfg(any(unix, windows, target_os = "wasi"))]
mod select;
#[cfg(all(
feature = "sqlite",
not(any(target_os = "android", target_arch = "wasm32"))
))]
mod sqlite;
#[cfg(all(not(target_arch = "wasm32"), feature = "ssl-openssl"))]
mod openssl;
@@ -105,131 +105,87 @@ mod tkinter;
use rustpython_common as common;
use rustpython_vm as vm;
use crate::vm::{builtins, stdlib::StdlibDefFunc};
use alloc::borrow::Cow;
use crate::vm::{Context, builtins};
/// Returns module definitions for multi-phase init modules.
/// These modules are added to sys.modules BEFORE their exec function runs,
/// allowing safe circular imports.
pub fn get_module_defs() -> impl Iterator<Item = (Cow<'static, str>, StdlibDefFunc)> {
macro_rules! modules {
{
$(
#[cfg($cfg:meta)]
{ $( $key:expr => $val:expr),* $(,)? }
)*
} => {{
[
$(
$(#[cfg($cfg)] (Cow::<'static, str>::from($key), $val as StdlibDefFunc),)*
)*
]
.into_iter()
}};
}
modules! {
#[cfg(all())]
{
"array" => array::module_def,
"binascii" => binascii::module_def,
"_bisect" => bisect::module_def,
"_blake2" => blake2::module_def,
"_bz2" => bz2::module_def,
"cmath" => cmath::module_def,
"_contextvars" => contextvars::module_def,
"_csv" => csv::module_def,
"faulthandler" => faulthandler::module_def,
"gc" => gc::module_def,
"_hashlib" => hashlib::module_def,
"_json" => json::module_def,
"math" => math::module_def,
"_md5" => md5::module_def,
"_opcode" => opcode::module_def,
"_random" => random::module_def,
"_sha1" => sha1::module_def,
"_sha256" => sha256::module_def,
"_sha3" => sha3::module_def,
"_sha512" => sha512::module_def,
"_statistics" => statistics::module_def,
"_struct" => pystruct::module_def,
"_suggestions" => suggestions::module_def,
"zlib" => zlib::module_def,
"pyexpat" => pyexpat::module_def,
"unicodedata" => unicodedata::module_def,
}
pub fn stdlib_module_defs(ctx: &Context) -> Vec<&'static builtins::PyModuleDef> {
vec![
_opcode::module_def(ctx),
array::module_def(ctx),
binascii::module_def(ctx),
bisect::module_def(ctx),
blake2::module_def(ctx),
bz2::module_def(ctx),
cmath::module_def(ctx),
contextvars::module_def(ctx),
csv::module_def(ctx),
faulthandler::module_def(ctx),
#[cfg(any(unix, target_os = "wasi"))]
{
"fcntl" => fcntl::module_def,
}
#[cfg(any(unix, windows, target_os = "wasi"))]
{
"select" => select::module_def,
}
#[cfg(not(target_arch = "wasm32"))]
{
"_multiprocessing" => multiprocessing::module_def,
"_socket" => socket::module_def,
}
#[cfg(not(any(target_os = "android", target_arch = "wasm32")))]
{
"_lzma" => lzma::module_def,
}
#[cfg(all(feature = "sqlite", not(any(target_os = "android", target_arch = "wasm32"))))]
{
"_sqlite3" => sqlite::module_def,
}
#[cfg(all(not(target_arch = "wasm32"), feature = "ssl-rustls"))]
{
"_ssl" => ssl::module_def,
}
#[cfg(all(not(target_arch = "wasm32"), feature = "ssl-openssl"))]
{
"_ssl" => openssl::module_def,
}
#[cfg(windows)]
{
"_overlapped" => overlapped::module_def,
}
#[cfg(unix)]
{
"_posixsubprocess" => posixsubprocess::module_def,
}
#[cfg(all(unix, not(target_os = "redox"), not(target_os = "android")))]
{
"_posixshmem" => posixshmem::module_def,
}
#[cfg(any(unix, windows))]
{
"mmap" => mmap::module_def,
}
#[cfg(all(unix, not(target_os = "redox")))]
{
"syslog" => syslog::module_def,
"resource" => resource::module_def,
}
#[cfg(all(unix, not(any(target_os = "ios", target_os = "redox"))))]
{
"termios" => termios::module_def,
}
fcntl::module_def(ctx),
gc::module_def(ctx),
#[cfg(all(unix, not(any(target_os = "android", target_os = "redox"))))]
{
"grp" => grp::module_def,
}
#[cfg(target_os = "macos")]
{
"_scproxy" => scproxy::module_def,
}
#[cfg(not(any(target_os = "android", target_os = "ios", target_os = "windows", target_arch = "wasm32", target_os = "redox")))]
{
"_uuid" => uuid::module_def,
}
grp::module_def(ctx),
hashlib::module_def(ctx),
json::module_def(ctx),
#[cfg(not(any(target_os = "ios", target_arch = "wasm32")))]
{
"_locale" => locale::module_def,
}
locale::module_def(ctx),
#[cfg(not(any(target_os = "android", target_arch = "wasm32")))]
lzma::module_def(ctx),
math::module_def(ctx),
md5::module_def(ctx),
#[cfg(any(unix, windows))]
mmap::module_def(ctx),
#[cfg(not(target_arch = "wasm32"))]
multiprocessing::module_def(ctx),
#[cfg(all(not(target_arch = "wasm32"), feature = "ssl-openssl"))]
openssl::module_def(ctx),
#[cfg(windows)]
overlapped::module_def(ctx),
#[cfg(unix)]
posixsubprocess::module_def(ctx),
#[cfg(all(unix, not(target_os = "redox"), not(target_os = "android")))]
posixshmem::module_def(ctx),
pyexpat::module_def(ctx),
pystruct::module_def(ctx),
random::module_def(ctx),
#[cfg(all(unix, not(target_os = "redox")))]
resource::module_def(ctx),
#[cfg(target_os = "macos")]
scproxy::module_def(ctx),
#[cfg(any(unix, windows, target_os = "wasi"))]
select::module_def(ctx),
sha1::module_def(ctx),
sha256::module_def(ctx),
sha3::module_def(ctx),
sha512::module_def(ctx),
#[cfg(not(target_arch = "wasm32"))]
socket::module_def(ctx),
#[cfg(all(
feature = "sqlite",
not(any(target_os = "android", target_arch = "wasm32"))
))]
_sqlite3::module_def(ctx),
#[cfg(all(not(target_arch = "wasm32"), feature = "ssl-rustls"))]
ssl::module_def(ctx),
statistics::module_def(ctx),
suggestions::module_def(ctx),
#[cfg(all(unix, not(target_os = "redox")))]
syslog::module_def(ctx),
#[cfg(all(unix, not(any(target_os = "ios", target_os = "redox"))))]
termios::module_def(ctx),
#[cfg(feature = "tkinter")]
{
"_tkinter" => tkinter::module_def,
}
}
tkinter::module_def(ctx),
unicodedata::module_def(ctx),
#[cfg(not(any(
target_os = "android",
target_os = "ios",
target_os = "windows",
target_arch = "wasm32",
target_os = "redox"
)))]
uuid::module_def(ctx),
zlib::module_def(ctx),
]
}
+6 -8
View File
@@ -33,20 +33,18 @@ use std::sync::LazyLock;
cfg_if::cfg_if! {
if #[cfg(openssl_vendored)] {
static PROBE: LazyLock<ProbeResult> = LazyLock::new(openssl_probe::probe);
fn probe() -> &'static ProbeResult { &PROBE }
} else {
static EMPTY_PROBE: LazyLock<ProbeResult> = LazyLock::new(|| ProbeResult { cert_file: None, cert_dir: vec![] });
fn probe() -> &'static ProbeResult {
&EMPTY_PROBE
}
static PROBE: LazyLock<ProbeResult> = LazyLock::new(|| ProbeResult { cert_file: None, cert_dir: vec![] });
}
}
fn probe() -> &'static ProbeResult {
&PROBE
}
#[allow(non_upper_case_globals)]
#[pymodule(with(cert::ssl_cert, ssl_error::ssl_error, ossl101, ossl111, windows))]
mod _ssl {
#[cfg(openssl_vendored)]
use super::PROBE;
use super::{bio, probe};
// Import error types and helpers used in this module (others are exposed via pymodule(with(...)))
@@ -103,7 +101,7 @@ mod _ssl {
// if openssl is vendored, it doesn't know the locations
// of system certificates - cache the probe result now.
#[cfg(openssl_vendored)]
std::sync::LazyLock::force(&PROBE);
std::sync::LazyLock::force(&super::PROBE);
__module_exec(vm, module);
Ok(())
+8 -4
View File
@@ -42,6 +42,7 @@ mod _pyexpat {
use crate::vm::{
Context, Py, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject, VirtualMachine,
builtins::{PyBytesRef, PyModule, PyStr, PyStrRef, PyType},
extend_module,
function::ArgBytesLike,
function::{Either, IntoFuncArgs, OptionalArg},
};
@@ -53,10 +54,13 @@ mod _pyexpat {
__module_exec(vm, module);
// Add submodules
let model = PyModule::from_def(super::_model::module_def(&vm.ctx)).into_ref(&vm.ctx);
let errors = PyModule::from_def(super::_errors::module_def(&vm.ctx)).into_ref(&vm.ctx);
module.set_attr("model", model, vm)?;
module.set_attr("errors", errors, vm)?;
let model = super::_model::module_def(&vm.ctx).create_module(vm)?;
let errors = super::_errors::module_def(&vm.ctx).create_module(vm)?;
extend_module!(vm, module, {
"model" => model,
"errors" => errors,
});
Ok(())
}
+40
View File
@@ -41,6 +41,46 @@ impl core::fmt::Debug for PyModuleSlots {
}
}
impl PyModuleDef {
/// Create a module from this definition (Phase 1 of multi-phase init).
///
/// This performs:
/// 1. Create module object (using create slot if provided)
/// 2. Initialize module dict from def
/// 3. Add methods to module
///
/// Does NOT add to sys.modules or call exec slot.
pub fn create_module(&'static self, vm: &VirtualMachine) -> PyResult<PyRef<PyModule>> {
use crate::PyPayload;
// Create module (use create slot if provided, else default creation)
let module = if let Some(create) = self.slots.create {
// Custom module creation
let spec = vm.ctx.new_str(self.name.as_str());
create(vm, spec.as_object(), self)?
} else {
// Default module creation
PyModule::from_def(self).into_ref(&vm.ctx)
};
// Initialize module dict and methods
PyModule::__init_dict_from_def(vm, &module);
module.__init_methods(vm)?;
Ok(module)
}
/// Execute the module's exec slot (Phase 2 of multi-phase init).
///
/// Calls the exec slot if present. Returns Ok(()) if no exec slot.
pub fn exec_module(&'static self, vm: &VirtualMachine, module: &Py<PyModule>) -> PyResult<()> {
if let Some(exec) = self.slots.exec {
exec(vm, module)?;
}
Ok(())
}
}
#[allow(clippy::new_without_default)] // avoid Default implementation
#[pyclass(module = false, name = "module")]
#[derive(Debug)]
+12 -23
View File
@@ -87,9 +87,6 @@ pub fn import_frozen(vm: &VirtualMachine, module_name: &str) -> PyResult {
}
pub fn import_builtin(vm: &VirtualMachine, module_name: &str) -> PyResult {
use crate::PyPayload;
use crate::builtins::PyModule;
let sys_modules = vm.sys_module.get_attr("modules", vm)?;
// Check if already in sys.modules (handles recursive imports)
@@ -98,36 +95,28 @@ pub fn import_builtin(vm: &VirtualMachine, module_name: &str) -> PyResult {
}
// Try multi-phase init first (preferred for modules that import other modules)
if let Some(def_func) = vm.state.module_defs.get(module_name) {
let def = def_func(&vm.ctx);
// Phase 1: Create module from definition
let module = PyModule::from_def(def).into_ref(&vm.ctx);
// Initialize module dict using proper method
PyModule::__init_dict_from_def(vm, &module);
if let Some(&def) = vm.state.module_defs.get(module_name) {
// Phase 1: Create and initialize module
let module = def.create_module(vm)?;
// Add to sys.modules BEFORE exec (critical for circular import handling)
sys_modules.set_item(module_name, module.clone().into(), vm)?;
// Phase 2: Call exec slot (can safely import other modules now)
if let Some(exec) = def.slots.exec {
exec(vm, &module)?;
// If exec fails, remove the partially-initialized module from sys.modules
if let Err(e) = def.exec_module(vm, &module) {
let _ = sys_modules.del_item(module_name, vm);
return Err(e);
}
return Ok(module.into());
}
// Fall back to legacy single-phase init
let make_module_func = vm.state.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),
)
})?;
let module = make_module_func(vm);
sys_modules.set_item(module_name, module.as_object().to_owned(), vm)?;
Ok(module.into())
// Module not found in module_defs
Err(vm.new_import_error(
format!("Cannot import builtin module {module_name}"),
vm.ctx.new_str(module_name),
))
}
#[cfg(feature = "rustpython-compiler")]
+1 -1
View File
@@ -99,7 +99,7 @@ pub use self::object::{
AsObject, Py, PyAtomicRef, PyExact, PyObject, PyObjectRef, PyPayload, PyRef, PyRefExact,
PyResult, PyWeakRef,
};
pub use self::vm::{Context, Interpreter, Settings, VirtualMachine};
pub use self::vm::{Context, Interpreter, InterpreterBuilder, Settings, VirtualMachine};
pub use rustpython_common as common;
pub use rustpython_compiler_core::{bytecode, frozen};
+1 -1
View File
@@ -3,7 +3,7 @@
//! This module provides the C implementation of Abstract Base Classes (ABCs)
//! as defined in PEP 3119.
pub(crate) use _abc::make_module;
pub(crate) use _abc::module_def;
#[pymodule]
mod _abc {
+3 -7
View File
@@ -1,14 +1,16 @@
//! `ast` standard module for abstract syntax trees.
//!
//! This module makes use of the parser logic, and translates all ast nodes
//! into python ast.AST objects.
pub(crate) use python::_ast::module_def;
mod pyast;
use crate::builtins::{PyInt, PyStr};
use crate::stdlib::ast::module::{Mod, ModInteractive};
use crate::stdlib::ast::node::BoxedSlice;
use crate::stdlib::ast::python::_ast;
use crate::{
AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyRefExact, PyResult,
TryFromObject, VirtualMachine,
@@ -365,9 +367,3 @@ pub const PY_COMPILE_FLAGS_MASK: i32 = PY_COMPILE_FLAG_AST_ONLY
| CO_FUTURE_BARRY_AS_BDFL
| CO_FUTURE_GENERATOR_STOP
| CO_FUTURE_ANNOTATIONS;
pub fn make_module(vm: &VirtualMachine) -> PyRef<PyModule> {
let module = _ast::make_module(vm);
pyast::extend_module_nodes(vm, &module);
module
}
+9
View File
@@ -96,4 +96,13 @@ pub(crate) mod _ast {
#[pyattr(name = "PyCF_TYPE_COMMENTS")]
use super::PY_CF_TYPE_COMMENTS;
pub(crate) fn module_exec(
vm: &VirtualMachine,
module: &Py<crate::builtins::PyModule>,
) -> PyResult<()> {
__module_exec(vm, module);
super::super::pyast::extend_module_nodes(vm, module);
Ok(())
}
}
+1 -1
View File
@@ -1,5 +1,5 @@
pub use atexit::_run_exitfuncs;
pub(crate) use atexit::make_module;
pub(crate) use atexit::module_def;
#[pymodule]
mod atexit {
+1
View File
@@ -1100,6 +1100,7 @@ pub fn init_module(vm: &VirtualMachine, module: &Py<PyModule>) {
crate::protocol::VecBuffer::make_class(&vm.ctx);
module.__init_methods(vm).unwrap();
builtins::module_exec(vm, module).unwrap();
let debug_mode: bool = vm.state.config.settings.optimize == 0;
+1 -1
View File
@@ -1,4 +1,4 @@
pub(crate) use _codecs::make_module;
pub(crate) use _codecs::module_def;
#[pymodule]
mod _codecs {
+1 -1
View File
@@ -1,4 +1,4 @@
pub(crate) use _collections::make_module;
pub(crate) use _collections::module_def;
#[pymodule]
mod _collections {
+37 -27
View File
@@ -10,8 +10,8 @@ mod structure;
mod union;
use crate::{
AsObject, Py, PyObjectRef, PyRef, PyResult, VirtualMachine,
builtins::{PyModule, PyStr, PyType},
AsObject, Py, PyObjectRef, PyResult, VirtualMachine,
builtins::{PyStr, PyType},
class::PyClassImpl,
types::TypeDataRef,
};
@@ -85,32 +85,9 @@ impl PyType {
}
// Dynamic type check helpers for PyCData
// These check if an object's type's metaclass is a subclass of a specific metaclass
pub(crate) use _ctypes::module_def;
pub(crate) fn make_module(vm: &VirtualMachine) -> PyRef<PyModule> {
let module = _ctypes::make_module(vm);
let ctx = &vm.ctx;
PyCSimpleType::make_class(ctx);
array::PyCArrayType::make_class(ctx);
pointer::PyCPointerType::make_class(ctx);
structure::PyCStructType::make_class(ctx);
union::PyCUnionType::make_class(ctx);
function::PyCFuncPtrType::make_class(ctx);
extend_module!(vm, &module, {
"_CData" => PyCData::make_class(ctx),
"_SimpleCData" => PyCSimple::make_class(ctx),
"Array" => PyCArray::make_class(ctx),
"CField" => PyCField::make_class(ctx),
"CFuncPtr" => function::PyCFuncPtr::make_class(ctx),
"_Pointer" => PyCPointer::make_class(ctx),
"_pointer_type_cache" => ctx.new_dict(),
"_array_type_cache" => ctx.new_dict(),
"Structure" => PyCStructure::make_class(ctx),
"CThunkObject" => function::PyCThunk::make_class(ctx),
"Union" => PyCUnion::make_class(ctx),
});
module
}
// These check if an object's type's metaclass is a subclass of a specific metaclass
/// Size of long double - platform dependent
/// x86_64 macOS/Linux: 16 bytes (80-bit extended + padding)
@@ -1300,4 +1277,37 @@ pub(crate) mod _ctypes {
Ok(S_OK)
}
pub(crate) fn module_exec(
vm: &VirtualMachine,
module: &Py<crate::builtins::PyModule>,
) -> PyResult<()> {
use super::*;
__module_exec(vm, module);
let ctx = &vm.ctx;
PyCSimpleType::make_class(ctx);
array::PyCArrayType::make_class(ctx);
pointer::PyCPointerType::make_class(ctx);
structure::PyCStructType::make_class(ctx);
union::PyCUnionType::make_class(ctx);
function::PyCFuncPtrType::make_class(ctx);
extend_module!(vm, module, {
"_CData" => PyCData::make_class(ctx),
"_SimpleCData" => PyCSimple::make_class(ctx),
"Array" => PyCArray::make_class(ctx),
"CField" => PyCField::make_class(ctx),
"CFuncPtr" => function::PyCFuncPtr::make_class(ctx),
"_Pointer" => PyCPointer::make_class(ctx),
"_pointer_type_cache" => ctx.new_dict(),
"_array_type_cache" => ctx.new_dict(),
"Structure" => PyCStructure::make_class(ctx),
"CThunkObject" => function::PyCThunk::make_class(ctx),
"Union" => PyCUnion::make_class(ctx),
});
Ok(())
}
}
+18 -17
View File
@@ -1,25 +1,26 @@
// spell-checker:disable
use crate::{PyRef, VirtualMachine, builtins::PyModule};
pub(crate) use errno_mod::module_def;
#[pymodule]
mod errno {}
#[pymodule(name = "errno")]
mod errno_mod {
use crate::{Py, PyResult, VirtualMachine, builtins::PyModule};
pub fn make_module(vm: &VirtualMachine) -> PyRef<PyModule> {
let module = errno::make_module(vm);
let errorcode = vm.ctx.new_dict();
extend_module!(vm, &module, {
"errorcode" => errorcode.clone(),
});
for (name, code) in ERROR_CODES {
let name = vm.ctx.intern_str(*name);
let code = vm.new_pyobj(*code);
errorcode
.set_item(&*code, name.to_owned().into(), vm)
.unwrap();
module.set_attr(name, code, vm).unwrap();
pub(crate) fn module_exec(vm: &VirtualMachine, module: &Py<PyModule>) -> PyResult<()> {
__module_exec(vm, module);
let errorcode = vm.ctx.new_dict();
extend_module!(vm, module, {
"errorcode" => errorcode.clone(),
});
for (name, code) in super::ERROR_CODES {
let name = vm.ctx.intern_str(*name);
let code = vm.new_pyobj(*code);
errorcode.set_item(&*code, name.to_owned().into(), vm)?;
module.set_attr(name, code, vm)?;
}
Ok(())
}
module
}
#[cfg(any(unix, windows, target_os = "wasi"))]
+1 -1
View File
@@ -1,4 +1,4 @@
pub(crate) use _functools::make_module;
pub(crate) use _functools::module_def;
#[pymodule]
mod _functools {
+15 -20
View File
@@ -1,6 +1,6 @@
use crate::frozen::FrozenModule;
use crate::{VirtualMachine, builtins::PyBaseExceptionRef};
pub(crate) use _imp::make_module;
pub(crate) use _imp::module_def;
pub use crate::vm::resolve_frozen_alias;
@@ -84,7 +84,7 @@ fn find_frozen(name: &str, vm: &VirtualMachine) -> Result<FrozenModule, FrozenEr
#[pymodule(with(lock))]
mod _imp {
use crate::{
PyObjectRef, PyRef, PyResult, VirtualMachine,
PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine,
builtins::{PyBytesRef, PyCode, PyMemoryView, PyModule, PyStrRef},
function::OptionalArg,
import, version,
@@ -106,8 +106,7 @@ mod _imp {
#[pyfunction]
fn is_builtin(name: PyStrRef, vm: &VirtualMachine) -> bool {
vm.state.module_inits.contains_key(name.as_str())
|| vm.state.module_defs.contains_key(name.as_str())
vm.state.module_defs.contains_key(name.as_str())
}
#[pyfunction]
@@ -117,9 +116,6 @@ mod _imp {
#[pyfunction]
fn create_builtin(spec: PyObjectRef, vm: &VirtualMachine) -> PyResult {
use crate::PyPayload;
use crate::builtins::PyModule;
let sys_modules = vm.sys_module.get_attr("modules", vm).unwrap();
let name: PyStrRef = spec.get_attr("name", vm)?.try_into_value(vm)?;
@@ -129,14 +125,20 @@ mod _imp {
}
// Try multi-phase init modules first (they need special handling)
if let Some(def_func) = vm.state.module_defs.get(name.as_str()) {
let def = def_func(&vm.ctx);
if let Some(&def) = vm.state.module_defs.get(name.as_str()) {
// Phase 1: Create module (use create slot if provided, else default creation)
let module = if let Some(create) = def.slots.create {
// Custom module creation
create(vm, &spec, def)?
} else {
// Default module creation
PyModule::from_def(def).into_ref(&vm.ctx)
};
// Phase 1: Create module from definition
let module = PyModule::from_def(def).into_ref(&vm.ctx);
// Initialize module dict using proper method
// Initialize module dict and methods
// Corresponds to PyModule_FromDefAndSpec: md_def, _add_methods_to_object, PyModule_SetDocString
PyModule::__init_dict_from_def(vm, &module);
module.__init_methods(vm)?;
// Add to sys.modules BEFORE exec (critical for circular import handling)
sys_modules.set_item(&*name, module.clone().into(), vm)?;
@@ -149,13 +151,6 @@ mod _imp {
return Ok(module.into());
}
// Fall back to legacy single-phase init
if let Some(make_module_func) = vm.state.module_inits.get(name.as_str()) {
let module = make_module_func(vm);
sys_modules.set_item(&*name, module.clone().into(), vm)?;
return Ok(module.into());
}
Ok(vm.ctx.none())
}
+19 -19
View File
@@ -1,6 +1,8 @@
/*
* I/O core tools.
*/
pub(crate) use _io::module_def;
cfg_if::cfg_if! {
if #[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))] {
use crate::common::crt_fd::Offset;
@@ -19,7 +21,7 @@ cfg_if::cfg_if! {
}
use crate::{
PyObjectRef, PyRef, PyResult, TryFromObject, VirtualMachine,
PyObjectRef, PyResult, TryFromObject, VirtualMachine,
builtins::{PyBaseExceptionRef, PyModule},
common::os::ErrorExt,
convert::{IntoPyException, ToPyException},
@@ -89,23 +91,6 @@ impl IntoPyException for std::io::Error {
}
}
pub(crate) fn make_module(vm: &VirtualMachine) -> PyRef<PyModule> {
let ctx = &vm.ctx;
let module = _io::make_module(vm);
#[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))]
fileio::module_exec(vm, &module).unwrap();
let unsupported_operation = _io::unsupported_operation().to_owned();
extend_module!(vm, &module, {
"UnsupportedOperation" => unsupported_operation,
"BlockingIOError" => ctx.exceptions.blocking_io_error.to_owned(),
});
module
}
// not used on all platforms
#[derive(Copy, Clone)]
#[repr(transparent)]
@@ -4743,8 +4728,23 @@ mod _io {
assert_eq!(buffered.getvalue(), data);
}
}
}
pub(crate) fn module_exec(vm: &VirtualMachine, module: &Py<PyModule>) -> PyResult<()> {
// Call auto-generated initialization first
__module_exec(vm, module);
// Initialize FileIO types on non-WASM platforms
#[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))]
super::fileio::module_exec(vm, module)?;
let unsupported_operation = unsupported_operation().to_owned();
extend_module!(vm, module, {
"UnsupportedOperation" => unsupported_operation,
"BlockingIOError" => vm.ctx.exceptions.blocking_io_error.to_owned(),
});
Ok(())
}
}
// disable FileIO on WASM
#[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))]
#[pymodule]
+1 -1
View File
@@ -1,4 +1,4 @@
pub(crate) use decl::make_module;
pub(crate) use decl::module_def;
#[pymodule(name = "itertools")]
mod decl {
+1 -1
View File
@@ -1,5 +1,5 @@
// spell-checker:ignore pyfrozen pycomplex
pub(crate) use decl::make_module;
pub(crate) use decl::module_def;
#[pymodule(name = "marshal")]
mod decl {
+56 -104
View File
@@ -62,114 +62,66 @@ mod winapi;
#[cfg(windows)]
mod winreg;
use crate::{Context, PyRef, VirtualMachine, builtins::PyModule, builtins::PyModuleDef};
use alloc::borrow::Cow;
use std::collections::HashMap;
use crate::{Context, builtins::PyModuleDef};
/// Legacy single-phase init: function that creates and populates a module in one step
pub type StdlibInitFunc = Box<py_dyn_fn!(dyn Fn(&VirtualMachine) -> PyRef<PyModule>)>;
/// Multi-phase init: function that returns a module definition
/// The import machinery will:
/// 1. Create module from def
/// 2. Add to sys.modules
/// 3. Call exec slot (which can safely import other modules)
pub type StdlibDefFunc = fn(&Context) -> &'static PyModuleDef;
pub type StdlibMap = HashMap<Cow<'static, str>, StdlibInitFunc, ahash::RandomState>;
pub type StdlibDefMap = HashMap<Cow<'static, str>, StdlibDefFunc, ahash::RandomState>;
pub fn get_module_inits() -> StdlibMap {
macro_rules! modules {
{
$(
#[cfg($cfg:meta)]
{ $( $key:expr => $val:expr),* $(,)? }
)*
} => {{
let modules = [
$(
$(#[cfg($cfg)] (Cow::<'static, str>::from($key), Box::new($val) as StdlibInitFunc),)*
)*
];
modules.into_iter().collect()
}};
}
modules! {
#[cfg(all())]
{
"_abc" => _abc::make_module,
"atexit" => atexit::make_module,
"_codecs" => codecs::make_module,
"_collections" => collections::make_module,
"errno" => errno::make_module,
"_functools" => functools::make_module,
"itertools" => itertools::make_module,
"_io" => io::make_module,
"marshal" => marshal::make_module,
"_operator" => operator::make_module,
"_signal" => signal::make_module,
"_sre" => sre::make_module,
"_stat" => stat::make_module,
"_sysconfig" => sysconfig::make_module,
"_string" => string::make_module,
"time" => time::make_module,
"_typing" => typing::make_module,
"_weakref" => weakref::make_module,
"_imp" => imp::make_module,
"_warnings" => warnings::make_module,
sys::sysconfigdata_name() => sysconfigdata::make_module,
}
// parser related modules:
/// Returns module definitions for multi-phase init modules.
///
/// These modules use multi-phase initialization pattern:
/// 1. Create module from def and add to sys.modules
/// 2. Call exec slot (can safely import other modules without circular import issues)
pub fn builtin_module_defs(ctx: &Context) -> Vec<&'static PyModuleDef> {
vec![
_abc::module_def(ctx),
#[cfg(feature = "ast")]
{
"_ast" => ast::make_module,
}
// compiler related modules:
#[cfg(feature = "compiler")]
{
"_symtable" => symtable::make_module,
}
#[cfg(any(unix, target_os = "wasi"))]
{
"posix" => posix::make_module,
// "fcntl" => fcntl::make_module,
}
#[cfg(feature = "threading")]
{
"_thread" => thread::make_module,
}
// Unix-only
#[cfg(all(
unix,
not(any(target_os = "ios", target_os = "wasi", target_os = "redox"))
))]
{
"pwd" => pwd::make_module,
}
// Windows-only
#[cfg(windows)]
{
"nt" => nt::make_module,
"msvcrt" => msvcrt::make_module,
"_winapi" => winapi::make_module,
"winreg" => winreg::make_module,
}
ast::module_def(ctx),
atexit::module_def(ctx),
codecs::module_def(ctx),
collections::module_def(ctx),
#[cfg(all(
any(target_os = "linux", target_os = "macos", target_os = "windows"),
not(any(target_env = "musl", target_env = "sgx"))
))]
{
"_ctypes" => ctypes::make_module,
}
}
}
/// Returns module definitions for multi-phase init modules.
/// These modules use CPython's two-phase initialization pattern:
/// 1. Create module from def and add to sys.modules
/// 2. Call exec slot (can safely import other modules without circular import issues)
pub fn get_module_defs() -> StdlibDefMap {
// Currently empty - modules will be migrated to multi-phase init as needed
HashMap::default()
ctypes::module_def(ctx),
errno::module_def(ctx),
functools::module_def(ctx),
imp::module_def(ctx),
io::module_def(ctx),
itertools::module_def(ctx),
marshal::module_def(ctx),
#[cfg(windows)]
msvcrt::module_def(ctx),
#[cfg(windows)]
nt::module_def(ctx),
operator::module_def(ctx),
#[cfg(any(unix, target_os = "wasi"))]
posix::module_def(ctx),
#[cfg(all(
any(not(target_arch = "wasm32"), target_os = "wasi"),
not(any(unix, windows))
))]
posix::module_def(ctx),
#[cfg(all(
unix,
not(any(target_os = "ios", target_os = "wasi", target_os = "redox"))
))]
pwd::module_def(ctx),
signal::module_def(ctx),
sre::module_def(ctx),
stat::module_def(ctx),
string::module_def(ctx),
#[cfg(feature = "compiler")]
symtable::module_def(ctx),
sysconfigdata::module_def(ctx),
sysconfig::module_def(ctx),
#[cfg(feature = "threading")]
thread::module_def(ctx),
time::module_def(ctx),
typing::module_def(ctx),
warnings::module_def(ctx),
weakref::module_def(ctx),
#[cfg(windows)]
winapi::module_def(ctx),
#[cfg(windows)]
winreg::module_def(ctx),
]
}
+11 -9
View File
@@ -1,19 +1,12 @@
// spell-checker:disable
use crate::{PyRef, VirtualMachine, builtins::PyModule};
pub(crate) use module::module_def;
pub use module::raw_set_handle_inheritable;
pub(crate) fn make_module(vm: &VirtualMachine) -> PyRef<PyModule> {
let module = module::make_module(vm);
super::os::module_exec(vm, &module);
module
}
#[pymodule(name = "nt", with(super::os::_os))]
pub(crate) mod module {
use crate::{
PyResult, TryFromObject, VirtualMachine,
Py, PyResult, TryFromObject, VirtualMachine,
builtins::{PyBaseExceptionRef, PyDictRef, PyListRef, PyStrRef, PyTupleRef},
common::{crt_fd, suppress_iph, windows::ToWideString},
convert::ToPyException,
@@ -1923,4 +1916,13 @@ pub(crate) mod module {
pub(crate) fn support_funcs() -> Vec<SupportFunc> {
Vec::new()
}
pub(crate) fn module_exec(
vm: &VirtualMachine,
module: &Py<crate::builtins::PyModule>,
) -> PyResult<()> {
__module_exec(vm, module);
super::super::os::module_exec(vm, module)?;
Ok(())
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
pub(crate) use _operator::make_module;
pub(crate) use _operator::module_def;
#[pymodule]
mod _operator {
+9 -6
View File
@@ -149,6 +149,8 @@ pub(super) mod _os {
use super::{DirFd, FollowSymlinks, SupportFunc};
#[cfg(windows)]
use crate::common::windows::ToWideString;
#[cfg(any(unix, windows))]
use crate::utils::ToCString;
use crate::{
AsObject, Py, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject,
builtins::{
@@ -168,7 +170,6 @@ pub(super) mod _os {
protocol::PyIterReturn,
recursion::ReprGuard,
types::{IterNext, Iterable, PyStructSequence, Representable, SelfIter},
utils::ToCString,
vm::VirtualMachine,
};
use core::time::Duration;
@@ -1896,21 +1897,21 @@ impl SupportFunc {
}
}
pub fn module_exec(vm: &VirtualMachine, module: &Py<PyModule>) {
pub fn module_exec(vm: &VirtualMachine, module: &Py<PyModule>) -> PyResult<()> {
let support_funcs = _os::support_funcs();
let supports_fd = PySet::default().into_ref(&vm.ctx);
let supports_dir_fd = PySet::default().into_ref(&vm.ctx);
let supports_follow_symlinks = PySet::default().into_ref(&vm.ctx);
for support in support_funcs {
let func_obj = module.get_attr(support.name, vm).unwrap();
let func_obj = module.get_attr(support.name, vm)?;
if support.fd.unwrap_or(false) {
supports_fd.clone().add(func_obj.clone(), vm).unwrap();
supports_fd.clone().add(func_obj.clone(), vm)?;
}
if support.dir_fd.unwrap_or(false) {
supports_dir_fd.clone().add(func_obj.clone(), vm).unwrap();
supports_dir_fd.clone().add(func_obj.clone(), vm)?;
}
if support.follow_symlinks.unwrap_or(false) {
supports_follow_symlinks.clone().add(func_obj, vm).unwrap();
supports_follow_symlinks.clone().add(func_obj, vm)?;
}
}
@@ -1920,6 +1921,8 @@ pub fn module_exec(vm: &VirtualMachine, module: &Py<PyModule>) {
"supports_follow_symlinks" => supports_follow_symlinks,
"error" => vm.ctx.exceptions.os_error.to_owned(),
});
Ok(())
}
#[cfg(not(windows))]
+11 -7
View File
@@ -1,8 +1,9 @@
// spell-checker:disable
use crate::{PyRef, VirtualMachine, builtins::PyModule};
use std::os::fd::BorrowedFd;
pub(crate) use module::module_def;
pub fn set_inheritable(fd: BorrowedFd<'_>, inheritable: bool) -> nix::Result<()> {
use nix::fcntl;
let flags = fcntl::FdFlag::from_bits_truncate(fcntl::fcntl(fd, fcntl::FcntlArg::F_GETFD)?);
@@ -14,12 +15,6 @@ pub fn set_inheritable(fd: BorrowedFd<'_>, inheritable: bool) -> nix::Result<()>
Ok(())
}
pub(crate) fn make_module(vm: &VirtualMachine) -> PyRef<PyModule> {
let module = module::make_module(vm);
super::os::module_exec(vm, &module);
module
}
#[pymodule(name = "posix", with(super::os::_os))]
pub mod module {
use crate::{
@@ -2606,4 +2601,13 @@ pub mod module {
}
Ok(buf)
}
pub(crate) fn module_exec(
vm: &VirtualMachine,
module: &Py<crate::builtins::PyModule>,
) -> PyResult<()> {
__module_exec(vm, module);
super::super::os::module_exec(vm, module)?;
Ok(())
}
}
+11 -7
View File
@@ -1,18 +1,13 @@
// spell-checker:disable
//! `posix` compatible module for `not(any(unix, windows))`
use crate::{PyRef, VirtualMachine, builtins::PyModule};
pub(crate) fn make_module(vm: &VirtualMachine) -> PyRef<PyModule> {
let module = module::make_module(vm);
super::os::module_exec(vm, &module);
module
}
pub(crate) use module::module_def;
#[pymodule(name = "posix", with(super::os::_os))]
pub(crate) mod module {
use crate::{
PyObjectRef, PyResult, VirtualMachine,
Py, PyObjectRef, PyResult, VirtualMachine,
builtins::PyStrRef,
convert::IntoPyException,
ospath::OsPath,
@@ -71,4 +66,13 @@ pub(crate) mod module {
pub(crate) fn support_funcs() -> Vec<SupportFunc> {
Vec::new()
}
pub(crate) fn module_exec(
vm: &VirtualMachine,
module: &Py<crate::builtins::PyModule>,
) -> PyResult<()> {
__module_exec(vm, module);
super::super::os::module_exec(vm, module)?;
Ok(())
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
// spell-checker:disable
pub(crate) use pwd::make_module;
pub(crate) use pwd::module_def;
#[pymodule]
mod pwd {
+15 -15
View File
@@ -1,24 +1,12 @@
// spell-checker:disable
use crate::{PyRef, VirtualMachine, builtins::PyModule};
pub(crate) fn make_module(vm: &VirtualMachine) -> PyRef<PyModule> {
let module = _signal::make_module(vm);
#[cfg(any(unix, windows))]
_signal::init_signal_handlers(&module, vm);
module
}
pub(crate) use _signal::module_def;
#[pymodule]
pub(crate) mod _signal {
#[cfg(any(unix, windows))]
use crate::{
Py,
convert::{IntoPyException, TryFromBorrowedObject},
};
use crate::{PyObjectRef, PyResult, VirtualMachine, signal};
use crate::convert::{IntoPyException, TryFromBorrowedObject};
use crate::{Py, PyObjectRef, PyResult, VirtualMachine, signal};
#[cfg(unix)]
use crate::{
builtins::PyTypeRef,
@@ -682,4 +670,16 @@ pub(crate) mod _signal {
// TODO: handle _res < 1, support warn_on_full_buffer
}
}
pub(crate) fn module_exec(
vm: &VirtualMachine,
module: &Py<crate::builtins::PyModule>,
) -> PyResult<()> {
__module_exec(vm, module);
#[cfg(any(unix, windows))]
init_signal_handlers(module, vm);
Ok(())
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
pub(crate) use _sre::make_module;
pub(crate) use _sre::module_def;
#[pymodule]
mod _sre {
+2 -6
View File
@@ -1,7 +1,7 @@
use crate::{PyRef, VirtualMachine, builtins::PyModule};
pub(crate) use _stat::module_def;
#[pymodule]
mod stat {
mod _stat {
// Use libc::mode_t for Mode to match the system's definition
#[cfg(unix)]
type Mode = libc::mode_t;
@@ -522,7 +522,3 @@ mod stat {
result
}
}
pub fn make_module(vm: &VirtualMachine) -> PyRef<PyModule> {
stat::make_module(vm)
}
+1 -1
View File
@@ -1,7 +1,7 @@
/* String builtin module
*/
pub(crate) use _string::make_module;
pub(crate) use _string::module_def;
#[pymodule]
mod _string {
+2 -2
View File
@@ -1,7 +1,7 @@
pub(crate) use symtable::make_module;
pub(crate) use _symtable::module_def;
#[pymodule]
mod symtable {
mod _symtable {
use crate::{
PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine,
builtins::{PyDictRef, PyStrRef},
+10 -8
View File
@@ -78,7 +78,7 @@ mod sys {
#[pyattr(name = "abiflags")]
const ABIFLAGS_ATTR: &str = "t"; // 't' for free-threaded (no GIL)
// Internal constant used for sysconfigdata_name
pub(crate) const ABIFLAGS: &str = "t";
pub const ABIFLAGS: &str = "t";
#[pyattr(name = "api_version")]
const API_VERSION: u32 = 0x0; // what C api?
#[pyattr(name = "copyright")]
@@ -94,7 +94,7 @@ mod sys {
#[pyattr(name = "maxunicode")]
const MAXUNICODE: u32 = core::char::MAX as u32;
#[pyattr(name = "platform")]
pub(crate) const PLATFORM: &str = {
pub const PLATFORM: &str = {
cfg_if::cfg_if! {
if #[cfg(target_os = "linux")] {
"linux"
@@ -166,11 +166,11 @@ mod sys {
#[pyattr]
fn builtin_module_names(vm: &VirtualMachine) -> PyTupleRef {
let mut module_names: Vec<_> = vm.state.module_inits.keys().cloned().collect();
// Also include multi-phase init modules
module_names.extend(vm.state.module_defs.keys().cloned());
module_names.push("sys".into());
module_names.push("builtins".into());
let mut module_names: Vec<String> =
vm.state.module_defs.keys().map(|&s| s.to_owned()).collect();
module_names.push("sys".to_owned());
module_names.push("builtins".to_owned());
module_names.sort();
vm.ctx.new_tuple(
module_names
@@ -1609,6 +1609,7 @@ mod sys {
}
pub(crate) fn init_module(vm: &VirtualMachine, module: &Py<PyModule>, builtins: &Py<PyModule>) {
module.__init_methods(vm).unwrap();
sys::module_exec(vm, module).unwrap();
let modules = vm.ctx.new_dict();
@@ -1620,7 +1621,8 @@ pub(crate) fn init_module(vm: &VirtualMachine, module: &Py<PyModule>, builtins:
.unwrap();
// Create sys._jit submodule
let jit_module = sys_jit::make_module(vm);
let jit_def = sys_jit::module_def(&vm.ctx);
let jit_module = jit_def.create_module(vm).unwrap();
extend_module!(vm, module, {
"__doc__" => sys::DOC.to_owned().to_pyobject(vm),
+3 -3
View File
@@ -1,7 +1,7 @@
pub(crate) use sysconfig::make_module;
pub(crate) use _sysconfig::module_def;
#[pymodule(name = "_sysconfig")]
pub(crate) mod sysconfig {
#[pymodule]
pub(crate) mod _sysconfig {
use crate::{VirtualMachine, builtins::PyDictRef, convert::ToPyObject};
#[pyfunction]
+22 -4
View File
@@ -1,11 +1,29 @@
// spell-checker: words LDSHARED ARFLAGS CPPFLAGS CCSHARED BASECFLAGS BLDSHARED
pub(crate) use _sysconfigdata::make_module;
pub(crate) use _sysconfigdata::module_def;
#[pymodule]
pub(crate) mod _sysconfigdata {
use crate::stdlib::sys::{RUST_MULTIARCH, multiarch};
use crate::{VirtualMachine, builtins::PyDictRef, convert::ToPyObject};
mod _sysconfigdata {
use crate::stdlib::sys::{RUST_MULTIARCH, multiarch, sysconfigdata_name};
use crate::{
Py, PyResult, VirtualMachine,
builtins::{PyDictRef, PyModule},
convert::ToPyObject,
};
fn module_exec(vm: &VirtualMachine, module: &Py<PyModule>) -> PyResult<()> {
// Set build_time_vars attribute
let build_time_vars = build_time_vars(vm);
module.set_attr("build_time_vars", build_time_vars, vm)?;
// Ensure the module is registered under the platform-specific name
// (import_builtin() already handles this, but double-check for safety)
let sys_modules = vm.sys_module.get_attr("modules", vm)?;
let sysconfigdata_name = sysconfigdata_name();
sys_modules.set_item(sysconfigdata_name.as_str(), module.to_owned().into(), vm)?;
Ok(())
}
#[pyattr]
fn build_time_vars(vm: &VirtualMachine) -> PyDictRef {
+1 -1
View File
@@ -4,7 +4,7 @@ pub(crate) use _thread::after_fork_child;
#[cfg_attr(target_arch = "wasm32", allow(unused_imports))]
pub(crate) use _thread::{
CurrentFrameSlot, HandleEntry, RawRMutex, ShutdownEntry, get_all_current_frames, get_ident,
init_main_thread_ident, make_module,
init_main_thread_ident, module_def,
};
#[pymodule]
+16 -10
View File
@@ -3,18 +3,10 @@
// See also:
// https://docs.python.org/3/library/time.html
use crate::{PyRef, VirtualMachine, builtins::PyModule};
pub use decl::time;
pub(crate) fn make_module(vm: &VirtualMachine) -> PyRef<PyModule> {
#[cfg(not(target_env = "msvc"))]
#[cfg(not(target_arch = "wasm32"))]
unsafe {
c_tzset()
};
decl::make_module(vm)
}
pub(crate) use decl::module_def;
#[cfg(not(target_env = "msvc"))]
#[cfg(not(target_arch = "wasm32"))]
@@ -34,7 +26,7 @@ unsafe extern "C" {
#[pymodule(name = "time", with(platform))]
mod decl {
use crate::{
AsObject, PyObjectRef, PyResult, VirtualMachine,
AsObject, Py, PyObjectRef, PyResult, VirtualMachine,
builtins::{PyStrRef, PyTypeRef, PyUtf8StrRef},
function::{Either, FuncArgs, OptionalArg},
types::{PyStructSequence, struct_sequence_new},
@@ -581,6 +573,20 @@ mod decl {
#[allow(unused_imports)]
use super::platform::*;
pub(crate) fn module_exec(
vm: &VirtualMachine,
module: &Py<crate::builtins::PyModule>,
) -> PyResult<()> {
#[cfg(not(target_env = "msvc"))]
#[cfg(not(target_arch = "wasm32"))]
unsafe {
super::c_tzset()
};
__module_exec(vm, module);
Ok(())
}
}
#[cfg(unix)]
+32 -22
View File
@@ -1,10 +1,11 @@
// spell-checker:ignore typevarobject funcobj
use crate::{Context, PyPayload, PyRef, VirtualMachine, class::PyClassImpl, stdlib::PyModule};
use crate::{Context, class::PyClassImpl};
pub use crate::stdlib::typevar::{
Generic, ParamSpec, ParamSpecArgs, ParamSpecKwargs, TypeVar, TypeVarTuple,
set_typeparam_default,
};
pub(crate) use decl::module_def;
pub use decl::*;
/// Initialize typing types (call extend_class)
@@ -12,32 +13,12 @@ pub fn init(ctx: &Context) {
NoDefault::extend_class(ctx, ctx.types.typing_no_default_type);
}
pub(crate) fn make_module(vm: &VirtualMachine) -> PyRef<PyModule> {
let module = decl::make_module(vm);
TypeVar::make_class(&vm.ctx);
ParamSpec::make_class(&vm.ctx);
TypeVarTuple::make_class(&vm.ctx);
ParamSpecArgs::make_class(&vm.ctx);
ParamSpecKwargs::make_class(&vm.ctx);
Generic::make_class(&vm.ctx);
extend_module!(vm, &module, {
"NoDefault" => vm.ctx.typing_no_default.clone(),
"TypeVar" => TypeVar::class(&vm.ctx).to_owned(),
"ParamSpec" => ParamSpec::class(&vm.ctx).to_owned(),
"TypeVarTuple" => TypeVarTuple::class(&vm.ctx).to_owned(),
"ParamSpecArgs" => ParamSpecArgs::class(&vm.ctx).to_owned(),
"ParamSpecKwargs" => ParamSpecKwargs::class(&vm.ctx).to_owned(),
"Generic" => Generic::class(&vm.ctx).to_owned(),
"Union" => vm.ctx.types.union_type.to_owned(),
});
module
}
#[pymodule(name = "_typing")]
pub(crate) mod decl {
use crate::{
Py, PyObjectRef, PyPayload, PyResult, VirtualMachine,
builtins::{PyStrRef, PyTupleRef, PyType, PyTypeRef, pystr::AsPyStr, type_},
class::PyClassImpl,
function::{FuncArgs, IntoFuncArgs},
protocol::PyNumberMethods,
types::{AsNumber, Constructor, Representable},
@@ -206,4 +187,33 @@ pub(crate) mod decl {
// &AS_MAPPING
// }
// }
pub(crate) fn module_exec(
vm: &VirtualMachine,
module: &Py<crate::builtins::PyModule>,
) -> PyResult<()> {
__module_exec(vm, module);
use super::{Generic, ParamSpec, ParamSpecArgs, ParamSpecKwargs, TypeVar, TypeVarTuple};
TypeVar::make_class(&vm.ctx);
ParamSpec::make_class(&vm.ctx);
TypeVarTuple::make_class(&vm.ctx);
ParamSpecArgs::make_class(&vm.ctx);
ParamSpecKwargs::make_class(&vm.ctx);
Generic::make_class(&vm.ctx);
extend_module!(vm, module, {
"NoDefault" => vm.ctx.typing_no_default.clone(),
"TypeVar" => TypeVar::class(&vm.ctx).to_owned(),
"ParamSpec" => ParamSpec::class(&vm.ctx).to_owned(),
"TypeVarTuple" => TypeVarTuple::class(&vm.ctx).to_owned(),
"ParamSpecArgs" => ParamSpecArgs::class(&vm.ctx).to_owned(),
"ParamSpecKwargs" => ParamSpecKwargs::class(&vm.ctx).to_owned(),
"Generic" => Generic::class(&vm.ctx).to_owned(),
"Union" => vm.ctx.types.union_type.to_owned(),
});
Ok(())
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
pub(crate) use _warnings::make_module;
pub(crate) use _warnings::module_def;
use crate::{Py, PyResult, VirtualMachine, builtins::PyType};
+1 -1
View File
@@ -4,7 +4,7 @@
//! - [python weakref module](https://docs.python.org/3/library/weakref.html)
//! - [rust weak struct](https://doc.rust-lang.org/std/rc/struct.Weak.html)
//!
pub(crate) use _weakref::make_module;
pub(crate) use _weakref::module_def;
#[pymodule]
mod _weakref {
+1 -1
View File
@@ -1,7 +1,7 @@
// spell-checker:disable
#![allow(non_snake_case)]
pub(crate) use _winapi::make_module;
pub(crate) use _winapi::module_def;
#[pymodule]
mod _winapi {
+1 -5
View File
@@ -1,11 +1,7 @@
// spell-checker:disable
#![allow(non_snake_case)]
use crate::{PyRef, VirtualMachine, builtins::PyModule};
pub(crate) fn make_module(vm: &VirtualMachine) -> PyRef<PyModule> {
winreg::make_module(vm)
}
pub(crate) use winreg::module_def;
#[pymodule]
mod winreg {
+337 -24
View File
@@ -1,7 +1,276 @@
use super::{Context, PyConfig, VirtualMachine, setting::Settings, thread};
use crate::{PyResult, getpath, stdlib::atexit, vm::PyBaseExceptionRef};
use super::{Context, PyConfig, PyGlobalState, VirtualMachine, setting::Settings, thread};
use crate::{
PyResult, builtins, common::rc::PyRc, frozen::FrozenModule, getpath, py_freeze, stdlib::atexit,
vm::PyBaseExceptionRef,
};
use core::sync::atomic::Ordering;
type InitFunc = Box<dyn FnOnce(&mut VirtualMachine)>;
/// Configuration builder for constructing an Interpreter.
///
/// This is the preferred way to configure and create an interpreter with custom modules.
/// Modules must be registered before the interpreter is built,
/// similar to CPython's `PyImport_AppendInittab` which must be called before `Py_Initialize`.
///
/// # Example
/// ```
/// use rustpython_vm::Interpreter;
///
/// let builder = Interpreter::builder(Default::default());
/// // In practice, add stdlib: builder.add_native_modules(&stdlib_module_defs(&builder.ctx))
/// let interp = builder.build();
/// ```
pub struct InterpreterBuilder {
settings: Settings,
pub ctx: PyRc<Context>,
module_defs: Vec<&'static builtins::PyModuleDef>,
frozen_modules: Vec<(&'static str, FrozenModule)>,
init_hooks: Vec<InitFunc>,
}
/// Private helper to initialize a VM with settings, context, and custom initialization.
fn initialize_main_vm<F>(
settings: Settings,
ctx: PyRc<Context>,
module_defs: Vec<&'static builtins::PyModuleDef>,
frozen_modules: Vec<(&'static str, FrozenModule)>,
init_hooks: Vec<InitFunc>,
init: F,
) -> (VirtualMachine, PyRc<PyGlobalState>)
where
F: FnOnce(&mut VirtualMachine),
{
use crate::codecs::CodecsRegistry;
use crate::common::hash::HashSecret;
use crate::common::lock::PyMutex;
use crate::warn::WarningsState;
use crossbeam_utils::atomic::AtomicCell;
use std::sync::atomic::AtomicBool;
let paths = getpath::init_path_config(&settings);
let config = PyConfig::new(settings, paths);
crate::types::TypeZoo::extend(&ctx);
crate::exceptions::ExceptionZoo::extend(&ctx);
// Build module_defs map from builtin modules + additional modules
let mut all_module_defs: std::collections::BTreeMap<
&'static str,
&'static builtins::PyModuleDef,
> = crate::stdlib::builtin_module_defs(&ctx)
.into_iter()
.chain(module_defs)
.map(|def| (def.name.as_str(), def))
.collect();
// Register sysconfigdata under platform-specific name as well
if let Some(&sysconfigdata_def) = all_module_defs.get("_sysconfigdata") {
use std::sync::OnceLock;
static SYSCONFIGDATA_NAME: OnceLock<&'static str> = OnceLock::new();
let leaked_name = *SYSCONFIGDATA_NAME.get_or_init(|| {
let name = crate::stdlib::sys::sysconfigdata_name();
Box::leak(name.into_boxed_str())
});
all_module_defs.insert(leaked_name, sysconfigdata_def);
}
// Create hash secret
let seed = match config.settings.hash_seed {
Some(seed) => seed,
None => super::process_hash_secret_seed(),
};
let hash_secret = HashSecret::new(seed);
// Create codec registry and warnings state
let codec_registry = CodecsRegistry::new(&ctx);
let warnings = WarningsState::init_state(&ctx);
// Create int_max_str_digits
let int_max_str_digits = AtomicCell::new(match config.settings.int_max_str_digits {
-1 => 4300,
other => other,
} as usize);
// Initialize frozen modules (core + user-provided)
let mut frozen: std::collections::HashMap<&'static str, FrozenModule, ahash::RandomState> =
core_frozen_inits().collect();
frozen.extend(frozen_modules);
// Create PyGlobalState
let global_state = PyRc::new(PyGlobalState {
config,
module_defs: all_module_defs,
frozen,
stacksize: AtomicCell::new(0),
thread_count: AtomicCell::new(0),
hash_secret,
atexit_funcs: PyMutex::default(),
codec_registry,
finalizing: AtomicBool::new(false),
warnings,
override_frozen_modules: AtomicCell::new(0),
before_forkers: PyMutex::default(),
after_forkers_child: PyMutex::default(),
after_forkers_parent: PyMutex::default(),
int_max_str_digits,
switch_interval: AtomicCell::new(0.005),
global_trace_func: PyMutex::default(),
global_profile_func: PyMutex::default(),
#[cfg(feature = "threading")]
main_thread_ident: AtomicCell::new(0),
#[cfg(feature = "threading")]
thread_frames: parking_lot::Mutex::new(std::collections::HashMap::new()),
#[cfg(feature = "threading")]
thread_handles: parking_lot::Mutex::new(Vec::new()),
#[cfg(feature = "threading")]
shutdown_handles: parking_lot::Mutex::new(Vec::new()),
});
// Create VM with the global state
// Note: Don't clone here - init_hooks need exclusive access to mutate state
let mut vm = VirtualMachine::new(ctx, global_state);
// Execute initialization hooks (can mutate vm.state)
for hook in init_hooks {
hook(&mut vm);
}
// Call custom init function (can mutate vm.state)
init(&mut vm);
vm.initialize();
// Clone global_state for Interpreter after all initialization is done
let global_state = vm.state.clone();
(vm, global_state)
}
impl InterpreterBuilder {
/// Create a new interpreter configuration with default settings.
pub fn new() -> Self {
Self {
settings: Settings::default(),
ctx: Context::genesis().clone(),
module_defs: Vec::new(),
frozen_modules: Vec::new(),
init_hooks: Vec::new(),
}
}
/// Set custom settings for the interpreter.
///
/// If called multiple times, only the last settings will be used.
pub fn settings(mut self, settings: Settings) -> Self {
self.settings = settings;
self
}
/// Add a single native module definition.
///
/// # Example
/// ```
/// use rustpython_vm::{Interpreter, builtins::PyModuleDef};
///
/// let builder = Interpreter::builder(Default::default());
/// // Note: In practice, use module_def from your #[pymodule]
/// // let def = mymodule::module_def(&builder.ctx);
/// // let interp = builder.add_native_module(def).build();
/// let interp = builder.build();
/// ```
pub fn add_native_module(self, def: &'static builtins::PyModuleDef) -> Self {
self.add_native_modules(&[def])
}
/// Add multiple native module definitions.
///
/// # Example
/// ```
/// use rustpython_vm::Interpreter;
///
/// let builder = Interpreter::builder(Default::default());
/// // In practice, use module_defs from rustpython_stdlib:
/// // let defs = rustpython_stdlib::stdlib_module_defs(&builder.ctx);
/// // let interp = builder.add_native_modules(&defs).build();
/// let interp = builder.build();
/// ```
pub fn add_native_modules(mut self, defs: &[&'static builtins::PyModuleDef]) -> Self {
self.module_defs.extend_from_slice(defs);
self
}
/// Add a custom initialization hook.
///
/// Hooks are executed in the order they are added during interpreter creation.
/// This function will be called after modules are registered but before
/// the VM is initialized, allowing for additional customization.
///
/// # Example
/// ```
/// use rustpython_vm::Interpreter;
///
/// let interp = Interpreter::builder(Default::default())
/// .init_hook(|vm| {
/// // Custom initialization
/// })
/// .build();
/// ```
pub fn init_hook<F>(mut self, init: F) -> Self
where
F: FnOnce(&mut VirtualMachine) + 'static,
{
self.init_hooks.push(Box::new(init));
self
}
/// Add frozen modules to the interpreter.
///
/// Frozen modules are Python modules compiled into the binary.
/// This method accepts any iterator of (name, FrozenModule) pairs.
///
/// # Example
/// ```
/// use rustpython_vm::Interpreter;
///
/// let interp = Interpreter::builder(Default::default())
/// // In practice: .add_frozen_modules(rustpython_pylib::FROZEN_STDLIB)
/// .build();
/// ```
pub fn add_frozen_modules<I>(mut self, frozen: I) -> Self
where
I: IntoIterator<Item = (&'static str, FrozenModule)>,
{
self.frozen_modules.extend(frozen);
self
}
/// Build the interpreter.
///
/// This consumes the configuration and returns a fully initialized Interpreter.
pub fn build(self) -> Interpreter {
let (vm, global_state) = initialize_main_vm(
self.settings,
self.ctx,
self.module_defs,
self.frozen_modules,
self.init_hooks,
|_| {}, // No additional init needed
);
Interpreter { global_state, vm }
}
/// Alias for `build()` for compatibility with the `interpreter()` pattern.
pub fn interpreter(self) -> Interpreter {
self.build()
}
}
impl Default for InterpreterBuilder {
fn default() -> Self {
Self::new()
}
}
/// The general interface for the VM
///
/// # Examples
@@ -21,43 +290,49 @@ use core::sync::atomic::Ordering;
/// });
/// ```
pub struct Interpreter {
pub global_state: PyRc<PyGlobalState>,
vm: VirtualMachine,
}
impl Interpreter {
/// Create a new interpreter configuration builder.
///
/// # Example
/// ```
/// use rustpython_vm::Interpreter;
///
/// let builder = Interpreter::builder(Default::default());
/// // In practice, add stdlib: builder.add_native_modules(&stdlib_module_defs(&builder.ctx))
/// let interp = builder.build();
/// ```
pub fn builder(settings: Settings) -> InterpreterBuilder {
InterpreterBuilder::new().settings(settings)
}
/// This is a bare unit to build up an interpreter without the standard library.
/// To create an interpreter with the standard library with the `rustpython` crate, use `rustpython::InterpreterConfig`.
/// To create an interpreter with the standard library with the `rustpython` crate, use `rustpython::InterpreterBuilder`.
/// To create an interpreter without the `rustpython` crate, but only with `rustpython-vm`,
/// try to build one from the source code of `InterpreterConfig`. It will not be a one-liner but it also will not be too hard.
/// try to build one from the source code of `InterpreterBuilder`. It will not be a one-liner but it also will not be too hard.
pub fn without_stdlib(settings: Settings) -> Self {
Self::with_init(settings, |_| {})
}
/// Create with initialize function taking mutable vm reference.
/// ```
/// use rustpython_vm::Interpreter;
/// Interpreter::with_init(Default::default(), |vm| {
/// // put this line to add stdlib to the vm
/// // vm.add_native_module_defs(rustpython_stdlib::get_module_defs());
/// }).enter(|vm| {
/// vm.run_code_string(vm.new_scope_with_builtins(), "print(1)", "<...>".to_owned());
/// });
/// ```
///
/// Note: This is a legacy API. To add stdlib, use `Interpreter::builder()` instead.
pub fn with_init<F>(settings: Settings, init: F) -> Self
where
F: FnOnce(&mut VirtualMachine),
{
// Compute path configuration from settings
let paths = getpath::init_path_config(&settings);
let config = PyConfig::new(settings, paths);
let ctx = Context::genesis();
crate::types::TypeZoo::extend(ctx);
crate::exceptions::ExceptionZoo::extend(ctx);
let mut vm = VirtualMachine::new(config, ctx.clone());
init(&mut vm);
vm.initialize();
Self { vm }
let (vm, global_state) = initialize_main_vm(
settings,
Context::genesis().clone(),
Vec::new(), // No module_defs
Vec::new(), // No frozen_modules
Vec::new(), // No init_hooks
init,
);
Self { global_state, vm }
}
/// Run a function with the main virtual machine and return a PyResult of the result.
@@ -157,6 +432,44 @@ impl Interpreter {
}
}
fn core_frozen_inits() -> impl Iterator<Item = (&'static str, FrozenModule)> {
let iter = core::iter::empty();
macro_rules! ext_modules {
($iter:ident, $($t:tt)*) => {
let $iter = $iter.chain(py_freeze!($($t)*));
};
}
// keep as example but use file one now
// ext_modules!(
// iter,
// source = "initialized = True; print(\"Hello world!\")\n",
// module_name = "__hello__",
// );
// Python modules that the vm calls into, but are not actually part of the stdlib. They could
// in theory be implemented in Rust, but are easiest to do in Python for one reason or another.
// Includes _importlib_bootstrap and _importlib_bootstrap_external
ext_modules!(
iter,
dir = "./Lib/python_builtins",
crate_name = "rustpython_compiler_core"
);
// core stdlib Python modules that the vm calls into, but are still used in Python
// application code, e.g. copyreg
// FIXME: Initializing core_modules here results duplicated frozen module generation for core_modules.
// We need a way to initialize this modules for both `Interpreter::without_stdlib()` and `InterpreterBuilder::new().init_stdlib().interpreter()`
// #[cfg(not(feature = "freeze-stdlib"))]
ext_modules!(
iter,
dir = "./Lib/core_modules",
crate_name = "rustpython_compiler_core"
);
iter
}
#[cfg(test)]
mod tests {
use super::*;
+41 -174
View File
@@ -19,7 +19,7 @@ mod vm_ops;
use crate::{
AsObject, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult,
builtins::{
PyBaseExceptionRef, PyDict, PyDictRef, PyInt, PyList, PyModule, PyStr, PyStrInterned,
self, PyBaseExceptionRef, PyDict, PyDictRef, PyInt, PyList, PyModule, PyStr, PyStrInterned,
PyStrRef, PyTypeRef,
code::PyCode,
dict::{PyDictItems, PyDictKeys, PyDictValues},
@@ -56,7 +56,7 @@ use std::{
};
pub use context::Context;
pub use interpreter::Interpreter;
pub use interpreter::{Interpreter, InterpreterBuilder};
pub(crate) use method::PyMethod;
pub use setting::{CheckHashPycsMode, Paths, PyConfig, Settings};
@@ -102,8 +102,7 @@ struct ExceptionStack {
pub struct PyGlobalState {
pub config: PyConfig,
pub module_inits: stdlib::StdlibMap,
pub module_defs: stdlib::StdlibDefMap,
pub module_defs: std::collections::BTreeMap<&'static str, &'static builtins::PyModuleDef>,
pub frozen: HashMap<&'static str, FrozenModule, ahash::RandomState>,
pub stacksize: AtomicCell<usize>,
pub thread_count: AtomicCell<usize>,
@@ -145,7 +144,7 @@ pub fn process_hash_secret_seed() -> u32 {
impl VirtualMachine {
/// Create a new `VirtualMachine` structure.
fn new(config: PyConfig, ctx: PyRc<Context>) -> Self {
pub(crate) fn new(ctx: PyRc<Context>, state: PyRc<PyGlobalState>) -> Self {
flame_guard!("new VirtualMachine");
// make a new module without access to the vm; doesn't
@@ -170,24 +169,7 @@ impl VirtualMachine {
const { RefCell::new([const { None }; signal::NSIG]) },
));
let module_inits = stdlib::get_module_inits();
let module_defs = stdlib::get_module_defs();
let seed = match config.settings.hash_seed {
Some(seed) => seed,
None => process_hash_secret_seed(),
};
let hash_secret = HashSecret::new(seed);
let codec_registry = CodecsRegistry::new(&ctx);
let warnings = WarningsState::init_state(&ctx);
let int_max_str_digits = AtomicCell::new(match config.settings.int_max_str_digits {
-1 => 4300,
other => other,
} as usize);
let mut vm = Self {
let vm = Self {
builtins,
sys_module,
ctx,
@@ -202,35 +184,7 @@ impl VirtualMachine {
signal_handlers,
signal_rx: None,
repr_guards: RefCell::default(),
state: PyRc::new(PyGlobalState {
config,
module_inits,
module_defs,
frozen: HashMap::default(),
stacksize: AtomicCell::new(0),
thread_count: AtomicCell::new(0),
hash_secret,
atexit_funcs: PyMutex::default(),
codec_registry,
finalizing: AtomicBool::new(false),
warnings,
override_frozen_modules: AtomicCell::new(0),
before_forkers: PyMutex::default(),
after_forkers_child: PyMutex::default(),
after_forkers_parent: PyMutex::default(),
int_max_str_digits,
switch_interval: AtomicCell::new(0.005),
global_trace_func: PyMutex::default(),
global_profile_func: PyMutex::default(),
#[cfg(feature = "threading")]
main_thread_ident: AtomicCell::new(0),
#[cfg(feature = "threading")]
thread_frames: parking_lot::Mutex::new(HashMap::new()),
#[cfg(feature = "threading")]
thread_handles: parking_lot::Mutex::new(Vec::new()),
#[cfg(feature = "threading")]
shutdown_handles: parking_lot::Mutex::new(Vec::new()),
}),
state,
initialized: false,
recursion_depth: Cell::new(0),
c_stack_soft_limit: Cell::new(Self::calculate_c_stack_soft_limit()),
@@ -248,9 +202,6 @@ impl VirtualMachine {
panic!("Interpreters in same process must share the hash seed");
}
let frozen = core_frozen_inits().collect();
PyRc::get_mut(&mut vm.state).unwrap().frozen = frozen;
vm.builtins.init_dict(
vm.ctx.intern_str("builtins"),
Some(vm.ctx.intern_str(stdlib::builtins::DOC.unwrap()).to_owned()),
@@ -278,7 +229,7 @@ impl VirtualMachine {
});
let guide_message = if cfg!(feature = "freeze-stdlib") {
"`rustpython_pylib` maybe not set while using `freeze-stdlib` feature. Try using `rustpython::InterpreterConfig::init_stdlib` or manually call `vm.add_frozen(rustpython_pylib::FROZEN_STDLIB)` in `rustpython_vm::Interpreter::with_init`."
"`rustpython_pylib` may not be set while using `freeze-stdlib` feature. Try using `rustpython::InterpreterBuilder::init_stdlib` or manually call `builder.add_frozen_modules(rustpython_pylib::FROZEN_STDLIB)` in `rustpython_vm::Interpreter::builder()`."
} else if !env_set {
"Neither RUSTPYTHONPATH nor PYTHONPATH is set. Try setting one of them to the stdlib directory."
} else if path_contains_env {
@@ -473,53 +424,6 @@ impl VirtualMachine {
self.initialized = true;
}
fn state_mut(&mut self) -> &mut PyGlobalState {
PyRc::get_mut(&mut self.state)
.expect("there should not be multiple threads while a user has a mut ref to a vm")
}
/// Can only be used in the initialization closure passed to [`Interpreter::with_init`]
#[deprecated(note = "use add_native_module_def(s) with multi-phase module initialization")]
pub fn add_native_module<S>(&mut self, name: S, module: stdlib::StdlibInitFunc)
where
S: Into<Cow<'static, str>>,
{
self.state_mut().module_inits.insert(name.into(), module);
}
#[deprecated(note = "use add_native_module_defs with multi-phase module initialization")]
pub fn add_native_modules<I>(&mut self, iter: I)
where
I: IntoIterator<Item = (Cow<'static, str>, stdlib::StdlibInitFunc)>,
{
self.state_mut().module_inits.extend(iter);
}
/// Add a module definition for multi-phase initialization.
/// These modules are added to sys.modules BEFORE their exec function runs,
/// allowing safe circular imports.
pub fn add_native_module_def<S>(&mut self, name: S, def_func: stdlib::StdlibDefFunc)
where
S: Into<Cow<'static, str>>,
{
self.state_mut().module_defs.insert(name.into(), def_func);
}
pub fn add_native_module_defs<I>(&mut self, iter: I)
where
I: IntoIterator<Item = (Cow<'static, str>, stdlib::StdlibDefFunc)>,
{
self.state_mut().module_defs.extend(iter);
}
/// Can only be used in the initialization closure passed to [`Interpreter::with_init`]
pub fn add_frozen<I>(&mut self, frozen: I)
where
I: IntoIterator<Item = (&'static str, FrozenModule)>,
{
self.state_mut().frozen.extend(frozen);
}
/// Set the custom signal channel for the interpreter
pub fn set_user_signal_channel(&mut self, signal_rx: signal::UserSignalReceiver) {
self.signal_rx = Some(signal_rx);
@@ -1403,89 +1307,52 @@ pub fn resolve_frozen_alias(name: &str) -> &str {
}
}
fn core_frozen_inits() -> impl Iterator<Item = (&'static str, FrozenModule)> {
let iter = core::iter::empty();
macro_rules! ext_modules {
($iter:ident, $($t:tt)*) => {
let $iter = $iter.chain(py_freeze!($($t)*));
};
}
// keep as example but use file one now
// ext_modules!(
// iter,
// source = "initialized = True; print(\"Hello world!\")\n",
// module_name = "__hello__",
// );
// Python modules that the vm calls into, but are not actually part of the stdlib. They could
// in theory be implemented in Rust, but are easiest to do in Python for one reason or another.
// Includes _importlib_bootstrap and _importlib_bootstrap_external
ext_modules!(
iter,
dir = "./Lib/python_builtins",
crate_name = "rustpython_compiler_core"
);
// core stdlib Python modules that the vm calls into, but are still used in Python
// application code, e.g. copyreg
// FIXME: Initializing core_modules here results duplicated frozen module generation for core_modules.
// We need a way to initialize this modules for both `Interpreter::without_stdlib()` and `InterpreterConfig::new().init_stdlib().interpreter()`
// #[cfg(not(feature = "freeze-stdlib"))]
ext_modules!(
iter,
dir = "./Lib/core_modules",
crate_name = "rustpython_compiler_core"
);
iter
}
#[test]
fn test_nested_frozen() {
use rustpython_vm as vm;
vm::Interpreter::with_init(Default::default(), |vm| {
// vm.add_native_module_defs(rustpython_stdlib::get_module_defs());
vm.add_frozen(rustpython_vm::py_freeze!(
vm::Interpreter::builder(Default::default())
.add_frozen_modules(rustpython_vm::py_freeze!(
dir = "../../extra_tests/snippets"
));
})
.enter(|vm| {
let scope = vm.new_scope_with_builtins();
))
.build()
.enter(|vm| {
let scope = vm.new_scope_with_builtins();
let source = "from dir_module.dir_module_inner import value2";
let code_obj = vm
.compile(source, vm::compiler::Mode::Exec, "<embedded>".to_owned())
.map_err(|err| vm.new_syntax_error(&err, Some(source)))
.unwrap();
let source = "from dir_module.dir_module_inner import value2";
let code_obj = vm
.compile(source, vm::compiler::Mode::Exec, "<embedded>".to_owned())
.map_err(|err| vm.new_syntax_error(&err, Some(source)))
.unwrap();
if let Err(e) = vm.run_code_obj(code_obj, scope) {
vm.print_exception(e);
panic!();
}
})
if let Err(e) = vm.run_code_obj(code_obj, scope) {
vm.print_exception(e);
panic!();
}
})
}
#[test]
fn frozen_origname_matches() {
use rustpython_vm as vm;
vm::Interpreter::with_init(Default::default(), |_vm| {}).enter(|vm| {
let check = |name, expected| {
let module = import::import_frozen(vm, name).unwrap();
let origname: PyStrRef = module
.get_attr("__origname__", vm)
.unwrap()
.try_into_value(vm)
.unwrap();
assert_eq!(origname.as_str(), expected);
};
vm::Interpreter::builder(Default::default())
.build()
.enter(|vm| {
let check = |name, expected| {
let module = import::import_frozen(vm, name).unwrap();
let origname: PyStrRef = module
.get_attr("__origname__", vm)
.unwrap()
.try_into_value(vm)
.unwrap();
assert_eq!(origname.as_str(), expected);
};
check("_frozen_importlib", "importlib._bootstrap");
check(
"_frozen_importlib_external",
"importlib._bootstrap_external",
);
});
check("_frozen_importlib", "importlib._bootstrap");
check(
"_frozen_importlib_external",
"importlib._bootstrap_external",
);
});
}
-7
View File
@@ -1,5 +1,3 @@
use rustpython_vm::VirtualMachine;
pub(crate) use _browser::module_def;
#[pymodule]
@@ -257,8 +255,3 @@ mod _browser {
Ok(PyPromise::from_future(future).into_pyobject(vm))
}
}
pub fn setup_browser_module(vm: &mut VirtualMachine) {
vm.add_native_module_def("_browser".to_owned(), module_def);
vm.add_frozen(py_freeze!(dir = "Lib"));
}
-5
View File
@@ -1,5 +1,4 @@
pub(crate) use _js::{PyJsValue, PyPromise};
use rustpython_vm::VirtualMachine;
#[pymodule]
mod _js {
@@ -622,7 +621,3 @@ mod _js {
}
pub(crate) use _js::module_def;
pub fn setup_js_module(vm: &mut VirtualMachine) {
vm.add_native_module_def("_js".to_owned(), module_def);
}
-1
View File
@@ -11,7 +11,6 @@ extern crate rustpython_vm;
use js_sys::{Reflect, WebAssembly::RuntimeError};
use std::panic;
pub use vm_class::add_init_func;
pub(crate) use vm_class::weak_vm;
use wasm_bindgen::prelude::*;
+27 -30
View File
@@ -1,5 +1,5 @@
use crate::{
browser_module::setup_browser_module,
browser_module,
convert::{self, PyResultExt},
js_module, wasm_builtins,
};
@@ -37,55 +37,52 @@ mod _window {
impl StoredVirtualMachine {
fn new(id: String, inject_browser_module: bool) -> StoredVirtualMachine {
let mut scope = None;
let mut settings = Settings::default();
settings.allow_external_library = false;
let interp = Interpreter::with_init(settings, |vm| {
#[cfg(feature = "freeze-stdlib")]
vm.add_native_module_defs(rustpython_stdlib::get_module_defs());
#[cfg(feature = "freeze-stdlib")]
vm.add_frozen(rustpython_pylib::FROZEN_STDLIB);
let mut builder = Interpreter::builder(settings);
vm.wasm_id = Some(id);
#[cfg(feature = "freeze-stdlib")]
{
let defs = rustpython_stdlib::stdlib_module_defs(&builder.ctx);
builder = builder
.add_native_modules(&defs)
.add_frozen_modules(rustpython_pylib::FROZEN_STDLIB);
}
js_module::setup_js_module(vm);
if inject_browser_module {
vm.add_native_module_def("_window".to_owned(), _window::module_def);
setup_browser_module(vm);
}
// Add wasm-specific modules
let js_def = js_module::module_def(&builder.ctx);
builder = builder.add_native_module(js_def);
VM_INIT_FUNCS.with_borrow(|funcs| {
for f in funcs {
f(vm)
}
});
if inject_browser_module {
let window_def = _window::module_def(&builder.ctx);
let browser_def = browser_module::module_def(&builder.ctx);
builder = builder
.add_native_modules(&[window_def, browser_def])
.add_frozen_modules(rustpython_vm::py_freeze!(dir = "Lib"));
}
scope = Some(vm.new_scope_with_builtins());
});
let interp = builder
.init_hook(move |vm| {
vm.wasm_id = Some(id);
})
.build();
let scope = interp.enter(|vm| vm.new_scope_with_builtins());
StoredVirtualMachine {
interp,
scope: scope.unwrap(),
scope,
held_objects: RefCell::new(Vec::new()),
}
}
}
/// Add a hook to add builtins or frozen modules to the RustPython VirtualMachine while it's
/// initializing.
pub fn add_init_func(f: fn(&mut VirtualMachine)) {
VM_INIT_FUNCS.with_borrow_mut(|funcs| funcs.push(f))
}
// It's fine that it's thread local, since WASM doesn't even have threads yet. thread_local!
// probably gets compiled down to a normal-ish static variable, like Atomic* types do:
// https://rustwasm.github.io/2018/10/24/multithreading-rust-and-wasm.html#atomic-instructions
thread_local! {
static STORED_VMS: RefCell<HashMap<String, Rc<StoredVirtualMachine>>> = RefCell::default();
static VM_INIT_FUNCS: RefCell<Vec<fn(&mut VirtualMachine)>> = const {
RefCell::new(Vec::new())
};
}
pub fn get_vm_id(vm: &VirtualMachine) -> &str {
+11 -12
View File
@@ -1,9 +1,10 @@
// spell-checker:ignore aheui
/// Setting up a project with a frozen stdlib can be done *either* by using `rustpython::InterpreterConfig` or `rustpython_vm::Interpreter::with_init`.
/// See each function for example.
///
/// See also: `aheui-rust.md` for freezing your own package.
//! Setting up a project with a frozen stdlib can be done *either* by using `rustpython::InterpreterBuilder` or `rustpython_vm::Interpreter::builder`.
//! See each function for example.
//!
//! See also: `aheui-rust.md` for freezing your own package.
use rustpython::InterpreterBuilderExt;
use rustpython_vm::{PyResult, VirtualMachine};
fn run(keyword: &str, vm: &VirtualMachine) -> PyResult<()> {
@@ -17,21 +18,19 @@ fn run(keyword: &str, vm: &VirtualMachine) -> PyResult<()> {
}
fn interpreter_with_config() {
let interpreter = rustpython::InterpreterConfig::new()
let interpreter = rustpython::InterpreterBuilder::new()
.init_stdlib()
.interpreter();
// Use interpreter.enter to reuse the same interpreter later
interpreter.run(|vm| run("rustpython::InterpreterConfig", vm));
interpreter.run(|vm| run("rustpython::InterpreterBuilder", vm));
}
fn interpreter_with_vm() {
let interpreter = rustpython_vm::Interpreter::with_init(Default::default(), |vm| {
// This is unintuitive, but the stdlib is out of the vm crate.
// Any suggestion to improve this is welcome.
vm.add_frozen(rustpython_pylib::FROZEN_STDLIB);
});
let interpreter = rustpython_vm::Interpreter::builder(Default::default())
.add_frozen_modules(rustpython_pylib::FROZEN_STDLIB)
.build();
// Use interpreter.enter to reuse the same interpreter later
interpreter.run(|vm| run("rustpython_vm::Interpreter::with_init", vm));
interpreter.run(|vm| run("rustpython_vm::Interpreter::builder", vm));
}
fn main() {
+4 -6
View File
@@ -1,14 +1,12 @@
use rustpython::InterpreterBuilderExt;
use rustpython::vm::{
PyObject, PyPayload, PyResult, TryFromBorrowedObject, VirtualMachine, pyclass, pymodule,
};
pub fn main() {
let interp = rustpython::InterpreterConfig::new()
.init_stdlib()
.init_hook(Box::new(|vm| {
vm.add_native_module_def("rust_py_module".to_owned(), rust_py_module::module_def);
}))
.interpreter();
let builder = rustpython::Interpreter::builder(Default::default());
let def = rust_py_module::module_def(&builder.ctx);
let interp = builder.init_stdlib().add_native_module(def).build();
interp.enter(|vm| {
vm.insert_sys_path(vm.new_pyobj("examples"))
+3 -3
View File
@@ -42,9 +42,9 @@ gen()
}
fn main() -> ExitCode {
let interp = vm::Interpreter::with_init(Default::default(), |vm| {
vm.add_native_module_defs(rustpython_stdlib::get_module_defs());
});
let builder = vm::Interpreter::builder(Default::default());
let defs = rustpython_stdlib::stdlib_module_defs(&builder.ctx);
let interp = builder.add_native_modules(&defs).build();
let result = py_main(&interp);
vm::common::os::exit_code(interp.run(|_vm| result))
}
+3 -3
View File
@@ -19,9 +19,9 @@ fn main() -> ExitCode {
// Add standard library path
let mut settings = vm::Settings::default();
settings.path_list.push("Lib".to_owned());
let interp = vm::Interpreter::with_init(settings, |vm| {
vm.add_native_module_defs(rustpython_stdlib::get_module_defs());
});
let builder = vm::Interpreter::builder(settings);
let defs = rustpython_stdlib::stdlib_module_defs(&builder.ctx);
let interp = builder.add_native_modules(&defs).build();
let result = py_main(&interp);
let result = result.map(|result| {
println!("name: {result}");
+25 -120
View File
@@ -1,139 +1,44 @@
use rustpython_vm::{
Interpreter, PyRef, Settings, VirtualMachine, builtins::PyModule, stdlib::StdlibDefFunc,
};
use rustpython_vm::InterpreterBuilder;
pub type InitHook = Box<dyn FnOnce(&mut VirtualMachine)>;
/// The convenient way to create [rustpython_vm::Interpreter] with stdlib and other stuffs.
///
/// Basic usage:
/// ```
/// let interpreter = rustpython::InterpreterConfig::new()
/// .init_stdlib()
/// .interpreter();
/// ```
///
/// To override [rustpython_vm::Settings]:
/// ```
/// use rustpython_vm::Settings;
/// // Override your settings here.
/// let mut settings = Settings::default();
/// settings.debug = 1;
/// // You may want to add paths to `rustpython_vm::Settings::path_list` to allow import python libraries.
/// settings.path_list.push("Lib".to_owned()); // add standard library directory
/// settings.path_list.push("".to_owned()); // add current working directory
/// let interpreter = rustpython::InterpreterConfig::new()
/// .settings(settings)
/// .interpreter();
/// ```
///
/// To add native modules:
/// ```
/// use rustpython_vm::pymodule;
///
/// #[pymodule]
/// mod your_module {}
///
/// let interpreter = rustpython::InterpreterConfig::new()
/// .init_stdlib()
/// .add_native_module_def(
/// "your_module_name".to_owned(),
/// your_module::module_def,
/// )
/// .interpreter();
/// ```
#[derive(Default)]
pub struct InterpreterConfig {
settings: Option<Settings>,
init_hooks: Vec<InitHook>,
}
impl InterpreterConfig {
/// Creates a new interpreter configuration with default settings.
pub fn new() -> Self {
Self::default()
}
/// Builds the interpreter with the current configuration.
pub fn interpreter(self) -> Interpreter {
let settings = self.settings.unwrap_or_default();
Interpreter::with_init(settings, |vm| {
for hook in self.init_hooks {
hook(vm);
}
})
}
/// Sets custom settings for the interpreter.
///
/// If called multiple times, only the last settings will be used.
pub fn settings(mut self, settings: Settings) -> Self {
self.settings = Some(settings);
self
}
/// Adds a custom initialization hook.
///
/// Hooks are executed in the order they are added during interpreter creation.
pub fn init_hook(mut self, hook: InitHook) -> Self {
self.init_hooks.push(hook);
self
}
/// Adds a native module definition to the interpreter.
pub fn add_native_module_def(self, name: String, def_func: StdlibDefFunc) -> Self {
self.init_hook(Box::new(move |vm| vm.add_native_module_def(name, def_func)))
}
/// Adds a native module to the interpreter.
#[deprecated(note = "use add_native_module_def with multi-phase module initialization")]
#[allow(deprecated)]
pub fn add_native_module(
self,
name: String,
make_module: fn(&VirtualMachine) -> PyRef<PyModule>,
) -> Self {
self.init_hook(Box::new(move |vm| {
vm.add_native_module(name, Box::new(make_module))
}))
}
/// Initializes the Python standard library.
/// Extension trait for InterpreterBuilder to add rustpython-specific functionality.
pub trait InterpreterBuilderExt {
/// Initialize the Python standard library.
///
/// Requires the `stdlib` feature to be enabled.
#[cfg(feature = "stdlib")]
pub fn init_stdlib(self) -> Self {
self.init_hook(Box::new(init_stdlib))
fn init_stdlib(self) -> Self;
}
impl InterpreterBuilderExt for InterpreterBuilder {
#[cfg(feature = "stdlib")]
fn init_stdlib(self) -> Self {
let defs = rustpython_stdlib::stdlib_module_defs(&self.ctx);
let builder = self.add_native_modules(&defs);
#[cfg(feature = "freeze-stdlib")]
let builder = builder
.add_frozen_modules(rustpython_pylib::FROZEN_STDLIB)
.init_hook(set_frozen_stdlib_dir);
#[cfg(not(feature = "freeze-stdlib"))]
let builder = builder.init_hook(setup_dynamic_stdlib);
builder
}
}
/// Initializes all standard library modules for the given VM.
#[cfg(feature = "stdlib")]
pub fn init_stdlib(vm: &mut VirtualMachine) {
vm.add_native_module_defs(rustpython_stdlib::get_module_defs());
#[cfg(feature = "freeze-stdlib")]
setup_frozen_stdlib(vm);
#[cfg(not(feature = "freeze-stdlib"))]
setup_dynamic_stdlib(vm);
}
/// Setup frozen standard library (compiled into the binary)
/// Set stdlib_dir for frozen standard library
#[cfg(all(feature = "stdlib", feature = "freeze-stdlib"))]
fn setup_frozen_stdlib(vm: &mut VirtualMachine) {
fn set_frozen_stdlib_dir(vm: &mut crate::VirtualMachine) {
use rustpython_vm::common::rc::PyRc;
vm.add_frozen(rustpython_pylib::FROZEN_STDLIB);
// Set stdlib_dir to the frozen stdlib path
let state = PyRc::get_mut(&mut vm.state).unwrap();
state.config.paths.stdlib_dir = Some(rustpython_pylib::LIB_PATH.to_owned());
}
/// Setup dynamic standard library loading from filesystem
#[cfg(all(feature = "stdlib", not(feature = "freeze-stdlib")))]
fn setup_dynamic_stdlib(vm: &mut VirtualMachine) {
fn setup_dynamic_stdlib(vm: &mut crate::VirtualMachine) {
use rustpython_vm::common::rc::PyRc;
let state = PyRc::get_mut(&mut vm.state).unwrap();
+24 -19
View File
@@ -1,23 +1,29 @@
//! This is the `rustpython` binary. If you're looking to embed RustPython into your application,
//! you're likely looking for the [`rustpython_vm`] crate.
//!
//! You can install `rustpython` with `cargo install rustpython`, or if you'd like to inject your
//! own native modules you can make a binary crate that depends on the `rustpython` crate (and
//! You can install `rustpython` with `cargo install rustpython`. If you'd like to inject your
//! own native modules, you can make a binary crate that depends on the `rustpython` crate (and
//! probably [`rustpython_vm`], too), and make a `main.rs` that looks like:
//!
//! ```no_run
//! use rustpython::{InterpreterBuilder, InterpreterBuilderExt};
//! use rustpython_vm::{pymodule, py_freeze};
//! fn main() {
//! rustpython::run(|vm| {
//! vm.add_native_module_def("my_mod".to_owned(), my_mod::module_def);
//! vm.add_frozen(py_freeze!(source = "def foo(): pass", module_name = "other_thing"));
//! });
//!
//! fn main() -> std::process::ExitCode {
//! let builder = InterpreterBuilder::new().init_stdlib();
//! // Add a native module using builder.ctx
//! let my_mod_def = my_mod::module_def(&builder.ctx);
//! let builder = builder
//! .add_native_module(my_mod_def)
//! // Add a frozen module
//! .add_frozen_modules(py_freeze!(source = "def foo(): pass", module_name = "other_thing"));
//!
//! rustpython::run(builder)
//! }
//!
//! #[pymodule]
//! mod my_mod {
//! use rustpython_vm::builtins::PyStrRef;
//TODO: use rustpython_vm::prelude::*;
//!
//! #[pyfunction]
//! fn do_thing(x: i32) -> i32 {
@@ -54,8 +60,8 @@ use std::env;
use std::io::IsTerminal;
use std::process::ExitCode;
pub use interpreter::InterpreterConfig;
pub use rustpython_vm as vm;
pub use interpreter::InterpreterBuilderExt;
pub use rustpython_vm::{self as vm, Interpreter, InterpreterBuilder};
pub use settings::{InstallPipMode, RunMode, parse_opts};
pub use shell::run_shell;
@@ -69,7 +75,11 @@ compile_error!(
/// The main cli of the `rustpython` interpreter. This function will return `std::process::ExitCode`
/// based on the return code of the python code ran through the cli.
pub fn run(init: impl FnOnce(&mut VirtualMachine) + 'static) -> ExitCode {
///
/// **Note**: This function provides no way to further initialize the VM after the builder is applied.
/// All VM initialization (adding native modules, init hooks, etc.) must be done through the
/// [`InterpreterBuilder`] parameter before calling this function.
pub fn run(mut builder: InterpreterBuilder) -> ExitCode {
env_logger::init();
// NOTE: This is not a WASI convention. But it will be convenient since POSIX shell always defines it.
@@ -101,14 +111,9 @@ pub fn run(init: impl FnOnce(&mut VirtualMachine) + 'static) -> ExitCode {
}
}
let mut config = InterpreterConfig::new().settings(settings);
#[cfg(feature = "stdlib")]
{
config = config.init_stdlib();
}
config = config.init_hook(Box::new(init));
builder = builder.settings(settings);
let interp = config.interpreter();
let interp = builder.interpreter();
let exitcode = interp.run(move |vm| run_rustpython(vm, run_mode));
rustpython_vm::common::os::exit_code(exitcode)
@@ -351,7 +356,7 @@ mod tests {
use rustpython_vm::Interpreter;
fn interpreter() -> Interpreter {
InterpreterConfig::new().init_stdlib().interpreter()
InterpreterBuilder::new().init_stdlib().interpreter()
}
#[test]
+8 -1
View File
@@ -1,3 +1,10 @@
use rustpython::{InterpreterBuilder, InterpreterBuilderExt};
pub fn main() -> std::process::ExitCode {
rustpython::run(|_vm| {})
let mut config = InterpreterBuilder::new();
#[cfg(feature = "stdlib")]
{
config = config.init_stdlib();
}
rustpython::run(config)
}