# HG changeset patch # User Paul Boddie # Date 1220225552 -7200 # Node ID f660fe1aac5cf22c911ca934ae65b7dbbe07fa33 # Parent 9fdd2c13dbac42b534940130739aff921a3902ab Added notes about calling initialisers and instantiators, adopting a strategy where instantiation detected at compile-time is performed using an initialiser directly, whereas that detected at run-time is done using an instantiator whose code is now generated in the image. Added a finalise method to the Importer in order to set attribute locations before code generation, since some code (use of initialisers) requires details of a different program unit's locals (although this is actually unnecessary, but done because Attr instances are employed in the generated code). Changed class invocation at compile-time to acquire the new object reference from the frame of an already invoked initialiser just before dropping the frame. Added some support for raw image encoding of classes and functions. Changed JumpWithFrame usage to involve the current callable, not the current value. Added RecoverFrame and AdjustFrame instructions. Improved the tests around instantiation. diff -r 9fdd2c13dbac -r f660fe1aac5c docs/invocation.txt --- a/docs/invocation.txt Wed Aug 27 00:45:14 2008 +0200 +++ b/docs/invocation.txt Mon Sep 01 01:32:32 2008 +0200 @@ -93,11 +93,13 @@ f(obj, 1, 2) # f known as C at compile-time - f -> C.__new__ - don't get any context information - -> any __init__ method will be called from C.__new__ - obj -> argument #1 - 1 -> argument #2 - 2 -> argument #3 + f -> C.__init__ + -> new instance is argument #1 + obj -> argument #2 + 1 -> argument #3 + 2 -> argument #4 + + The new instance must be manually provided as the result after the call. Argument lists for unknown callables: @@ -116,6 +118,24 @@ is instance: no change +Argument lists in instantiators: + + f(obj, 1, 2) # f not known at compile-time + + f -> C.__new__ (known and called at run-time) + + Need to call C.__init__(, obj, 1, 2), preferably with the existing + frame: + + + obj -> argument #1 + 1 -> argument #2 + 2 -> argument #3 + + Then jump without switching frames. + It should be possible to replace the old, tentative context information in the + frame. + Defaults for unknown callables: f(obj) # f not known at compile-time diff -r 9fdd2c13dbac -r f660fe1aac5c docs/structures.txt --- a/docs/structures.txt Wed Aug 27 00:45:14 2008 +0200 +++ b/docs/structures.txt Mon Sep 01 01:32:32 2008 +0200 @@ -166,7 +166,8 @@ 0 1 2 3 4 5 6 classcode attrcode invocation invocation __class__ attribute ... reference #args, reference reference - #defaults + defaults + reference Here, the classcode refers to the attribute lookup table for the object. Since classes and instances share the same classcode, they might resemble the @@ -177,14 +178,16 @@ 0 1 2 3 4 5 6 code for C attrcode __new__ __new__ class type attribute ... for C reference #args, reference reference - #defaults + defaults + reference Instance of C: 0 1 2 3 4 5 6 code for C attrcode C.__call__ C.__call__ class C attribute ... for C reference #args, reference reference - (if exists) #defaults + (if exists) defaults + reference The __new__ reference would lead to code consisting of the following instructions: @@ -203,7 +206,8 @@ 0 1 2 3 4 5 6 code for attrcode code code class attribute ... function for reference #args, function (default) - function #defaults reference reference + function defaults reference reference + reference Here, the code reference would lead to code for the function. Note that the function locals are completely distinct from this structure and are not diff -r 9fdd2c13dbac -r f660fe1aac5c lib/builtins.py --- a/lib/builtins.py Wed Aug 27 00:45:14 2008 +0200 +++ b/lib/builtins.py Mon Sep 01 01:32:32 2008 +0200 @@ -96,6 +96,9 @@ class frozenset(object): def __init__(self, iterable): pass +class function(object): + pass + class int(object): def __init__(self, number_or_string=None): pass def __iadd__(self, other): pass diff -r 9fdd2c13dbac -r f660fe1aac5c micropython/__init__.py --- a/micropython/__init__.py Wed Aug 27 00:45:14 2008 +0200 +++ b/micropython/__init__.py Mon Sep 01 01:32:32 2008 +0200 @@ -103,6 +103,20 @@ else: del self.modules[name] + def finalise(self): + + "Finalise the program." + + for module in self.get_modules(): + + # Fix the attributes. + + module.finalise_attributes() + + for obj in module.all_objects: + if isinstance(obj, (micropython.inspect.Class, micropython.inspect.Function)): + obj.finalise_attributes() + def get_modules(self): "Return all modules known to the importer." @@ -113,8 +127,12 @@ "Return a dictionary mapping modules to structures." + if self.code is not None: + return self.code + objtable = self.get_object_table() paramtable = self.get_parameter_table() + self.finalise() image = [] @@ -129,10 +147,6 @@ if not with_builtins and module.name == "__builtins__": continue - # Fix the attributes. - - module.finalise_attributes() - pos = len(image) # Position the module in the image and make a translation. @@ -156,10 +170,6 @@ for obj in module.all_objects: if isinstance(obj, micropython.inspect.Class): - # Fix the attributes. - - obj.finalise_attributes() - # Position the class in the image. obj.location = pos @@ -175,18 +185,21 @@ image += obj.attributes_as_list() pos += len(attributes.keys()) - # Class-level code is generated separately. - # The code location is set within the code generation - # process for the module. + # Generate the instantiator/initialiser. + # Append the function code to the image. - # NOTE: Generate module and function code here. + instantiator = obj.get_instantiator() + instantiator.code_location = pos + code = trans.get_instantiator_code(obj) + image += code + pos += len(code) + + # Class-level code is generated separately at the module + # level, and the code location is set within the code + # generation process for the module. elif isinstance(obj, micropython.inspect.Function): - # Fix the attributes. - - obj.finalise_attributes() - # Position the function in the image. obj.location = pos diff -r 9fdd2c13dbac -r f660fe1aac5c micropython/ast.py --- a/micropython/ast.py Wed Aug 27 00:45:14 2008 +0200 +++ b/micropython/ast.py Mon Sep 01 01:32:32 2008 +0200 @@ -138,6 +138,44 @@ self.unit.temp_usage = self.max_temp_position + 1 return self.code + def get_instantiator_code(self, cls): + + "Return the code for the given class 'cls'." + + self.unit = cls.get_instantiator() + self.code = [] + self.temp_positions = set() + self.max_temp_position = -1 + + init_method = cls.get_init_method() + + # Convert this frame back to being an invocation frame. + + self.new_op(RecoverFrame()) + + # Fix the current frame to include a new storage slot at the beginning. + + self.new_op(AdjustFrame(-1)) + + # Make an object. + + self.new_op(MakeObject(len(cls.instance_attributes()))) + self.new_op(StoreFrame(0)) + + # Invoke the appropriate initialiser. + + self.new_op(LoadAddress(init_method)) + self.new_op(LoadCallable()) + self.new_op(JumpWithFrame()) + + # Store the object as the result. + + self.new_op(LoadName(init_method.all_locals()["self"])) # load the context in the invocation frame + self.new_op(StoreResult()) + self.new_op(Return()) + + return self.code + # Name-related methods. def get_scope(self, name): @@ -261,6 +299,9 @@ if temp_position in self.temp_positions: self.temp_positions.remove(temp_position) + def set_frame_usage(self, node, extend): + extend.attr = self.max_temp_position + node.unit.local_usage # NOTE: See get_code for similar code. + # Code writing methods. def new_op(self, op): @@ -390,8 +431,7 @@ LoadCallable, TestIdentity, TestIdentityAddress, CheckSelf, # as one of the operands CheckFrame, - LoadContext, # as the object providing the result - JumpWithFrame # as the target + LoadContext # as the object providing the result )) def _is_resultant_no_operation(self, instruction): @@ -532,15 +572,6 @@ target = last.attr.value context = last.attr.context - # Handle calls to classes. - - if isinstance(target, Class): - target = target.get_instantiator() - context = Undefined() - - # A special context is chosen to avoid generating unnecessary - # context loading and checking instructions. - return target, context else: return None @@ -785,7 +816,7 @@ target, context, temp = self._generateCallFuncContext() self._generateCallFuncArgs(target, context, temp, args, node) - return temp + return temp, target def _generateCallFuncContext(self): @@ -803,6 +834,8 @@ target, context = None, None # Store the target in temporary storage for subsequent referencing. + # NOTE: This may not be appropriate for class invocations + # NOTE: (instantiation). temp = self._optimise_temp_storage() @@ -814,6 +847,13 @@ self.new_op(LoadContext()) self.new_op(StoreFrame(0)) + # For known instantiations, provide a new object as the first argument + # to the __init__ method. + + elif isinstance(target, Class): + self.new_op(MakeObject(len(target.instance_attributes()))) + self.new_op(StoreFrame(0)) + # Otherwise omit the context. else: @@ -849,9 +889,22 @@ if target is None or isinstance(context, Instance): ncontext = 1 expect_context = 0 + + # Handle calls to classes. + + elif isinstance(target, Class): + ncontext = 1 + expect_context = 0 + target = target.get_init_method() + + # Method calls via classes. + elif isinstance(context, Class): ncontext = 0 expect_context = 1 + + # Function calls. + else: ncontext = 0 expect_context = 0 @@ -991,7 +1044,7 @@ raise TranslateError(self.module.full_name(), node, "Argument %r not supplied for %r: need at least %d argument(s)." % (i+1, target.name, nargs_min)) - nargs = len(args) + nargs = frame_pos if nargs > nargs_max and not target.has_star and not target.has_dstar: raise TranslateError(self.module.full_name(), node, @@ -1049,11 +1102,15 @@ self.new_op(LoadAddress(target.default_attrs[pos - nargs_min])) self.new_op(StoreFrame(pos)) - def _doCallFunc(self, instruction): + def _doCallFunc(self, instruction, target=None): "Make the invocation." - self.new_op(instruction) + if isinstance(target, Class): + self.new_op(LoadAddress(target.get_init_method())) + else: + self.new_op(instruction) + self.new_op(LoadCallable()) self.new_op(JumpWithFrame()) def _endCallFuncArgs(self, nargs): @@ -1063,10 +1120,13 @@ self.frame_makers[-1].attr = nargs self.frame_makers.pop() - def _endCallFunc(self, instruction=None, load_result=1): + def _endCallFunc(self, instruction=None, target=None, load_result=1): "Finish the invocation and tidy up afterwards." + if isinstance(target, Class): + self.new_op(LoadName(target.get_init_method().all_locals()["self"])) # load the context in the invocation frame + self.new_op(StoreResult()) self.new_op(DropFrame()) if load_result: self.new_op(LoadResult()) @@ -1482,9 +1542,9 @@ self._startCallFunc() self.dispatch(node.node) - temp = self._generateCallFunc(node.args, node) - self._doCallFunc(temp) - self._endCallFunc(temp) + temp, target = self._generateCallFunc(node.args, node) + self._doCallFunc(temp, target) + self._endCallFunc(temp, target) def visitClass(self, node): @@ -1632,9 +1692,9 @@ self._startCallFunc() self.dispatch(node.list) self._generateAttr(node, "__iter__", self.attribute_load_instructions) - temp = self._generateCallFunc([], node) - self._doCallFunc(temp) - self._endCallFunc(temp) + temp, target = self._generateCallFunc([], node) + self._doCallFunc(temp, target) + self._endCallFunc(temp, target) temp_iterator = self._optimise_temp_storage() @@ -1647,9 +1707,9 @@ self._startCallFunc() self.new_op(temp_iterator) self._generateAttr(node, "next", self.attribute_load_instructions) - temp = self._generateCallFunc([], node) - self._doCallFunc(temp) - self._endCallFunc(temp) + temp, target = self._generateCallFunc([], node) + self._doCallFunc(temp, target) + self._endCallFunc(temp, target) # Test for StopIteration. @@ -1717,7 +1777,7 @@ self.new_op(Return()) - extend.attr = self.max_temp_position + node.unit.local_usage # NOTE: See get_code for similar code. + self.set_frame_usage(node, extend) def visitGenExpr(self, node): raise TranslationNotImplementedError(self.module.full_name(), node, "GenExpr") diff -r 9fdd2c13dbac -r f660fe1aac5c micropython/data.py --- a/micropython/data.py Wed Aug 27 00:45:14 2008 +0200 +++ b/micropython/data.py Mon Sep 01 01:32:32 2008 +0200 @@ -436,10 +436,12 @@ "Return a function which can be used to instantiate the class." if self.instantiator is None: - init_method = self.all_class_attributes()["__init__"].value - self.instantiator = init_method.function_from_method() + self.instantiator = self.get_init_method().function_from_method() return self.instantiator + def get_init_method(self): + return self.all_class_attributes()["__init__"].value + # Class-specific methods. def add_base(self, base): diff -r 9fdd2c13dbac -r f660fe1aac5c micropython/rsvp.py --- a/micropython/rsvp.py Wed Aug 27 00:45:14 2008 +0200 +++ b/micropython/rsvp.py Mon Sep 01 01:32:32 2008 +0200 @@ -20,13 +20,39 @@ """ from micropython.common import Label -from micropython.data import Attr, Const, Instance +from micropython.data import Attr, Class, Const, Function -def raw(code): +def raw(code, objtable, paramtable): new_code = [] for item in code: if isinstance(item, Attr): - new_code.append((item.context and item.context.location, item.value and item.value.location)) # no useful context is provided + new_code.append(( + item.context and item.context.location, + item.value and item.value.location # no useful context is provided + )) + elif isinstance(item, Class): + # NOTE: Need initialiser details! + new_code.append(( + objtable.as_list().get_code(item.full_name()), + objtable.get_index(item.full_name()), + item.get_instantiator().code_location, + len(item.get_instantiator().positional_names) + )) + elif isinstance(item, Const): + # NOTE: Need class details! + new_code.append(( + None, #objtable.as_list().get_code(item.full_name()), + None, #objtable.get_index(item.full_name()), + None, None + )) + elif isinstance(item, Function): + # NOTE: Need class and parameter details! Should arguably be types.FunctionType. + new_code.append(( + objtable.as_list().get_code("__builtins__.function"), + objtable.get_index("__builtins__.function"), + item.code_location, + len(item.positional_names) + )) else: new_code.append(item) return new_code @@ -66,6 +92,9 @@ else: return "" + def get_operand(self): + return None + class FrameRelativeInstruction(Instruction): "An instruction operating on the current frame." @@ -175,6 +204,7 @@ class MakeFrame(Immediate): "Make a new invocation frame." class DropFrame(Instruction): "Drop an invocation frame." +class RecoverFrame(Instruction): "Recover the current frame as an invocation frame." class StoreFrame(Immediate): "Store the current value as an argument for the parameter with the given position." class StoreFrameIndex(Immediate): "Store the current value as an argument for the parameter with the given index." class LoadCallable(Instruction): "Load the target of an invocation." @@ -185,8 +215,9 @@ # Invocation-related instructions, using a special result "register". -class JumpWithFrame(Instruction): "Jump, adopting the invocation frame, to the callable found as the current value." +class JumpWithFrame(Instruction): "Jump, adopting the invocation frame, to the current callable." class ExtendFrame(Immediate): "Extend the current frame for temporary storage use." +class AdjustFrame(Immediate): "Adjust the current frame for corrected invocations." class Return(Instruction): "Return from a subprogram." class LoadResult(Instruction): "Load into the current value a returned value." class StoreResult(Instruction): "Store the current value as a value to be returned." diff -r 9fdd2c13dbac -r f660fe1aac5c micropython/table.py --- a/micropython/table.py Wed Aug 27 00:45:14 2008 +0200 +++ b/micropython/table.py Mon Sep 01 01:32:32 2008 +0200 @@ -46,6 +46,12 @@ def __getitem__(self, i): return self.displaced[i] + def get_code(self, name): + + "Return the code/offset of the given 'name'." + + return self.offsets.get(name) + # Simulation methods. def access(self, objname, attrname): @@ -222,15 +228,6 @@ self.names = self.names or list(self.attributes) return self.names - def get_code(self, name): - - "Return the code of the given 'name'." - - try: - return self.object_names().index(name) - except ValueError: - raise TableError, "Name %r is not registered as an object in the table." % name - def get_index(self, name): "Return the index of the given 'name'." @@ -320,6 +317,12 @@ return self.displaced_list + def as_raw(self): + + "Return the raw contents of the table as a list of values." + + return self.as_list().as_raw() + class ObjectTable(Table): "An object table." diff -r 9fdd2c13dbac -r f660fe1aac5c rsvp.py --- a/rsvp.py Wed Aug 27 00:45:14 2008 +0200 +++ b/rsvp.py Mon Sep 01 01:32:32 2008 +0200 @@ -90,6 +90,23 @@ self.result = None self.exception = None + def dump(self): + print "PC", self.pc + print "PC stack", self.pc_stack + print "Frame stack", self.frame_stack + print "Local stack pointers", self.local_sp_stack + print "Invocation stack pointers", self.invocation_sp_stack + print "Handler stack", self.handler_stack + print + print "Instruction", self.instruction + print "Operand", self.operand + print "Value", self.value + print "Status", self.status + print "Source", self.source + print "Callable", self.callable + print "Result", self.result + print "Exception", self.exception + def load(self, address): "Return the value at the given 'address'." @@ -149,18 +166,14 @@ "Execute code in the memory at the current PC address." self.instruction = self.load(self.pc) - self.operand = self.instruction.get_operand() - - instruction_name = self.instruction.__class__.__name__ - if self.debug: - print "%8d %s" % (self.pc, instruction_name) - - method = self.get_method(instruction_name) # Process any inputs of the instruction. self.process_inputs() - next_pc = method() + + # Perform the instruction itself. + + next_pc = self.perform(self.instruction) # Update the program counter. @@ -169,15 +182,26 @@ else: self.pc = next_pc - def get_method(self, instruction_name): + def get_method(self, instruction): + + "Return the handler method for the given 'instruction'." - "Return the handler method for the given 'instruction_name'." - + instruction_name = instruction.__class__.__name__ + if self.debug: + print "%8d %s" % (self.pc, instruction_name) method = getattr(self, instruction_name, None) if method is None: raise IllegalInstruction, (self.pc, instruction_name) return method + def perform(self, instruction): + + "Perform the 'instruction', returning the next PC value or None." + + self.operand = instruction.get_operand() + method = self.get_method(instruction) + return method() + def process_inputs(self): """ @@ -188,8 +212,7 @@ for input in (self.instruction.input, self.instruction.source): if input is not None: - method = self.get_method(input) - method() + self.perform(input) def jump(self, addr, next): @@ -294,6 +317,9 @@ frame = self.invocation_sp_stack.pop() self.frame_stack = self.frame_stack[:frame] # reset stack before call + def RecoverFrame(self): + self.local_sp_stack.pop() + def StoreFrame(self): frame = self.invocation_sp_stack[-1] # different from the current frame after MakeFrame self.frame_stack[frame + self.operand] = self.value @@ -372,6 +398,14 @@ frame = self.local_sp_stack[-1] frame.extend([None] * self.operand) + def AdjustFrame(self): + if self.operand > 0: + frame.append([None] * self.operand) + elif self.operand == -1: + self.invocation_sp_stack[-1] -= 1 + else: + raise Exception, "AdjustFrame %r" % self.operand + def Return(self): self.pc = self.pull_pc() diff -r 9fdd2c13dbac -r f660fe1aac5c test.py --- a/test.py Wed Aug 27 00:45:14 2008 +0200 +++ b/test.py Mon Sep 01 01:32:32 2008 +0200 @@ -28,8 +28,14 @@ "".join(entry and "#" or "_" for entry in table_slice) def machine(importer): - rc = raw(importer.code) - rm = rsvp.RSVPMachine(rc) + print "Making the image..." + make(importer) + rc = raw(importer.get_image()) + print "Getting raw tables..." + objtable = importer.get_object_table().as_raw() + paramtable = importer.get_parameter_table().as_raw() + print "Initialising the machine..." + rm = rsvp.RSVPMachine(rc, objtable, paramtable) rm.pc = importer.code_location return rm diff -r 9fdd2c13dbac -r f660fe1aac5c tests/call_method.py --- a/tests/call_method.py Wed Aug 27 00:45:14 2008 +0200 +++ b/tests/call_method.py Mon Sep 01 01:32:32 2008 +0200 @@ -1,6 +1,9 @@ #!/usr/bin/env python class C: + def __init__(self, x): + pass + def f(self, a, b, c): self.g(a) m = self.g @@ -12,7 +15,7 @@ def h(self, p): pass -c = C() +c = C(123) c.f(1, 2, 3) f = c.f diff -r 9fdd2c13dbac -r f660fe1aac5c tests/instance.py --- a/tests/instance.py Wed Aug 27 00:45:14 2008 +0200 +++ b/tests/instance.py Mon Sep 01 01:32:32 2008 +0200 @@ -11,8 +11,18 @@ def __init__(self, x): pass +class F: + def __init__(self, x=1): + pass + c = C() +X = C +x = X() d = D() e = E(1) +f = F() +f = F(2) +X = F +x = X() # vim: tabstop=4 expandtab shiftwidth=4