1 #!/usr/bin/env python 2 3 """ 4 A really simple virtual processor employing a simple set of instructions which 5 ignore low-level operations and merely concentrate on variable access, structure 6 access, structure allocation and function invocations. 7 8 Copyright (C) 2007, 2008 Paul Boddie <paul@boddie.org.uk> 9 10 This program is free software; you can redistribute it and/or modify it under 11 the terms of the GNU General Public License as published by the Free Software 12 Foundation; either version 3 of the License, or (at your option) any later 13 version. 14 15 This program is distributed in the hope that it will be useful, but WITHOUT 16 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 17 FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 18 details. 19 20 You should have received a copy of the GNU General Public License along with 21 this program. If not, see <http://www.gnu.org/licenses/>. 22 23 -------- 24 25 The execution model of the virtual processor involves the following things: 26 27 * Memory 28 * PC (program counter) stack 29 * Frame stack (containing invocation frames in use and in preparation plus 30 temporary storage) 31 * Local frame pointer stack 32 * Invocation frame pointer stack 33 * Exception handler stack 34 * Registers: current value, boolean status value, source value, result, 35 current exception 36 37 The memory contains constants, global variable references and program code. 38 39 The PC stack contains the return address associated with each function 40 invocation. 41 42 The frame pointer stack tracks the position of frames within the frame stack. 43 """ 44 45 class IllegalInstruction(Exception): 46 pass 47 48 class IllegalAddress(Exception): 49 pass 50 51 class EmptyPCStack(Exception): 52 pass 53 54 class EmptyFrameStack(Exception): 55 pass 56 57 class RSVPMachine: 58 59 "A really simple virtual processor." 60 61 def __init__(self, memory, pc=None, debug=0): 62 63 """ 64 Initialise the processor with a 'memory' (a list of values containing 65 instructions and data) and the optional program counter 'pc'. 66 """ 67 68 self.memory = memory 69 self.pc = pc or 0 70 self.debug = debug 71 72 # Stacks. 73 74 self.pc_stack = [] 75 self.frame_stack = [] 76 self.local_sp_stack = [] 77 self.invocation_sp_stack = [] 78 self.handler_stack = [] 79 80 # Registers. 81 82 self.value = None 83 self.status = None 84 self.source = None 85 self.result = None 86 self.exception = None 87 88 def load(self, address): 89 90 "Return the value at the given 'address'." 91 92 try: 93 return self.memory[address] 94 except IndexError: 95 raise IllegalAddress, address 96 97 def save(self, address, value): 98 99 "Save to the given 'address' the specified 'value'." 100 101 try: 102 self.memory[address] = value 103 except IndexError: 104 raise IllegalAddress, address 105 106 def new(self, size): 107 108 """ 109 Allocate space of the given 'size', returning the address of the space. 110 """ 111 112 addr = len(self.memory) 113 for i in range(0, size): 114 self.memory.append(None) 115 return addr 116 117 def push_pc(self, data): 118 119 "Push 'data' onto the PC stack." 120 121 self.pc_stack.append(data) 122 123 def pull_pc(self): 124 125 "Pull a value from the PC stack and return it." 126 127 try: 128 return self.pc_stack.pop() 129 except IndexError: 130 raise EmptyPCStack 131 132 def run(self): 133 134 "Execute code in the memory, starting from the current PC address." 135 136 try: 137 while 1: 138 self.execute() 139 except EmptyPCStack: 140 pass 141 142 def execute(self): 143 144 "Execute code in the memory at the current PC address." 145 146 instruction = self.load(self.pc).__class__.__name__ 147 if self.debug: 148 print "%8d %s" % (self.pc, instruction) 149 method = getattr(self, instruction, None) 150 if method is None: 151 raise IllegalInstruction, (self.pc, instruction) 152 else: 153 method() 154 155 def jump(self, addr, next): 156 157 """ 158 Jump to the subroutine at (or identified by) 'addr'. If 'addr' 159 identifies a library function then invoke the library function and set 160 PC to 'next' afterwards; otherwise, set PC to 'addr'. 161 """ 162 163 if isinstance(addr, str): 164 getattr(self, addr)() 165 self.pc = next 166 else: 167 self.push_pc(self.pc + 2) 168 self.pc = addr 169 170 # Instructions. 171 172 def LoadConst(self): 173 op = self.load(self.pc) 174 addr = op.get_operand() 175 self.value = (None, addr) 176 self.pc += 1 177 178 def LoadName(self): 179 n = self.load(self.pc).get_operand() 180 frame = self.local_sp_stack[-1] 181 self.value = self.frame_stack[frame + n] 182 self.pc += 1 183 184 def StoreName(self): 185 n = self.load(self.pc).get_operand() 186 frame = self.local_sp_stack[-1] 187 self.frame_stack[frame + n] = self.value 188 self.pc += 1 189 190 LoadTemp = LoadName 191 StoreTemp = StoreName 192 193 def LoadAttr(self): 194 n = self.load(self.pc).get_operand() 195 context, ref = self.value 196 self.value = self.load(ref + n) 197 self.pc += 1 198 199 def StoreAttr(self): 200 n = self.load(self.pc).get_operand() 201 context, ref = self.value 202 self.save(ref + n, self.source) 203 self.pc += 1 204 205 def LoadAttrIndex(self): pass 206 207 def StoreAttrIndex(self): pass 208 209 def LoadAddress(self): 210 addr = self.load(self.pc).get_operand() 211 self.value = self.load(addr) 212 self.pc += 1 213 214 def LoadAddressContext(self): 215 addr = self.load(self.pc).get_operand() 216 value = self.load(addr) 217 self.value = (self.value, value[1]) # replace the context with the current value 218 self.pc += 1 219 220 def StoreAddress(self): 221 addr = self.load(self.pc).get_operand() 222 self.save(addr, self.value) 223 self.pc += 1 224 225 def MakeObject(self): 226 n = self.load(self.pc).get_operand() 227 self.value = self.new(n) 228 self.pc += 1 229 230 def MakeFrame(self): 231 n = self.load(self.pc).get_operand() 232 self.invocation_sp_stack.append(len(self.frame_stack)) 233 self.frame_stack.extend([None] * n) 234 self.pc += 1 235 236 def JumpWithFrame(self): 237 self.local_sp_stack.append(self.invocation_sp_stack[-1]) # adopt the invocation frame 238 context, ref = self.value 239 self.jump(ref.code_location, self.pc + 1) # return to the instruction after this one 240 241 def DropFrame(self): 242 result = self.pull() 243 self.local_sp_stack.pop() 244 frame = self.invocation_sp_stack.pop() 245 self.frame_stack = self.frame_stack[:frame] # reset stack before call 246 self.pc += 1 247 248 def CheckFrame(self): pass 249 250 def LoadCallable(self): pass 251 252 def LoadContext(self): 253 context, ref = self.value 254 self.push((None, context)) 255 self.pc += 1 256 257 def CheckSelf(self): pass 258 259 def CheckContext(self): pass 260 261 def Return(self): 262 self.pc = self.pull_pc() 263 264 def LoadResult(self): 265 self.value = self.result 266 267 def StoreResult(self): 268 self.result = self.value 269 270 def LoadException(self): 271 self.value = self.exception 272 273 def StoreException(self): 274 self.exception = self.value 275 276 def LoadBoolean(self): 277 self.value = self.status 278 279 def Jump(self): pass 280 281 def JumpIfTrue(self): 282 addr = self.load(self.pc).get_operand() 283 if self.status: 284 self.pc = addr 285 else: 286 self.pc += 1 287 288 def JumpIfFalse(self): 289 addr = self.load(self.pc).get_operand() 290 if not self.status: 291 self.pc = addr 292 else: 293 self.pc += 1 294 295 def StoreFrame(self): 296 pos = self.load(self.pc).get_operand() 297 frame = self.invocation_sp_stack[-1] # different from the current frame after MakeFrame 298 self.frame_stack[frame + pos] = self.value 299 self.pc += 1 300 301 # vim: tabstop=4 expandtab shiftwidth=4