# HG changeset patch # User Paul Boddie # Date 1298771138 -3600 # Node ID 4a8dd27c299420de7198020e8e81e8af762ece74 # Parent 5c5cddf971cbb05989926b299821e283c1887dff Switched back to providing a common __class__ attribute for instances, intercepting __class__ accesses on classes during inspection, compilation and, through appropriate instruction sequences, execution. Exposed target details in the optimise_constant_accessor method's results. Exposed dynamic/static details in the possible_accessor_types ASTVisitor method's results. Improved frame stack visualisation in the RSVP machine. diff -r 5c5cddf971cb -r 4a8dd27c2994 TO_DO.txt --- a/TO_DO.txt Sat Feb 26 01:12:25 2011 +0100 +++ b/TO_DO.txt Sun Feb 27 02:45:38 2011 +0100 @@ -2,9 +2,15 @@ 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 instance definition of __class__ should be stored in the object table. + + Each common definition for instances of a class shall be stored as the first + attribute of each class. - The object-relative definition of __class__ should be stored in the object table. + Access to __class__ has to be detected and a special instruction (sequence) + generated to handle the class/instance logic. + + The type class should be exposed as each class's __class__ attribute. 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. diff -r 5c5cddf971cb -r 4a8dd27c2994 docs/concepts.txt --- a/docs/concepts.txt Sat Feb 26 01:12:25 2011 +0100 +++ b/docs/concepts.txt Sun Feb 27 02:45:38 2011 +0100 @@ -192,8 +192,8 @@ A limitation of this representation is that instance attributes may not shadow class attributes: if an attribute with a given name is not defined on an instance, an attribute with the same name cannot be provided by the class of -the instance or any superclass of the instance's class. The exception to this -restriction is the __class__ attribute, as described below. +the instance or any superclass of the instance's class. This impacts the +provision of the __class__ attribute, as described below. The table can be compacted using a representation known as a displacement list (referred to as an object list in this context): @@ -236,20 +236,30 @@ 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. Using a common object-relative -location of 0 permits the first attribute to be accessed via an object address -regardless of whether a class or instance is involved. +In Python 2.x, at least with new-style classes, instances have __class__ +attributes which indicate the class from which they have been instantiated, +whereas classes have __class__ attributes which reference the type class. +With the object table, it is not possible to provide absolute addresses which +can be used for both classes and instances, since this would result in classes +and instances having the same class, and thus the class of a class would be +the class itself. -Obviously, this requires both classes and instances to retain an attribute -location specifically to hold the value appropriate for each object type, -whereas a scheme which omits the __class__ attribute on classes would be able -to employ an absolute address in the table and maintain only a single address -to refer to the class for all instances. +One solution is to use object-relative values in the table so that referencing +the __class__ attribute of an instance produces a value which can be combined +with an instance's address to yield the address of the attribute, which itself +refers to the instance's class, whereas referencing the __class__ attribute of +a class produces a similar object-relative value that is combined with the +class's address to yield the address of the attribute, which itself refers to +the special type class. + +Obviously, the above solution requires both classes and instances to retain an +attribute location specifically to hold the value appropriate for each object +type, whereas a scheme which omits the __class__ attribute on classes would be +able to employ an absolute address in the table and maintain only a single +address to refer to the class for all instances. The only problem with not +providing a sensible __class__ attribute entry for classes would be the need +for special treatment of __class__ to prevent inappropriate consultation of +the table for classes. Comparing Tables as Matrices with Displacement Lists ---------------------------------------------------- @@ -289,11 +299,11 @@ Header.................................................... Attributes................. - Identifier Identifier Address Identifier Size Object Object ... + Identifier Identifier Address Identifier Size Object ... - 0 1 2 3 4 5 6 7 - classcode attrcode/ invocation funccode size __class__ attribute ... - instance reference reference reference + 0 1 2 3 4 5 6 + classcode attrcode/ invocation funccode size attribute ... + instance reference reference status Classcode @@ -358,41 +368,52 @@ Class C: - 0 1 2 3 4 5 6 7 - classcode (unused) __new__ funccode size class type attribute ... - for C reference for reference reference + 0 1 2 3 4 5 6 + classcode (unused) __new__ funccode size attribute ... + for C reference for reference instantiator Instance of C: - 0 1 2 3 4 5 6 7 - classcode attrcode C.__call__ funccode size class C attribute ... - for C for C reference for reference reference + 0 1 2 3 4 5 6 + classcode attrcode C.__call__ funccode size attribute ... + for C for C reference for reference (if exists) C.__call__ Function f: - 0 1 2 3 4 5 6 7 - classcode attrcode code funccode size class attribute ... - for for reference function (default) - function function reference reference + 0 1 2 3 4 5 6 + classcode attrcode code funccode size attribute ... + for for reference (default) + function function reference Module m: - 0 1 2 3 4 5 6 7 - classcode attrcode (unused) (unused) (unused) module type attribute ... - for m for m reference (global) - reference + 0 1 2 3 4 5 6 + classcode attrcode (unused) (unused) (unused) attribute ... + for m for m (global) + reference The __class__ Attribute ----------------------- -All objects support the __class__ attribute and this is illustrated above with -the first attribute. +All objects should support the __class__ attribute, and in most cases this is +done using the object table, yielding a common address for all instances of a +given class. + +Function: refers to the function class +Instance: refers to the class instantiated to make the object + +The object table cannot support two definitions simultaneously for both +instances and their classes. Consequently, __class__ access on classes must be +tested for and a special result returned. Class: refers to the type class (type.__class__ also refers to the type class) -Function: refers to the function class -Instance: refers to the class instantiated to make the object + +For convenience, the first attribute of a class will be the common __class__ +attribute for all its instances. As noted above, direct access to this +attribute will not be possible for classes, and a constant result will be +returned instead. Lists and Tuples ---------------- @@ -539,8 +560,6 @@ resulting instance, along with locations for the attributes of the instance. Since the instance header contains data common to all instances of a class, a template header is copied to the start of the newly reserved memory region. -The __class__ attribute is also an essential part of instances, and this is -also copied to the new memory region. Register Usage ============== diff -r 5c5cddf971cb -r 4a8dd27c2994 lib/builtins.py --- a/lib/builtins.py Sat Feb 26 01:12:25 2011 +0100 +++ b/lib/builtins.py Sun Feb 27 02:45:38 2011 +0100 @@ -619,8 +619,8 @@ function AttributeError #IndexError -NoneType -NotImplementedType +#NoneType +#NotImplementedType #StopIteration TypeError @@ -628,6 +628,7 @@ #ellipsis #list tuple +type #xrange # vim: tabstop=4 expandtab shiftwidth=4 diff -r 5c5cddf971cb -r 4a8dd27c2994 micropython/common.py --- a/micropython/common.py Sat Feb 26 01:12:25 2011 +0100 +++ b/micropython/common.py Sun Feb 27 02:45:38 2011 +0100 @@ -114,12 +114,13 @@ """ Given annotations made during the inspection process, return all possible - types for a 'node' involved in attribute access, or return None if no - annotations are available. + type names and indications of static usage for a 'node' involved in + attribute access. """ + target_names = set() + if hasattr(node, "_attrusers"): - target_names = set() # Visit each attribute user. @@ -130,12 +131,9 @@ 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) + target_names.add((target_name, is_static)) - return target_names - - else: - return None + return target_names def used_by_unit(node): diff -r 5c5cddf971cb -r 4a8dd27c2994 micropython/data.py --- a/micropython/data.py Sat Feb 26 01:12:25 2011 +0100 +++ b/micropython/data.py Sun Feb 27 02:45:38 2011 +0100 @@ -958,7 +958,7 @@ as a class or a module. """ - return isinstance(self.parent, (Class, Module)) and self.name != "__class__" + return isinstance(self.parent, (Class, Module)) def defines_ambiguous_class(self): @@ -1159,20 +1159,18 @@ self.local_usage = 0 self.all_local_usage = 0 - # 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__") + # Add an attribute to this class for use by instances. + + self.set("__class__", self) def set_context(self, parent, module, node): + + "Set the 'parent', 'module' and 'node' of a class created in advance." + self.parent = parent self.module = module self.astnode = node - self.initialise_class_attribute() - self.add_instance_attribute("__class__") - def reset_caches(self): "Reset the caches." @@ -1398,13 +1396,7 @@ # instance. for name in self.instattr: - - # Special case: __class__ has to be at position 0. - - if name == "__class__": - instattr[name] = set([0]) - else: - instattr[name] = set() # position not yet defined + instattr[name] = set() # position not yet defined reversed_bases = self.bases[:] reversed_bases.reverse() @@ -1498,8 +1490,8 @@ 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. + Note that __class__ acts like a class attribute for both instances and + classes, and must be able to convey distinct values. """ if self.allattr is None: @@ -1515,15 +1507,13 @@ "A special class for the type class." - def initialise_class_attribute(self): - self.set("__class__", self) + pass class CommonClass(Class): "An inspected class." - def initialise_class_attribute(self): - self.set("__class__", type_class) + pass class Function(NamespaceDict, Naming, Constant): diff -r 5c5cddf971cb -r 4a8dd27c2994 micropython/inspect.py --- a/micropython/inspect.py Sat Feb 26 01:12:25 2011 +0100 +++ b/micropython/inspect.py Sun Feb 27 02:45:38 2011 +0100 @@ -453,7 +453,13 @@ # Get the attribute and record its usage. if isinstance(value, (Class, Module)): - attr = value.get(attrname) + + # Check for class.__class__. + + if attrname == "__class__" and isinstance(value, Class): + attr = type_class + else: + attr = value.get(attrname) self.use_specific_attribute(value.full_name(), attrname) elif isinstance(value, UnresolvedName): diff -r 5c5cddf971cb -r 4a8dd27c2994 micropython/opt.py --- a/micropython/opt.py Sat Feb 26 01:12:25 2011 +0100 +++ b/micropython/opt.py Sun Feb 27 02:45:38 2011 +0100 @@ -307,7 +307,7 @@ """ Where the object whose attribute is being accessed is constant, provide - information about its full name. + information about the object and its full name. """ if self.should_optimise_constant_accessor() and self.have_constant_input(): @@ -323,11 +323,11 @@ if target is None: return None # no clearly defined target elif isinstance(target, Const): - return target.value_type_name() + return target, target.value_type_name() elif isinstance(target, Instance): return None # skip production of optimised code else: - return target.full_name() + return target, target.full_name() else: return None diff -r 5c5cddf971cb -r 4a8dd27c2994 micropython/rsvp.py --- a/micropython/rsvp.py Sat Feb 26 01:12:25 2011 +0100 +++ b/micropython/rsvp.py Sun Feb 27 02:45:38 2011 +0100 @@ -85,7 +85,7 @@ # Include the instance template and __class__ attribute in the size. - return RSVPObject.set_location(self, location + 2, with_builtins) + return RSVPObject.set_location(self, location + 1, with_builtins) def finalise_location(self, with_builtins): self._finalise_location(with_builtins) @@ -120,17 +120,10 @@ attrcode, # is instance call_method_code_location, item.full_name(), - len(item.instance_attributes()) + 1, # size + len(item.instance_attributes()), # size (excluding __class__) call_method_funccode # funccode ), - # The __class__ attribute for instances. - - DataValue( - PlaceholderContext, - item.location - ), - # Class... DataObject( @@ -149,33 +142,19 @@ def set_location(self, location, with_builtins): location = RSVPObject.set_location(self, location, with_builtins) - - # Include the __class__ attribute in the size. - - return location + 1 + len(self.raw_data()) + return location + len(self.raw_data()) def as_raw(self, objtable, paramtable, with_builtins): item = self.item classcode = objtable.as_list().get_code(item.value_type_name()) attrcode = objtable.get_index(item.value_type_name()) - name_parts = item.value_type_name_parts() - attr = objtable.access(*name_parts) - cls = attr.get_value() - return [ DataObject( classcode, attrcode, # is instance None, item.value_type_name(), - 3 # size (header plus __class__ plus data) - ), - - # The __class__ attribute for instances. - - DataValue( - PlaceholderContext, - cls.location + 2 # size (header plus data) ) # NOTE: The RSVP library needs changing if more attributes are added @@ -206,27 +185,23 @@ if not self.is_generated(with_builtins): item.code_location = item.full_name() - # Skip __class__ plus any defaults for static functions. + # Skip any defaults for static functions. elif not item.is_dynamic(): - item.code_location = location + 1 + len(item.defaults) + item.code_location = location + len(item.defaults) - # Skip __class__ plus any defaults for dynamic functions. + # Skip any defaults for dynamic functions. else: - item.code_location = location + 1 + item.code_location = location - # Include the __class__ attribute. - - return location + 1 + return location def finalise_location(self, with_builtins): self._finalise_location(with_builtins) def as_raw(self, objtable, paramtable, with_builtins): item = self.item - attr = objtable.access("__builtins__", "function") - cls = attr.get_value() # NOTE: Need class and parameter details! Should arguably be an instance of types.FunctionType. return [ DataObject( @@ -236,13 +211,6 @@ "__builtins__.function", len(item.defaults) + 1, # size (not accurate for lambda functions before instantiation) paramtable.as_list().get_code(item.full_name()) # funccode - ), - - # The __class__ attribute for functions. - - DataValue( - PlaceholderContext, - cls.location ) ] diff -r 5c5cddf971cb -r 4a8dd27c2994 micropython/trans.py --- a/micropython/trans.py Sat Feb 26 01:12:25 2011 +0100 +++ b/micropython/trans.py Sun Feb 27 02:45:38 2011 +0100 @@ -49,7 +49,7 @@ "Make an exception of the given 'name' using 'node'." - # NOTE: Reserving an attribute plus __class__. + # NOTE: Reserving an attribute. self.make_instance(self.get_builtin_class(name, node), 1) @@ -415,13 +415,24 @@ # Where the last operation (defining the attribute owner) yields a # constant... - target_name = self.optimiser.optimise_constant_accessor() + target_plus_name = self.optimiser.optimise_constant_accessor() # Only try and discover the position if the target can be resolved. # Since instances cannot be constants, this involves classes and # modules. + # It is acceptable to replace the instruction providing the constant + # input because doing so does not lose any input information required by + # the replacement instructions. - if target_name is not None: + if target_plus_name is not None: + target, target_name = target_plus_name + + # Check for class.__class__. + + if attrname == "__class__": + if isinstance(target, Class): + self.replace_active_value(LoadAddress(self.get_builtin("type", node))) + return # Access the object table to get the attribute. @@ -432,10 +443,8 @@ # Produce a suitable instruction. - if attr.is_static_attribute() and AddressInstruction is not None: + if 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) @@ -517,7 +526,14 @@ target_names = self.possible_accessor_types(node) if target_names is not None and len(target_names) == 1: - target_name = list(target_names)[0] + target_name, is_static = list(target_names)[0] + + # Check for class.__class__. + + if attrname == "__class__": + if is_static: + self.load_builtin("type", node) + return # Access the object table to get the attribute. @@ -541,6 +557,27 @@ return + # Check for class.__class__. + + if attrname == "__class__": + + # Remember the accessor. + + temp_accessor = self.optimiser.optimise_temp_storage() + + attr_block = self.new_block() + end_block = self.new_block() + + self.new_op(CheckClass()) + self.new_op(JumpIfFalse(attr_block)) + self.load_builtin("type", node) + self.new_op(Jump(end_block)) + self.set_block(attr_block) + + # Recall the accessor. + + self.new_op(temp_accessor) + # Otherwise, perform a normal operation. try: @@ -564,6 +601,11 @@ else: self.new_op(AttrIndexInstruction(index)) + # Where __class__ was involved, define the start of the following code. + + if attrname == "__class__": + self.set_block(end_block) + # Invocations involve the following: # # 1. Reservation of a frame for the arguments @@ -1385,14 +1427,14 @@ "Make a tuple using the given program 'node'." - # Reserve space for __class__ plus the elements themselves. + # Reserve space for the elements themselves. - self.make_instance(self.get_builtin_class("tuple", node), len(node.nodes) + 1) + self.make_instance(self.get_builtin_class("tuple", node), len(node.nodes)) temp = self.get_temp() - # Store using 1-based index values, since __class__ should be at position 0. + # Store using 0-based index values. - self._populateSequence(temp, node, 1) + self._populateSequence(temp, node) self.new_op(temp) self.discard_temp(temp) @@ -1409,12 +1451,12 @@ self.new_op(temp) self.record_value() - # Reserve space for __class__ plus _elements (the fragment reference). + # Reserve space for _elements (the fragment reference). - self.make_instance(self.get_builtin_class("list", node), 2) + self.make_instance(self.get_builtin_class("list", node), 1) list_temp = self.get_temp() self.new_op(list_temp) - self.new_op(StoreAttr(Attr(1, None, None))) + self.new_op(StoreAttr(Attr(0, None, None))) # _elements is at position 0 self.set_source() self.discard_value() diff -r 5c5cddf971cb -r 4a8dd27c2994 rsvp.py --- a/rsvp.py Sat Feb 26 01:12:25 2011 +0100 +++ b/rsvp.py Sun Feb 27 02:45:38 2011 +0100 @@ -158,7 +158,15 @@ def dump(self): print "PC", self.pc, "->", self.load(self.pc) print "PC stack", self.pc_stack - print "Frame stack", self.frame_stack + print "Frame stack:" + if self.local_sp_stack: + start = self.local_sp_stack[0] + for end in self.local_sp_stack[1:]: + print " %2d" % start, self.frame_stack[start:end] + start = end + else: + print " %2d" % start, self.frame_stack[start:] + print print "Local stack pointers", self.local_sp_stack print "Invocation stack pointers", self.invocation_sp_stack print "Handler stack", self.handler_stack @@ -206,12 +214,8 @@ def up(self): retaddr = self.pc_stack[-1] - new_breakpoint = retaddr not in self.breakpoints - if new_breakpoint: - self.set_break(retaddr) + self.set_break(retaddr) self.run() - if new_breakpoint: - self.breakpoints.remove(retaddr) # Internal operations. @@ -469,7 +473,7 @@ size = self.operand value = self.value # NOTE: Referencing the instance template. - addr = self._MakeObject(size, value.ref - 2) + addr = self._MakeObject(size, value.ref - Library.instance_template_size) # Introduce object as context for the new object. self.value = DataValue(addr, addr) @@ -752,7 +756,7 @@ def RaiseException(self): # NOTE: Adding the program counter as the first attribute after __class__. - self.save(self.exception + 2, self.pc) + self.save(self.exception + Library.instance_data_offset, self.pc) # Jumping to the current handler. if self.abort_upon_exception: raise Exception @@ -843,9 +847,6 @@ 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 5c5cddf971cb -r 4a8dd27c2994 rsvplib.py --- a/rsvplib.py Sat Feb 26 01:12:25 2011 +0100 +++ b/rsvplib.py Sun Feb 27 02:45:38 2011 +0100 @@ -3,7 +3,7 @@ """ A native function library for a really simple virtual processor. -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 @@ -28,8 +28,8 @@ # NOTE: These attributes need changing if the instance layout changes. - instance_data_offset = 2 - instance_size = 3 + instance_template_size = instance_data_offset = 1 + instance_size = instance_template_size + 1 def __init__(self, machine, constants): diff -r 5c5cddf971cb -r 4a8dd27c2994 tests/class_class_attr_indirect.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/class_class_attr_indirect.py Sun Feb 27 02:45:38 2011 +0100 @@ -0,0 +1,20 @@ +#!/usr/bin/env python + +class A: + pass + +class B(A): + pass + +class C: + pass + +a = A.__class__ +b = B.__class__ +c = C.__class__ + +result_1 = a is type and 1 or 0 +result_2 = b is type and 2 or 0 +result_3 = c is type and 3 or 0 + +# vim: tabstop=4 expandtab shiftwidth=4