diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 01e7fe1d6b..9fd818536e 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -2,11 +2,16 @@ ## Code organization +- `bytecode/src`: python bytecode representation in rust structures +- `compiler/src`: python compilation to bytecode - `parser/src`: python lexing, parsing and ast +- `Lib`: Carefully selected / copied files from CPython sourcecode. This is + the python side of the standard library. - `vm/src`: python virtual machine - `builtins.rs`: Builtin functions - `compile.rs`: the python compiler from ast to bytecode - `obj`: python builtin types + - `stdlib`: Standard library parts implemented in rust. - `src`: using the other subcrates to bring rustpython to life. - `docs`: documentation (work in progress) - `py_code_object`: CPython bytecode to rustpython bytecode converter (work in @@ -28,8 +33,7 @@ To test rustpython, there is a collection of python snippets located in the ```shell $ cd tests -$ pipenv install -$ pipenv run pytest -v +$ pytest -v ``` There also are some unit tests, you can run those with cargo: diff --git a/README.md b/README.md index 70b8f24dc1..0091356608 100644 --- a/README.md +++ b/README.md @@ -81,7 +81,8 @@ Documentation HTML files can then be found in the `target/doc` directory. ## Contributing Contributions are more than welcome, and in many cases we are happy to guide -contributors through PRs or on gitter. +contributors through PRs or on gitter. Please refer to the +[development guide](DEVELOPMENT.md) as well for tips on developments. With that in mind, please note this project is maintained by volunteers, some of the best ways to get started are below: diff --git a/src/main.rs b/src/main.rs index d4a0f2c4af..63af32d61f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,11 +8,11 @@ use clap::{App, Arg, ArgMatches}; use rustpython_compiler::{compile, error::CompileError, error::CompileErrorType}; use rustpython_parser::error::ParseErrorType; use rustpython_vm::{ - frame::Scope, import, obj::objstr, print_exception, pyobject::{ItemProtocol, PyResult}, + scope::Scope, util, PySettings, VirtualMachine, }; use std::convert::TryInto; diff --git a/vm/src/builtins.rs b/vm/src/builtins.rs index ec71ac5fae..f422688e8e 100644 --- a/vm/src/builtins.rs +++ b/vm/src/builtins.rs @@ -22,12 +22,12 @@ use crate::obj::objtype::{self, PyClassRef}; use rustpython_compiler::compile; use crate::eval::get_compile_mode; -use crate::frame::Scope; use crate::function::{single_or_tuple_any, Args, KwArgs, OptionalArg, PyFuncArgs}; use crate::pyobject::{ Either, IdProtocol, IntoPyObject, ItemProtocol, PyIterable, PyObjectRef, PyResult, PyValue, TryFromObject, TypeProtocol, }; +use crate::scope::Scope; use crate::vm::VirtualMachine; use crate::obj::objbyteinner::PyByteInner; diff --git a/vm/src/dictdatatype.rs b/vm/src/dictdatatype.rs index 4fdb5ce303..8b20fd34a0 100644 --- a/vm/src/dictdatatype.rs +++ b/vm/src/dictdatatype.rs @@ -110,6 +110,7 @@ impl Dict { } /// Retrieve a key + #[cfg_attr(feature = "flame-it", flame("Dict"))] pub fn get(&self, vm: &VirtualMachine, key: &PyObjectRef) -> PyResult> { if let LookupResult::Existing(index) = self.lookup(vm, key)? { Ok(Some(self.unchecked_get(index))) @@ -197,6 +198,7 @@ impl Dict { } /// Lookup the index for the given key. + #[cfg_attr(feature = "flame-it", flame("Dict"))] fn lookup(&self, vm: &VirtualMachine, key: &PyObjectRef) -> PyResult { let hash_value = collection_hash(vm, key)?; let perturb = hash_value; @@ -271,6 +273,7 @@ enum LookupResult { Existing(EntryIndex), // Existing record, index into entries } +#[cfg_attr(feature = "flame-it", flame())] fn collection_hash(vm: &VirtualMachine, object: &PyObjectRef) -> PyResult { let raw_hash = vm._hash(object)?; let mut hasher = DefaultHasher::new(); diff --git a/vm/src/eval.rs b/vm/src/eval.rs index 40f084df7a..48106cd5c7 100644 --- a/vm/src/eval.rs +++ b/vm/src/eval.rs @@ -1,5 +1,5 @@ -use crate::frame::Scope; use crate::pyobject::PyResult; +use crate::scope::Scope; use crate::vm::VirtualMachine; use rustpython_compiler::compile; diff --git a/vm/src/frame.rs b/vm/src/frame.rs index 5e16cdd05f..b70cb658c4 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -1,6 +1,5 @@ use std::cell::RefCell; use std::fmt; -use std::rc::Rc; use crate::builtins; use crate::bytecode; @@ -17,183 +16,13 @@ use crate::obj::objtuple::PyTuple; use crate::obj::objtype; use crate::obj::objtype::PyClassRef; use crate::pyobject::{ - IdProtocol, ItemProtocol, PyContext, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject, - TypeProtocol, + IdProtocol, ItemProtocol, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject, TypeProtocol, }; +use crate::scope::{NameProtocol, Scope}; use crate::vm::VirtualMachine; use indexmap::IndexMap; use itertools::Itertools; -/* - * So a scope is a linked list of scopes. - * When a name is looked up, it is check in its scope. - */ -#[derive(Debug)] -struct RcListNode { - elem: T, - next: Option>>, -} - -#[derive(Debug, Clone)] -struct RcList { - head: Option>>, -} - -struct Iter<'a, T: 'a> { - next: Option<&'a RcListNode>, -} - -impl RcList { - pub fn new() -> Self { - RcList { head: None } - } - - pub fn insert(self, elem: T) -> Self { - RcList { - head: Some(Rc::new(RcListNode { - elem, - next: self.head, - })), - } - } - - pub fn iter(&self) -> Iter { - Iter { - next: self.head.as_ref().map(|node| &**node), - } - } -} - -impl<'a, T> Iterator for Iter<'a, T> { - type Item = &'a T; - - fn next(&mut self) -> Option { - self.next.map(|node| { - self.next = node.next.as_ref().map(|node| &**node); - &node.elem - }) - } -} - -#[derive(Clone)] -pub struct Scope { - locals: RcList, - pub globals: PyDictRef, -} - -impl fmt::Debug for Scope { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - // TODO: have a more informative Debug impl that DOESN'T recurse and cause a stack overflow - f.write_str("Scope") - } -} - -impl Scope { - pub fn new(locals: Option, globals: PyDictRef) -> Scope { - let locals = match locals { - Some(dict) => RcList::new().insert(dict), - None => RcList::new(), - }; - Scope { locals, globals } - } - - pub fn with_builtins( - locals: Option, - globals: PyDictRef, - vm: &VirtualMachine, - ) -> Scope { - if !globals.contains_key("__builtins__", vm) { - globals - .clone() - .set_item("__builtins__", vm.builtins.clone(), vm) - .unwrap(); - } - Scope::new(locals, globals) - } - - pub fn get_locals(&self) -> PyDictRef { - match self.locals.iter().next() { - Some(dict) => dict.clone(), - None => self.globals.clone(), - } - } - - pub fn get_only_locals(&self) -> Option { - self.locals.iter().next().cloned() - } - - pub fn new_child_scope_with_locals(&self, locals: PyDictRef) -> Scope { - Scope { - locals: self.locals.clone().insert(locals), - globals: self.globals.clone(), - } - } - - pub fn new_child_scope(&self, ctx: &PyContext) -> Scope { - self.new_child_scope_with_locals(ctx.new_dict()) - } -} - -pub trait NameProtocol { - fn load_name(&self, vm: &VirtualMachine, name: &str) -> Option; - fn store_name(&self, vm: &VirtualMachine, name: &str, value: PyObjectRef); - fn delete_name(&self, vm: &VirtualMachine, name: &str) -> PyResult; - fn load_cell(&self, vm: &VirtualMachine, name: &str) -> Option; - fn store_cell(&self, vm: &VirtualMachine, name: &str, value: PyObjectRef); - fn load_global(&self, vm: &VirtualMachine, name: &str) -> Option; - fn store_global(&self, vm: &VirtualMachine, name: &str, value: PyObjectRef); -} - -impl NameProtocol for Scope { - fn load_name(&self, vm: &VirtualMachine, name: &str) -> Option { - for dict in self.locals.iter() { - if let Some(value) = dict.get_item_option(name, vm).unwrap() { - return Some(value); - } - } - - if let Some(value) = self.globals.get_item_option(name, vm).unwrap() { - return Some(value); - } - - vm.get_attribute(vm.builtins.clone(), name).ok() - } - - fn load_cell(&self, vm: &VirtualMachine, name: &str) -> Option { - for dict in self.locals.iter().skip(1) { - if let Some(value) = dict.get_item_option(name, vm).unwrap() { - return Some(value); - } - } - None - } - - fn store_cell(&self, vm: &VirtualMachine, name: &str, value: PyObjectRef) { - self.locals - .iter() - .nth(1) - .expect("no outer scope for non-local") - .set_item(name, value, vm) - .unwrap(); - } - - fn store_name(&self, vm: &VirtualMachine, key: &str, value: PyObjectRef) { - self.get_locals().set_item(key, value, vm).unwrap(); - } - - fn delete_name(&self, vm: &VirtualMachine, key: &str) -> PyResult { - self.get_locals().del_item(key, vm) - } - - fn load_global(&self, vm: &VirtualMachine, name: &str) -> Option { - self.globals.get_item_option(name, vm).unwrap() - } - - fn store_global(&self, vm: &VirtualMachine, name: &str, value: PyObjectRef) { - self.globals.set_item(name, value, vm).unwrap(); - } -} - #[derive(Clone, Debug)] struct Block { /// The type of block. @@ -242,7 +71,7 @@ pub enum ExecutionResult { } /// A valid execution result, or an exception -pub type FrameResult = Result, PyObjectRef>; +pub type FrameResult = PyResult>; impl Frame { pub fn new(code: PyCodeRef, scope: Scope) -> Frame { @@ -269,7 +98,7 @@ impl Frame { } // #[cfg_attr(feature = "flame-it", flame("Frame"))] - pub fn run(&self, vm: &VirtualMachine) -> Result { + pub fn run(&self, vm: &VirtualMachine) -> PyResult { flame_guard!(format!("Frame::run({})", self.code.obj_name)); let filename = &self.code.source_path.to_string(); @@ -318,11 +147,7 @@ impl Frame { } } - pub fn throw( - &self, - vm: &VirtualMachine, - exception: PyObjectRef, - ) -> Result { + pub fn throw(&self, vm: &VirtualMachine, exception: PyObjectRef) -> PyResult { match self.unwind_exception(vm, exception) { None => self.run(vm), Some(exception) => Err(exception), @@ -620,64 +445,7 @@ impl Frame { } } } - bytecode::Instruction::MakeFunction { flags } => { - let qualified_name = self - .pop_value() - .downcast::() - .expect("qualified name to be a string"); - let code_obj = self - .pop_value() - .downcast() - .expect("Second to top value on the stack must be a code object"); - - let annotations = if flags.contains(bytecode::FunctionOpArg::HAS_ANNOTATIONS) { - self.pop_value() - } else { - vm.ctx.new_dict().into_object() - }; - - let kw_only_defaults = - if flags.contains(bytecode::FunctionOpArg::HAS_KW_ONLY_DEFAULTS) { - Some( - self.pop_value().downcast::().expect( - "Stack value for keyword only defaults expected to be a dict", - ), - ) - } else { - None - }; - - let defaults = if flags.contains(bytecode::FunctionOpArg::HAS_DEFAULTS) { - Some( - self.pop_value() - .downcast::() - .expect("Stack value for defaults expected to be a tuple"), - ) - } else { - None - }; - - // pop argc arguments - // argument: name, args, globals - let scope = self.scope.clone(); - let func_obj = vm - .ctx - .new_function(code_obj, scope, defaults, kw_only_defaults); - - let name = qualified_name.value.split('.').next_back().unwrap(); - vm.set_attr(&func_obj, "__name__", vm.new_str(name.to_string()))?; - vm.set_attr(&func_obj, "__qualname__", qualified_name)?; - let module = self - .scope - .globals - .get_item_option("__name__", vm)? - .unwrap_or_else(|| vm.get_none()); - vm.set_attr(&func_obj, "__module__", module)?; - vm.set_attr(&func_obj, "__annotations__", annotations)?; - - self.push_value(func_obj); - Ok(None) - } + bytecode::Instruction::MakeFunction { flags } => self.execute_make_function(vm, *flags), bytecode::Instruction::CallFunction { typ } => { let args = match typ { bytecode::CallType::Positional(count) => { @@ -903,7 +671,7 @@ impl Frame { vm: &VirtualMachine, size: usize, unpack: bool, - ) -> Result, PyObjectRef> { + ) -> PyResult> { let elements = self.pop_multiple(size); if unpack { let mut result: Vec = vec![]; @@ -1173,6 +941,68 @@ impl Frame { *self.lasti.borrow_mut() = target_pc; } + fn execute_make_function( + &self, + vm: &VirtualMachine, + flags: bytecode::FunctionOpArg, + ) -> FrameResult { + let qualified_name = self + .pop_value() + .downcast::() + .expect("qualified name to be a string"); + let code_obj = self + .pop_value() + .downcast() + .expect("Second to top value on the stack must be a code object"); + + let annotations = if flags.contains(bytecode::FunctionOpArg::HAS_ANNOTATIONS) { + self.pop_value() + } else { + vm.ctx.new_dict().into_object() + }; + + let kw_only_defaults = if flags.contains(bytecode::FunctionOpArg::HAS_KW_ONLY_DEFAULTS) { + Some( + self.pop_value() + .downcast::() + .expect("Stack value for keyword only defaults expected to be a dict"), + ) + } else { + None + }; + + let defaults = if flags.contains(bytecode::FunctionOpArg::HAS_DEFAULTS) { + Some( + self.pop_value() + .downcast::() + .expect("Stack value for defaults expected to be a tuple"), + ) + } else { + None + }; + + // pop argc arguments + // argument: name, args, globals + let scope = self.scope.clone(); + let func_obj = vm + .ctx + .new_function(code_obj, scope, defaults, kw_only_defaults); + + let name = qualified_name.value.split('.').next_back().unwrap(); + vm.set_attr(&func_obj, "__name__", vm.new_str(name.to_string()))?; + vm.set_attr(&func_obj, "__qualname__", qualified_name)?; + let module = self + .scope + .globals + .get_item_option("__name__", vm)? + .unwrap_or_else(|| vm.get_none()); + vm.set_attr(&func_obj, "__module__", module)?; + vm.set_attr(&func_obj, "__annotations__", annotations)?; + + self.push_value(func_obj); + Ok(None) + } + #[cfg_attr(feature = "flame-it", flame("Frame"))] fn execute_binop( &self, @@ -1182,37 +1012,42 @@ impl Frame { ) -> FrameResult { let b_ref = self.pop_value(); let a_ref = self.pop_value(); - let value = match *op { - bytecode::BinaryOperator::Subtract if inplace => vm._isub(a_ref, b_ref), - bytecode::BinaryOperator::Subtract => vm._sub(a_ref, b_ref), - bytecode::BinaryOperator::Add if inplace => vm._iadd(a_ref, b_ref), - bytecode::BinaryOperator::Add => vm._add(a_ref, b_ref), - bytecode::BinaryOperator::Multiply if inplace => vm._imul(a_ref, b_ref), - bytecode::BinaryOperator::Multiply => vm._mul(a_ref, b_ref), - bytecode::BinaryOperator::MatrixMultiply if inplace => vm._imatmul(a_ref, b_ref), - bytecode::BinaryOperator::MatrixMultiply => vm._matmul(a_ref, b_ref), - bytecode::BinaryOperator::Power if inplace => vm._ipow(a_ref, b_ref), - bytecode::BinaryOperator::Power => vm._pow(a_ref, b_ref), - bytecode::BinaryOperator::Divide if inplace => vm._itruediv(a_ref, b_ref), - bytecode::BinaryOperator::Divide => vm._truediv(a_ref, b_ref), - bytecode::BinaryOperator::FloorDivide if inplace => vm._ifloordiv(a_ref, b_ref), - bytecode::BinaryOperator::FloorDivide => vm._floordiv(a_ref, b_ref), - // TODO: Subscript should probably have its own op - bytecode::BinaryOperator::Subscript if inplace => unreachable!(), - bytecode::BinaryOperator::Subscript => a_ref.get_item(b_ref, vm), - bytecode::BinaryOperator::Modulo if inplace => vm._imod(a_ref, b_ref), - bytecode::BinaryOperator::Modulo => vm._mod(a_ref, b_ref), - bytecode::BinaryOperator::Lshift if inplace => vm._ilshift(a_ref, b_ref), - bytecode::BinaryOperator::Lshift => vm._lshift(a_ref, b_ref), - bytecode::BinaryOperator::Rshift if inplace => vm._irshift(a_ref, b_ref), - bytecode::BinaryOperator::Rshift => vm._rshift(a_ref, b_ref), - bytecode::BinaryOperator::Xor if inplace => vm._ixor(a_ref, b_ref), - bytecode::BinaryOperator::Xor => vm._xor(a_ref, b_ref), - bytecode::BinaryOperator::Or if inplace => vm._ior(a_ref, b_ref), - bytecode::BinaryOperator::Or => vm._or(a_ref, b_ref), - bytecode::BinaryOperator::And if inplace => vm._iand(a_ref, b_ref), - bytecode::BinaryOperator::And => vm._and(a_ref, b_ref), - }?; + let value = if inplace { + match *op { + bytecode::BinaryOperator::Subtract => vm._isub(a_ref, b_ref), + bytecode::BinaryOperator::Add => vm._iadd(a_ref, b_ref), + bytecode::BinaryOperator::Multiply => vm._imul(a_ref, b_ref), + bytecode::BinaryOperator::MatrixMultiply => vm._imatmul(a_ref, b_ref), + bytecode::BinaryOperator::Power => vm._ipow(a_ref, b_ref), + bytecode::BinaryOperator::Divide => vm._itruediv(a_ref, b_ref), + bytecode::BinaryOperator::FloorDivide => vm._ifloordiv(a_ref, b_ref), + bytecode::BinaryOperator::Subscript => unreachable!(), + bytecode::BinaryOperator::Modulo => vm._imod(a_ref, b_ref), + bytecode::BinaryOperator::Lshift => vm._ilshift(a_ref, b_ref), + bytecode::BinaryOperator::Rshift => vm._irshift(a_ref, b_ref), + bytecode::BinaryOperator::Xor => vm._ixor(a_ref, b_ref), + bytecode::BinaryOperator::Or => vm._ior(a_ref, b_ref), + bytecode::BinaryOperator::And => vm._iand(a_ref, b_ref), + }? + } else { + match *op { + bytecode::BinaryOperator::Subtract => vm._sub(a_ref, b_ref), + bytecode::BinaryOperator::Add => vm._add(a_ref, b_ref), + bytecode::BinaryOperator::Multiply => vm._mul(a_ref, b_ref), + bytecode::BinaryOperator::MatrixMultiply => vm._matmul(a_ref, b_ref), + bytecode::BinaryOperator::Power => vm._pow(a_ref, b_ref), + bytecode::BinaryOperator::Divide => vm._truediv(a_ref, b_ref), + bytecode::BinaryOperator::FloorDivide => vm._floordiv(a_ref, b_ref), + // TODO: Subscript should probably have its own op + bytecode::BinaryOperator::Subscript => a_ref.get_item(b_ref, vm), + bytecode::BinaryOperator::Modulo => vm._mod(a_ref, b_ref), + bytecode::BinaryOperator::Lshift => vm._lshift(a_ref, b_ref), + bytecode::BinaryOperator::Rshift => vm._rshift(a_ref, b_ref), + bytecode::BinaryOperator::Xor => vm._xor(a_ref, b_ref), + bytecode::BinaryOperator::Or => vm._or(a_ref, b_ref), + bytecode::BinaryOperator::And => vm._and(a_ref, b_ref), + }? + }; self.push_value(value); Ok(None) @@ -1348,7 +1183,7 @@ impl Frame { } fn nth_value(&self, depth: usize) -> PyObjectRef { - let stack = self.stack.borrow_mut(); + let stack = self.stack.borrow(); stack[stack.len() - depth - 1].clone() } diff --git a/vm/src/import.rs b/vm/src/import.rs index 88d66ce82b..e61c876403 100644 --- a/vm/src/import.rs +++ b/vm/src/import.rs @@ -3,9 +3,9 @@ */ use crate::bytecode::CodeObject; -use crate::frame::Scope; use crate::obj::{objcode, objsequence, objstr, objtype}; use crate::pyobject::{ItemProtocol, PyObjectRef, PyResult, PyValue}; +use crate::scope::Scope; use crate::vm::VirtualMachine; #[cfg(feature = "rustpython-compiler")] use rustpython_compiler::compile; diff --git a/vm/src/lib.rs b/vm/src/lib.rs index 60dfa725ee..c4f0428580 100644 --- a/vm/src/lib.rs +++ b/vm/src/lib.rs @@ -56,7 +56,7 @@ mod dictdatatype; pub mod eval; mod exceptions; pub mod format; -pub mod frame; +mod frame; mod frozen; pub mod function; pub mod import; @@ -64,6 +64,7 @@ pub mod obj; pub mod py_serde; mod pyhash; pub mod pyobject; +pub mod scope; pub mod stdlib; mod sysmodule; mod traceback; diff --git a/vm/src/obj/objdict.rs b/vm/src/obj/objdict.rs index e80e3ffd18..267678b206 100644 --- a/vm/src/obj/objdict.rs +++ b/vm/src/obj/objdict.rs @@ -202,6 +202,7 @@ impl PyDictRef { self.entries.borrow_mut().insert(vm, &key, value) } + #[cfg_attr(feature = "flame-it", flame("PyDictRef"))] fn inner_getitem(self, key: PyObjectRef, vm: &VirtualMachine) -> PyResult { if let Some(value) = self.entries.borrow().get(vm, &key)? { return Ok(value); diff --git a/vm/src/obj/objfunction.rs b/vm/src/obj/objfunction.rs index 05b66c3460..1c7ff642af 100644 --- a/vm/src/obj/objfunction.rs +++ b/vm/src/obj/objfunction.rs @@ -1,10 +1,10 @@ -use crate::frame::Scope; use crate::function::{Args, KwArgs}; use crate::obj::objcode::PyCodeRef; use crate::obj::objdict::PyDictRef; use crate::obj::objtuple::PyTupleRef; use crate::obj::objtype::PyClassRef; use crate::pyobject::{IdProtocol, PyContext, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol}; +use crate::scope::Scope; use crate::vm::VirtualMachine; pub type PyFunctionRef = PyRef; diff --git a/vm/src/obj/objsuper.rs b/vm/src/obj/objsuper.rs index 194212a492..016c2419c0 100644 --- a/vm/src/obj/objsuper.rs +++ b/vm/src/obj/objsuper.rs @@ -6,7 +6,6 @@ https://github.com/python/cpython/blob/50b48572d9a90c5bb36e2bef6179548ea927a35a/ */ -use crate::frame::NameProtocol; use crate::function::{OptionalArg, PyFuncArgs}; use crate::obj::objfunction::PyMethod; use crate::obj::objstr; @@ -14,6 +13,7 @@ use crate::obj::objtype::{PyClass, PyClassRef}; use crate::pyobject::{ ItemProtocol, PyContext, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject, TypeProtocol, }; +use crate::scope::NameProtocol; use crate::vm::VirtualMachine; use super::objtype; diff --git a/vm/src/obj/objtype.rs b/vm/src/obj/objtype.rs index a0692ff642..2cc65d6709 100644 --- a/vm/src/obj/objtype.rs +++ b/vm/src/obj/objtype.rs @@ -228,6 +228,7 @@ fn _mro(cls: &PyClassRef) -> Vec { /// Determines if `obj` actually an instance of `cls`, this doesn't call __instancecheck__, so only /// use this if `cls` is known to have not overridden the base __instancecheck__ magic method. +#[cfg_attr(feature = "flame-it", flame("objtype"))] pub fn isinstance(obj: &PyObjectRef, cls: &PyClassRef) -> bool { issubclass(&obj.class(), &cls) } diff --git a/vm/src/pyobject.rs b/vm/src/pyobject.rs index 74f5b2e664..bc9891511e 100644 --- a/vm/src/pyobject.rs +++ b/vm/src/pyobject.rs @@ -15,7 +15,6 @@ use num_traits::{One, Zero}; use crate::bytecode; use crate::exceptions; -use crate::frame::Scope; use crate::function::{IntoPyNativeFunc, PyFuncArgs}; use crate::obj::objbool; use crate::obj::objbuiltinfunc::PyBuiltinFunction; @@ -56,6 +55,7 @@ use crate::obj::objtype::{self, PyClass, PyClassRef}; use crate::obj::objweakproxy; use crate::obj::objweakref; use crate::obj::objzip; +use crate::scope::Scope; use crate::vm::VirtualMachine; use indexmap::IndexMap; @@ -1021,6 +1021,8 @@ pub trait ItemProtocol { vm: &VirtualMachine, ) -> PyResult; fn del_item(&self, key: T, vm: &VirtualMachine) -> PyResult; + + #[cfg_attr(feature = "flame-it", flame("ItemProtocol"))] fn get_item_option( &self, key: T, diff --git a/vm/src/scope.rs b/vm/src/scope.rs new file mode 100644 index 0000000000..7bc34dd0c6 --- /dev/null +++ b/vm/src/scope.rs @@ -0,0 +1,181 @@ +use std::fmt; +use std::rc::Rc; + +use crate::obj::objdict::PyDictRef; +use crate::pyobject::{ItemProtocol, PyContext, PyObjectRef, PyResult}; +use crate::vm::VirtualMachine; + +/* + * So a scope is a linked list of scopes. + * When a name is looked up, it is check in its scope. + */ +#[derive(Debug)] +struct RcListNode { + elem: T, + next: Option>>, +} + +#[derive(Debug, Clone)] +struct RcList { + head: Option>>, +} + +struct Iter<'a, T: 'a> { + next: Option<&'a RcListNode>, +} + +impl RcList { + pub fn new() -> Self { + RcList { head: None } + } + + pub fn insert(self, elem: T) -> Self { + RcList { + head: Some(Rc::new(RcListNode { + elem, + next: self.head, + })), + } + } + + #[cfg_attr(feature = "flame-it", flame("RcList"))] + pub fn iter(&self) -> Iter { + Iter { + next: self.head.as_ref().map(|node| &**node), + } + } +} + +impl<'a, T> Iterator for Iter<'a, T> { + type Item = &'a T; + + #[cfg_attr(feature = "flame-it", flame("Iter"))] + fn next(&mut self) -> Option { + self.next.map(|node| { + self.next = node.next.as_ref().map(|node| &**node); + &node.elem + }) + } +} + +#[derive(Clone)] +pub struct Scope { + locals: RcList, + pub globals: PyDictRef, +} + +impl fmt::Debug for Scope { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + // TODO: have a more informative Debug impl that DOESN'T recurse and cause a stack overflow + f.write_str("Scope") + } +} + +impl Scope { + pub fn new(locals: Option, globals: PyDictRef) -> Scope { + let locals = match locals { + Some(dict) => RcList::new().insert(dict), + None => RcList::new(), + }; + Scope { locals, globals } + } + + pub fn with_builtins( + locals: Option, + globals: PyDictRef, + vm: &VirtualMachine, + ) -> Scope { + if !globals.contains_key("__builtins__", vm) { + globals + .clone() + .set_item("__builtins__", vm.builtins.clone(), vm) + .unwrap(); + } + Scope::new(locals, globals) + } + + pub fn get_locals(&self) -> PyDictRef { + match self.locals.iter().next() { + Some(dict) => dict.clone(), + None => self.globals.clone(), + } + } + + pub fn get_only_locals(&self) -> Option { + self.locals.iter().next().cloned() + } + + pub fn new_child_scope_with_locals(&self, locals: PyDictRef) -> Scope { + Scope { + locals: self.locals.clone().insert(locals), + globals: self.globals.clone(), + } + } + + pub fn new_child_scope(&self, ctx: &PyContext) -> Scope { + self.new_child_scope_with_locals(ctx.new_dict()) + } +} + +pub trait NameProtocol { + fn load_name(&self, vm: &VirtualMachine, name: &str) -> Option; + fn store_name(&self, vm: &VirtualMachine, name: &str, value: PyObjectRef); + fn delete_name(&self, vm: &VirtualMachine, name: &str) -> PyResult; + fn load_cell(&self, vm: &VirtualMachine, name: &str) -> Option; + fn store_cell(&self, vm: &VirtualMachine, name: &str, value: PyObjectRef); + fn load_global(&self, vm: &VirtualMachine, name: &str) -> Option; + fn store_global(&self, vm: &VirtualMachine, name: &str, value: PyObjectRef); +} + +impl NameProtocol for Scope { + #[cfg_attr(feature = "flame-it", flame("Scope"))] + fn load_name(&self, vm: &VirtualMachine, name: &str) -> Option { + for dict in self.locals.iter() { + if let Some(value) = dict.get_item_option(name, vm).unwrap() { + return Some(value); + } + } + + if let Some(value) = self.globals.get_item_option(name, vm).unwrap() { + return Some(value); + } + + vm.get_attribute(vm.builtins.clone(), name).ok() + } + + #[cfg_attr(feature = "flame-it", flame("Scope"))] + fn load_cell(&self, vm: &VirtualMachine, name: &str) -> Option { + for dict in self.locals.iter().skip(1) { + if let Some(value) = dict.get_item_option(name, vm).unwrap() { + return Some(value); + } + } + None + } + + fn store_cell(&self, vm: &VirtualMachine, name: &str, value: PyObjectRef) { + self.locals + .iter() + .nth(1) + .expect("no outer scope for non-local") + .set_item(name, value, vm) + .unwrap(); + } + + fn store_name(&self, vm: &VirtualMachine, key: &str, value: PyObjectRef) { + self.get_locals().set_item(key, value, vm).unwrap(); + } + + fn delete_name(&self, vm: &VirtualMachine, key: &str) -> PyResult { + self.get_locals().del_item(key, vm) + } + + #[cfg_attr(feature = "flame-it", flame("Scope"))] + fn load_global(&self, vm: &VirtualMachine, name: &str) -> Option { + self.globals.get_item_option(name, vm).unwrap() + } + + fn store_global(&self, vm: &VirtualMachine, name: &str, value: PyObjectRef) { + self.globals.set_item(name, value, vm).unwrap(); + } +} diff --git a/vm/src/vm.rs b/vm/src/vm.rs index 4c0ea1678e..c36b1287eb 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -13,7 +13,7 @@ use std::sync::{Mutex, MutexGuard}; use crate::builtins; use crate::bytecode; -use crate::frame::{ExecutionResult, Frame, FrameRef, Scope}; +use crate::frame::{ExecutionResult, Frame, FrameRef}; use crate::frozen; use crate::function::PyFuncArgs; use crate::import; @@ -35,6 +35,7 @@ use crate::pyobject::{ IdProtocol, ItemProtocol, PyContext, PyObjectRef, PyResult, PyValue, TryFromObject, TryIntoRef, TypeProtocol, }; +use crate::scope::Scope; use crate::stdlib; use crate::sysmodule; use num_bigint::BigInt; @@ -243,6 +244,7 @@ impl VirtualMachine { self.ctx.new_bool(b) } + #[cfg_attr(feature = "flame-it", flame("VirtualMachine"))] fn new_exception_obj(&self, exc_type: PyClassRef, args: Vec) -> PyResult { // TODO: add repr of args into logging? vm_trace!("New exception created: {}", exc_type.name); @@ -476,6 +478,7 @@ impl VirtualMachine { } } + #[cfg_attr(feature = "flame-it", flame("VirtualMachine"))] fn _invoke(&self, func_ref: PyObjectRef, args: PyFuncArgs) -> PyResult { vm_trace!("Invoke: {:?} {:?}", func_ref, args); @@ -744,6 +747,7 @@ impl VirtualMachine { } // get_attribute should be used for full attribute access (usually from user code). + #[cfg_attr(feature = "flame-it", flame("VirtualMachine"))] pub fn get_attribute(&self, obj: PyObjectRef, attr_name: T) -> PyResult where T: TryIntoRef, diff --git a/wasm/lib/src/vm_class.rs b/wasm/lib/src/vm_class.rs index 75b110730a..c75b68d6ec 100644 --- a/wasm/lib/src/vm_class.rs +++ b/wasm/lib/src/vm_class.rs @@ -6,10 +6,10 @@ use js_sys::{Object, Reflect, SyntaxError, TypeError}; use wasm_bindgen::prelude::*; use rustpython_compiler::compile; -use rustpython_vm::frame::{NameProtocol, Scope}; use rustpython_vm::function::PyFuncArgs; use rustpython_vm::import; use rustpython_vm::pyobject::{PyObject, PyObjectPayload, PyObjectRef, PyResult, PyValue}; +use rustpython_vm::scope::{NameProtocol, Scope}; use rustpython_vm::VirtualMachine; use crate::browser_module::setup_browser_module;