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