diff --git a/bytecode/src/bytecode.rs b/bytecode/src/bytecode.rs index 3802429b8..990a9e1bf 100644 --- a/bytecode/src/bytecode.rs +++ b/bytecode/src/bytecode.rs @@ -59,10 +59,19 @@ bitflags! { pub type Label = usize; #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +/// An indication where the name must be accessed. pub enum NameScope { + /// The name will be in the local scope. Local, + + /// The name will be located in scope surrounding the current scope. NonLocal, + + /// The name will be in global scope. Global, + + /// The name will be located in any scope between the current scope and the top scope. + Free, } /// Transforms a value prior to formatting it. diff --git a/compiler/src/compile.rs b/compiler/src/compile.rs index 23b46f1b9..2a95190d6 100644 --- a/compiler/src/compile.rs +++ b/compiler/src/compile.rs @@ -282,8 +282,8 @@ impl Compiler { match symbol.scope { SymbolScope::Global => bytecode::NameScope::Global, SymbolScope::Nonlocal => bytecode::NameScope::NonLocal, - SymbolScope::Unknown => bytecode::NameScope::Local, - SymbolScope::Local => bytecode::NameScope::Local, + SymbolScope::Unknown => bytecode::NameScope::Free, + SymbolScope::Local => bytecode::NameScope::Free, } } @@ -500,7 +500,7 @@ impl Compiler { self.compile_jump_if(test, true, end_label)?; self.emit(Instruction::LoadName { name: String::from("AssertionError"), - scope: bytecode::NameScope::Local, + scope: bytecode::NameScope::Global, }); match msg { Some(e) => { @@ -736,7 +736,7 @@ impl Compiler { // Check exception type: self.emit(Instruction::LoadName { name: String::from("isinstance"), - scope: bytecode::NameScope::Local, + scope: bytecode::NameScope::Global, }); self.emit(Instruction::Rotate { amount: 2 }); self.compile_expression(exc_type)?; @@ -931,11 +931,11 @@ impl Compiler { self.emit(Instruction::LoadName { name: "__name__".to_string(), - scope: bytecode::NameScope::Local, + scope: bytecode::NameScope::Free, }); self.emit(Instruction::StoreName { name: "__module__".to_string(), - scope: bytecode::NameScope::Local, + scope: bytecode::NameScope::Free, }); self.compile_statements(new_body)?; self.emit(Instruction::LoadConst { diff --git a/compiler/src/symboltable.rs b/compiler/src/symboltable.rs index 46174c3ba..d99ae8218 100644 --- a/compiler/src/symboltable.rs +++ b/compiler/src/symboltable.rs @@ -213,21 +213,21 @@ impl SymbolTableAnalyzer { if symbol.is_assigned || symbol.is_parameter { symbol.scope = SymbolScope::Local; } else { - // TODO: comment this out and make it work properly: - /* - */ - let found_in_outer_scope = self - .tables - .iter() - .skip(1) - .any(|t| t.symbols.contains_key(&symbol.name)); + // Interesting stuff about the __class__ variable: + // https://docs.python.org/3/reference/datamodel.html?highlight=__class__#creating-the-class-object + let found_in_outer_scope = (symbol.name == "__class__") + || self + .tables + .iter() + .skip(1) + .any(|t| t.symbols.contains_key(&symbol.name)); if found_in_outer_scope { // Symbol is in some outer scope. symbol.is_free = true; } else { // Well, it must be a global then :) - // symbol.scope = SymbolScope::Global; + symbol.scope = SymbolScope::Global; } } } diff --git a/crawl_sourcecode.py b/crawl_sourcecode.py index 33708edbf..7e1290220 100644 --- a/crawl_sourcecode.py +++ b/crawl_sourcecode.py @@ -73,4 +73,4 @@ print() print('======== dis.dis ========') print() co = compile(source, filename, 'exec') -print(dis.dis(co)) +dis.dis(co) diff --git a/vm/src/frame.rs b/vm/src/frame.rs index f205f4089..1b5b64786 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -907,6 +907,9 @@ impl Frame { bytecode::NameScope::Local => { self.scope.store_name(vm, name, obj); } + bytecode::NameScope::Free => { + self.scope.store_name(vm, name, obj); + } } Ok(None) } @@ -928,7 +931,8 @@ impl Frame { let optional_value = match name_scope { bytecode::NameScope::Global => self.scope.load_global(vm, name), bytecode::NameScope::NonLocal => self.scope.load_cell(vm, name), - bytecode::NameScope::Local => self.scope.load_name(&vm, name), + bytecode::NameScope::Local => self.scope.load_local(&vm, name), + bytecode::NameScope::Free => self.scope.load_name(&vm, name), }; let value = match optional_value { diff --git a/vm/src/obj/objtype.rs b/vm/src/obj/objtype.rs index 68b2feba6..b88fccb00 100644 --- a/vm/src/obj/objtype.rs +++ b/vm/src/obj/objtype.rs @@ -312,6 +312,8 @@ fn type_dict_setter(_instance: PyClassRef, _value: PyObjectRef, vm: &VirtualMach /// This is the internal get_attr implementation for fast lookup on a class. pub fn class_get_attr(class: &PyClassRef, attr_name: &str) -> Option { + flame_guard!(format!("class_get_attr({:?})", attr_name)); + if let Some(item) = class.attributes.borrow().get(attr_name).cloned() { return Some(item); } diff --git a/vm/src/scope.rs b/vm/src/scope.rs index c451e8106..3ba068f6e 100644 --- a/vm/src/scope.rs +++ b/vm/src/scope.rs @@ -123,6 +123,7 @@ 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_local(&self, vm: &VirtualMachine, name: &str) -> Option; 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; @@ -142,6 +143,12 @@ impl NameProtocol for Scope { self.load_global(vm, name) } + #[cfg_attr(feature = "flame-it", flame("Scope"))] + /// Load a local name. Only check the local dictionary for the given name. + fn load_local(&self, vm: &VirtualMachine, name: &str) -> Option { + self.get_locals().get_item_option(name, vm).unwrap() + } + #[cfg_attr(feature = "flame-it", flame("Scope"))] fn load_cell(&self, vm: &VirtualMachine, name: &str) -> Option { for dict in self.locals.iter().skip(1) { @@ -170,7 +177,17 @@ impl NameProtocol for Scope { } #[cfg_attr(feature = "flame-it", flame("Scope"))] + /// Load a global name. fn load_global(&self, vm: &VirtualMachine, name: &str) -> Option { + // First, take a look in the outmost local scope (the scope at top level) + let last_local_dict = self.locals.iter().last(); + if let Some(local_dict) = last_local_dict { + if let Some(value) = local_dict.get_item_option(name, vm).unwrap() { + return Some(value); + } + } + + // Now, take a look at the globals or builtins. if let Some(value) = self.globals.get_item_option(name, vm).unwrap() { Some(value) } else { diff --git a/vm/src/vm.rs b/vm/src/vm.rs index ccb18f2d9..fc904a60b 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -527,6 +527,8 @@ impl VirtualMachine { where T: Into, { + flame_guard!(format!("call_method({:?})", method_name)); + // This is only used in the vm for magic methods, which use a greatly simplified attribute lookup. let cls = obj.class(); match objtype::class_get_attr(&cls, method_name) { @@ -545,7 +547,6 @@ impl VirtualMachine { } } - #[cfg_attr(feature = "flame-it", flame("VirtualMachine"))] fn _invoke(&self, func_ref: &PyObjectRef, args: PyFuncArgs) -> PyResult { vm_trace!("Invoke: {:?} {:?}", func_ref, args);