# HG changeset patch # User Paul Boddie # Date 1296331647 -3600 # Node ID 9c6deab845e033e3d5dd7b0be7982be06b340be9 # Parent 820ab4a8b723a95d9702e8fa77f780bffe2ab552 Introduced a degree of support for classes and instances having separate __class__ attributes that are accessed in a class/instance-relative fashion. Added the __class__ attribute as a special case of a non-static attribute. This changes code generation such that a constant accessor (a class) may yield an attribute which should not be accessed via a fixed location. (Although this may be cautious and also limited by the behaviour of the object table, if class attribute assignment is to be permitted in future, more distinctions between apparently static attributes may need to be enforced.) Made the type class a special case which is created in advance so that all classes may refer to it via their __class__ attributes, and whose details are populated when the class is actually encountered in the __builtins__ module. Made extra space in the instance templates for the __class__ attribute, adding support for the copying of this data when new instances are created. Introduced type-specific vacuum support in order to correctly perform vacuuming. Changed various class references in the micropython module. Tidied up to "to do" document, putting items into separate sections. Added various tests of the new features. diff -r 820ab4a8b723 -r 9c6deab845e0 TO_DO.txt --- a/TO_DO.txt Thu Jan 13 23:58:12 2011 +0100 +++ b/TO_DO.txt Sat Jan 29 21:07:27 2011 +0100 @@ -1,28 +1,29 @@ +Permit __class__ as being differently defined for classes and instances. Since __class__ +is always defined, no shadowing may occur for the attribute exposing it at different +levels. + + The type class should be exposed as each class's __class__ attribute. + + The object-relative definition of __class__ should be stored in the object table. + + Note that object table information will not be able to reflect the class-type + relationship, but isinstance will need to check for instances and classes, anyway. + +Tuple references to stack locations in a merged stack/heap memory model. + +Attribute Usage +=============== + Consider attribute assignment observations, along with the possibility of class attribute assignment. -Local assignment detection plus frame re-use. Example: slice.__init__ calls -xrange.__init__ with the same arguments which are unchanged in xrange.__init__. There is -therefore no need to build a new frame for this call. - Consider attribute usage observations being suspended inside blocks where AttributeError may be caught (although this doesn't anticipate such exceptions being caught outside a function altogether). -Fix object table entries for attributes not provided by any known object, or provide an -error, potentially overridden by options. For example, the augmented assignment methods -are not supported by the built-in objects and thus the operator module functions cause -the compilation to fail. Alternatively, just supply the methods since something has to do -so in the builtins. - Consider type deduction and its consequences where types belong to the same hierarchy and where a guard could be generated for the most general type. -Consider attribute merging where many attributes are just aliases for the same underlying -definition. - -Consider merging the InspectedModule.store tests with the scope conflict handling. - Consider permitting multiple class alternatives where the attributes are all identical. Support class attribute positioning similar to instance attribute positioning, potentially @@ -30,10 +31,52 @@ the class attribute could be exposed at a similar relative position to the class (and potentially accessible using a LoadAttr-style instruction). +**** Constant attribute users need not maintain usage since they are already resolved. **** + +Loop entry points should capture usage to update later assignments in the loop. +The continue and break statements should affect usage propagation. + +Consider handling CallFunc in micropython.inspect in order to produce instances of specific classes. +Then, consider adding support for guard removal/verification where known instances are involved. +Consider handling branches of values within namespaces in order to support more precise value usage. + +Frame Optimisations +=================== + +Stack frame replacement where a local frame is unused after a call, such as in a tail call +situation. + +Local assignment detection plus frame re-use. Example: slice.__init__ calls +xrange.__init__ with the same arguments which are unchanged in xrange.__init__. There is +therefore no need to build a new frame for this call. + +Function Specialisation +======================= + +Specialisation of certain functions, such as isinstance(x, cls) where cls is a known +constant. + +Structure and Object Table Optimisations +======================================== + +Fix object table entries for attributes not provided by any known object, or provide an +error, potentially overridden by options. For example, the augmented assignment methods +are not supported by the built-in objects and thus the operator module functions cause +the compilation to fail. Alternatively, just supply the methods since something has to do +so in the builtins. + +Consider attribute merging where many attributes are just aliases for the same underlying +definition. + Consider references to defaults as occurring only within the context of a particular function, thus eliminating default value classes if such functions are not themselves invoked. +Scope Handling +============== + +Consider merging the InspectedModule.store tests with the scope conflict handling. + Consider labelling _scope on assignments and dealing with the assignment of removed attributes, possibly removing the entire assignment, and distinguishing between such cases and unknown names. @@ -49,19 +92,16 @@ set # could be confused by the local definition at run-time ---- +Object Coverage +=============== + Support __init__ traversal (and other implicit names) more effectively. +Other +===== + Check context_value initialisation (avoiding or handling None effectively). __getitem__ could be written in Python, using a native method only to access fragments. Consider better "macro" support where new expressions need to be generated and processed. - -**** Constant attribute users need not maintain usage since they are already resolved. **** - -Loop entry points should capture usage to update later assignments in the loop. -The continue and break statements should affect usage propagation. - -Consider handling CallFunc in micropython.inspect in order to produce instances of specific classes. -Then, consider adding support for guard removal/verification where known instances are involved. -Consider handling branches of values within namespaces in order to support more precise value usage. diff -r 820ab4a8b723 -r 9c6deab845e0 docs/concepts.txt --- a/docs/concepts.txt Thu Jan 13 23:58:12 2011 +0100 +++ b/docs/concepts.txt Sat Jan 29 21:07:27 2011 +0100 @@ -217,11 +217,10 @@ Attribute Locations ------------------- -The locations stored in table/list elements are for instance attributes -relative to the location of the instance, whereas those for class attributes -and modules are absolute addresses (although these could also be changed to -object-relative locations). Thus, each occupied table cell has the following -structure: +The locations stored in table/list elements are generally for instance +attributes relative to the location of the instance, whereas those for class +attributes and module attributes are generally absolute addresses. Thus, each +occupied table cell has the following structure: attrcode, uses-absolute-address, address (or location) @@ -233,6 +232,18 @@ is a need to test for classes and modules to prevent assignment to attributes of such objects, this particular information is always required. +The __class__ Attribute +----------------------- + +The exception to the above general rules about relative locations and absolute +addresses involves the __class__ attribute which is defined differently for +each class and its instances. Since the table elements can only refer to a +single absolute address, thus providing only a single value, such absolute +references which are sufficient for most class attributes would not be +appropriate for the __class__ attribute. However, using an object-relative +location would require both classes and instances to retain an attribute +location specifically to hold the value appropriate for each object type. + Comparing Tables as Matrices with Displacement Lists ---------------------------------------------------- diff -r 820ab4a8b723 -r 9c6deab845e0 micropython/__init__.py --- a/micropython/__init__.py Thu Jan 13 23:58:12 2011 +0100 +++ b/micropython/__init__.py Sat Jan 29 21:07:27 2011 +0100 @@ -5,7 +5,7 @@ from the simplify package but has had various details related to that package removed. -Copyright (C) 2006, 2007, 2008, 2009, 2010 Paul Boddie +Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011 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 @@ -142,7 +142,7 @@ # Append classes and functions to the image. for obj in module.all_objects: - if isinstance(obj, micropython.inspect.Class): + if isinstance(obj, micropython.data.Class): # Add header details. @@ -168,7 +168,7 @@ # level, and the code location is set within the code # generation process for the module. - elif isinstance(obj, micropython.inspect.Function): + elif isinstance(obj, micropython.data.Function): # Add header details. @@ -270,7 +270,7 @@ # with descendant information. for obj in module.all_objects: - if isinstance(obj, micropython.inspect.Class): + if isinstance(obj, micropython.data.Class): # Prevent ambiguous classes. @@ -302,14 +302,14 @@ for module in self.importer.get_modules(): for obj in module.all_objects: - if isinstance(obj, micropython.inspect.Function): + if isinstance(obj, micropython.data.Function): t.add(obj.full_name(), obj.parameters()) # Classes are callable, too. # Take details of the appropriate __init__ method to make an # entry for an instantiation function for the class. - elif isinstance(obj, micropython.inspect.Class): + elif isinstance(obj, micropython.data.Class): t.add(obj.get_instantiator().full_name(), obj.get_instantiator().parameters()) # Filter out all parameter table entries not referenced by keyword diff -r 820ab4a8b723 -r 9c6deab845e0 micropython/data.py --- a/micropython/data.py Thu Jan 13 23:58:12 2011 +0100 +++ b/micropython/data.py Sat Jan 29 21:07:27 2011 +0100 @@ -7,7 +7,7 @@ program but which are wrapped in context-dependent structures in the running program. -Copyright (C) 2007, 2008, 2009, 2010 Paul Boddie +Copyright (C) 2007, 2008, 2009, 2010, 2011 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 @@ -150,6 +150,9 @@ def vacuum_item(self, name): if self.has_key(name): del self[name] + return 1 + else: + return 0 def add_lambda(self, obj): attr = Attr(None, self, obj.name) @@ -955,7 +958,7 @@ as a class or a module. """ - return isinstance(self.parent, (Class, Module)) + return isinstance(self.parent, (Class, Module)) and self.name != "__class__" def defines_ambiguous_class(self): @@ -1109,13 +1112,14 @@ class Class(NamespaceDict, Naming, Constant): - "An inspected class." - - def __init__(self, name, parent, module=None, node=None): + "A base class for common/normal classes and the type class." + + def __init__(self, name, parent=None, module=None, node=None): """ - Initialise the class with the given 'name', 'parent' object, optional - 'module' and optional AST 'node'. + Initialise the class with the given 'name', optional 'parent' object, + 'module' and AST 'node'. The optional information must be set at a later + point using the 'set_context' method if omitted. """ NamespaceDict.__init__(self, module) @@ -1152,9 +1156,19 @@ self.local_usage = 0 self.all_local_usage = 0 - # Add this class to its attributes. - - self.set("__class__", self) + # Add __class__ attributes to this class and to instances of it. + + if self.parent is not None: + self.initialise_class_attribute() + self.add_instance_attribute("__class__") + + def set_context(self, parent, module, node): + self.parent = parent + self.module = module + self.astnode = node + + self.initialise_class_attribute() + self.add_instance_attribute("__class__") def reset_caches(self): @@ -1204,6 +1218,17 @@ # Administrative methods. + def items_for_vacuum(self): + items = [] + for name in self.instattr: + items.append((name, None)) + return NamespaceDict.items_for_vacuum(self) + items + + def vacuum_item(self, name): + if not NamespaceDict.vacuum_item(self, name): + self.instattr.remove(name) + return 1 + def finalise_attributes(self): "Make sure that all attributes are fully defined." @@ -1457,17 +1482,34 @@ """ Return all attributes for an instance, indicating either the class which provides them or that the instance itself provides them. + + Note that __class__ acts like an instance attribute for both instances + and classes. """ if self.allattr is None: self.allattr = {} self.allattr.update(self.all_class_attributes()) for name, attr in self.instance_attributes().items(): - if self.allattr.has_key(name): + if self.allattr.has_key(name) and name != "__class__": print "Warning: instance attribute %r in %r overrides class attribute." % (name, self) self.allattr[name] = attr return self.allattr +class TypeClass(Class): + + "A special class for the type class." + + def initialise_class_attribute(self): + self.set("__class__", self) + +class CommonClass(Class): + + "An inspected class." + + def initialise_class_attribute(self): + self.set("__class__", type_class) + class Function(NamespaceDict, Naming, Constant): "An inspected function." @@ -1716,6 +1758,7 @@ def vacuum_item(self, name): del self.lambdas[name] + return 1 def finalise_attributes(self): @@ -1868,4 +1911,23 @@ return dict(self) +# Pre-made instances. + +type_class = TypeClass("type") # details to be filled in later + +# Class construction. + +def get_class(name, parent, module, node): + + """ + Return a Class instance for the class with the given 'name', 'parent', + 'module' and 'node'. + """ + + if name == "type" and module.full_name() == "__builtins__": + type_class.set_context(parent, module, node) + return type_class + else: + return CommonClass(name, parent, module, node) + # vim: tabstop=4 expandtab shiftwidth=4 diff -r 820ab4a8b723 -r 9c6deab845e0 micropython/inspect.py --- a/micropython/inspect.py Thu Jan 13 23:58:12 2011 +0100 +++ b/micropython/inspect.py Sat Jan 29 21:07:27 2011 +0100 @@ -3,7 +3,7 @@ """ Inspect source files, obtaining details of classes and attributes. -Copyright (C) 2007, 2008, 2009, 2010 Paul Boddie +Copyright (C) 2007, 2008, 2009, 2010, 2011 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 @@ -213,7 +213,9 @@ if attr is not None and attr.assignments == 1: value = attr.get_value() - if value is not obj and value in self.all_objects: + # The value must have this object as a parent. + + if value is not obj and value.parent is obj and value in self.all_objects: self.all_objects.remove(value) # Delete class contents and lambdas from functions. @@ -686,7 +688,7 @@ if self.in_loop: print "Warning: class %r in %r defined in a loop." % (node.name, self.full_name()) - cls = Class(node.name, self.get_namespace(), self, node) + cls = get_class(node.name, self.get_namespace(), self, node) # Make a back reference from the node for code generation. diff -r 820ab4a8b723 -r 9c6deab845e0 micropython/rsvp.py --- a/micropython/rsvp.py Thu Jan 13 23:58:12 2011 +0100 +++ b/micropython/rsvp.py Sat Jan 29 21:07:27 2011 +0100 @@ -3,7 +3,7 @@ """ RSVP instruction and serialisation classes. -Copyright (C) 2007, 2008, 2009, 2010 Paul Boddie +Copyright (C) 2007, 2008, 2009, 2010, 2011 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 @@ -20,7 +20,7 @@ """ from micropython.data import Attr, Const, Class, Function, Module -from micropython.program import Block, DataObject, DataValue +from micropython.program import Block, DataObject, DataValue, PlaceholderContext from micropython.raw import RawObject def name(attr): @@ -82,7 +82,10 @@ def set_location(self, location, with_builtins): self.item.instance_template_location = location - return RSVPObject.set_location(self, location + 1, with_builtins) + + # Include the instance template and __class__ attribute in the size. + + return RSVPObject.set_location(self, location + 2, with_builtins) def finalise_location(self, with_builtins): self._finalise_location(with_builtins) @@ -121,6 +124,13 @@ call_method_funccode # funccode ), + # The __class__ attribute for instances. + + DataValue( + PlaceholderContext, + item.location + ), + # Class... DataObject( diff -r 820ab4a8b723 -r 9c6deab845e0 micropython/table.py --- a/micropython/table.py Thu Jan 13 23:58:12 2011 +0100 +++ b/micropython/table.py Sat Jan 29 21:07:27 2011 +0100 @@ -175,10 +175,13 @@ if isinstance(attr, Class): return (attr_index, None, None) - if attr.parent is not None: + # Get the absolute location for classes and modules. + + if attr.parent is not None and attr.is_static_attribute(): location = attr.parent.location or 0 else: location = 0 + if attr.position is not None: position = attr.position + location + 1 # skip structure header else: diff -r 820ab4a8b723 -r 9c6deab845e0 micropython/trans.py --- a/micropython/trans.py Thu Jan 13 23:58:12 2011 +0100 +++ b/micropython/trans.py Sat Jan 29 21:07:27 2011 +0100 @@ -426,8 +426,10 @@ # Produce a suitable instruction. - if AddressInstruction is not None: + if attr.is_static_attribute() and AddressInstruction is not None: self.replace_active_value(AddressInstruction(attr)) + elif not attr.is_static_attribute() and AttrInstruction is not None: + self.replace_active_value(AttrInstruction(attr)) else: raise TranslateError("Storing of class or module attribute %r via an object is not permitted." % attrname) diff -r 820ab4a8b723 -r 9c6deab845e0 rsvp.py --- a/rsvp.py Thu Jan 13 23:58:12 2011 +0100 +++ b/rsvp.py Sat Jan 29 21:07:27 2011 +0100 @@ -5,7 +5,7 @@ ignore low-level operations and merely concentrate on variable access, structure access, structure allocation and function invocations. -Copyright (C) 2007, 2008, 2009, 2010 Paul Boddie +Copyright (C) 2007, 2008, 2009, 2010, 2011 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 @@ -455,7 +455,7 @@ size = self.operand value = self.value # NOTE: Referencing the instance template. - addr = self._MakeObject(size, value.ref - 1) + addr = self._MakeObject(size, value.ref - 2) # Introduce object as context for the new object. self.value = DataValue(addr, addr) @@ -817,6 +817,9 @@ addr = self.new(size) # Save the header, overriding the size. self.save(addr, data.with_size(size)) + # Copy the __class__ attribute. + cls_attr = self.load(ref + 1) + self.save(addr + 1, cls_attr) return addr def _MakeFragment(self, occupied, size): diff -r 820ab4a8b723 -r 9c6deab845e0 tests/class_attr_ref_to_class.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/class_attr_ref_to_class.py Sat Jan 29 21:07:27 2011 +0100 @@ -0,0 +1,18 @@ +#!/usr/bin/env python + +class A: + pass + +class B: + x = A + +class C: + x = A + +class D: + x = type + +result_3 = C.x is A and 3 or 0 +result_4 = D.x is type and 4 or 0 + +# vim: tabstop=4 expandtab shiftwidth=4 diff -r 820ab4a8b723 -r 9c6deab845e0 tests/class_class_attr.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/class_class_attr.py Sat Jan 29 21:07:27 2011 +0100 @@ -0,0 +1,16 @@ +#!/usr/bin/env python + +class A: + pass + +class B(A): + pass + +class C: + pass + +result_1 = A.__class__ is type and 1 or 0 +result_2 = B.__class__ is type and 2 or 0 +result_3 = C.__class__ is type and 3 or 0 + +# vim: tabstop=4 expandtab shiftwidth=4 diff -r 820ab4a8b723 -r 9c6deab845e0 tests/class_instance_attr.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/class_instance_attr.py Sat Jan 29 21:07:27 2011 +0100 @@ -0,0 +1,20 @@ +#!/usr/bin/env python + +class A: + pass + +class B(A): + pass + +class C: + pass + +a = A() +b = B() +c = C() + +result_1 = a.__class__ is A and 1 or 0 +result_2 = b.__class__ is B and 2 or 0 +result_3 = c.__class__ is C and 3 or 0 + +# vim: tabstop=4 expandtab shiftwidth=4 diff -r 820ab4a8b723 -r 9c6deab845e0 tests/subclass.py --- a/tests/subclass.py Thu Jan 13 23:58:12 2011 +0100 +++ b/tests/subclass.py Sat Jan 29 21:07:27 2011 +0100 @@ -5,18 +5,21 @@ self.x = x def a(self): - pass + return self.x class B(A): def b(self): - pass + return 2 class C(A, B): def a(self): - A.a(self) + return self.y def b(self): - self.a() + return self.a() + + def c(self): + return A.a(self) def __init__(self, x, y): self.x = x @@ -24,7 +27,10 @@ class D: def __init__(self, y): - self.y = y + self.z = y + + def a(self): + return self.z class E(C, D): pass @@ -32,4 +38,20 @@ class F(A, D): pass +a = A(1) +b = B(1) +c = C(1, 2) +d = D(3) +e = E(3, 4) +f = F(5) + +result1_1 = a.a() +result1_2 = b.b() +result2_2 = c.a() +result3_2 = c.b() +result2_1 = c.c() +result_3 = d.a() +result_4 = e.a() +result_5 = f.a() + # vim: tabstop=4 expandtab shiftwidth=4