micropython

Annotated rsvp.py

513:e43264ed5c5d
2012-06-03 Paul Boddie Added missing operator handler.
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