micropython

rsvp.py

261:49e650e83030
2009-09-30 Paul Boddie Moved the native function library into a separate module. Added notes about narrowing type candidates when predicting accesses to attributes.
     1 #!/usr/bin/env python     2      3 """     4 A really simple virtual processor employing a simple set of instructions which     5 ignore low-level operations and merely concentrate on variable access, structure     6 access, structure allocation and function invocations.     7      8 Copyright (C) 2007, 2008, 2009 Paul Boddie <paul@boddie.org.uk>     9     10 This program is free software; you can redistribute it and/or modify it under    11 the terms of the GNU General Public License as published by the Free Software    12 Foundation; either version 3 of the License, or (at your option) any later    13 version.    14     15 This program is distributed in the hope that it will be useful, but WITHOUT    16 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS    17 FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more    18 details.    19     20 You should have received a copy of the GNU General Public License along with    21 this program.  If not, see <http://www.gnu.org/licenses/>.    22     23 --------    24     25 The execution model of the virtual processor involves the following things:    26     27   * Memory                          contains constants, global variable    28                                     references and program code    29     30   * PC (program counter) stack      contains the return address associated    31                                     with each function invocation    32     33   * Frame stack                     contains invocation frames in use and in    34                                     preparation plus temporary storage    35     36   * Local frame pointer stack       refers to the frames in the frame stack    37     38   * Invocation frame pointer stack    39     40   * Exception handler stack    41     42   * Exception handler locals stack  refers to the state of the local frame    43                                     pointer stack    44     45   * Exception handler PC stack      refers to the state of the PC stack    46     47   * Registers: current value,    48                boolean status value,    49                source value,    50                current result,    51                current exception,    52                current callable    53 """    54     55 from micropython.program import ReplaceableContext, PlaceholderContext, FragmentObject    56 from rsvplib import Library    57     58 class IllegalInstruction(Exception):    59     pass    60     61 class IllegalAddress(Exception):    62     def __init__(self, address):    63         self.address = address    64     def __repr__(self):    65         return "IllegalAddress(%r)" % self.address    66     def __str__(self):    67         return repr(self)    68     69 class EmptyPCStack(Exception):    70     pass    71     72 class EmptyFrameStack(Exception):    73     pass    74     75 class BreakpointReached(Exception):    76     pass    77     78 class RSVPMachine:    79     80     "A really simple virtual processor."    81     82     def __init__(self, memory, objlist, paramlist, pc=None, debug=0, abort_upon_exception=0):    83     84         """    85         Initialise the processor with a 'memory' (a list of values containing    86         instructions and data), the object and parameter lists 'objlist' and    87         'paramlist', and the optional program counter 'pc'.    88         """    89     90         self.memory = memory    91         self._objlist = objlist    92         self._paramlist = paramlist    93         self.objlist = objlist.as_raw()    94         self.paramlist = paramlist.as_raw()    95         self.library = None    96     97         self.pc = pc or 0    98         self.debug = debug    99         self.abort_upon_exception = abort_upon_exception   100    101         # Stacks.   102    103         self.pc_stack = []   104         self.frame_stack = []   105         self.local_sp_stack = [0]   106         self.invocation_sp_stack = []   107         self.handler_stack = [len(self.memory) - 1] # final handler is the end of the code   108         self.handler_local_sp_stack = []   109         self.handler_pc_stack = []   110    111         # Registers.   112    113         self.instruction = None   114         self.operand = None   115         self.value = None   116         self.status = None   117         self.source = None   118         self.callable = None   119         self.result = None   120         self.exception = None   121    122         # Constants.   123    124         cls = self._get_class("__builtins__", "AttributeError")   125         self.attr_error = cls.location   126         self.attr_error_instance = cls.instance_template_location   127         cls = self._get_class("__builtins__", "TypeError")   128         self.type_error = cls.location   129         self.type_error_instance = cls.instance_template_location   130         cls = self._get_class("__builtins__", "tuple")   131         self.tuple_class = cls.location   132         self.tuple_instance = cls.instance_template_location   133    134         # Debugging attributes.   135    136         self.breakpoints = set()   137    138     def _get_class(self, module, name):   139         return self._objlist.access(module, name).get_value()   140    141     # Debugging methods.   142    143     def dump(self):   144         print "PC", self.pc, "->", self.load(self.pc)   145         print "PC stack", self.pc_stack   146         print "Frame stack", self.frame_stack   147         print "Local stack pointers", self.local_sp_stack   148         print "Invocation stack pointers", self.invocation_sp_stack   149         print "Handler stack", self.handler_stack   150         print "Handler frame stack", self.handler_local_sp_stack   151         print "Handler PC stack", self.handler_pc_stack   152         print   153         print "Instruction", self.instruction   154         print "Operand", self.operand   155         print "Value", self.value   156         print "Status", self.status   157         print "Source", self.source   158         print "Callable", self.callable   159         print "Result", self.result   160         print "Exception", self.exception   161    162     def show(self):   163         self.show_memory(self.memory, 0)   164    165     def show_pc(self, run_in=10):   166         start = max(0, self.pc - run_in)   167         end = self.pc + run_in   168         memory = self.memory[start:end]   169         self.show_memory(memory, start)   170    171     def show_memory(self, memory, start):   172         for i, x in enumerate(memory):   173             location = start + i   174             if location == self.pc:   175                 print "->",   176             else:   177                 print "  ",   178             print "%5d %r" % (location, x)   179    180     def step(self, dump=0):   181         self.execute()   182         self.show_pc()   183         if dump:   184             self.dump()   185    186     def set_break(self, location):   187         self.breakpoints.add(location)   188    189     # Internal operations.   190    191     def load(self, address):   192    193         "Return the value at the given 'address'."   194    195         try:   196             return self.memory[address]   197         except IndexError:   198             raise IllegalAddress(address)   199         except TypeError:   200             raise IllegalAddress(address)   201    202     def save(self, address, value):   203    204         "Save to the given 'address' the specified 'value'."   205    206         try:   207             self.memory[address] = value   208         except IndexError:   209             raise IllegalAddress(address)   210         except TypeError:   211             raise IllegalAddress(address)   212    213     def new(self, size):   214    215         """   216         Allocate space of the given 'size', returning the address of the space.   217         """   218    219         addr = len(self.memory)   220         for i in range(0, size):   221             self.memory.append(None)   222         return addr   223    224     def push_pc(self, data):   225    226         "Push 'data' onto the PC stack."   227    228         self.pc_stack.append(data)   229    230     def pull_pc(self):   231    232         "Pull a value from the PC stack and return it."   233    234         try:   235             return self.pc_stack.pop()   236         except IndexError:   237             raise EmptyPCStack   238    239     def run(self):   240    241         "Execute code in the memory, starting from the current PC address."   242    243         breakpoint = 0   244    245         try:   246             while 1:   247                 self.execute()   248         except EmptyPCStack:   249             pass   250         except BreakpointReached:   251             breakpoint = 1   252    253         print "Execution terminated",   254         if self.exception is not None:   255             ref = self.exception   256             addr = self.load(ref + 1)   257             print "with exception:", self.load(ref)   258             print "At address %d: %r" % (addr, self.load(addr))   259         elif breakpoint:   260             print "with breakpoint."   261             print "At address", self.pc   262         else:   263             print "successfully."   264    265     def test(self, module):   266    267         """   268         Test the code in the memory by running the code and investigating the   269         contents of variables. Use 'module' to identify result variables.   270         """   271    272         self.run()   273         success = 1   274    275         if self.exception is None:   276             for name in module.keys():   277                 if name.startswith("result"):   278                     label, expected = name.split("_")   279                     attr = module[name]   280    281                     # NOTE: Assumptions about headers and content made.   282    283                     attr_location = module.location + 1 + attr.position   284                     context, ref = self.load(attr_location)   285    286                     if ref is not None:   287                         content = self.load(ref + 1)   288                         print label, expected, content   289                         success = success and (int(expected) == content)   290                     else:   291                         print label, expected, "missing"   292                         success = 0   293    294             return success   295         else:   296             return 0   297    298     def execute(self):   299    300         "Execute code in the memory at the current PC address."   301    302         if self.pc in self.breakpoints:   303             self.breakpoints.remove(self.pc)   304             raise BreakpointReached   305    306         self.instruction = self.load(self.pc)   307    308         # Process any inputs of the instruction.   309    310         self.process_inputs()   311    312         # Perform the instruction itself.   313    314         next_pc = self.perform(self.instruction)   315    316         # Update the program counter.   317    318         if next_pc is None:   319             self.pc += 1   320         else:   321             self.pc = next_pc   322    323     def get_method(self, instruction):   324    325         "Return the handler method for the given 'instruction'."   326    327         instruction_name = instruction.__class__.__name__   328         if self.debug:   329             print "%8d %s" % (self.pc, instruction_name)   330         method = getattr(self, instruction_name, None)   331         if method is None:   332             raise IllegalInstruction, (self.pc, instruction_name)   333         return method   334    335     def perform(self, instruction):   336    337         "Perform the 'instruction', returning the next PC value or None."   338    339         self.operand = instruction.get_operand()   340         method = self.get_method(instruction)   341         return method()   342    343     def process_inputs(self):   344    345         """   346         Process any inputs of the current instruction. This permits any directly   347         connected sub-instructions to produce the effects that separate   348         instructions would otherwise have.   349         """   350    351         value = self.value   352         if self.instruction.source is not None:   353             self.perform(self.instruction.source)   354             self.source = self.value   355         self.value = value   356         if self.instruction.input is not None:   357             self.perform(self.instruction.input)   358    359     def jump(self, addr, next):   360    361         """   362         Jump to the subroutine at (or identified by) 'addr'. If 'addr'   363         identifies a library function then invoke the library function and set   364         PC to 'next' afterwards; otherwise, set PC to 'addr'.   365         """   366    367         # Trap library functions introduced through the use of strings instead   368         # of proper locations.   369    370         if isinstance(addr, str):   371             handler = self.library and self.library.native_functions[addr](self.library)   372             if handler is None:   373                 return next   374             else:   375                 return handler   376         else:   377             self.push_pc(self.pc + 1)   378             return addr   379    380     # Instructions.   381    382     def LoadConst(self):   383         self.value = self.operand, self.operand   384    385     def LoadClass(self):   386         self.value = PlaceholderContext, self.operand   387    388     def LoadFunction(self):   389         self.value = ReplaceableContext, self.operand   390    391     def LoadName(self):   392         frame = self.local_sp_stack[-1]   393         self.value = self.frame_stack[frame + self.operand]   394    395     def StoreName(self):   396         frame = self.local_sp_stack[-1]   397         self.frame_stack[frame + self.operand] = self.source # uses the source value   398    399     LoadTemp = LoadName   400    401     def StoreTemp(self):   402         frame = self.local_sp_stack[-1]   403         self.frame_stack[frame + self.operand] = self.value   404    405     def LoadAddress(self):   406         # Preserve context (potentially null).   407         self.value = self.load(self.operand)   408    409     def LoadAddressContext(self):   410         context, ref = self.load(self.operand)   411         inst_context, inst_ref = self.value   412         self.value = inst_ref, ref   413    414     def LoadAddressContextCond(self):   415         context, ref = self.load(self.operand)   416         inst_context, inst_ref = self.value   417         self.value = self._LoadAddressContextCond(context, ref, inst_ref)   418    419     def StoreAddress(self):   420         # Preserve context.   421         self.save(self.operand, self.source)   422    423     def StoreAddressContext(self):   424         # Overwrite context if null.   425         context_context, context_ref = self.value   426         source_context, source_ref = self.source   427         if source_context is ReplaceableContext:   428             context = context_ref   429         else:   430             context = source_context   431         self.save(self.operand, (context, source_ref))   432    433     def MakeInstance(self):   434         size = self.operand   435         context, ref = self.value   436         # NOTE: Referencing the instance template.   437         addr = self._MakeObject(size, ref - 1)   438         # Introduce object as context for the new object.   439         self.value = addr, addr   440    441     def MakeFragment(self):   442         size = self.operand   443         addr = self._MakeFragment(size)   444         # NOTE: Context is not relevant for fragments.   445         self.value = None, addr   446    447     def LoadAttr(self):   448         context, ref = self.value   449         # Retrieved context should already be appropriate for the instance.   450         # NOTE: Adding 1 to skip any header.   451         self.value = self.load(ref + self.operand + 1)   452    453     def StoreAttr(self):   454         context, ref = self.value   455         # Target should already be an instance.   456         # NOTE: Adding 1 to skip any header.   457         self.save(ref + self.operand + 1, self.source)   458    459     def LoadAttrIndex(self):   460         context, ref = self.value   461         data = self.load(ref)   462         element = self.objlist[data.classcode + self.operand]   463    464         if element is not None:   465             attr_index, static_attr, offset = element   466             if attr_index == self.operand:   467                 if static_attr:   468                     self.value = self.load(offset) # offset is address of class/module attribute   469                 else:   470                     self.value = self.load(ref + offset)   471                 return   472    473         self.exception = self._MakeObject(2, self.attr_error_instance)   474         return self.RaiseException()   475    476     # LoadAttrIndexContext not defined.   477    478     def LoadAttrIndexContextCond(self):   479         inst_context, inst_ref = self.value   480         data = self.load(inst_ref)   481         element = self.objlist[data.classcode + self.operand]   482    483         if element is not None:   484             attr_index, static_attr, offset = element   485             if attr_index == self.operand:   486                 if static_attr:   487                     loaded_context, loaded_ref = self.load(offset) # offset is address of class/module attribute   488                     if data.attrcode is None: # absent attrcode == class/module   489                         self.value = loaded_context, loaded_ref   490                     else:   491                         self.value = self._LoadAddressContextCond(loaded_context, loaded_ref, inst_ref)   492                 else:   493                     self.value = self.load(inst_ref + offset)   494                 return   495    496         self.exception = self._MakeObject(2, self.attr_error_instance)   497         return self.RaiseException()   498    499     def StoreAttrIndex(self):   500         context, ref = self.value   501         data = self.load(ref)   502         element = self.objlist[data.classcode + self.operand]   503    504         if element is not None:   505             attr_index, static_attr, offset = element   506             if attr_index == self.operand:   507                 if static_attr:   508                     self.exception = self._MakeObject(2, self.type_error_instance)   509                     return self.RaiseException()   510                 else:   511                     self.save(ref + offset, self.source)   512                     return   513    514         self.exception = self._MakeObject(2, self.attr_error_instance)   515         return self.RaiseException()   516    517     # NOTE: LoadAttrIndexContext is a possibility if a particular attribute can always be overridden.   518    519     def MakeFrame(self):   520         self.invocation_sp_stack.append(len(self.frame_stack))   521         self.frame_stack.extend([None] * self.operand)   522    523     def DropFrame(self):   524         self.local_sp_stack.pop()   525         frame = self.invocation_sp_stack.pop()   526         del self.frame_stack[frame:] # reset stack before call   527    528     def StoreFrame(self):   529         frame = self.invocation_sp_stack[-1] # different from the current frame after MakeFrame   530         self.frame_stack[frame + self.operand] = self.value   531    532     def StoreFrameIndex(self):   533         context, ref = self.value   534         frame = self.invocation_sp_stack[-1] # different from the current frame after MakeFrame   535         data = self.load(ref)   536         element = self.paramlist[data.funccode + self.operand]   537    538         if element is not None:   539             # NOTE: Need to ensure correct positioning where a context has been generated.   540             param_index, offset = element   541             if param_index == self.operand:   542                 self.frame_stack[frame + offset] = self.source   543                 return   544    545         self.exception = self._MakeObject(2, self.type_error_instance)   546         return self.RaiseException()   547    548     def LoadCallable(self):   549         context, ref = self.value   550         data = self.load(ref)   551         self.callable = data.codeaddr   552    553     def StoreCallable(self):   554         context, ref = self.value   555         # NOTE: Should improve the representation and permit direct saving.   556         data = self.load(ref)   557         self.save(ref, data.with_callable(self.callable))   558    559     def LoadContext(self):   560         context, ref = self.value   561         # NOTE: Omission of the context of the context would make things like   562         # NOTE: self() inside methods impossible.   563         self.value = context, context   564    565     def CheckContext(self):   566         self.status = self.value[1] is not ReplaceableContext   567    568     def CheckClass(self):   569         context, ref = self.value   570         if ref in (ReplaceableContext, PlaceholderContext):   571             self.status = 0   572             return   573    574         data = self.load(ref)   575    576         # Classes are not themselves usable as the self argument.   577         # NOTE: This may change at some point.   578         # However, where classes appear as the context, instance   579         # compatibility is required in the first argument.   580    581         self.status = data.attrcode is None # absent attrcode == class   582    583     def CheckFrame(self):   584         (nargs, ndefaults) = self.operand   585    586         # The frame is actually installed as the locals.   587         # Retrieve the context from the first local.   588    589         frame = self.local_sp_stack[-1]   590         nlocals = len(self.frame_stack[frame:])   591    592         if not ((nargs - ndefaults) <= nlocals):   593             raise Exception, "CheckFrame %r (%r <= %r <= %r)" % (self.operand, nargs - ndefaults, nlocals, nargs)   594             self.exception = self._MakeObject(2, self.type_error_instance)   595             return self.RaiseException()   596    597     def CheckExtra(self):   598         nargs = self.operand   599    600         # The frame is actually installed as the locals.   601         # Retrieve the context from the first local.   602    603         frame = self.local_sp_stack[-1]   604         nlocals = len(self.frame_stack[frame:])   605    606         # Provide the extra star parameter if necessary.   607    608         if nlocals == nargs:   609             self.frame_stack.extend([None]) # ExtendFrame(1)   610    611     def FillDefaults(self):   612         context, ref = self.value   613         (nargs, ndefaults) = self.operand   614    615         # The frame is actually installed as the locals.   616    617         frame = self.local_sp_stack[-1]   618         nlocals = len(self.frame_stack[frame:])   619    620         # Support population of defaults.   621         # This involves copying the "attributes" of a function into the frame.   622    623         default = nlocals - (nargs - ndefaults)   624         self.frame_stack.extend([None] * (nargs - nlocals))   625         pos = nlocals   626    627         while pos < nargs:   628             self.frame_stack[frame + pos] = self.load(ref + default + 1) # skip header   629             default += 1   630             pos += 1   631    632     def CopyExtra(self):   633         start = self.operand   634    635         # The frame is the source of the extra arguments.   636    637         frame = self.local_sp_stack[-1]   638         nlocals = len(self.frame_stack[frame:])   639    640         # Make a tuple to hold the arguments.   641    642         ref = self._MakeObject(nlocals - start + 1, self.tuple_instance)   643    644         extra = 0   645         pos = start   646    647         while pos < nlocals:   648             self.save(ref + extra + 1, self.frame_stack[frame + pos]) # skip header when storing   649             extra += 1   650             pos += 1   651    652         self.value = ref, ref   653    654     def CheckSelf(self):   655         context, ref = self.value   656         context_context, context_ref = self.source   657    658         # Check the details of the proposed context and the target's context.   659    660         self.status = self._CheckInstance(ref, context_ref)   661    662     def JumpInFrame(self):   663         codeaddr = self.callable   664         return self.jump(codeaddr, self.pc + 1) # return to the instruction after this one   665    666     def JumpWithFrame(self):   667         codeaddr = self.callable   668         self.local_sp_stack.append(self.invocation_sp_stack[-1]) # adopt the invocation frame   669         return self.jump(codeaddr, self.pc + 1) # return to the instruction after this one   670    671     def JumpWithFrameDirect(self):   672         operand = self.operand   673         self.local_sp_stack.append(self.invocation_sp_stack[-1]) # adopt the invocation frame   674         return self.jump(operand, self.pc + 1) # return to the instruction after this one   675    676     def ExtendFrame(self):   677         self.frame_stack.extend([None] * self.operand)   678    679     def AdjustFrame(self):   680         self.invocation_sp_stack[-1] += self.operand   681    682     def Return(self):   683         return self.pull_pc()   684    685     def LoadResult(self):   686         self.value = self.result   687    688     def StoreResult(self):   689         self.result = self.value   690    691     def Jump(self):   692         return self.operand   693    694     def JumpIfTrue(self):   695         if self.status:   696             return self.operand   697    698     def JumpIfFalse(self):   699         if not self.status:   700             return self.operand   701    702     def LoadException(self):   703         self.value = self.exception, self.exception   704    705     def StoreException(self):   706         self.exception = self.value[1]   707    708     def ClearException(self):   709         self.exception = None   710    711     def RaiseException(self):   712         # NOTE: Adding the program counter as the first attribute.   713         self.save(self.exception + 1, self.pc)   714         # Jumping to the current handler.   715         if self.abort_upon_exception:   716             raise Exception   717         return self.handler_stack[-1]   718    719     def PushHandler(self):   720         self.handler_stack.append(self.operand)   721         self.handler_local_sp_stack.append(len(self.local_sp_stack))   722         self.handler_pc_stack.append(len(self.pc_stack))   723    724     def PopHandler(self):   725         # Reduce the local frame pointer stack to refer to the handler's frame.   726         del self.local_sp_stack[self.handler_local_sp_stack.pop():]   727         # Reduce the PC stack to discard all superfluous return addresses.   728         self.pc_stack = self.pc_stack[:self.handler_pc_stack.pop()]   729         self.handler_stack.pop()   730    731     def CheckException(self):   732         self.status = self.exception is not None and self._CheckInstance(self.exception, self.value[1])   733    734     def TestIdentity(self):   735         self.status = self.value[1] == self.source[1]   736    737     def TestIdentityAddress(self):   738         self.status = self.value[1] == self.operand   739    740     # LoadBoolean is implemented in the generated code.   741     # StoreBoolean is implemented by testing against the True value.   742    743     def InvertBoolean(self):   744         self.status = not self.status   745    746     # Common implementation details.   747    748     def _CheckInstance(self, ref, cls):   749         data = self.load(ref)   750         target_data = self.load(cls)   751    752         # Insist on instance vs. class.   753    754         if data.attrcode is None: # absent attrcode == class/module   755             return 0   756    757         if target_data.attrcode is not None: # present attrcode == instance   758             return 0   759    760         # Find the table entry for the descendant.   761    762         element = self.objlist[target_data.classcode + data.attrcode]   763    764         if element is not None:   765             attr_index, static_attr, offset = element   766             return attr_index == data.attrcode   767         else:   768             return 0   769    770     def _MakeObject(self, size, ref):   771         # Load the template.   772         data = self.load(ref)   773         addr = self.new(size)   774         # Save the header, overriding the size.   775         self.save(addr, data.with_size(size))   776         return addr   777    778     def _MakeFragment(self, size):   779         # Reserve twice the amount of space.   780         addr = self.new(size * 2)   781         # Save the header, overriding the size.   782         self.save(addr, FragmentObject(size, size * 2))   783         return addr   784    785     def _LoadAddressContextCond(self, context, ref, inst_ref):   786         # Check the instance context against the target's context.   787         # This provides the context overriding for methods.   788         if context is ReplaceableContext or context is not PlaceholderContext and self._CheckInstance(inst_ref, context):   789             # Replace the context with the instance.   790             return inst_ref, ref   791         else:   792             return context, ref   793    794 # Convenience functions.   795    796 def machine(program, with_builtins=0, debug=0, abort_upon_exception=0):   797     print "Making the image..."   798     code = program.get_image(with_builtins)   799     print "Getting raw structures..."   800     ot = program.get_object_table()   801     pt = program.get_parameter_table()   802     objlist = ot.as_list()   803     paramlist = pt.as_list()   804     print "Getting raw image..."   805     rc = program.get_raw_image()   806     print "Initialising the machine..."   807     importer = program.get_importer()   808     true_constant = importer.get_constant(True).location   809     false_constant = importer.get_constant(False).location   810     rm = RSVPMachine(rc, objlist, paramlist, debug=debug, abort_upon_exception=abort_upon_exception)   811     library = Library(rm, true_constant, false_constant)   812     rm.library = library   813     rm.pc = program.code_location   814     print "Returning program occupying %d locations." % len(rm.memory)   815     return rm   816    817 # vim: tabstop=4 expandtab shiftwidth=4