| 1 | """Implementations of Python fundamental objects for Byterun."""
|
| 2 | from __future__ import print_function
|
| 3 |
|
| 4 | import collections
|
| 5 | import sys
|
| 6 | import types
|
| 7 |
|
| 8 | from opy.lib import dis
|
| 9 | from opy.lib import inspect
|
| 10 |
|
| 11 |
|
| 12 | def debug1(msg, *args):
|
| 13 | if args:
|
| 14 | msg = msg % args
|
| 15 | print(msg, file=sys.stderr)
|
| 16 |
|
| 17 |
|
| 18 | def make_cell(value):
|
| 19 | # Thanks to Alex Gaynor for help with this bit of twistiness.
|
| 20 | # Construct an actual cell object by creating a closure right here,
|
| 21 | # and grabbing the cell object out of the function we create.
|
| 22 | fn = (lambda x: lambda: x)(value)
|
| 23 | return fn.func_closure[0]
|
| 24 |
|
| 25 |
|
| 26 | class Function(object):
|
| 27 | """
|
| 28 | CPython equivalent:
|
| 29 |
|
| 30 | x = PyFunction_New(v, f->f_globals);
|
| 31 | PyFunction_SetClosure(x, v)
|
| 32 | """
|
| 33 |
|
| 34 | __slots__ = [
|
| 35 | 'func_code', 'func_name', 'func_defaults', 'func_globals',
|
| 36 | 'func_dict', 'func_closure',
|
| 37 | '__name__', '__dict__', '__doc__',
|
| 38 | '_vm', '_func',
|
| 39 | ]
|
| 40 |
|
| 41 | def __init__(self, name, code, globs, defaults, closure, vm):
|
| 42 | self._vm = vm
|
| 43 | self.func_code = code
|
| 44 | self.func_name = self.__name__ = name or code.co_name
|
| 45 | self.func_defaults = tuple(defaults)
|
| 46 | self.func_globals = globs
|
| 47 | self.__dict__ = {}
|
| 48 | self.func_closure = closure
|
| 49 | self.__doc__ = code.co_consts[0] if code.co_consts else None
|
| 50 |
|
| 51 | # Sometimes, we need a real Python function. This is for that.
|
| 52 | # For some reason types.FunctionType doesn't accept normal keyword
|
| 53 | # args? Like args=, closure= ?
|
| 54 | # Is this only used for inspect.getcallargs?
|
| 55 |
|
| 56 | kw = {'argdefs': self.func_defaults}
|
| 57 | if closure:
|
| 58 | kw['closure'] = tuple(make_cell(0) for _ in closure)
|
| 59 | self._func = types.FunctionType(code, globs, **kw)
|
| 60 |
|
| 61 | def __repr__(self): # pragma: no cover
|
| 62 | return '<byterun Function %s at 0x%08x>' % (self.func_name, id(self))
|
| 63 |
|
| 64 | def __get__(self, instance, owner):
|
| 65 | # used in unit tests? But I don't see it triggered in real code.
|
| 66 | #raise AssertionError('Function.__get__')
|
| 67 | m = Method(instance, owner, self)
|
| 68 | #debug1('*** METHOD %s', m)
|
| 69 | return m
|
| 70 |
|
| 71 | def __call__(self, *args, **kwargs):
|
| 72 | #if PY2 and self.func_name in ["<setcomp>", "<dictcomp>", "<genexpr>"]:
|
| 73 | # D'oh! http://bugs.python.org/issue19611 Py2 doesn't know how to
|
| 74 | # inspect set comprehensions, dict comprehensions, or generator
|
| 75 | # expressions properly. They are always functions of one argument,
|
| 76 | # so just do the right thing.
|
| 77 | #assert len(args) == 1 and not kwargs, "Surprising comprehension!"
|
| 78 | #callargs = {".0": args[0]}
|
| 79 |
|
| 80 | # Different workaround for issue 19611 that works with
|
| 81 | # compiler2-generated code. Note that byterun does not use fastlocals,
|
| 82 | # so the name matters. With fastlocals, the co_varnames entry is just
|
| 83 | # a comment; the index is used instead.
|
| 84 | code = self.func_code
|
| 85 | if code.co_argcount == 1 and code.co_varnames[0] == '.0':
|
| 86 | callargs = {".0": args[0]}
|
| 87 | else:
|
| 88 | # NOTE: Can get ValueError due to issue 19611
|
| 89 | callargs = inspect.getcallargs(self._func, *args, **kwargs)
|
| 90 | #print('-- func_name %s CALLS ARGS %s' % (self.func_name, callargs))
|
| 91 |
|
| 92 | frame = self._vm.make_frame(self.func_code, callargs,
|
| 93 | self.func_globals, {})
|
| 94 |
|
| 95 | CO_GENERATOR = 32 # flag for "this code uses yield"
|
| 96 | if self.func_code.co_flags & CO_GENERATOR:
|
| 97 | gen = Generator(frame, self._vm)
|
| 98 | frame.generator = gen
|
| 99 | retval = gen
|
| 100 | else:
|
| 101 | # NOTE: Can raise exceptions!
|
| 102 | retval = self._vm.run_frame(frame)
|
| 103 | return retval
|
| 104 |
|
| 105 |
|
| 106 | class Method(object):
|
| 107 | def __init__(self, obj, _class, func):
|
| 108 | self.im_self = obj
|
| 109 | self.im_class = _class
|
| 110 | self.im_func = func
|
| 111 |
|
| 112 | def __repr__(self): # pragma: no cover
|
| 113 | name = "%s.%s" % (self.im_class.__name__, self.im_func.func_name)
|
| 114 | if self.im_self is not None:
|
| 115 | return '<Bound Method %s of %s>' % (name, self.im_self)
|
| 116 | else:
|
| 117 | return '<Unbound Method %s>' % (name,)
|
| 118 |
|
| 119 | def __call__(self, *args, **kwargs):
|
| 120 | if self.im_self is not None:
|
| 121 | return self.im_func(self.im_self, *args, **kwargs)
|
| 122 | else:
|
| 123 | return self.im_func(*args, **kwargs)
|
| 124 |
|
| 125 |
|
| 126 | class Cell(object):
|
| 127 | """A fake cell for closures.
|
| 128 |
|
| 129 | Closures keep names in scope by storing them not in a frame, but in a
|
| 130 | separate object called a cell. Frames share references to cells, and
|
| 131 | the LOAD_DEREF and STORE_DEREF opcodes get and set the value from cells.
|
| 132 |
|
| 133 | This class acts as a cell, though it has to jump through two hoops to make
|
| 134 | the simulation complete:
|
| 135 |
|
| 136 | 1. In order to create actual FunctionType functions, we have to have
|
| 137 | actual cell objects, which are difficult to make. See the twisty
|
| 138 | double-lambda in __init__.
|
| 139 |
|
| 140 | 2. Actual cell objects can't be modified, so to implement STORE_DEREF,
|
| 141 | we store a one-element list in our cell, and then use [0] as the
|
| 142 | actual value.
|
| 143 |
|
| 144 | """
|
| 145 | def __init__(self, value):
|
| 146 | self.contents = value
|
| 147 |
|
| 148 | def get(self):
|
| 149 | return self.contents
|
| 150 |
|
| 151 | def set(self, value):
|
| 152 | self.contents = value
|
| 153 |
|
| 154 |
|
| 155 | # PyTryBlock in CPython, for SETUP_EXCEPT, etc.
|
| 156 | Block = collections.namedtuple("Block", "type, handler, level")
|
| 157 |
|
| 158 |
|
| 159 | class Frame(object):
|
| 160 | def __init__(self, f_code, f_globals, f_locals, f_back):
|
| 161 | self.f_code = f_code
|
| 162 | self.f_globals = f_globals
|
| 163 | self.f_locals = f_locals
|
| 164 | self.f_back = f_back
|
| 165 | self.stack = []
|
| 166 | if f_back:
|
| 167 | self.f_builtins = f_back.f_builtins
|
| 168 | else:
|
| 169 | self.f_builtins = f_locals['__builtins__']
|
| 170 | if hasattr(self.f_builtins, '__dict__'):
|
| 171 | self.f_builtins = self.f_builtins.__dict__
|
| 172 |
|
| 173 | self.f_lineno = f_code.co_firstlineno
|
| 174 | self.f_lasti = 0
|
| 175 |
|
| 176 | if f_code.co_cellvars:
|
| 177 | self.cells = {}
|
| 178 | if not f_back.cells:
|
| 179 | f_back.cells = {}
|
| 180 | for var in f_code.co_cellvars:
|
| 181 | # Make a cell for the variable in our locals, or None.
|
| 182 | cell = Cell(self.f_locals.get(var))
|
| 183 | f_back.cells[var] = self.cells[var] = cell
|
| 184 | else:
|
| 185 | self.cells = None
|
| 186 |
|
| 187 | if f_code.co_freevars:
|
| 188 | if not self.cells:
|
| 189 | self.cells = {}
|
| 190 | for var in f_code.co_freevars:
|
| 191 | assert self.cells is not None
|
| 192 | assert f_back.cells, "f_back.cells: %r" % (f_back.cells,)
|
| 193 | self.cells[var] = f_back.cells[var]
|
| 194 |
|
| 195 | self.block_stack = []
|
| 196 | self.generator = None
|
| 197 |
|
| 198 | def __repr__(self): # pragma: no cover
|
| 199 | return '<Frame at 0x%08x: %r @ %d>' % (
|
| 200 | id(self), self.f_code.co_filename, self.f_lineno
|
| 201 | )
|
| 202 |
|
| 203 | def top(self):
|
| 204 | """Return the value at the top of the stack, with no changes."""
|
| 205 | return self.stack[-1]
|
| 206 |
|
| 207 | def pop(self, i=0):
|
| 208 | """Pop a value from the stack.
|
| 209 |
|
| 210 | Default to the top of the stack, but `i` can be a count from the top
|
| 211 | instead.
|
| 212 |
|
| 213 | """
|
| 214 | return self.stack.pop(-1-i)
|
| 215 |
|
| 216 | def push(self, *vals):
|
| 217 | """Push values onto the value stack."""
|
| 218 | self.stack.extend(vals)
|
| 219 |
|
| 220 | def popn(self, n):
|
| 221 | """Pop a number of values from the value stack.
|
| 222 |
|
| 223 | A list of `n` values is returned, the deepest value first.
|
| 224 | """
|
| 225 | if n:
|
| 226 | ret = self.stack[-n:]
|
| 227 | del self.stack[-n:]
|
| 228 | return ret
|
| 229 | else:
|
| 230 | return []
|
| 231 |
|
| 232 | def peek(self, n):
|
| 233 | """Get a value `n` entries down in the stack, without changing the stack."""
|
| 234 | return self.stack[-n]
|
| 235 |
|
| 236 | def jump(self, offset):
|
| 237 | """Move the bytecode pointer to `offset`, so it will execute next."""
|
| 238 | self.f_lasti = offset
|
| 239 |
|
| 240 | def push_block(self, type, handler=None, level=None):
|
| 241 | """Used for SETUP_{LOOP,EXCEPT,FINALLY,WITH}."""
|
| 242 | if level is None:
|
| 243 | level = len(self.stack)
|
| 244 | self.block_stack.append(Block(type, handler, level))
|
| 245 |
|
| 246 | def pop_block(self):
|
| 247 | return self.block_stack.pop()
|
| 248 |
|
| 249 | def _unwind_block(self, block, vm):
|
| 250 | """
|
| 251 | Args:
|
| 252 | vm: VirtualMachineError to possibly mutate
|
| 253 | """
|
| 254 | if block.type == 'except-handler':
|
| 255 | offset = 3
|
| 256 | else:
|
| 257 | offset = 0
|
| 258 |
|
| 259 | while len(self.stack) > block.level + offset:
|
| 260 | self.pop()
|
| 261 |
|
| 262 | if block.type == 'except-handler':
|
| 263 | tb, value, exctype = self.popn(3)
|
| 264 | vm.last_exception = exctype, value, tb
|
| 265 |
|
| 266 | def handle_block_stack(self, why, vm):
|
| 267 | """
|
| 268 | After every bytecode that returns why != None, handle everything on the
|
| 269 | block stack.
|
| 270 |
|
| 271 | The block stack and data stack are shuffled for looping, exception
|
| 272 | handling, or returning.
|
| 273 | """
|
| 274 | assert why != 'yield'
|
| 275 |
|
| 276 | block = self.block_stack[-1]
|
| 277 | if block.type == 'loop' and why == 'continue':
|
| 278 | self.jump(vm.return_value)
|
| 279 | why = None
|
| 280 | return why
|
| 281 |
|
| 282 | self.pop_block()
|
| 283 | self._unwind_block(block, vm)
|
| 284 |
|
| 285 | if block.type == 'loop' and why == 'break':
|
| 286 | why = None
|
| 287 | self.jump(block.handler)
|
| 288 | return why
|
| 289 |
|
| 290 | if (block.type in ('finally', 'with') or
|
| 291 | block.type == 'setup-except' and why == 'exception'):
|
| 292 | if why == 'exception':
|
| 293 | exctype, value, tb = vm.last_exception
|
| 294 | self.push(tb, value, exctype)
|
| 295 | else:
|
| 296 | if why in ('return', 'continue'):
|
| 297 | self.push(vm.return_value)
|
| 298 | self.push(why)
|
| 299 |
|
| 300 | why = None
|
| 301 | self.jump(block.handler)
|
| 302 | return why
|
| 303 |
|
| 304 | return why
|
| 305 |
|
| 306 | def decode_next(self):
|
| 307 | """
|
| 308 | Parse 1 - 3 bytes of bytecode into an instruction and maybe arguments.
|
| 309 | """
|
| 310 | byteCode = ord(self.f_code.co_code[self.f_lasti])
|
| 311 | self.f_lasti += 1
|
| 312 |
|
| 313 | arguments = []
|
| 314 | if byteCode >= dis.HAVE_ARGUMENT:
|
| 315 | arg = self.f_code.co_code[self.f_lasti : self.f_lasti+2]
|
| 316 | self.f_lasti += 2
|
| 317 | intArg = ord(arg[0]) + (ord(arg[1]) << 8)
|
| 318 | if byteCode in dis.hasconst:
|
| 319 | arg = self.f_code.co_consts[intArg]
|
| 320 | elif byteCode in dis.hasfree:
|
| 321 | if intArg < len(self.f_code.co_cellvars):
|
| 322 | arg = self.f_code.co_cellvars[intArg]
|
| 323 | else:
|
| 324 | var_idx = intArg - len(self.f_code.co_cellvars)
|
| 325 | arg = self.f_code.co_freevars[var_idx]
|
| 326 | elif byteCode in dis.hasname:
|
| 327 | arg = self.f_code.co_names[intArg]
|
| 328 | elif byteCode in dis.hasjrel:
|
| 329 | arg = self.f_lasti + intArg
|
| 330 | elif byteCode in dis.hasjabs:
|
| 331 | arg = intArg
|
| 332 | elif byteCode in dis.haslocal:
|
| 333 | arg = self.f_code.co_varnames[intArg]
|
| 334 | else:
|
| 335 | arg = intArg
|
| 336 | arguments = [arg]
|
| 337 |
|
| 338 | byteName = dis.opname[byteCode]
|
| 339 | return byteName, arguments
|
| 340 |
|
| 341 | def line_number(self):
|
| 342 | """Get the current line number the frame is executing."""
|
| 343 | # We don't keep f_lineno up to date, so calculate it based on the
|
| 344 | # instruction address and the line number table.
|
| 345 | lnotab = self.f_code.co_lnotab
|
| 346 | byte_increments = lnotab[0::2]
|
| 347 | line_increments = lnotab[1::2]
|
| 348 |
|
| 349 | byte_num = 0
|
| 350 | line_num = self.f_code.co_firstlineno
|
| 351 |
|
| 352 | for byte_incr, line_incr in zip(byte_increments, line_increments):
|
| 353 | byte_incr = ord(byte_incr)
|
| 354 | line_incr = ord(line_incr)
|
| 355 |
|
| 356 | byte_num += byte_incr
|
| 357 | if byte_num > self.f_lasti:
|
| 358 | break
|
| 359 | line_num += line_incr
|
| 360 |
|
| 361 | return line_num
|
| 362 |
|
| 363 |
|
| 364 | class Generator(object):
|
| 365 | """A wrapper around a Frame that can run for one generator tick."""
|
| 366 |
|
| 367 | def __init__(self, g_frame, vm):
|
| 368 | self.g_frame = g_frame
|
| 369 | self._vm = vm
|
| 370 | self.started = False
|
| 371 | self.finished = False
|
| 372 |
|
| 373 | # Part of the iterator protocol.
|
| 374 | def __iter__(self):
|
| 375 | """DO_NOT_INTERPRET"""
|
| 376 | return self
|
| 377 |
|
| 378 | # Part of the iterator protocol.
|
| 379 | def next(self):
|
| 380 | """DO_NOT_INTERPRET"""
|
| 381 | # Docstring is a hack for pyvm2 ! Is there a better way?
|
| 382 | # This is a THIRD path for a function.
|
| 383 |
|
| 384 | return self.send(None)
|
| 385 |
|
| 386 | # Part of the iterator protocol.
|
| 387 | def send(self, value=None):
|
| 388 | """DO_NOT_INTERPRET"""
|
| 389 | if not self.started and value is not None:
|
| 390 | raise TypeError("Can't send non-None value to a just-started generator")
|
| 391 | self.g_frame.stack.append(value)
|
| 392 | self.started = True
|
| 393 | # NOTE: Can raise exceptions!
|
| 394 | val = self._vm.resume_frame(self.g_frame)
|
| 395 | if self.finished:
|
| 396 | raise StopIteration(val)
|
| 397 | return val
|