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