Interpreter struct for managing thread-local vm stack

This commit is contained in:
Noah committed 2020-09-15 16:44:39 -05:00
1 parent 6eb5daf9f6
commit 18273da108
16 files changed
+379 -303

No files matched your search

+21 -19
View File
@@ -2,16 +2,18 @@
extern crate test;
use test::Bencher;
use rustpython_compiler::compile;
use rustpython_vm::pyobject::PyResult;
use rustpython_vm::VirtualMachine;
use rustpython_vm::Interpreter;
const MINIDOM: &str = include_str!("./benchmarks/minidom.py");
const NBODY: &str = include_str!("./benchmarks/nbody.py");
const MANDELBROT: &str = include_str!("./benchmarks/mandelbrot.py");
#[bench]
fn bench_tokenization(b: &mut test::Bencher) {
fn bench_tokenization(b: &mut Bencher) {
use rustpython_parser::lexer::{make_tokenizer, Tok};
let source = MINIDOM;
@@ -26,7 +28,7 @@ fn bench_tokenization(b: &mut test::Bencher) {
}
#[bench]
fn bench_rustpy_parse_to_ast(b: &mut test::Bencher) {
fn bench_rustpy_parse_to_ast(b: &mut Bencher) {
use rustpython_parser::parser::parse_program;
let source = MINIDOM;
@@ -36,7 +38,7 @@ fn bench_rustpy_parse_to_ast(b: &mut test::Bencher) {
}
#[bench]
fn bench_cpython_parse_to_ast(b: &mut test::Bencher) {
fn bench_cpython_parse_to_ast(b: &mut Bencher) {
let source = MINIDOM;
let gil = cpython::Python::acquire_gil();
@@ -56,7 +58,7 @@ fn bench_cpython_parse_to_ast(b: &mut test::Bencher) {
})
}
fn bench_cpython(b: &mut test::Bencher, source: &str) {
fn bench_cpython(b: &mut Bencher, source: &str) {
let gil = cpython::Python::acquire_gil();
let python = gil.python();
@@ -70,36 +72,36 @@ fn bench_cpython(b: &mut test::Bencher, source: &str) {
}
#[bench]
fn bench_cpython_nbody(b: &mut test::Bencher) {
fn bench_cpython_nbody(b: &mut Bencher) {
bench_cpython(b, NBODY)
}
#[bench]
fn bench_cpython_mandelbrot(b: &mut test::Bencher) {
fn bench_cpython_mandelbrot(b: &mut Bencher) {
bench_cpython(b, MANDELBROT)
}
fn bench_rustpy(b: &mut test::Bencher, name: &str, source: &str) {
fn bench_rustpy(b: &mut Bencher, name: &str, source: &str) {
// NOTE: Take long time.
let vm = VirtualMachine::default();
Interpreter::default().enter(|vm| {
let code = vm
.compile(source, compile::Mode::Exec, name.to_owned())
.unwrap();
let code = vm
.compile(source, compile::Mode::Exec, name.to_owned())
.unwrap();
b.iter(|| {
let scope = vm.new_scope_with_builtins();
let res: PyResult = vm.run_code_obj(code.clone(), scope);
vm.unwrap_pyresult(res);
b.iter(|| {
let scope = vm.new_scope_with_builtins();
let res: PyResult = vm.run_code_obj(code.clone(), scope);
vm.unwrap_pyresult(res);
})
})
}
#[bench]
fn bench_rustpy_nbody(b: &mut test::Bencher) {
fn bench_rustpy_nbody(b: &mut Bencher) {
bench_rustpy(b, "nbody.py", NBODY)
}
#[bench]
fn bench_rustpy_mandelbrot(b: &mut test::Bencher) {
fn bench_rustpy_mandelbrot(b: &mut Bencher) {
bench_rustpy(b, "mandelbrot.py", MANDELBROT)
}
+3 -1
View File
@@ -3,8 +3,10 @@ use std::collections::HashMap;
use rustpython_vm as vm;
fn main() -> vm::pyobject::PyResult<()> {
let vm = vm::VirtualMachine::new(vm::PySettings::default());
vm::Interpreter::default().enter(run)
}
fn run(vm: &vm::VirtualMachine) -> vm::pyobject::PyResult<()> {
let scope = vm.new_scope_with_builtins();
// the file parameter is relevant to the directory where the crate's Cargo.toml is located, see $CARGO_MANIFEST_DIR:
+12 -12
View File
@@ -2,19 +2,19 @@ use rustpython_compiler as compiler;
use rustpython_vm as vm;
fn main() -> vm::pyobject::PyResult<()> {
let vm = vm::VirtualMachine::new(vm::PySettings::default());
vm::Interpreter::default().enter(|vm| {
let scope = vm.new_scope_with_builtins();
let scope = vm.new_scope_with_builtins();
let code_obj = vm
.compile(
r#"print("Hello World!")"#,
compiler::compile::Mode::Exec,
"<embedded>".to_owned(),
)
.map_err(|err| vm.new_syntax_error(&err))?;
let code_obj = vm
.compile(
r#"print("Hello World!")"#,
compiler::compile::Mode::Exec,
"<embedded>".to_owned(),
)
.map_err(|err| vm.new_syntax_error(&err))?;
vm.run_code_obj(code_obj, scope)?;
vm.run_code_obj(code_obj, scope)?;
Ok(())
Ok(())
})
}
+7 -4
View File
@@ -59,14 +59,17 @@ fn on(b: bool) {
}
fn main() -> vm::pyobject::PyResult<()> {
vm::Interpreter::default().enter(run)
}
fn run(vm: &vm::VirtualMachine) -> vm::pyobject::PyResult<()> {
let mut input = String::with_capacity(50);
let stdin = std::io::stdin();
let vm = vm::VirtualMachine::new(vm::PySettings::default());
let scope: vm::scope::Scope = vm.new_scope_with_builtins();
// typing `quit()` is too long, let's make `on(False)` work instead.
scope.globals.set_item("on", vm.ctx.new_function(on), &vm)?;
scope.globals.set_item("on", vm.ctx.new_function(on), vm)?;
// let's include a fibonacci function, but let's be lazy and write it in Python
add_python_function!(
@@ -97,11 +100,11 @@ fn main() -> vm::pyobject::PyResult<()> {
Ok(output) => {
// store the last value in the "last" variable
if !vm.is_none(&output) {
scope.globals.set_item("last", output, &vm)?;
scope.globals.set_item("last", output, vm)?;
}
}
Err(e) => {
vm::exceptions::print_exception(&vm, e);
vm::exceptions::print_exception(vm, e);
}
}
}
+52 -50
View File
@@ -12,7 +12,7 @@ use rustpython_vm::{
obj::{objint::PyInt, objtype},
pyobject::{BorrowValue, ItemProtocol, PyResult},
scope::Scope,
util, InitParameter, PySettings, VirtualMachine,
util, InitParameter, Interpreter, PySettings, VirtualMachine,
};
use std::convert::TryInto;
@@ -49,49 +49,51 @@ fn main() {
}
}
let vm = VirtualMachine::new(settings);
let interp = Interpreter::new(settings);
let res = run_rustpython(&vm, &matches);
interp.enter(move |vm| {
let res = run_rustpython(vm, &matches);
#[cfg(feature = "flame-it")]
{
main_guard.end();
if let Err(e) = write_profile(&matches) {
error!("Error writing profile information: {}", e);
#[cfg(feature = "flame-it")]
{
main_guard.end();
if let Err(e) = write_profile(&matches) {
error!("Error writing profile information: {}", e);
}
}
}
// See if any exception leaked out:
if let Err(err) = res {
if objtype::isinstance(&err, &vm.ctx.exceptions.system_exit) {
let args = err.args();
match args.borrow_value().len() {
0 => return,
1 => match_class!(match args.borrow_value()[0].clone() {
i @ PyInt => {
use num_traits::cast::ToPrimitive;
process::exit(i.borrow_value().to_i32().unwrap_or(0));
}
arg => {
if vm.is_none(&arg) {
return;
// See if any exception leaked out:
if let Err(err) = res {
if objtype::isinstance(&err, &vm.ctx.exceptions.system_exit) {
let args = err.args();
match args.borrow_value().len() {
0 => return,
1 => match_class!(match args.borrow_value()[0].clone() {
i @ PyInt => {
use num_traits::cast::ToPrimitive;
process::exit(i.borrow_value().to_i32().unwrap_or(0));
}
if let Ok(s) = vm.to_str(&arg) {
eprintln!("{}", s);
arg => {
if vm.is_none(&arg) {
return;
}
if let Ok(s) = vm.to_str(&arg) {
eprintln!("{}", s);
}
}
}),
_ => {
if let Ok(r) = vm.to_repr(args.as_object()) {
eprintln!("{}", r);
}
}
}),
_ => {
if let Ok(r) = vm.to_repr(args.as_object()) {
eprintln!("{}", r);
}
}
} else {
print_exception(&vm, err);
}
} else {
print_exception(&vm, err);
process::exit(1);
}
process::exit(1);
}
})
}
fn parse_arguments<'a>(app: App<'a, '_>) -> ArgMatches<'a> {
@@ -493,21 +495,21 @@ fn run_script(vm: &VirtualMachine, scope: Scope, script_file: &str) -> PyResult<
#[test]
fn test_run_script() {
let vm: VirtualMachine = Default::default();
Interpreter::default().enter(|vm| {
// test file run
let r = run_script(
vm,
vm.new_scope_with_builtins(),
"extra_tests/snippets/dir_main/__main__.py",
);
assert!(r.is_ok());
// test file run
let r = run_script(
&vm,
vm.new_scope_with_builtins(),
"extra_tests/snippets/dir_main/__main__.py",
);
assert!(r.is_ok());
// test module run
let r = run_script(
&vm,
vm.new_scope_with_builtins(),
"extra_tests/snippets/dir_main",
);
assert!(r.is_ok());
// test module run
let r = run_script(
vm,
vm.new_scope_with_builtins(),
"extra_tests/snippets/dir_main",
);
assert!(r.is_ok());
})
}
+32 -29
View File
@@ -504,39 +504,41 @@ impl DictKey for &str {
#[cfg(test)]
mod tests {
use super::{Dict, DictKey, VirtualMachine};
use super::{Dict, DictKey};
use crate::Interpreter;
#[test]
fn test_insert() {
let vm: VirtualMachine = Default::default();
let dict = Dict::default();
assert_eq!(0, dict.len());
Interpreter::default().enter(|vm| {
let dict = Dict::default();
assert_eq!(0, dict.len());
let key1 = vm.ctx.new_bool(true);
let value1 = vm.ctx.new_str("abc");
dict.insert(&vm, key1.clone(), value1.clone()).unwrap();
assert_eq!(1, dict.len());
let key1 = vm.ctx.new_bool(true);
let value1 = vm.ctx.new_str("abc");
dict.insert(&vm, key1.clone(), value1.clone()).unwrap();
assert_eq!(1, dict.len());
let key2 = vm.ctx.new_str("x");
let value2 = vm.ctx.new_str("def");
dict.insert(&vm, key2.clone(), value2.clone()).unwrap();
assert_eq!(2, dict.len());
let key2 = vm.ctx.new_str("x");
let value2 = vm.ctx.new_str("def");
dict.insert(&vm, key2.clone(), value2.clone()).unwrap();
assert_eq!(2, dict.len());
dict.insert(&vm, key1.clone(), value2.clone()).unwrap();
assert_eq!(2, dict.len());
dict.insert(&vm, key1.clone(), value2.clone()).unwrap();
assert_eq!(2, dict.len());
dict.delete(&vm, key1.clone()).unwrap();
assert_eq!(1, dict.len());
dict.delete(&vm, key1.clone()).unwrap();
assert_eq!(1, dict.len());
dict.insert(&vm, key1.clone(), value2.clone()).unwrap();
assert_eq!(2, dict.len());
dict.insert(&vm, key1.clone(), value2.clone()).unwrap();
assert_eq!(2, dict.len());
assert_eq!(true, dict.contains(&vm, &key1).unwrap());
assert_eq!(true, dict.contains(&vm, &"x").unwrap());
assert_eq!(true, dict.contains(&vm, &key1).unwrap());
assert_eq!(true, dict.contains(&vm, &"x").unwrap());
let val = dict.get(&vm, &"x").unwrap().unwrap();
vm.bool_eq(val, value2)
.expect("retrieved value must be equal to inserted value.");
let val = dict.get(&vm, &"x").unwrap().unwrap();
vm.bool_eq(val, value2)
.expect("retrieved value must be equal to inserted value.");
})
}
macro_rules! hash_tests {
@@ -556,12 +558,13 @@ mod tests {
}
fn check_hash_equivalence(text: &str) {
let vm: VirtualMachine = Default::default();
let value1 = text;
let value2 = vm.ctx.new_str(value1.to_owned());
Interpreter::default().enter(|vm| {
let value1 = text;
let value2 = vm.ctx.new_str(value1.to_owned());
let hash1 = value1.key_hash(&vm).expect("Hash should not fail.");
let hash2 = value2.key_hash(&vm).expect("Hash should not fail.");
assert_eq!(hash1, hash2);
let hash1 = value1.key_hash(&vm).expect("Hash should not fail.");
let hash2 = value2.key_hash(&vm).expect("Hash should not fail.");
assert_eq!(hash1, hash2);
})
}
}
+9 -9
View File
@@ -1,6 +1,6 @@
use crate::pyobject::PyResult;
use crate::scope::Scope;
use crate::vm::VirtualMachine;
use crate::VirtualMachine;
use rustpython_compiler::compile;
pub fn eval(vm: &VirtualMachine, source: &str, scope: Scope, source_path: &str) -> PyResult {
@@ -15,16 +15,16 @@ pub fn eval(vm: &VirtualMachine, source: &str, scope: Scope, source_path: &str)
#[cfg(test)]
mod tests {
use super::eval;
use super::VirtualMachine;
use crate::pyobject::IdProtocol;
use super::*;
use crate::Interpreter;
#[test]
fn test_print_42() {
let source = String::from("print('Hello world')");
let vm = VirtualMachine::default();
let vars = vm.new_scope_with_builtins();
let result = eval(&vm, &source, vars, "<unittest>").expect("this should pass");
assert!(result.is(&vm.ctx.none()));
Interpreter::default().enter(|vm| {
let source = String::from("print('Hello world')");
let vars = vm.new_scope_with_builtins();
let result = eval(&vm, &source, vars, "<unittest>").expect("this should pass");
assert!(vm.is_none(&result));
})
}
}
+1 -1
View File
@@ -71,6 +71,6 @@ mod version;
mod vm;
// pub use self::pyobject::Executor;
pub use self::vm::{InitParameter, PySettings, VirtualMachine};
pub use self::vm::{InitParameter, Interpreter, PySettings, VirtualMachine};
pub use rustpython_bytecode::*;
pub use rustpython_common as common;
+4 -4
View File
@@ -93,13 +93,12 @@ macro_rules! py_namespace {
/// use num_bigint::ToBigInt;
/// use num_traits::Zero;
///
/// use rustpython_vm::VirtualMachine;
/// use rustpython_vm::match_class;
/// use rustpython_vm::obj::objfloat::PyFloat;
/// use rustpython_vm::obj::objint::PyInt;
/// use rustpython_vm::pyobject::PyValue;
///
/// let vm: VirtualMachine = Default::default();
/// # rustpython_vm::Interpreter::default().enter(|vm| {
/// let obj = PyInt::from(0).into_ref(&vm).into_object();
/// assert_eq!(
/// "int",
@@ -109,6 +108,7 @@ macro_rules! py_namespace {
/// _ => "neither",
/// })
/// );
/// # });
///
/// ```
///
@@ -118,13 +118,12 @@ macro_rules! py_namespace {
/// use num_bigint::ToBigInt;
/// use num_traits::Zero;
///
/// use rustpython_vm::VirtualMachine;
/// use rustpython_vm::match_class;
/// use rustpython_vm::obj::objfloat::PyFloat;
/// use rustpython_vm::obj::objint::PyInt;
/// use rustpython_vm::pyobject::{PyValue, BorrowValue};
///
/// let vm: VirtualMachine = Default::default();
/// # rustpython_vm::Interpreter::default().enter(|vm| {
/// let obj = PyInt::from(0).into_ref(&vm).into_object();
///
/// let int_value = match_class!(match obj {
@@ -134,6 +133,7 @@ macro_rules! py_namespace {
/// });
///
/// assert!(int_value.is_zero());
/// # });
/// ```
#[macro_export]
macro_rules! match_class {
+22 -21
View File
@@ -1258,6 +1258,7 @@ fn char_is_printable(c: char) -> bool {
#[cfg(test)]
mod tests {
use super::*;
use crate::Interpreter;
#[test]
fn str_title() {
@@ -1307,27 +1308,27 @@ mod tests {
#[test]
fn str_maketrans_and_translate() {
let vm: VirtualMachine = Default::default();
let table = vm.ctx.new_dict();
table.set_item("a", vm.ctx.new_str("🎅"), &vm).unwrap();
table.set_item("b", vm.get_none(), &vm).unwrap();
table.set_item("c", vm.ctx.new_str("xda"), &vm).unwrap();
let translated = PyString::maketrans(
table.into_object(),
OptionalArg::Missing,
OptionalArg::Missing,
&vm,
)
.unwrap();
let text = PyString::from("abc");
let translated = text.translate(translated, &vm).unwrap();
assert_eq!(translated, "🎅xda".to_owned());
let translated = text.translate(vm.ctx.new_int(3), &vm);
assert_eq!(
translated.unwrap_err().lease_class().name,
"TypeError".to_owned()
);
Interpreter::default().enter(|vm| {
let table = vm.ctx.new_dict();
table.set_item("a", vm.ctx.new_str("🎅"), &vm).unwrap();
table.set_item("b", vm.get_none(), &vm).unwrap();
table.set_item("c", vm.ctx.new_str("xda"), &vm).unwrap();
let translated = PyString::maketrans(
table.into_object(),
OptionalArg::Missing,
OptionalArg::Missing,
&vm,
)
.unwrap();
let text = PyString::from("abc");
let translated = text.translate(translated, &vm).unwrap();
assert_eq!(translated, "🎅xda".to_owned());
let translated = text.translate(vm.ctx.new_int(3), &vm);
assert_eq!(
translated.unwrap_err().lease_class().name,
"TypeError".to_owned()
);
})
}
}
+1 -1
View File
@@ -147,7 +147,7 @@ where
// CPython-compatible drop implementation
let zelf = Self::into_ref(self.clone());
if let Some(del_method) = zelf.inner.get_class_attr("__del__") {
crate::vm::thread::with_vm(|vm| {
crate::vm::thread::with_vm(&zelf, |vm| {
if let Err(e) = vm.invoke(&del_method, vec![zelf.clone()]) {
// exception in del will be ignored but printed
print!("Exception ignored in: ",);
+32 -30
View File
@@ -221,44 +221,46 @@ fn thread_start_new_thread(
kwargs: OptionalArg<PyDictRef>,
vm: &VirtualMachine,
) -> PyResult<u64> {
let args = PyFuncArgs::from((
Args::from(args.borrow_value().to_owned()),
KwArgs::from(kwargs.map_or_else(Default::default, |k| k.to_attributes())),
));
let thread_vm = vm.new_thread();
let mut thread_builder = thread::Builder::new();
let stacksize = vm.state.stacksize.load();
if stacksize != 0 {
thread_builder = thread_builder.stack_size(stacksize);
}
let res = thread_builder.spawn(move || {
let vm = &thread_vm;
let prior_vm = crate::vm::thread::VM.with(|tvm| tvm.replace(vm as *const _));
assert!(prior_vm.is_null());
let args = Args::from(args.borrow_value().to_owned());
let kwargs = KwArgs::from(kwargs.map_or_else(Default::default, |k| k.to_attributes()));
if let Err(exc) = func.invoke(PyFuncArgs::from((args, kwargs)), vm) {
// TODO: sys.unraisablehook
let stderr = std::io::stderr();
let mut stderr = stderr.lock();
let repr = vm.to_repr(&func.into_object()).ok();
let repr = repr
.as_ref()
.map_or("<object repr() failed>", |s| s.borrow_value());
writeln!(stderr, "Exception ignored in thread started by: {}", repr)
.and_then(|()| exceptions::write_exception(&mut stderr, vm, &exc))
.ok();
}
SENTINELS.with(|sents| {
for lock in sents.replace(Default::default()) {
if lock.mu.is_locked() {
unsafe { lock.mu.unlock() };
}
thread_builder
.spawn(move || thread_vm.enter(move |vm| run_thread(func, args, vm)))
.map(|handle| {
vm.state.thread_count.fetch_add(1);
thread_to_id(&handle.thread())
})
.map_err(|err| err.into_pyexception(vm))
}
fn run_thread(func: PyCallable, args: PyFuncArgs, vm: &VirtualMachine) {
if let Err(exc) = func.invoke(args, vm) {
// TODO: sys.unraisablehook
let stderr = std::io::stderr();
let mut stderr = stderr.lock();
let repr = vm.to_repr(&func.into_object()).ok();
let repr = repr
.as_ref()
.map_or("<object repr() failed>", |s| s.borrow_value());
writeln!(stderr, "Exception ignored in thread started by: {}", repr)
.and_then(|()| exceptions::write_exception(&mut stderr, vm, &exc))
.ok();
}
SENTINELS.with(|sents| {
for lock in sents.replace(Default::default()) {
if lock.mu.is_locked() {
unsafe { lock.mu.unlock() };
}
});
vm.state.thread_count.fetch_sub(1);
}
});
res.map(|handle| {
vm.state.thread_count.fetch_add(1);
thread_to_id(&handle.thread())
})
.map_err(|err| err.into_pyexception(vm))
vm.state.thread_count.fetch_sub(1);
}
thread_local!(static SENTINELS: RefCell<Vec<PyLockRef>> = RefCell::default());
+83 -34
View File
@@ -69,24 +69,39 @@ pub struct VirtualMachine {
}
pub(crate) mod thread {
use super::VirtualMachine;
use std::cell::Cell;
use super::{Interpreter, PyObjectRef, VirtualMachine};
use itertools::Itertools;
use std::cell::RefCell;
use std::ptr::NonNull;
use std::thread_local;
thread_local! {
pub(crate) static VM: Cell<*const VirtualMachine> = Cell::new(std::ptr::null());
pub(super) static VM_STACK: RefCell<Vec<NonNull<Interpreter>>> = Vec::with_capacity(1).into();
}
pub(crate) fn with_vm<F, R>(f: F) -> R
pub(crate) fn with_vm<F, R>(obj: &PyObjectRef, f: F) -> R
where
F: Fn(&VirtualMachine) -> R,
{
VM.with(|tvm| {
// vm is guaranteed to be existing unless it is not null
let pvm = tvm.get();
debug_assert!(!pvm.is_null());
let vm = unsafe { &*pvm };
f(vm)
let vm_owns_obj = |intp: NonNull<Interpreter>| {
// SAFETY: all references in VM_STACK should be valid
let vm = unsafe { &intp.as_ref().vm };
crate::obj::objtype::isinstance(obj, &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))
.expect("can't get a vm; none on stack"),
};
// 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 Interpreter::enter call
let intp = unsafe { intp.as_ref() };
f(&intp.vm)
})
}
}
@@ -191,7 +206,7 @@ impl Default for PySettings {
impl VirtualMachine {
/// Create a new `VirtualMachine` structure.
pub fn new(settings: PySettings) -> VirtualMachine {
fn new(settings: PySettings) -> VirtualMachine {
flame_guard!("new VirtualMachine");
let ctx = PyContext::new();
@@ -244,7 +259,6 @@ impl VirtualMachine {
}),
initialized: false,
};
thread::VM.with(|tvm| tvm.replace(&vm as *const _));
objmodule::init_module_dict(
&vm,
@@ -316,8 +330,8 @@ impl VirtualMachine {
}
#[cfg(feature = "threading")]
pub(crate) fn new_thread(&self) -> VirtualMachine {
VirtualMachine {
pub(crate) fn new_thread(&self) -> Interpreter {
let vm = VirtualMachine {
builtins: self.builtins.clone(),
sys_module: self.sys_module.clone(),
ctx: self.ctx.clone(),
@@ -333,7 +347,8 @@ impl VirtualMachine {
repr_guards: RefCell::default(),
state: self.state.clone(),
initialized: self.initialized,
}
};
Interpreter { vm }
}
pub fn run_code_obj(&self, code: PyCodeRef, scope: Scope) -> PyResult {
@@ -1526,12 +1541,6 @@ impl VirtualMachine {
}
}
impl Default for VirtualMachine {
fn default() -> Self {
VirtualMachine::new(Default::default())
}
}
pub struct ReprGuard<'vm> {
vm: &'vm VirtualMachine,
id: usize,
@@ -1561,29 +1570,69 @@ impl<'vm> Drop for ReprGuard<'vm> {
}
}
pub struct Interpreter {
vm: VirtualMachine,
}
impl Interpreter {
pub fn new(settings: PySettings) -> Self {
Self {
vm: VirtualMachine::new(settings),
}
}
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::VM_STACK.with(|vms| vms.borrow_mut().push(self.into()));
let ret = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| f(&self.vm)));
thread::VM_STACK.with(|vms| vms.borrow_mut().pop());
ret.unwrap_or_else(|e| std::panic::resume_unwind(e))
}
}
impl Default for Interpreter {
fn default() -> Self {
Self::new(PySettings::default())
}
}
#[cfg(test)]
mod tests {
use super::VirtualMachine;
use super::Interpreter;
use crate::obj::{objint, objstr};
use num_bigint::ToBigInt;
#[test]
fn test_add_py_integers() {
let vm: VirtualMachine = Default::default();
let a = vm.ctx.new_int(33_i32);
let b = vm.ctx.new_int(12_i32);
let res = vm._add(a, b).unwrap();
let value = objint::get_value(&res);
assert_eq!(*value, 45_i32.to_bigint().unwrap());
Interpreter::default().enter(|vm| {
let a = vm.ctx.new_int(33_i32);
let b = vm.ctx.new_int(12_i32);
let res = vm._add(a, b).unwrap();
let value = objint::get_value(&res);
assert_eq!(*value, 45_i32.to_bigint().unwrap());
})
}
#[test]
fn test_multiply_str() {
let vm: VirtualMachine = Default::default();
let a = vm.ctx.new_str(String::from("Hello "));
let b = vm.ctx.new_int(4_i32);
let res = vm._mul(a, b).unwrap();
let value = objstr::borrow_value(&res);
assert_eq!(value, String::from("Hello Hello Hello Hello "))
Interpreter::default().enter(|vm| {
let a = vm.ctx.new_str(String::from("Hello "));
let b = vm.ctx.new_int(4_i32);
let res = vm._mul(a, b).unwrap();
let value = objstr::borrow_value(&res);
assert_eq!(value, String::from("Hello Hello Hello Hello "))
})
}
}
+36 -27
View File
@@ -127,13 +127,14 @@ fn browser_request_animation_frame(func: PyCallable, vm: &VirtualMachine) -> PyR
let stored_vm = weak_vm
.upgrade()
.expect("that the vm is valid from inside of request_animation_frame");
let vm = &stored_vm.vm;
let func = func.clone();
let args = vec![vm.ctx.new_float(time)];
let _ = vm.invoke(&func.into_object(), args);
stored_vm.interp.enter(|vm| {
let func = func.clone();
let args = vec![vm.ctx.new_float(time)];
let _ = vm.invoke(&func.into_object(), args);
let closure = f.borrow_mut().take();
drop(closure);
let closure = f.borrow_mut().take();
drop(closure);
})
}) as Box<dyn Fn(f64)>));
let id = window()
@@ -195,26 +196,32 @@ impl PyPromise {
let stored_vm = &weak_vm
.upgrade()
.expect("that the vm is valid when the promise resolves");
let vm = &stored_vm.vm;
let ret = match prom.await {
Ok(val) => {
let res = prom.await;
match res {
Ok(val) => stored_vm.interp.enter(move |vm| {
let args = if val.is_null() {
vec![]
} else {
vec![convert::js_to_py(vm, val)]
};
vm.invoke(&on_fulfill.into_object(), PyFuncArgs::new(args, vec![]))
}
let res = vm.invoke(&on_fulfill.into_object(), PyFuncArgs::new(args, vec![]));
convert::pyresult_to_jsresult(vm, res)
}),
Err(err) => {
if let OptionalArg::Present(on_reject) = on_reject {
let err = convert::js_to_py(vm, err);
vm.invoke(&on_reject.into_object(), PyFuncArgs::new(vec![err], vec![]))
stored_vm.interp.enter(move |vm| {
let err = convert::js_to_py(vm, err);
let res = vm.invoke(
&on_reject.into_object(),
PyFuncArgs::new(vec![err], vec![]),
);
convert::pyresult_to_jsresult(vm, res)
})
} else {
return Err(err);
Err(err)
}
}
};
convert::pyresult_to_jsresult(vm, ret)
}
};
PyPromise::from_future(ret_future).into_ref(vm)
@@ -233,10 +240,11 @@ impl PyPromise {
let stored_vm = weak_vm
.upgrade()
.expect("that the vm is valid when the promise resolves");
let vm = &stored_vm.vm;
let err = convert::js_to_py(vm, err);
let res = vm.invoke(&on_reject.into_object(), PyFuncArgs::new(vec![err], vec![]));
convert::pyresult_to_jsresult(vm, res)
stored_vm.interp.enter(move |vm| {
let err = convert::js_to_py(vm, err);
let res = vm.invoke(&on_reject.into_object(), PyFuncArgs::new(vec![err], vec![]));
convert::pyresult_to_jsresult(vm, res)
})
};
PyPromise::from_future(ret_future).into_ref(vm)
@@ -327,13 +335,14 @@ fn browser_load_module(module: PyStringRef, path: PyStringRef, vm: &VirtualMachi
let stored_vm = &weak_vm
.upgrade()
.expect("that the vm is valid when the promise resolves");
let vm = &stored_vm.vm;
let resp_text = text.as_string().unwrap();
let res = import_file(vm, module.borrow_value(), "WEB".to_owned(), resp_text);
match res {
Ok(_) => Ok(JsValue::null()),
Err(err) => Err(convert::py_err_to_js_err(vm, &err)),
}
stored_vm.interp.enter(move |vm| {
let resp_text = text.as_string().unwrap();
let res = import_file(vm, module.borrow_value(), "WEB".to_owned(), resp_text);
match res {
Ok(_) => Ok(JsValue::null()),
Err(err) => Err(convert::py_err_to_js_err(vm, &err)),
}
})
};
Ok(PyPromise::from_future(future).into_object(vm))
+16 -15
View File
@@ -83,23 +83,24 @@ pub fn py_to_js(vm: &VirtualMachine, py_obj: PyObjectRef) -> JsValue {
return Err(err);
}
};
let vm = &stored_vm_from_wasm(&wasm_vm).vm;
let mut py_func_args = PyFuncArgs::default();
if let Some(ref args) = args {
for arg in args.values() {
py_func_args.args.push(js_to_py(vm, arg?));
stored_vm_from_wasm(&wasm_vm).interp.enter(move |vm| {
let mut py_func_args = PyFuncArgs::default();
if let Some(ref args) = args {
for arg in args.values() {
py_func_args.args.push(js_to_py(vm, arg?));
}
}
}
if let Some(ref kwargs) = kwargs {
for pair in object_entries(kwargs) {
let (key, val) = pair?;
py_func_args
.kwargs
.insert(js_sys::JsString::from(key).into(), js_to_py(vm, val));
if let Some(ref kwargs) = kwargs {
for pair in object_entries(kwargs) {
let (key, val) = pair?;
py_func_args
.kwargs
.insert(js_sys::JsString::from(key).into(), js_to_py(vm, val));
}
}
}
let result = vm.invoke(&py_obj, py_func_args);
pyresult_to_jsresult(vm, result)
let result = vm.invoke(&py_obj, py_func_args);
pyresult_to_jsresult(vm, result)
})
};
let closure = Closure::wrap(Box::new(closure)
as Box<dyn FnMut(Option<Array>, Option<Object>) -> Result<JsValue, JsValue>>);
+48 -46
View File
@@ -9,7 +9,7 @@ use rustpython_compiler::compile;
use rustpython_vm::common::rc::PyRc;
use rustpython_vm::pyobject::{ItemProtocol, PyObjectRef, PyObjectWeak, PyValue};
use rustpython_vm::scope::{NameProtocol, Scope};
use rustpython_vm::{InitParameter, PySettings, VirtualMachine};
use rustpython_vm::{InitParameter, Interpreter, PySettings, VirtualMachine};
use crate::browser_module::setup_browser_module;
use crate::convert::{self, PyResultExt};
@@ -18,7 +18,7 @@ use crate::wasm_builtins;
use rustpython_compiler::mode::Mode;
pub(crate) struct StoredVirtualMachine {
pub vm: VirtualMachine,
pub interp: Interpreter,
pub scope: RefCell<Scope>,
/// you can put a Rc in here, keep it as a Weak, and it'll be held only for
/// as long as the StoredVM is alive
@@ -27,34 +27,37 @@ pub(crate) struct StoredVirtualMachine {
impl StoredVirtualMachine {
fn new(id: String, inject_browser_module: bool) -> StoredVirtualMachine {
let mut settings = PySettings::default();
let settings = PySettings {
// After js, browser modules injected, the VM will not be initialized.
initialization_parameter: InitParameter::NoInitialize,
..Default::default()
};
// After js, browser modules injected, the VM will not be initialized.
settings.initialization_parameter = InitParameter::NoInitialize;
let mut scope = None;
let interp = Interpreter::new_with_init(settings, |vm| {
vm.wasm_id = Some(id);
let mut vm: VirtualMachine = VirtualMachine::new(settings);
js_module::setup_js_module(vm);
if inject_browser_module {
PyRc::get_mut(&mut vm.state).unwrap().stdlib_inits.insert(
"_window".to_owned(),
Box::new(|vm| {
py_module!(vm, "_window", {
"window" => js_module::PyJsValue::new(wasm_builtins::window()).into_ref(vm),
})
}),
);
setup_browser_module(vm);
}
vm.wasm_id = Some(id);
let scope = vm.new_scope_with_builtins();
scope = Some(vm.new_scope_with_builtins());
js_module::setup_js_module(&mut vm);
if inject_browser_module {
PyRc::get_mut(&mut vm.state).unwrap().stdlib_inits.insert(
"_window".to_owned(),
Box::new(|vm| {
py_module!(vm, "_window", {
"window" => js_module::PyJsValue::new(wasm_builtins::window()).into_ref(vm),
})
}),
);
setup_browser_module(&mut vm);
}
vm.initialize(InitParameter::InitializeInternal);
InitParameter::InitializeInternal
});
StoredVirtualMachine {
vm,
scope: RefCell::new(scope),
interp,
scope: RefCell::new(scope.unwrap()),
held_objects: RefCell::new(Vec::new()),
}
}
@@ -170,6 +173,13 @@ impl WASMVirtualMachine {
Ok(self.with_unchecked(f))
}
pub(crate) fn with_vm<F, R>(&self, f: F) -> Result<R, JsValue>
where
F: FnOnce(&VirtualMachine, &StoredVirtualMachine) -> R,
{
self.with(|stored| stored.interp.enter(|vm| f(vm, stored)))
}
pub fn valid(&self) -> bool {
STORED_VMS.with(|cell| cell.borrow().contains_key(&self.id))
}
@@ -201,19 +211,15 @@ impl WASMVirtualMachine {
#[wasm_bindgen(js_name = addToScope)]
pub fn add_to_scope(&self, name: String, value: JsValue) -> Result<(), JsValue> {
self.with(
move |StoredVirtualMachine {
ref vm, ref scope, ..
}| {
let value = convert::js_to_py(vm, value);
scope.borrow_mut().store_name(&vm, &name, value);
},
)
self.with_vm(move |vm, StoredVirtualMachine { ref scope, .. }| {
let value = convert::js_to_py(vm, value);
scope.borrow_mut().store_name(&vm, &name, value);
})
}
#[wasm_bindgen(js_name = setStdout)]
pub fn set_stdout(&self, stdout: JsValue) -> Result<(), JsValue> {
self.with(move |StoredVirtualMachine { ref vm, .. }| {
self.with_vm(|vm, _| {
fn error() -> JsValue {
TypeError::new("Unknown stdout option, please pass a function or 'console'").into()
}
@@ -249,7 +255,7 @@ impl WASMVirtualMachine {
source: &str,
imports: Option<Object>,
) -> Result<(), JsValue> {
self.with(|StoredVirtualMachine { ref vm, .. }| {
self.with_vm(|vm, _| {
let code = vm
.compile(source, Mode::Exec, name.clone())
.map_err(convert::syntax_err)?;
@@ -284,7 +290,7 @@ impl WASMVirtualMachine {
#[wasm_bindgen(js_name = injectJSModule)]
pub fn inject_js_module(&self, name: String, module: Object) -> Result<(), JsValue> {
self.with(|StoredVirtualMachine { ref vm, .. }| {
self.with_vm(|vm, _| {
let py_module = vm.new_module(&name, vm.ctx.new_dict());
for entry in convert::object_entries(&module) {
let (key, value) = entry?;
@@ -309,17 +315,13 @@ impl WASMVirtualMachine {
mode: compile::Mode,
source_path: Option<String>,
) -> Result<JsValue, JsValue> {
self.with(
|StoredVirtualMachine {
ref vm, ref scope, ..
}| {
let source_path = source_path.unwrap_or_else(|| "<wasm>".to_owned());
let code = vm.compile(source, mode, source_path);
let code = code.map_err(convert::syntax_err)?;
let result = vm.run_code_obj(code, scope.borrow().clone());
convert::pyresult_to_jsresult(vm, result)
},
)?
self.with_vm(|vm, StoredVirtualMachine { ref scope, .. }| {
let source_path = source_path.unwrap_or_else(|| "<wasm>".to_owned());
let code = vm.compile(source, mode, source_path);
let code = code.map_err(convert::syntax_err)?;
let result = vm.run_code_obj(code, scope.borrow().clone());
convert::pyresult_to_jsresult(vm, result)
})?
}
pub fn exec(&self, source: &str, source_path: Option<String>) -> Result<JsValue, JsValue> {