micropython

Annotated rsvp.py

132:dff32649a6d4
2008-08-24 Paul Boddie Changed context loading conditions to depend on the target instead of a context of None (since this may occur for identified functions). Added source instruction tracking in order to provide an optimisation around intermediate source storage. Added ExtendFrame for temporary storage allocation. Added specific classes for the different table and list types. Provided more RSVP instruction implementations. Added a callable register to the RSVP implementation.
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@68 8
Copyright (C) 2007, 2008 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@68 27
  * Memory
paul@68 28
  * PC (program counter) stack
paul@117 29
  * Frame stack (containing invocation frames in use and in preparation plus
paul@117 30
                temporary storage)
paul@117 31
  * Local frame pointer stack
paul@117 32
  * Invocation frame pointer stack
paul@117 33
  * Exception handler stack
paul@117 34
  * Registers: current value, boolean status value, source value, result,
paul@132 35
               current exception, current callable
paul@68 36
paul@68 37
The memory contains constants, global variable references and program code.
paul@68 38
paul@68 39
The PC stack contains the return address associated with each function
paul@68 40
invocation.
paul@68 41
paul@117 42
The frame pointer stack tracks the position of frames within the frame stack.
paul@68 43
"""
paul@68 44
paul@68 45
class IllegalInstruction(Exception):
paul@68 46
    pass
paul@68 47
paul@68 48
class IllegalAddress(Exception):
paul@68 49
    pass
paul@68 50
paul@68 51
class EmptyPCStack(Exception):
paul@68 52
    pass
paul@68 53
paul@92 54
class EmptyFrameStack(Exception):
paul@68 55
    pass
paul@68 56
paul@68 57
class RSVPMachine:
paul@68 58
paul@68 59
    "A really simple virtual processor."
paul@68 60
paul@124 61
    def __init__(self, memory, objtable, paramtable, clstable, pc=None, debug=0):
paul@68 62
paul@68 63
        """
paul@68 64
        Initialise the processor with a 'memory' (a list of values containing
paul@68 65
        instructions and data) and the optional program counter 'pc'.
paul@68 66
        """
paul@68 67
paul@68 68
        self.memory = memory
paul@118 69
        self.objtable = objtable
paul@118 70
        self.paramtable = paramtable
paul@124 71
        self.clstable = clstable
paul@68 72
        self.pc = pc or 0
paul@68 73
        self.debug = debug
paul@117 74
paul@117 75
        # Stacks.
paul@117 76
paul@68 77
        self.pc_stack = []
paul@68 78
        self.frame_stack = []
paul@117 79
        self.local_sp_stack = []
paul@117 80
        self.invocation_sp_stack = []
paul@117 81
        self.handler_stack = []
paul@117 82
paul@117 83
        # Registers.
paul@117 84
paul@119 85
        self.instruction = None
paul@119 86
        self.operand = None
paul@117 87
        self.value = None
paul@117 88
        self.status = None
paul@117 89
        self.source = None
paul@132 90
        self.callable = None
paul@117 91
        self.result = None
paul@117 92
        self.exception = None
paul@68 93
paul@68 94
    def load(self, address):
paul@68 95
paul@68 96
        "Return the value at the given 'address'."
paul@68 97
paul@68 98
        try:
paul@68 99
            return self.memory[address]
paul@68 100
        except IndexError:
paul@68 101
            raise IllegalAddress, address
paul@68 102
paul@68 103
    def save(self, address, value):
paul@68 104
paul@68 105
        "Save to the given 'address' the specified 'value'."
paul@68 106
paul@68 107
        try:
paul@68 108
            self.memory[address] = value
paul@68 109
        except IndexError:
paul@68 110
            raise IllegalAddress, address
paul@68 111
paul@68 112
    def new(self, size):
paul@68 113
paul@68 114
        """
paul@68 115
        Allocate space of the given 'size', returning the address of the space.
paul@68 116
        """
paul@68 117
paul@68 118
        addr = len(self.memory)
paul@68 119
        for i in range(0, size):
paul@68 120
            self.memory.append(None)
paul@68 121
        return addr
paul@68 122
paul@68 123
    def push_pc(self, data):
paul@68 124
paul@68 125
        "Push 'data' onto the PC stack."
paul@68 126
paul@68 127
        self.pc_stack.append(data)
paul@68 128
paul@68 129
    def pull_pc(self):
paul@68 130
paul@68 131
        "Pull a value from the PC stack and return it."
paul@68 132
paul@68 133
        try:
paul@68 134
            return self.pc_stack.pop()
paul@68 135
        except IndexError:
paul@68 136
            raise EmptyPCStack
paul@68 137
paul@90 138
    def run(self):
paul@68 139
paul@68 140
        "Execute code in the memory, starting from the current PC address."
paul@68 141
paul@68 142
        try:
paul@68 143
            while 1:
paul@90 144
                self.execute()
paul@68 145
        except EmptyPCStack:
paul@68 146
            pass
paul@68 147
paul@90 148
    def execute(self):
paul@90 149
paul@90 150
        "Execute code in the memory at the current PC address."
paul@90 151
paul@119 152
        self.instruction = self.load(self.pc)
paul@119 153
        self.operand = self.instruction.get_operand()
paul@119 154
paul@119 155
        instruction_name = self.instruction.__class__.__name__
paul@90 156
        if self.debug:
paul@119 157
            print "%8d %s" % (self.pc, instruction_name)
paul@118 158
paul@119 159
        method = self.get_method(instruction_name)
paul@118 160
paul@118 161
        # Process any inputs of the instruction.
paul@118 162
paul@119 163
        self.process_inputs()
paul@119 164
        next_pc = method()
paul@118 165
paul@119 166
        # Update the program counter.
paul@118 167
paul@119 168
        if next_pc is None:
paul@119 169
            self.pc += 1
paul@119 170
        else:
paul@119 171
            self.pc = next_pc
paul@118 172
paul@119 173
    def get_method(self, instruction_name):
paul@119 174
paul@119 175
        "Return the handler method for the given 'instruction_name'."
paul@119 176
paul@119 177
        method = getattr(self, instruction_name, None)
paul@90 178
        if method is None:
paul@119 179
            raise IllegalInstruction, (self.pc, instruction_name)
paul@118 180
        return method
paul@118 181
paul@119 182
    def process_inputs(self):
paul@118 183
paul@118 184
        """
paul@119 185
        Process any inputs of the current instruction. This permits any directly
paul@118 186
        connected sub-instructions to produce the effects that separate
paul@118 187
        instructions would otherwise have.
paul@118 188
        """
paul@118 189
paul@119 190
        for input in (self.instruction.input, self.instruction.source):
paul@118 191
            if input is not None:
paul@118 192
                method = self.get_method(input)
paul@118 193
                method()
paul@90 194
paul@68 195
    def jump(self, addr, next):
paul@68 196
paul@68 197
        """
paul@68 198
        Jump to the subroutine at (or identified by) 'addr'. If 'addr'
paul@68 199
        identifies a library function then invoke the library function and set
paul@68 200
        PC to 'next' afterwards; otherwise, set PC to 'addr'.
paul@68 201
        """
paul@68 202
paul@68 203
        if isinstance(addr, str):
paul@68 204
            getattr(self, addr)()
paul@119 205
            return next
paul@68 206
        else:
paul@68 207
            self.push_pc(self.pc + 2)
paul@119 208
            return addr
paul@68 209
paul@68 210
    # Instructions.
paul@68 211
paul@98 212
    def LoadConst(self):
paul@119 213
        self.value = None, self.operand
paul@68 214
paul@68 215
    def LoadName(self):
paul@117 216
        frame = self.local_sp_stack[-1]
paul@119 217
        self.value = self.frame_stack[frame + self.operand]
paul@68 218
paul@68 219
    def StoreName(self):
paul@117 220
        frame = self.local_sp_stack[-1]
paul@119 221
        self.frame_stack[frame + self.operand] = self.value
paul@68 222
paul@72 223
    LoadTemp = LoadName
paul@72 224
    StoreTemp = StoreName
paul@72 225
paul@72 226
    def LoadAddress(self):
paul@119 227
        # Preserve context (potentially null).
paul@119 228
        self.value = self.load(self.operand)
paul@72 229
paul@117 230
    def LoadAddressContext(self):
paul@119 231
        value = self.load(self.operand)
paul@119 232
        # Replace the context with the current value.
paul@119 233
        self.value = self.value[1], value[1]
paul@72 234
paul@72 235
    def StoreAddress(self):
paul@119 236
        # Preserve context.
paul@119 237
        self.save(self.operand, self.value)
paul@72 238
paul@117 239
    def MakeObject(self):
paul@119 240
        # Introduce null context for new object.
paul@119 241
        self.value = None, self.new(self.operand)
paul@72 242
paul@118 243
    def LoadAttr(self):
paul@118 244
        context, ref = self.value
paul@127 245
        # Retrieved context should already be appropriate for the instance.
paul@127 246
        self.value = self.load(ref + self.operand)
paul@118 247
paul@118 248
    def StoreAttr(self):
paul@119 249
        context, ref = self.value
paul@129 250
        # Target should already be an instance.
paul@119 251
        self.save(ref + self.operand, self.source)
paul@119 252
paul@119 253
    def LoadAttrIndex(self):
paul@118 254
        context, ref = self.value
paul@119 255
        code = self.load(ref) # + 0 (the classcode)
paul@119 256
        element = self.objtable[code + self.operand]
paul@127 257
        found_code, class_attr, replace_context, offset = element
paul@119 258
        if found_code == code:
paul@127 259
            if class_attr:
paul@127 260
                loaded_context, loaded_ref = self.load(offset) # offset is address of class attribute
paul@127 261
                if replace_context:
paul@127 262
                    self.value = ref, loaded_ref # classes can also replace the context if compatible
paul@127 263
                    return
paul@127 264
                self.value = loaded_context, loaded_ref
paul@127 265
            else:
paul@127 266
                self.value = self.load(ref + offset)
paul@119 267
        else:
paul@119 268
            # NOTE: This should cause an attribute error.
paul@129 269
            raise Exception, "LoadAttrIndex % r" % element
paul@118 270
paul@129 271
    def StoreAttrIndex(self):
paul@129 272
        context, ref = self.value
paul@129 273
        code = self.load(ref) # + 0 (the classcode)
paul@129 274
        element = self.objtable[code + self.operand]
paul@129 275
        found_code, class_attr, replace_context, offset = element
paul@129 276
        if found_code == code:
paul@129 277
            if class_attr:
paul@129 278
                # NOTE: This should cause an attribute or type error.
paul@129 279
                # Class attributes cannot be changed at run-time.
paul@129 280
                raise Exception, "StoreAttrIndex % r" % element
paul@129 281
            else:
paul@129 282
                self.save(ref + offset, self.source)
paul@129 283
        else:
paul@129 284
            # NOTE: This should cause an attribute error.
paul@129 285
            raise Exception, "StoreAttrIndex % r" % element
paul@118 286
paul@127 287
    # NOTE: LoadAttrIndexContext is a possibility if a particular attribute can always be overridden.
paul@127 288
paul@98 289
    def MakeFrame(self):
paul@117 290
        self.invocation_sp_stack.append(len(self.frame_stack))
paul@119 291
        self.frame_stack.extend([None] * self.operand)
paul@98 292
paul@98 293
    def DropFrame(self):
paul@117 294
        self.local_sp_stack.pop()
paul@117 295
        frame = self.invocation_sp_stack.pop()
paul@117 296
        self.frame_stack = self.frame_stack[:frame] # reset stack before call
paul@98 297
paul@129 298
    def StoreFrame(self):
paul@129 299
        frame = self.invocation_sp_stack[-1] # different from the current frame after MakeFrame
paul@129 300
        self.frame_stack[frame + self.operand] = self.value
paul@129 301
paul@129 302
    def StoreFrameIndex(self):
paul@129 303
        frame = self.invocation_sp_stack[-1] # different from the current frame after MakeFrame
paul@129 304
        code = self.load(ref) # + 0 (the functioncode)
paul@129 305
        element = self.objtable[code + self.operand]
paul@129 306
        found_code, offset = element
paul@129 307
        if found_code == code:
paul@129 308
            self.frame_stack[frame + offset] = self.value
paul@129 309
        else:
paul@129 310
            # NOTE: This should cause an argument error.
paul@129 311
            raise Exception, "StoreFrameIndex % r" % element
paul@129 312
paul@132 313
    def LoadCallable(self):
paul@132 314
        context, ref = self.value
paul@132 315
        self.callable = self.load(ref + 1)
paul@132 316
paul@132 317
    def StoreCallable(self):
paul@132 318
        context, ref = self.value
paul@132 319
        self.save(ref + 1, self.callable)
paul@132 320
paul@132 321
    def LoadContext(self):
paul@132 322
        context, ref = self.value
paul@132 323
        self.value = None, context
paul@132 324
paul@132 325
    def CheckFrame(self):
paul@132 326
        context, ref = self.value
paul@132 327
        nargs, ndefaults = self.load(ref + 2)
paul@132 328
        if not (nargs - ndefaults <= self.operand <= nargs):
paul@132 329
            raise Exception, "CheckFrame %r" % (nargs - ndefaults, self.operand, nargs)
paul@132 330
        # NOTE: Support population of defaults.
paul@132 331
        # NOTE: Support sliding of the frame to exclude any inappropriate context.
paul@117 332
paul@124 333
    def CheckSelf(self): pass
paul@124 334
paul@132 335
    def JumpWithFrame(self):
paul@132 336
        self.local_sp_stack.append(self.invocation_sp_stack[-1]) # adopt the invocation frame
paul@132 337
        return self.jump(self.callable, self.pc + 1) # return to the instruction after this one
paul@98 338
paul@132 339
    def ExtendFrame(self):
paul@132 340
        frame = self.local_sp_stack[-1]
paul@132 341
        frame.extend([None] * self.operand)
paul@98 342
paul@68 343
    def Return(self):
paul@68 344
        self.pc = self.pull_pc()
paul@68 345
paul@117 346
    def LoadResult(self):
paul@117 347
        self.value = self.result
paul@117 348
paul@117 349
    def StoreResult(self):
paul@117 350
        self.result = self.value
paul@117 351
paul@118 352
    def Jump(self):
paul@119 353
        return self.operand
paul@98 354
paul@68 355
    def JumpIfTrue(self):
paul@117 356
        if self.status:
paul@119 357
            return self.operand
paul@68 358
paul@68 359
    def JumpIfFalse(self):
paul@117 360
        if not self.status:
paul@119 361
            return self.operand
paul@68 362
paul@129 363
    def LoadException(self):
paul@129 364
        self.value = self.exception
paul@129 365
paul@129 366
    def StoreException(self):
paul@129 367
        self.exception = self.value
paul@129 368
paul@129 369
    def RaiseException(self):
paul@129 370
        return self.handler_stack.pop()
paul@129 371
paul@129 372
    def PushHandler(self):
paul@129 373
        self.handler_stack.append(self.operand)
paul@129 374
paul@129 375
    def PopHandler(self):
paul@129 376
        self.handler_stack.pop()
paul@129 377
paul@129 378
    def CheckException(self):
paul@129 379
        self.status = self.value[1] == self.exception
paul@129 380
paul@129 381
    def TestIdentity(self):
paul@129 382
        self.status = self.value[1] == self.source
paul@129 383
paul@129 384
    def TestIdentityAddress(self):
paul@129 385
        self.status = self.value[1] == self.operand
paul@129 386
paul@129 387
    # LoadBoolean is implemented in the generated code.
paul@129 388
    # StoreBoolean is implemented by testing against the True value.
paul@68 389
paul@132 390
    def InvertBoolean(self):
paul@132 391
        self.status = not self.status
paul@132 392
paul@68 393
# vim: tabstop=4 expandtab shiftwidth=4