diff --git a/benchmarks/bench.rs b/benchmarks/bench.rs index 9ffeffcba..a2892fb8e 100644 --- a/benchmarks/bench.rs +++ b/benchmarks/bench.rs @@ -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) } diff --git a/examples/freeze/main.rs b/examples/freeze/main.rs index 39a335619..b04d30e67 100644 --- a/examples/freeze/main.rs +++ b/examples/freeze/main.rs @@ -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: diff --git a/examples/hello_embed.rs b/examples/hello_embed.rs index 72b321a4f..bdafaf689 100644 --- a/examples/hello_embed.rs +++ b/examples/hello_embed.rs @@ -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, + "".to_owned(), + ) + .map_err(|err| vm.new_syntax_error(&err))?; - let code_obj = vm - .compile( - r#"print("Hello World!")"#, - compiler::compile::Mode::Exec, - "".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(()) + }) } diff --git a/examples/mini_repl.rs b/examples/mini_repl.rs index 3a19e75f0..8565496ab 100644 --- a/examples/mini_repl.rs +++ b/examples/mini_repl.rs @@ -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); } } } diff --git a/src/main.rs b/src/main.rs index 53503743a..a268d5835 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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()); + }) } diff --git a/vm/src/dictdatatype.rs b/vm/src/dictdatatype.rs index 5c662b778..4a33646f3 100644 --- a/vm/src/dictdatatype.rs +++ b/vm/src/dictdatatype.rs @@ -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); + }) } } diff --git a/vm/src/eval.rs b/vm/src/eval.rs index 68255302b..d82f2f46d 100644 --- a/vm/src/eval.rs +++ b/vm/src/eval.rs @@ -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, "").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, "").expect("this should pass"); + assert!(vm.is_none(&result)); + }) } } diff --git a/vm/src/lib.rs b/vm/src/lib.rs index cc6cd4e63..ad4e71edb 100644 --- a/vm/src/lib.rs +++ b/vm/src/lib.rs @@ -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; diff --git a/vm/src/macros.rs b/vm/src/macros.rs index 90cc01144..582b2f749 100644 --- a/vm/src/macros.rs +++ b/vm/src/macros.rs @@ -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 { diff --git a/vm/src/obj/objstr.rs b/vm/src/obj/objstr.rs index 4ee71b42c..9faa2f188 100644 --- a/vm/src/obj/objstr.rs +++ b/vm/src/obj/objstr.rs @@ -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() + ); + }) } } diff --git a/vm/src/pyobjectrc.rs b/vm/src/pyobjectrc.rs index 64a1f380c..0e58b50cf 100644 --- a/vm/src/pyobjectrc.rs +++ b/vm/src/pyobjectrc.rs @@ -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: ",); diff --git a/vm/src/stdlib/thread.rs b/vm/src/stdlib/thread.rs index fc1dcef16..33ad84dca 100644 --- a/vm/src/stdlib/thread.rs +++ b/vm/src/stdlib/thread.rs @@ -221,44 +221,46 @@ fn thread_start_new_thread( kwargs: OptionalArg, vm: &VirtualMachine, ) -> PyResult { + 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("", |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("", |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> = RefCell::default()); diff --git a/vm/src/vm.rs b/vm/src/vm.rs index 747e0549d..54ae4a1af 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -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::with_capacity(1).into(); } - pub(crate) fn with_vm(f: F) -> R + pub(crate) fn with_vm(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| { + // 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(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(&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 ")) + }) } } diff --git a/wasm/lib/src/browser_module.rs b/wasm/lib/src/browser_module.rs index db9947f46..b260e1b0f 100644 --- a/wasm/lib/src/browser_module.rs +++ b/wasm/lib/src/browser_module.rs @@ -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)); 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)) diff --git a/wasm/lib/src/convert.rs b/wasm/lib/src/convert.rs index a95e366c0..98862738e 100644 --- a/wasm/lib/src/convert.rs +++ b/wasm/lib/src/convert.rs @@ -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, Option) -> Result>); diff --git a/wasm/lib/src/vm_class.rs b/wasm/lib/src/vm_class.rs index 29ba5cc16..0fc3fcda4 100644 --- a/wasm/lib/src/vm_class.rs +++ b/wasm/lib/src/vm_class.rs @@ -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, /// 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(&self, f: F) -> Result + 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, ) -> 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, ) -> Result { - self.with( - |StoredVirtualMachine { - ref vm, ref scope, .. - }| { - let source_path = source_path.unwrap_or_else(|| "".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(|| "".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) -> Result {