vm breakdown

This commit is contained in:
Jeong Yunwon
2022-04-23 06:21:00 +09:00
parent bb19b7c43a
commit 7f5d683af4
8 changed files with 275 additions and 268 deletions

View File

@@ -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
View 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 ")
})
}
}

View File

@@ -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
View 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
View 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))
}
}