mirror of
https://github.com/RustPython/RustPython.git
synced 2026-06-09 22:49:57 +09:00
vm breakdown
This commit is contained in:
@@ -77,9 +77,6 @@ pub mod types;
|
||||
pub mod utils;
|
||||
pub mod version;
|
||||
mod vm;
|
||||
mod vm_new;
|
||||
mod vm_object;
|
||||
mod vm_ops;
|
||||
|
||||
mod pyobject {
|
||||
pub use super::_pyobject::*;
|
||||
|
||||
93
vm/src/vm/interpreter.rs
Normal file
93
vm/src/vm/interpreter.rs
Normal file
@@ -0,0 +1,93 @@
|
||||
use super::{setting::PySettings, thread, InitParameter, VirtualMachine};
|
||||
|
||||
/// The general interface for the VM
|
||||
///
|
||||
/// # Examples
|
||||
/// Runs a simple embedded hello world program.
|
||||
/// ```
|
||||
/// use rustpython_vm::Interpreter;
|
||||
/// use rustpython_vm::compile::Mode;
|
||||
/// Interpreter::default().enter(|vm| {
|
||||
/// let scope = vm.new_scope_with_builtins();
|
||||
/// let code_obj = vm.compile(r#"print("Hello World!")"#,
|
||||
/// Mode::Exec,
|
||||
/// "<embedded>".to_owned(),
|
||||
/// ).map_err(|err| vm.new_syntax_error(&err)).unwrap();
|
||||
/// vm.run_code_obj(code_obj, scope).unwrap();
|
||||
/// });
|
||||
/// ```
|
||||
pub struct Interpreter {
|
||||
vm: VirtualMachine,
|
||||
}
|
||||
|
||||
impl Interpreter {
|
||||
pub fn new(settings: PySettings, init: InitParameter) -> Self {
|
||||
Self::new_with_init(settings, |_| init)
|
||||
}
|
||||
|
||||
pub fn new_with_init<F>(settings: PySettings, init: F) -> Self
|
||||
where
|
||||
F: FnOnce(&mut VirtualMachine) -> InitParameter,
|
||||
{
|
||||
let mut vm = VirtualMachine::new(settings);
|
||||
let init = init(&mut vm);
|
||||
vm.initialize(init);
|
||||
Self { vm }
|
||||
}
|
||||
|
||||
pub fn enter<F, R>(&self, f: F) -> R
|
||||
where
|
||||
F: FnOnce(&VirtualMachine) -> R,
|
||||
{
|
||||
thread::enter_vm(&self.vm, || f(&self.vm))
|
||||
}
|
||||
|
||||
// TODO: interpreter shutdown
|
||||
// pub fn run<F>(self, f: F)
|
||||
// where
|
||||
// F: FnOnce(&VirtualMachine),
|
||||
// {
|
||||
// self.enter(f);
|
||||
// self.shutdown();
|
||||
// }
|
||||
|
||||
// pub fn shutdown(self) {}
|
||||
}
|
||||
|
||||
impl Default for Interpreter {
|
||||
fn default() -> Self {
|
||||
Self::new(PySettings::default(), InitParameter::External)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{
|
||||
builtins::{int, PyStr},
|
||||
PyObjectRef,
|
||||
};
|
||||
use num_bigint::ToBigInt;
|
||||
|
||||
#[test]
|
||||
fn test_add_py_integers() {
|
||||
Interpreter::default().enter(|vm| {
|
||||
let a: PyObjectRef = vm.ctx.new_int(33_i32).into();
|
||||
let b: PyObjectRef = vm.ctx.new_int(12_i32).into();
|
||||
let res = vm._add(&a, &b).unwrap();
|
||||
let value = int::get_value(&res);
|
||||
assert_eq!(*value, 45_i32.to_bigint().unwrap());
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_multiply_str() {
|
||||
Interpreter::default().enter(|vm| {
|
||||
let a = vm.new_pyobj(crate::common::ascii!("Hello "));
|
||||
let b = vm.new_pyobj(4_i32);
|
||||
let res = vm._mul(&a, &b).unwrap();
|
||||
let value = res.payload::<PyStr>().unwrap();
|
||||
assert_eq!(value.as_ref(), "Hello Hello Hello Hello ")
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,6 @@
|
||||
//!
|
||||
//! See also:
|
||||
//! <https://github.com/ProgVal/pythonvm-rust/blob/master/src/processor/mod.rs>
|
||||
//!
|
||||
|
||||
#[cfg(feature = "rustpython-compiler")]
|
||||
use crate::compile::{self, CompileError, CompileOpts};
|
||||
@@ -33,6 +32,17 @@ use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
};
|
||||
|
||||
mod interpreter;
|
||||
mod setting;
|
||||
pub mod thread;
|
||||
mod vm_new;
|
||||
mod vm_object;
|
||||
mod vm_ops;
|
||||
|
||||
pub use interpreter::Interpreter;
|
||||
pub use setting::PySettings;
|
||||
pub use thread::PyThread;
|
||||
|
||||
// Objects are live when they are on stack, or referenced by a name (for now)
|
||||
|
||||
/// Top level container of a python virtual machine. In theory you could
|
||||
@@ -65,49 +75,6 @@ struct ExceptionStack {
|
||||
prev: Option<Box<ExceptionStack>>,
|
||||
}
|
||||
|
||||
pub(crate) mod thread {
|
||||
use super::{AsObject, PyObject, VirtualMachine};
|
||||
use itertools::Itertools;
|
||||
use std::{cell::RefCell, ptr::NonNull, thread_local};
|
||||
|
||||
thread_local! {
|
||||
pub(super) static VM_STACK: RefCell<Vec<NonNull<VirtualMachine>>> = Vec::with_capacity(1).into();
|
||||
}
|
||||
|
||||
pub fn enter_vm<R>(vm: &VirtualMachine, f: impl FnOnce() -> R) -> R {
|
||||
VM_STACK.with(|vms| {
|
||||
vms.borrow_mut().push(vm.into());
|
||||
let ret = std::panic::catch_unwind(std::panic::AssertUnwindSafe(f));
|
||||
vms.borrow_mut().pop();
|
||||
ret.unwrap_or_else(|e| std::panic::resume_unwind(e))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn with_vm<F, R>(obj: &PyObject, f: F) -> Option<R>
|
||||
where
|
||||
F: Fn(&VirtualMachine) -> R,
|
||||
{
|
||||
let vm_owns_obj = |intp: NonNull<VirtualMachine>| {
|
||||
// SAFETY: all references in VM_STACK should be valid
|
||||
let vm = unsafe { intp.as_ref() };
|
||||
obj.fast_isinstance(&vm.ctx.types.object_type)
|
||||
};
|
||||
VM_STACK.with(|vms| {
|
||||
let intp = match vms.borrow().iter().copied().exactly_one() {
|
||||
Ok(x) => {
|
||||
debug_assert!(vm_owns_obj(x));
|
||||
x
|
||||
}
|
||||
Err(mut others) => others.find(|x| vm_owns_obj(*x))?,
|
||||
};
|
||||
// SAFETY: all references in VM_STACK should be valid, and should not be changed or moved
|
||||
// at least until this function returns and the stack unwinds to an enter_vm() call
|
||||
let vm = unsafe { intp.as_ref() };
|
||||
Some(f(vm))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PyGlobalState {
|
||||
pub settings: PySettings,
|
||||
pub module_inits: stdlib::StdlibMap,
|
||||
@@ -125,98 +92,6 @@ pub enum InitParameter {
|
||||
External,
|
||||
}
|
||||
|
||||
/// Struct containing all kind of settings for the python vm.
|
||||
#[non_exhaustive]
|
||||
pub struct PySettings {
|
||||
/// -d command line switch
|
||||
pub debug: bool,
|
||||
|
||||
/// -i
|
||||
pub inspect: bool,
|
||||
|
||||
/// -i, with no script
|
||||
pub interactive: bool,
|
||||
|
||||
/// -O optimization switch counter
|
||||
pub optimize: u8,
|
||||
|
||||
/// -s
|
||||
pub no_user_site: bool,
|
||||
|
||||
/// -S
|
||||
pub no_site: bool,
|
||||
|
||||
/// -E
|
||||
pub ignore_environment: bool,
|
||||
|
||||
/// verbosity level (-v switch)
|
||||
pub verbose: u8,
|
||||
|
||||
/// -q
|
||||
pub quiet: bool,
|
||||
|
||||
/// -B
|
||||
pub dont_write_bytecode: bool,
|
||||
|
||||
/// -b
|
||||
pub bytes_warning: u64,
|
||||
|
||||
/// -Xfoo[=bar]
|
||||
pub xopts: Vec<(String, Option<String>)>,
|
||||
|
||||
/// -I
|
||||
pub isolated: bool,
|
||||
|
||||
/// -Xdev
|
||||
pub dev_mode: bool,
|
||||
|
||||
/// -Wfoo
|
||||
pub warnopts: Vec<String>,
|
||||
|
||||
/// Environment PYTHONPATH and RUSTPYTHONPATH:
|
||||
pub path_list: Vec<String>,
|
||||
|
||||
/// sys.argv
|
||||
pub argv: Vec<String>,
|
||||
|
||||
/// PYTHONHASHSEED=x
|
||||
pub hash_seed: Option<u32>,
|
||||
|
||||
/// -u, PYTHONUNBUFFERED=x
|
||||
// TODO: use this; can TextIOWrapper even work with a non-buffered?
|
||||
pub stdio_unbuffered: bool,
|
||||
}
|
||||
|
||||
/// Sensible default settings.
|
||||
impl Default for PySettings {
|
||||
fn default() -> Self {
|
||||
PySettings {
|
||||
debug: false,
|
||||
inspect: false,
|
||||
interactive: false,
|
||||
optimize: 0,
|
||||
no_user_site: false,
|
||||
no_site: false,
|
||||
ignore_environment: false,
|
||||
verbose: 0,
|
||||
quiet: false,
|
||||
dont_write_bytecode: false,
|
||||
bytes_warning: 0,
|
||||
xopts: vec![],
|
||||
isolated: false,
|
||||
dev_mode: false,
|
||||
warnopts: vec![],
|
||||
path_list: vec![
|
||||
#[cfg(all(feature = "pylib", not(feature = "freeze-stdlib")))]
|
||||
rustpython_pylib::LIB_PATH.to_owned(),
|
||||
],
|
||||
argv: vec![],
|
||||
hash_seed: None,
|
||||
stdio_unbuffered: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl VirtualMachine {
|
||||
/// Create a new `VirtualMachine` structure.
|
||||
fn new(settings: PySettings) -> VirtualMachine {
|
||||
@@ -969,132 +844,3 @@ impl<'vm> Drop for ReprGuard<'vm> {
|
||||
self.vm.repr_guards.borrow_mut().remove(&self.id);
|
||||
}
|
||||
}
|
||||
|
||||
/// The general interface for the VM
|
||||
///
|
||||
/// # Examples
|
||||
/// Runs a simple embedded hello world program.
|
||||
/// ```
|
||||
/// use rustpython_vm::Interpreter;
|
||||
/// use rustpython_vm::compile::Mode;
|
||||
/// Interpreter::default().enter(|vm| {
|
||||
/// let scope = vm.new_scope_with_builtins();
|
||||
/// let code_obj = vm.compile(r#"print("Hello World!")"#,
|
||||
/// Mode::Exec,
|
||||
/// "<embedded>".to_owned(),
|
||||
/// ).map_err(|err| vm.new_syntax_error(&err)).unwrap();
|
||||
/// vm.run_code_obj(code_obj, scope).unwrap();
|
||||
/// });
|
||||
/// ```
|
||||
pub struct Interpreter {
|
||||
vm: VirtualMachine,
|
||||
}
|
||||
|
||||
impl Interpreter {
|
||||
pub fn new(settings: PySettings, init: InitParameter) -> Self {
|
||||
Self::new_with_init(settings, |_| init)
|
||||
}
|
||||
|
||||
pub fn new_with_init<F>(settings: PySettings, init: F) -> Self
|
||||
where
|
||||
F: FnOnce(&mut VirtualMachine) -> InitParameter,
|
||||
{
|
||||
let mut vm = VirtualMachine::new(settings);
|
||||
let init = init(&mut vm);
|
||||
vm.initialize(init);
|
||||
Self { vm }
|
||||
}
|
||||
|
||||
pub fn enter<F, R>(&self, f: F) -> R
|
||||
where
|
||||
F: FnOnce(&VirtualMachine) -> R,
|
||||
{
|
||||
thread::enter_vm(&self.vm, || f(&self.vm))
|
||||
}
|
||||
|
||||
// TODO: interpreter shutdown
|
||||
// pub fn run<F>(self, f: F)
|
||||
// where
|
||||
// F: FnOnce(&VirtualMachine),
|
||||
// {
|
||||
// self.enter(f);
|
||||
// self.shutdown();
|
||||
// }
|
||||
|
||||
// pub fn shutdown(self) {}
|
||||
}
|
||||
|
||||
impl Default for Interpreter {
|
||||
fn default() -> Self {
|
||||
Self::new(PySettings::default(), InitParameter::External)
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use = "PyThread does nothing unless you move it to another thread and call .run()"]
|
||||
#[cfg(feature = "threading")]
|
||||
pub struct PyThread {
|
||||
thread_vm: VirtualMachine,
|
||||
}
|
||||
|
||||
#[cfg(feature = "threading")]
|
||||
impl PyThread {
|
||||
/// Create a `FnOnce()` that can easily be passed to a function like [`std::thread::Builder::spawn`]
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// If you return a `PyObjectRef` (or a type that contains one) from `F`, and don't `join()`
|
||||
/// on the thread this `FnOnce` runs in, there is a possibility that that thread will panic
|
||||
/// as `PyObjectRef`'s `Drop` implementation tries to run the `__del__` destructor of a
|
||||
/// Python object but finds that it's not in the context of any vm.
|
||||
pub fn make_spawn_func<F, R>(self, f: F) -> impl FnOnce() -> R
|
||||
where
|
||||
F: FnOnce(&VirtualMachine) -> R,
|
||||
{
|
||||
move || self.run(f)
|
||||
}
|
||||
|
||||
/// Run a function in this thread context
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// If you return a `PyObjectRef` (or a type that contains one) from `F`, and don't return the object
|
||||
/// to the parent thread and then `join()` on the `JoinHandle` (or similar), there is a possibility that
|
||||
/// the current thread will panic as `PyObjectRef`'s `Drop` implementation tries to run the `__del__`
|
||||
/// destructor of a python object but finds that it's not in the context of any vm.
|
||||
pub fn run<F, R>(self, f: F) -> R
|
||||
where
|
||||
F: FnOnce(&VirtualMachine) -> R,
|
||||
{
|
||||
let vm = &self.thread_vm;
|
||||
thread::enter_vm(vm, || f(vm))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::builtins::{int, PyStr};
|
||||
use num_bigint::ToBigInt;
|
||||
|
||||
#[test]
|
||||
fn test_add_py_integers() {
|
||||
Interpreter::default().enter(|vm| {
|
||||
let a: PyObjectRef = vm.ctx.new_int(33_i32).into();
|
||||
let b: PyObjectRef = vm.ctx.new_int(12_i32).into();
|
||||
let res = vm._add(&a, &b).unwrap();
|
||||
let value = int::get_value(&res);
|
||||
assert_eq!(*value, 45_i32.to_bigint().unwrap());
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_multiply_str() {
|
||||
Interpreter::default().enter(|vm| {
|
||||
let a = vm.new_pyobj(crate::common::ascii!("Hello "));
|
||||
let b = vm.new_pyobj(4_i32);
|
||||
let res = vm._mul(&a, &b).unwrap();
|
||||
let value = res.payload::<PyStr>().unwrap();
|
||||
assert_eq!(value.as_ref(), "Hello Hello Hello Hello ")
|
||||
})
|
||||
}
|
||||
}
|
||||
91
vm/src/vm/setting.rs
Normal file
91
vm/src/vm/setting.rs
Normal file
@@ -0,0 +1,91 @@
|
||||
/// Struct containing all kind of settings for the python vm.
|
||||
#[non_exhaustive]
|
||||
pub struct PySettings {
|
||||
/// -d command line switch
|
||||
pub debug: bool,
|
||||
|
||||
/// -i
|
||||
pub inspect: bool,
|
||||
|
||||
/// -i, with no script
|
||||
pub interactive: bool,
|
||||
|
||||
/// -O optimization switch counter
|
||||
pub optimize: u8,
|
||||
|
||||
/// -s
|
||||
pub no_user_site: bool,
|
||||
|
||||
/// -S
|
||||
pub no_site: bool,
|
||||
|
||||
/// -E
|
||||
pub ignore_environment: bool,
|
||||
|
||||
/// verbosity level (-v switch)
|
||||
pub verbose: u8,
|
||||
|
||||
/// -q
|
||||
pub quiet: bool,
|
||||
|
||||
/// -B
|
||||
pub dont_write_bytecode: bool,
|
||||
|
||||
/// -b
|
||||
pub bytes_warning: u64,
|
||||
|
||||
/// -Xfoo[=bar]
|
||||
pub xopts: Vec<(String, Option<String>)>,
|
||||
|
||||
/// -I
|
||||
pub isolated: bool,
|
||||
|
||||
/// -Xdev
|
||||
pub dev_mode: bool,
|
||||
|
||||
/// -Wfoo
|
||||
pub warnopts: Vec<String>,
|
||||
|
||||
/// Environment PYTHONPATH and RUSTPYTHONPATH:
|
||||
pub path_list: Vec<String>,
|
||||
|
||||
/// sys.argv
|
||||
pub argv: Vec<String>,
|
||||
|
||||
/// PYTHONHASHSEED=x
|
||||
pub hash_seed: Option<u32>,
|
||||
|
||||
/// -u, PYTHONUNBUFFERED=x
|
||||
// TODO: use this; can TextIOWrapper even work with a non-buffered?
|
||||
pub stdio_unbuffered: bool,
|
||||
}
|
||||
|
||||
/// Sensible default settings.
|
||||
impl Default for PySettings {
|
||||
fn default() -> Self {
|
||||
PySettings {
|
||||
debug: false,
|
||||
inspect: false,
|
||||
interactive: false,
|
||||
optimize: 0,
|
||||
no_user_site: false,
|
||||
no_site: false,
|
||||
ignore_environment: false,
|
||||
verbose: 0,
|
||||
quiet: false,
|
||||
dont_write_bytecode: false,
|
||||
bytes_warning: 0,
|
||||
xopts: vec![],
|
||||
isolated: false,
|
||||
dev_mode: false,
|
||||
warnopts: vec![],
|
||||
path_list: vec![
|
||||
#[cfg(all(feature = "pylib", not(feature = "freeze-stdlib")))]
|
||||
rustpython_pylib::LIB_PATH.to_owned(),
|
||||
],
|
||||
argv: vec![],
|
||||
hash_seed: None,
|
||||
stdio_unbuffered: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
80
vm/src/vm/thread.rs
Normal file
80
vm/src/vm/thread.rs
Normal file
@@ -0,0 +1,80 @@
|
||||
use crate::{AsObject, PyObject, VirtualMachine};
|
||||
use itertools::Itertools;
|
||||
use std::{cell::RefCell, ptr::NonNull, thread_local};
|
||||
|
||||
thread_local! {
|
||||
pub(super) static VM_STACK: RefCell<Vec<NonNull<VirtualMachine>>> = Vec::with_capacity(1).into();
|
||||
}
|
||||
|
||||
pub fn enter_vm<R>(vm: &VirtualMachine, f: impl FnOnce() -> R) -> R {
|
||||
VM_STACK.with(|vms| {
|
||||
vms.borrow_mut().push(vm.into());
|
||||
let ret = std::panic::catch_unwind(std::panic::AssertUnwindSafe(f));
|
||||
vms.borrow_mut().pop();
|
||||
ret.unwrap_or_else(|e| std::panic::resume_unwind(e))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn with_vm<F, R>(obj: &PyObject, f: F) -> Option<R>
|
||||
where
|
||||
F: Fn(&VirtualMachine) -> R,
|
||||
{
|
||||
let vm_owns_obj = |intp: NonNull<VirtualMachine>| {
|
||||
// SAFETY: all references in VM_STACK should be valid
|
||||
let vm = unsafe { intp.as_ref() };
|
||||
obj.fast_isinstance(&vm.ctx.types.object_type)
|
||||
};
|
||||
VM_STACK.with(|vms| {
|
||||
let intp = match vms.borrow().iter().copied().exactly_one() {
|
||||
Ok(x) => {
|
||||
debug_assert!(vm_owns_obj(x));
|
||||
x
|
||||
}
|
||||
Err(mut others) => others.find(|x| vm_owns_obj(*x))?,
|
||||
};
|
||||
// SAFETY: all references in VM_STACK should be valid, and should not be changed or moved
|
||||
// at least until this function returns and the stack unwinds to an enter_vm() call
|
||||
let vm = unsafe { intp.as_ref() };
|
||||
Some(f(vm))
|
||||
})
|
||||
}
|
||||
|
||||
#[must_use = "PyThread does nothing unless you move it to another thread and call .run()"]
|
||||
#[cfg(feature = "threading")]
|
||||
pub struct PyThread {
|
||||
pub(super) thread_vm: VirtualMachine,
|
||||
}
|
||||
|
||||
#[cfg(feature = "threading")]
|
||||
impl PyThread {
|
||||
/// Create a `FnOnce()` that can easily be passed to a function like [`std::thread::Builder::spawn`]
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// If you return a `PyObjectRef` (or a type that contains one) from `F`, and don't `join()`
|
||||
/// on the thread this `FnOnce` runs in, there is a possibility that that thread will panic
|
||||
/// as `PyObjectRef`'s `Drop` implementation tries to run the `__del__` destructor of a
|
||||
/// Python object but finds that it's not in the context of any vm.
|
||||
pub fn make_spawn_func<F, R>(self, f: F) -> impl FnOnce() -> R
|
||||
where
|
||||
F: FnOnce(&VirtualMachine) -> R,
|
||||
{
|
||||
move || self.run(f)
|
||||
}
|
||||
|
||||
/// Run a function in this thread context
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// If you return a `PyObjectRef` (or a type that contains one) from `F`, and don't return the object
|
||||
/// to the parent thread and then `join()` on the `JoinHandle` (or similar), there is a possibility that
|
||||
/// the current thread will panic as `PyObjectRef`'s `Drop` implementation tries to run the `__del__`
|
||||
/// destructor of a python object but finds that it's not in the context of any vm.
|
||||
pub fn run<F, R>(self, f: F) -> R
|
||||
where
|
||||
F: FnOnce(&VirtualMachine) -> R,
|
||||
{
|
||||
let vm = &self.thread_vm;
|
||||
enter_vm(vm, || f(vm))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user