# HG changeset patch # User Paul Boddie # Date 1366412398 -7200 # Node ID bdc835dee81d84a3fadb5c8b73906b9ab0b73c03 # Parent 81b95dd9c4f8b07b61fc453970ec37f979317f14 Moved some deduction code into the common visitor module with some reorganising. Improved syspython Getattr support and changed the special accessor method names to match the documentation. diff -r 81b95dd9c4f8 -r bdc835dee81d micropython/common.py --- a/micropython/common.py Fri Apr 19 23:38:12 2013 +0200 +++ b/micropython/common.py Sat Apr 20 00:59:58 2013 +0200 @@ -3,7 +3,7 @@ """ Common classes. -Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012 Paul Boddie +Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012, 2013 Paul Boddie This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -21,6 +21,7 @@ from compiler.ast import AssAttr, Getattr, Name import compiler.ast +from micropython.basicdata import Const, Constant from micropython.data import Attr, Class, Module from micropython.errors import * @@ -35,6 +36,23 @@ "A base class for visitors." + def process_definitions(self, node): + + """ + Process and return all definitions beneath 'node', but not definitions + within definitions. + """ + + definitions = [] + for n in node.getChildNodes(): + if isinstance(n, (compiler.ast.Class, compiler.ast.Function)): + definitions.append(self.dispatch(n)) + else: + definitions += self.process_definitions(n) + return definitions + + # Visitor support methods. + def default(self, node, *args): for n in node.getChildNodes(): self.dispatch(n) @@ -56,6 +74,25 @@ exc.unit_name = self.get_unit().full_name() raise + # Deduction-related methods. + + def provides_self_access(self, node, unit): + + """ + Return whether the 'node' in the given 'unit' provides a self-based + attribute access. + """ + + attr_value = self.get_attribute_and_value(node._expr) + + if attr_value: + target, value = attr_value + + return target and target.name == "self" and target.parent is unit and \ + unit.is_method() + + return False + def possible_accessor_types(self, node, defining_users=1): """ @@ -74,69 +111,22 @@ # not that of a general instance or an unresolved name, attempt to # identify it. - if isinstance(node, (AssAttr, Getattr, Name)): - - # Use any explicit attribute annotation. - - if isinstance(node._attr, Attr): - attr = node._attr - all_target_names.append(set([(attr.parent.full_name(), attr.is_static_attribute())])) - - # Otherwise, try and use an expression annotation. - - if isinstance(node, (AssAttr, Getattr)): - expr = node._expr - - # Permitting multiple expression types if they provide the - # attribute. - - if isinstance(expr, Attr): - exprs = expr.get_values() - elif expr: - exprs = [expr] - else: - exprs = None - - if exprs: - target_names = set() - - # For each expression value try and get a concrete - # attribute. - - for expr in exprs: - attr = expr.all_attributes().get(node.attrname) + # Use explicit annotations on the node. - # Where an attribute can be obtained, record its - # details. - - if attr: - target_names.add((attr.parent.full_name(), attr.is_static_attribute())) - - if target_names: - all_target_names.append(target_names) - - # Otherwise, attempt to employ the attribute usage observations. - - if node._attrusers: - target_names = set() - - # Visit each attribute user. + attrs = self.possible_attributes_from_annotation(node) + if attrs: + target_names = set() + for (attr, value) in attrs: + # NOTE: Ignoring constant objects. + if attr: + target_names.add((attr.parent.full_name(), attr.is_static_attribute())) + all_target_names.append(target_names) - for user in node._attrusers: - - # Since users such as branches may not provide type information, - # attempt to find defining users. + # Use attribute usage observations. - if defining_users: - for def_user in user._attrdefs or [user]: - for target_name, is_static in def_user._attrtypes.get(node._username, []): - target_names.add((target_name, is_static)) - else: - for target_name, is_static in user._attrspecifictypes.get(node._username, []): - target_names.add((target_name, is_static)) - - if target_names: - all_target_names.append(target_names) + target_names = self.possible_accessor_types_from_usage(node, defining_users) + if target_names: + all_target_names.append(target_names) # Return the smallest set of target names. @@ -144,6 +134,112 @@ return all_target_names and all_target_names[0] + def get_attribute_and_value(self, obj): + + """ + Return (attribute, value) details for the given 'obj', where an + attribute of None can be returned for constant objects, and where None + can be returned as the result where no concrete details can be provided. + """ + + if isinstance(obj, Constant): + return None, obj + + if isinstance(obj, Attr): + return obj, obj.get_value() + + return None + + def possible_attributes_from_annotation(self, node): + + """ + Return (attribute, value) details provided by any _expr or _attr + annotations on 'node'. + """ + + attr_value = self.get_attribute_and_value(node._attr) + + if attr_value: + return [attr_value] + + attrs = set() + expr = node._expr + + if expr: + + # Permitting multiple expression types if they provide the + # attribute. + + if isinstance(expr, Attr): + exprs = expr.get_values() + else: + exprs = [expr] + + # For each expression value try and get a concrete + # attribute. + + for expr in exprs: + attr = expr.all_attributes().get(node.attrname) + + # Where an attribute can be obtained, record its + # details. + + if attr: + attrs.add((attr, attr.get_value())) + + return attrs + + def possible_accessor_types_from_usage(self, node, defining_users=1): + + """ + Return a set of (target name, static) tuples from an investigation of + attribute usage observations stored on the given 'node'. + + If 'defining_users' is set to a false value, attempt to get the type + names specifically applicable to the node, rather than retrieving more + general definition-based type observations. + """ + + target_names = set() + + if node._attrusers: + + # Visit each attribute user. + + for user in node._attrusers: + + # Since users such as branches may not provide type information, + # attempt to find defining users. + + if defining_users: + for def_user in user._attrdefs or [user]: + for target_name, is_static in def_user._attrtypes.get(node._username, []): + target_names.add((target_name, is_static)) + else: + for target_name, is_static in user._attrspecifictypes.get(node._username, []): + target_names.add((target_name, is_static)) + + return target_names + + def possible_accessors_from_usage(self, node, defining_users=1): + + """ + Return possible accessors from the usage recorded on the given 'node'. + + If 'defining_users' is set to a false value, attempt to get the type + names specifically applicable to the node, rather than retrieving more + general definition-based type observations. + """ + + targets = set() + target_names = self.possible_accessor_types_from_usage(node, defining_users) + + if target_names: + for target_name, is_static in target_names: + targets.add(self.objtable.get_object(target_name)) + + return targets + def used_by_unit(node): """ diff -r 81b95dd9c4f8 -r bdc835dee81d micropython/syspython.py --- a/micropython/syspython.py Fri Apr 19 23:38:12 2013 +0200 +++ b/micropython/syspython.py Sat Apr 20 00:59:58 2013 +0200 @@ -36,7 +36,12 @@ module_attribute = compiler.ast.Getattr special_name = compiler.ast.Name -quoted_name = compiler.ast.Const + +def quoted_ref(obj): + return compiler.ast.CallFunc("__static__", [compiler.ast.Const(obj.full_name())]) + +def quoted_name(s): + return compiler.ast.Const(s) # Source code classes. @@ -48,6 +53,7 @@ self.visitor = self self.module = module self.program = program + self.objtable = program.get_object_table() self.in_main = False self.units = [] @@ -71,7 +77,7 @@ module = node.unit self.units.append(module) - definitions = self._findDefinitions(node) + definitions = self.process_definitions(node) # __globalnames__(name, ...) @@ -97,15 +103,6 @@ return compiler.ast.Module(node.doc, compiler.ast.Stmt(definitions + [main])) - def _findDefinitions(self, node): - definitions = [] - for n in node.getChildNodes(): - if isinstance(n, (compiler.ast.Class, compiler.ast.Function)): - definitions.append(self.dispatch(n)) - else: - definitions += self._findDefinitions(n) - return definitions - # Statements. def visitAssert(self, node): @@ -183,7 +180,7 @@ inherited.append( compiler.ast.CallFunc( special_name("__inherited__"), - [special_name(supercls.full_name())] + [special_name(name) for name in attrnames] + [quoted_ref(supercls)] + [special_name(name) for name in attrnames] )) # __descendants__(name, ...) @@ -197,7 +194,7 @@ # Process all the definitions defined inside the class. - definitions = self._findDefinitions(node) + definitions = self.process_definitions(node) return compiler.ast.Class(node.name, [], node.doc, compiler.ast.Stmt(instattrs + clsattrs + inherited + descendants + definitions) @@ -238,11 +235,11 @@ else_nodes = node.else_ and self.dispatch(node.else_).nodes or [] return compiler.ast.Stmt([ - # __storetemp__(_it, __loadaddress__(__builtins__, iter)()) + # __storetemp__(_it, __loadattr__(__builtins__, iter)()) compiler.ast.CallFunc(special_name("__storetemp__"), [ temp, compiler.ast.CallFunc( - compiler.ast.CallFunc(special_name("__loadaddress__"), + compiler.ast.CallFunc(special_name("__loadattr__"), [special_name("__builtins__"), special_name("iter")] ), [self.dispatch(node.list)] @@ -322,10 +319,10 @@ # __localnames__(name, ...) # __globalnames__(name, ...) - localnames = fn.locals() and [ + localnames = fn.all_locals() and [ compiler.ast.CallFunc( special_name("__localnames__"), - [special_name(name) for name in fn.locals().keys()] + [special_name(name) for name in fn.all_locals().keys()] ) ] or [] @@ -338,6 +335,8 @@ defaults = [self.dispatch(n) for n in node.defaults] + # NOTE: Should generate guards for attribute usage operations. + code = self.dispatch(node.code) return compiler.ast.Function(node.decorators, node.name, node.argnames, defaults, node.flags, node.doc, @@ -376,7 +375,11 @@ return compiler.ast.Stmt(statements) - visitPass = NOP + def visitPass(self, node): + if not isinstance(self.get_unit(), Class): + return compiler.ast.Pass() + else: + return compiler.ast.Stmt([]) def visitPrint(self, node): return compiler.ast.Print( @@ -458,6 +461,20 @@ [self.dispatch(node.expr)] ) + def _generateValue(self, value): + + # Literal constants. + + if isinstance(value, Const): + return compiler.ast.Const(value.get_value()) + + # Other constant structures. + + if isinstance(value, Constant): + return quoted_ref(value) + + return None + # Expressions. def visitAdd(self, node): @@ -467,7 +484,6 @@ return compiler.ast.And([self.dispatch(n) for n in node.nodes]) def visitAssAttr(self, node, expr): - possible_types = self.possible_accessor_types(node, defining_users=0) # NOTE: Derived from Getattr support. @@ -478,37 +494,37 @@ # NOTE: nodes, such as whether an expression yields a constant. # NOTE: Known targets: - # NOTE: __storeaddress__ and __storeaddresscontext__ + # NOTE: __storeattr__ and __storeattrcontext__ # NOTE: Attributes of self. # Usage observations. - possible_types = self.possible_accessor_types(node, defining_users=0) + targets = self.possible_accessors_from_usage(node, defining_users=0) # Record whether types were already deduced. If not, get types using # only this attribute. - if not possible_types: - possible_types = self.get_possible_types(node.attrname) + if not targets: + targets = self.possible_accessors_for_attribute(node.attrname) - attributes = self.get_attributes(possible_types, node.attrname) + attrs = self.get_attributes(targets, node.attrname) # Generate optimisations where only a single attribute applies. - if len(attributes) == 1: - value, target, target_name = attributes[0] + if len(attrs) == 1: + attr = attrs[0] # Static attributes. - if value is not None: + if attr.is_static_attribute(): # Static attributes may be accompanied by a different context # depending on the accessor. # NOTE: Should determine whether the context is always replaced. return compiler.ast.CallFunc( - special_name("__storeaddresscontextcond__"), + special_name("__storeattrcontextcond__"), [accessor, special_name(node.attrname), expr] ) @@ -539,6 +555,7 @@ unit = self.get_unit() # Generate appropriate name access operation. + # NOTE: Should generate guards for attribute usage operations. scope = getattr(node, "_scope", None) if not scope: @@ -560,16 +577,16 @@ elif isinstance(unit, Class): return compiler.ast.CallFunc( - special_name("__storeaddresscontext__"), - [quoted_name(unit.full_name()), special_name(node.name), expr] + special_name("__storeattrcontext__"), + [quoted_ref(unit), special_name(node.name), expr] ) # Module locals are module attribute references. elif isinstance(unit, Module): return compiler.ast.CallFunc( - special_name("__storeaddress__"), - [quoted_name(unit.full_name()), special_name(node.name), expr] + special_name("__storeattr__"), + [quoted_ref(unit), special_name(node.name), expr] ) else: raise TranslateError("Program unit has no local %r." % name) @@ -579,8 +596,8 @@ # Globals are references to module attributes. return compiler.ast.CallFunc( - special_name("__storeaddress__"), - [quoted_name(self.get_module().full_name()), special_name(node.name), expr] + special_name("__storeattr__"), + [quoted_ref(self.get_module()), special_name(node.name), expr] ) elif scope == "builtin": @@ -588,7 +605,7 @@ # Builtins are accessed via the __builtins__ module. return compiler.ast.CallFunc( - special_name("__storeaddress__"), + special_name("__storeattr__"), [special_name("__builtins__"), special_name(node.name), expr] ) @@ -646,53 +663,118 @@ if expr: return self.visitAssAttr(node, expr) + unit = self.get_unit() + accessor = self.dispatch(node.expr) - # NOTE: Replicate the _generateAttr logic. - # NOTE: Should be able to store concrete value details on generated - # NOTE: nodes, such as whether an expression yields a constant. + # The target, on which the access is performed, may influence the effect + # on the context. We can only reliably assume that a literal constant is + # an instance: all other "instances" may actually be classes in certain + # cases. + + target = node._expr + instance_target = isinstance(target, Const) + + # Attempt to deduce attributes from explicit annotations. + + attrs = self.possible_attributes_from_annotation(node) + + if len(attrs) == 1: + attr, value = attrs[0] + + # Constant values can be obtained directly. + + v = self._generateValue(value) + if v: return v + + # Static attributes can be obtained via their parent. + + if attr.is_static_attribute(): + return compiler.ast.CallFunc( + special_name(instance_target and "__loadattrcontext__" or "__loadattr__"), + [self._generateValue(attr.parent), special_name(node.attrname)]) + + # Attributes of self, which is by definition an instance. + + if self.provides_self_access(node, unit): + + # Find instance attributes. + + attr = unit.parent.instance_attributes().get(node.attrname) + + if attr: + + # Emit self.attrname without context overriding. - # NOTE: Known targets: - # NOTE: class.__class__ => __builtins__.type - # NOTE: __loadaddress__ and __loadaddresscontext__ + return compiler.ast.CallFunc( + special_name("__loadattr__"), [ + accessor, special_name(node.attrname) + ]) + + # Find class attributes. + # The context will be overridden for compatible class attributes + # only. + + attr = unit.parent.get(node.attrname) + + if attr: + + # Constant attributes. + + if attr.is_strict_constant(): + v = self._generateValue(attr.get_value()) + if v: return v + + # Compatible class attributes. - # NOTE: Attributes of self. + if attr.defined_within_hierarchy(): + return compiler.ast.CallFunc( + special_name("__loadattrcontext__"), [ + self._generateValue(attr.parent), special_name(node.attrname) + ]) + + # Incompatible class attributes. + + elif attr.defined_outside_hierarchy(): + return compiler.ast.CallFunc( + special_name("__loadattr__"), [ + self._generateValue(attr.parent), special_name(node.attrname) + ]) + + # Unknown or mixed compatibility. + + return compiler.ast.CallFunc( + special_name("__loadattrcontextcond__"), [ + self._generateValue(attr.parent), special_name(node.attrname) + ]) # Usage observations. - possible_types = self.possible_accessor_types(node, defining_users=0) + targets = self.possible_accessors_from_usage(node, defining_users=0) # Record whether types were already deduced. If not, get types using # only this attribute. - if not possible_types: - possible_types = self.get_possible_types(node.attrname) + if not targets: + targets = self.possible_accessors_for_attribute(node.attrname) - attributes = self.get_attributes(possible_types, node.attrname) + attrs = self.get_attributes(targets, node.attrname) # Generate optimisations where only a single attribute applies. - if len(attributes) == 1: - value, target, target_name = attributes[0] - - # Static attributes. - - if value is not None: + if len(attrs) == 1: + attr = attrs[0] - # class.__class__ => __builtins__.type + # Static attributes, but potentially non-static targets. - if node.attrname == "__class__": - return compiler.ast.CallFunc( - special_name("__loadaddress__"), - [special_name("__builtins__"), special_name("type")] - ) + if attr.is_static_attribute(): # Static attributes may be accompanied by a different context # depending on the accessor. # NOTE: Should determine whether the context is always replaced. return compiler.ast.CallFunc( - special_name("__loadaddresscontextcond__"), + special_name(instance_target and "__loadattrcontext__" or "__loadattrcontextcond__"), [accessor, special_name(node.attrname)] ) @@ -705,11 +787,43 @@ # With no usable deductions, generate a table-based access. - return compiler.ast.CallFunc( - special_name("__loadattrindex__"), + access = compiler.ast.CallFunc( + special_name("__loadattrindexcontextcond__"), [accessor, special_name(node.attrname)] ) + # class.__class__ => __builtins__.type + + if node.attrname == "__class__": + + # __storetemp__(n, ) + # __isclass__(n) and __loadattr__(__builtins__, type) or + + temp = quoted_name(unit.temp_usage) + unit.temp_usage += 1 + + return compiler.ast.Stmt([ + compiler.ast.CallFunc(special_name("__storetemp__"), [temp, access]), + compiler.ast.Or([ + compiler.ast.And([ + compiler.ast.CallFunc( + special_name("__isclass__"), + [compiler.ast.CallFunc(special_name("__loadtemp__"), [temp])] + ), + compiler.ast.CallFunc( + special_name("__loadattr__"), + [special_name("__builtins__"), special_name("type")] + ) + ]), + access + ]) + ]) + + # General accesses. + + else: + return access + def visitGenExpr(self, node): return compiler.ast.GenExpr(self.dispatch(node.code)) @@ -795,53 +909,31 @@ return self.visitAssName(node, expr) unit = self.get_unit() + attr = node._attr + scope = node._scope # Generate appropriate name access operation. - scope = getattr(node, "_scope", None) - if not scope: - attr, scope, from_name = self.get_unit()._get_with_scope(node.name) - if scope == "constant": return node - elif scope == "local": - # Function locals are referenced normally. + # Function locals are referenced normally. - if isinstance(unit, Function): - return node - - # Class locals are class attribute references. - # Module locals are module attribute references. + elif scope == "local" and isinstance(unit, Function): + return node - elif isinstance(unit, (Class, Module)): - return compiler.ast.CallFunc( - special_name("__loadaddress__"), - [quoted_name(unit.full_name()), special_name(node.name)] - ) - else: - raise TranslateError("Program unit has no local %r." % name) + # Other attributes should already be resolved. - elif scope == "global": - - # Globals are references to module attributes. - + elif attr is not None: return compiler.ast.CallFunc( - special_name("__loadaddress__"), - [quoted_name(self.get_module().full_name()), special_name(node.name)] + special_name("__loadattr__"), + [quoted_ref(attr.parent), special_name(node.name)] ) - elif scope == "builtin": - - # Builtins are accessed via the __builtins__ module. - - return compiler.ast.CallFunc( - special_name("__loadaddress__"), - [special_name("__builtins__"), special_name(node.name)] - ) + # NOTE: Unknown attributes may arise because a class attribute has been + # NOTE: optimised away. else: - # NOTE: This may happen because a class attribute is optimised away. return compiler.ast.CallFunc( special_name("__loadunknown__"), [special_name(node.name)] @@ -895,24 +987,25 @@ def possible_accessor_types(self, node, defining_users=1): return set([tn for (tn, st) in ASTVisitor.possible_accessor_types(self, node, defining_users)]) - def get_possible_types(self, attrname): - objtable = self.program.get_object_table() - return objtable.any_possible_objects([attrname]) + def get_possible_accessors(self, attrname): + + "Return a list of accessors supporting 'attrname'." + + targets = [] - def get_attributes(self, possible_types, attrname): - objtable = self.program.get_object_table() + for target_name in self.objtable.any_possible_objects([attrname]): + targets.append(self.objtable.get_object(target_name)) + + return targets + + def get_attributes(self, targets, attrname): + + "Return a list of attributes for 'targets' supporting 'attrname'." + attributes = [] - for target_name in possible_types: - target = objtable.get_object(target_name) - try: - attr = objtable.access(target_name, attrname) - except TableError: - continue - if attr.is_static_attribute(): - for v in attr.get_values(): - attributes.append((v, target, target_name)) - else: - attributes.append((None, target, target_name)) + + for target in targets: + attributes.append(self.objtable.access(target.full_name(), attrname)) return attributes diff -r 81b95dd9c4f8 -r bdc835dee81d micropython/trans.py --- a/micropython/trans.py Fri Apr 19 23:38:12 2013 +0200 +++ b/micropython/trans.py Sat Apr 20 00:59:58 2013 +0200 @@ -251,33 +251,18 @@ # Attempt to deduce the target of an attribute access by searching for a # unique type providing the names associated with the accessed object. - elif self.optimiser.should_optimise_accesses_by_attribute_usage(): - - target_names = self.possible_accessor_types(node) - - if target_names is not None and len(target_names) == 1: - target_name, is_static = list(target_names)[0] - - # Check for class.__class__. + elif self.optimiser.should_optimise_accesses_by_attribute_usage() and \ + isinstance(node, compiler.ast.AttributeAccessor): - if attrname == "__class__": - if is_static: - self.load_builtin("type", node) - return - - # Access the object table to get the attribute. + targets = self.possible_accessors_from_usage(node) - try: - attr = self.objtable.access(target_name, attrname) - - # Disallow non-class/instance optimisations. - - except TableError, exc: - print "Possible optimisation for", target_name, "not permissable." + if targets and len(targets) == 1: + target = list(targets)[0] + attr = target.all_attributes().get(attrname) # Produce a suitable instruction. - else: + if attr: if AddressContextCondInstruction is not None and attr.is_static_attribute(): self.new_op(AddressContextCondInstruction(attr)) elif AttrInstruction is not None and not attr.is_static_attribute(): diff -r 81b95dd9c4f8 -r bdc835dee81d tests/attributes_class_inherited.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/attributes_class_inherited.py Sat Apr 20 00:59:58 2013 +0200 @@ -0,0 +1,15 @@ +#!/usr/bin/env python + +class C: + x = 1 + +class D(C): + pass + +def f(c): + return c.x + +result_1 = f(C) +result_2 = f(D) + +# vim: tabstop=4 expandtab shiftwidth=4