micropython

Annotated rsvp.py

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