# HG changeset patch # User Paul Boddie # Date 1210629256 -7200 # Node ID 12f29a090aa753f077acd8196a22e23b39884da0 # Parent 5076b9976f4d432c0aed193a1edcd46520523b59 Added notes about the operations performed by various instructions in relation to the context of loaded data. Attempted to make invocation code generation slightly more modular. Attempted to fix argument production with respect to invocations which have an explicit context as their first argument. Made Const a subclass of Instance. Added a context attribute to instructions which should be used when examining instructions in certain optimisation situations. diff -r 5076b9976f4d -r 12f29a090aa7 README.txt --- a/README.txt Sun May 11 21:50:30 2008 +0200 +++ b/README.txt Mon May 12 23:54:16 2008 +0200 @@ -64,6 +64,41 @@ There may be some scope for simplifying the above, to the detriment of Python compatibility, since the unbound vs. bound methods situation can be confusing. +Manipulating Values +------------------- + +According to the table describing value acquisition, different instructions +must implement different operations when acquiring values: + + Instruction Purpose Context Operations + ----------- ------- ------------------ + + LoadConst Load class, function, Combine null context with loaded + module, constant object + + LoadAddress Load attribute from Classes, functions and modules + known object stored as cause the loaded attribute to be + an attribute retrieved unchanged... + whereas constants (representing + instances) cause the constant to + override the attribute's own + context + + LoadAttr Load attribute from Combine the instance as context + instance stored as an with the object from the attribute + attribute + + LoadAttrIndex Load attribute from Classes, functions and modules as + unknown object stored the unknown object accessor cause + as an attribute the loaded attribute to be + retrieved unchanged... + whereas instances cause the + accessor to override the + attribute's own context + +Note that of the above context operations, only the decision logic in +connection with LoadAttrIndex is performed at run-time. + Objects ------- diff -r 5076b9976f4d -r 12f29a090aa7 micropython/ast.py --- a/micropython/ast.py Sun May 11 21:50:30 2008 +0200 +++ b/micropython/ast.py Mon May 12 23:54:16 2008 +0200 @@ -265,44 +265,50 @@ if isinstance(target, Const): target_name = target.value_type_name() + elif isinstance(target, Instance): + target_name = None # skip production of optimised code else: target_name = target.full_name() - # Access the object table to get the attribute position. + # Only try and discover the position if the target can be resolved. - try: - table_entry = self.objtable.table[target_name] - except KeyError: - raise TranslateError(self.module.full_name(), node, - "No object entry exists for target %r." % target_name) + if target_name is not None: + + # Access the object table to get the attribute position. - try: - pos = table_entry[attrname] - except KeyError: - raise TranslateError(self.module.full_name(), node, - "No attribute entry exists for name %r in target %r." % (attrname, target_name)) + try: + table_entry = self.objtable.table[target_name] + except KeyError: + raise TranslateError(self.module.full_name(), node, + "No object entry exists for target %r." % target_name) - # Produce a suitable instruction. + try: + pos = table_entry[attrname] + except KeyError: + raise TranslateError(self.module.full_name(), node, + "No attribute entry exists for name %r in target %r." % (attrname, target_name)) - self.replace_op(AddressInstruction(pos)) + # Produce a suitable instruction. + + self.replace_op(AddressInstruction(pos)) + return # Where the last operation involves the special 'self' name, check to # see if the attribute is acceptably positioned and produce a direct # access to the attribute. - elif self._optimise_self_access(attrname, AttrInstruction): - pass + elif self._optimise_self_access(attrname, (AddressInstruction, AttrInstruction)): + return # Otherwise, perform a normal operation. - else: - try: - index = self.objtable.get_index(attrname) - except self.objtable.TableError: - raise TranslateError(self.module.full_name(), node, - "No attribute entry exists for name %r." % attrname) + try: + index = self.objtable.get_index(attrname) + except self.objtable.TableError: + raise TranslateError(self.module.full_name(), node, + "No attribute entry exists for name %r." % attrname) - self.new_op(AttrIndexInstruction(index)) + self.new_op(AttrIndexInstruction(index)) def _startCallFunc(self): @@ -312,7 +318,30 @@ def _generateCallFunc(self, args, node): - # NOTE: Only simple cases are used for optimisations. + """ + Support a generic function invocation using the given 'args', occurring + on the given 'node', where the expression providing the invocation + target has just been generated. + + In other situations, the invocation is much simpler and does not need to + handle the full flexibility of a typical Python invocation. Internal + invocations, such as those employed by operators and certain + control-flow mechanisms, use predetermined arguments and arguably do not + need to support the same things as the more general invocations. + """ + + target, context, temp = self._generateCallFuncContext() + self._generateCallFuncArgs(target, context, args, node) + return temp + + def _generateCallFuncContext(self): + + """ + Produce code which loads and checks the context of the current + invocation, the instructions for whose target have already been + produced, returning a list of instructions which reference the + invocation target. + """ t = self._optimise_known_target() if t: @@ -334,17 +363,39 @@ self.new_op(LoadContext()) self.new_op(CheckContext()) self.new_op(JumpIfTrue(continue_label)) + + # Where the context is inappropriate, drop the incomplete frame and + # raise an exception. + + self.new_op(DropFrame()) self.dispatch(compiler.ast.Name("TypeError")) self.new_op(RaiseException()) self.set_label(continue_label) else: pass # NOTE: Class methods should be supported. + return target, context, temp + + def _generateCallFuncArgs(self, target, context, args, node): + + """ + Given invocation 'target' and 'context' information, a list of nodes + representing the 'args' (arguments), generate instructions which load + the arguments for the invocation defined by the given 'node'. + """ + # Evaluate the arguments. employed_positions = set() extra_keywords = [] + # NOTE: Fix context for self-accessed methods. + + if context is not None and isinstance(context, Instance): + ncontext = 1 + else: + ncontext = 0 + for frame_pos, arg in enumerate(args): # Handle positional and keyword arguments separately. @@ -385,8 +436,8 @@ # Add space for arguments appearing before this one. - if frame_pos < pos: - self.new_op(ReserveFrame(pos - frame_pos)) + if frame_pos + ncontext < pos: + self.new_op(ReserveFrame(pos - frame_pos - ncontext)) # Generate code for the keyword and the positioning # operation. @@ -396,7 +447,7 @@ # If the position corresponds to the current frame element, # skip generating the store instruction. - if frame_pos > pos: + if frame_pos + ncontext > pos: self.new_op(StoreFrame(pos)) # Otherwise, generate the code needed to obtain the details of @@ -429,9 +480,9 @@ else: self.dispatch(arg) - employed_positions.add(frame_pos) + employed_positions.add(frame_pos + ncontext) - frame_pos = len(args) + frame_pos = len(args) + ncontext # NOTE: Extra keywords are not supported. # NOTE: Somehow, the above needs to be combined with * arguments. @@ -446,7 +497,7 @@ ndefaults = len(target.defaults) nargs_min = nargs_max - ndefaults - for i in range(0, nargs_min): + for i in range(ncontext, nargs_min): if i not in employed_positions: raise TranslateError(self.module.full_name(), node, "Argument %r not supplied for %r: need at least %d arguments." % (i+1, target.name, nargs_min)) @@ -481,8 +532,6 @@ else: self.new_op(CheckFrame()) - return temp - def _endCallFunc(self, temp): "Make the invocation and tidy up afterwards." @@ -659,7 +708,7 @@ if self._should_optimise_known_target() and self._have_known_target(): last = self.last_op() target = last.attr.value - context = last.attr.parent + context = last.context # Handle calls to classes. @@ -674,19 +723,25 @@ else: return None - def _optimise_self_access(self, attrname, instruction): + def _optimise_self_access(self, attrname, classes): """ Where the provided 'attrname' accesses an attribute which occupies the same position in all possible objects which can be accessed, generate an - 'instruction' accessing the attribute directly. + instruction using one of the given 'classes', accessing the attribute + directly. """ + AddressInstruction, AttrInstruction = classes + if self._should_optimise_self_access() and self._have_self_input() and \ not self.unit.is_relocated(attrname): attr = self.unit.parent.all_attributes()[attrname] - self.new_op(instruction(attr)) + if isinstance(attr.parent, Instance): + self.new_op(AttrInstruction(attr)) + else: + self.new_op(AddressInstruction(attr, Instance())) return 1 else: return 0 @@ -737,6 +792,7 @@ temp_method = self._optimise_temp_storage() # Add arguments. + # NOTE: No support for defaults. self.new_ops(temp) # Explicit context as first argument. self._endCallFunc(temp_method) @@ -812,6 +868,7 @@ temp_method = self._optimise_temp_storage() # Add arguments. + # NOTE: No support for defaults. self.new_ops(temp1) # Explicit context as first argument. self.new_ops(temp2) @@ -852,6 +909,7 @@ temp_method = self._optimise_temp_storage() # Add arguments. + # NOTE: No support for defaults. self.new_ops(temp2) # Explicit context as first argument. self.new_ops(temp1) diff -r 5076b9976f4d -r 12f29a090aa7 micropython/data.py --- a/micropython/data.py Sun May 11 21:50:30 2008 +0200 +++ b/micropython/data.py Mon May 12 23:54:16 2008 +0200 @@ -197,13 +197,23 @@ def __repr__(self): return "Attr(%r, %r, %r, %r, %r)" % (self.position, self.parent, self.name, self.value, self.assignments) +# Instances are special in that they need to be wrapped together with context in +# a running program, but they are not generally constant. + +class Instance: + + "A placeholder indicating the involvement of an instance." + + def __repr__(self): + return "Instance()" + class Constant: "A superclass for all constant or context-free structures." pass -class Const(Constant): +class Const(Constant, Instance): "A constant object with no context." @@ -774,14 +784,4 @@ return dict(self) -# Instances are special in that they need to be wrapped together with context in -# a running program, but they are not generally constant. - -class Instance: - - "A placeholder indicating the involvement of an instance." - - def __repr__(self): - return "Instance()" - # vim: tabstop=4 expandtab shiftwidth=4 diff -r 5076b9976f4d -r 12f29a090aa7 micropython/rsvp.py --- a/micropython/rsvp.py Sun May 11 21:50:30 2008 +0200 +++ b/micropython/rsvp.py Mon May 12 23:54:16 2008 +0200 @@ -27,8 +27,12 @@ stack_usage = 0 - def __init__(self, attr=None): + def __init__(self, attr=None, context=None): self.attr = attr + if self.attr is not None and isinstance(self.attr, Attr): + self.context = context or attr.parent + else: + self.context = context def __repr__(self): if self.attr is not None: @@ -114,10 +118,13 @@ # Instructions operating on the value stack. -class LoadConst(StackAdd, Address): "Load the constant, class, function, module from the specified location." class Duplicate(StackAdd, Instruction): "Duplicate the top of the stack." class Pop(StackRemove, Immediate): "Pop entries from the top of the stack." +# Access to stored constant data. + +class LoadConst(StackAdd, Address): "Load the constant, class, function, module from the specified location." + # Access within an invocation frame. class LoadName(StackAdd, SR): "Load the object from the given local attribute/variable." @@ -127,8 +134,7 @@ # Access to address-relative data. -class MakeObject(StackAdd, Instruction): "Make a new object." -# ... DropObject not defined: Assume garbage collection. +class MakeObject(StackAdd, Instruction): "Make a new object. There isn't a complementary DropObject." class LoadAttr(AR): "Load the object from the given attribute." class StoreAttr(StackRemove2, AR): "Store an object in the given attribute." class LoadAttrIndex(Immediate): "Load the object for the attribute with the given index." diff -r 5076b9976f4d -r 12f29a090aa7 rsvp.py --- a/rsvp.py Sun May 11 21:50:30 2008 +0200 +++ b/rsvp.py Mon May 12 23:54:16 2008 +0200 @@ -236,9 +236,10 @@ """ LoadConst addr Load the reference to memory: get the address addr, push the value onto - the stack. + the stack as a value without context. This is useful for loading constants. + NOTE: This assumes that constants are encoded with context. """ addr = self.load(self.pc + 1) diff -r 5076b9976f4d -r 12f29a090aa7 tests/attributes.py --- a/tests/attributes.py Sun May 11 21:50:30 2008 +0200 +++ b/tests/attributes.py Mon May 12 23:54:16 2008 +0200 @@ -6,4 +6,7 @@ def __init__(self, value): self.instattr = value +C +C.clsattr + # vim: tabstop=4 expandtab shiftwidth=4 diff -r 5076b9976f4d -r 12f29a090aa7 tests/call_method.py --- a/tests/call_method.py Sun May 11 21:50:30 2008 +0200 +++ b/tests/call_method.py Mon May 12 23:54:16 2008 +0200 @@ -2,6 +2,12 @@ class C: def f(self, a, b, c): + self.g(a) + + def g(self, x): + C.h(self, x) + + def h(self, p): pass c = C()