Source code for enaml.core.code_tracing

#------------------------------------------------------------------------------
#  Copyright (c) 2012, Enthought, Inc.
#  All rights reserved.
#------------------------------------------------------------------------------
from .byteplay import (
    LOAD_ATTR, LOAD_CONST, ROT_TWO, DUP_TOP, CALL_FUNCTION, POP_TOP, LOAD_FAST,
    BUILD_TUPLE, ROT_THREE, UNPACK_SEQUENCE, DUP_TOPX, BINARY_SUBSCR, GET_ITER,
    LOAD_NAME, RETURN_VALUE
)


[docs]class CodeTracer(object): """ A base class for implementing code tracers. This class defines the interface for a code tracer object, which is an object which can be passed as the first argument to a code object which has been transformed to enable tracing. Methods on the tracer are called with relevant arguments from the Python stack when that particular code segment is executing. The return value of a tracer method is ignored; exceptions are propagated. """
[docs] def load_attr(self, obj, attr): """ Called before the LOAD_ATTR opcode is executed. Parameters ---------- obj : object The object which owns the attribute. attr : str The attribute being loaded. """ pass
[docs] def call_function(self, func, argtuple, argspec): """ Called before the CALL_FUNCTION opcode is executed. Parameters ---------- func : object The object being called. argtuple : tuple The argument tuple from the stack (see notes). argspec : int The argument tuple specification. Notes ----- The `argstuple` contains both positional and keyword argument information. `argspec` is an int which specifies how to parse the information. The lower 16bits of `argspec` are significant. The lowest 8 bits are the number of positional arguments which are the first n items in `argtuple`. The second 8 bits are the number of keyword arguments which follow the positional args in `argtuple` and alternate name -> value. `argtuple` can be parsed into a conventional tuple and dict with the following: nargs = argspec & 0xFF args = argtuple[:nargs] kwargs = dict(zip(argtuple[nargs::2], argtuple[nargs+1::2])) """ pass
[docs] def binary_subscr(self, obj, idx): """ Called before the BINARY_SUBSCR opcode is executed. Parameters ---------- obj : object The object being indexed. idx : object The index. """ pass
[docs] def get_iter(self, obj): """ Called before the GET_ITER opcode is executed. Parameters ---------- obj : object The object which should return an iterator. """ pass
[docs]class CodeInverter(object): """ A base class for implementing code inverters. This class defines the interface for a code inverter object, which is an object which can be passed as the first argument to a code object which has been transformed to enable inversion. The methods on the inverter are called with relevant arguments from the Python stack when that particular code segment is executing. The return values of a tracer method is ignored; exceptions are propagated. The default behavior of an inverter is to raise. Implementations must provide their own code in order to enable inversion. """
[docs] def fail(self): """ Called by handlers to raise an inversion exception. """ raise RuntimeError("can't assign to expression")
[docs] def load_name(self, name, value): """ Called before the LOAD_NAME opcode is executed. This method should perform a STORE_NAME operation. Parameters ---------- name : str The name being loaded. value : object The value to store. """ self.fail()
[docs] def load_attr(self, obj, attr, value): """ Called before the LOAD_ATTR opcode is executed. This method should perform a STORE_ATTR operation. Parameters ---------- obj : object The object which owns the attribute. attr : str The attribute being loaded. value : object The value to store """ self.fail()
[docs] def call_function(self, func, argtuple, argspec, value): """ Called before the CALL_FUNCTION opcode is executed. This method should perform an appropriate store operation. Parameters ---------- func : object The object being called. argtuple : tuple The argument tuple from the stack (see Notes). argspec : int The argument tuple specification. value : object The value to store. Notes ----- The semantics of the arguments is identical to the method `call_function` on the `CodeTracer` type. """ self.fail()
[docs] def binary_subscr(self, obj, idx, value): """ Called before the BINARY_SUBSCR opcode is executed. This method should perform a STORE_SUBSCR operation. Parameters ---------- obj : object The object being indexed. idx : object The index. value : object The value to store. """ self.fail()
[docs]def inject_tracing(codelist): """ Inject tracing code into the given code list. This will inject the bytecode operations required to trace the execution of the code using a `CodeTracer` object. The generated opcodes expect a fast local '_[tracer]' to be available when the code is executed. Parameters ---------- codelist : list The list of byteplay code ops to modify. Returns ------- result : list A *new* list of code ops which implement the desired behavior. """ # This builds a mapping of code idx to a list of ops, which are the # tracing bytecode instructions which will be inserted into the code # object being transformed. The ops assume that a tracer object is # available in the fast locals using a non-clashable name. All of # the ops have a net-zero effect on the execution stack. Provided # that the tracer has no visible side effects, the tracing is # transparent. inserts = {} for idx, (op, op_arg) in enumerate(codelist): if op == LOAD_ATTR: code = [ # obj (DUP_TOP, None), # obj -> obj (LOAD_FAST, '_[tracer]'), # obj -> obj -> tracer (LOAD_ATTR, 'load_attr'), # obj -> obj -> tracefunc (ROT_TWO, None), # obj -> tracefunc -> obj (LOAD_CONST, op_arg), # obj -> tracefunc -> obj -> attr (CALL_FUNCTION, 0x0002), # obj -> retval (POP_TOP, None), # obj ] inserts[idx] = code elif op == CALL_FUNCTION: # This computes the number of objects on the stack between # TOS and the object being called. Only the last 16bits of # the op_arg are signifcant. The lowest 8 are the number of # positional args on the stack, the upper 8 is the number of # kwargs. For kwargs, the number of items on the stack is # twice this number since the values on the stack alternate # name, value. n_stack_args = (op_arg & 0xFF) + 2 * ((op_arg >> 8) & 0xFF) code = [ # func -> arg(0) -> arg(1) -> ... -> arg(n-1) (BUILD_TUPLE, n_stack_args), # func -> argtuple (DUP_TOPX, 2), # func -> argtuple -> func -> argtuple (LOAD_FAST, '_[tracer]'), # func -> argtuple -> func -> argtuple -> tracer (LOAD_ATTR, 'call_function'), # func -> argtuple -> func -> argtuple -> tracefunc (ROT_THREE, None), # func -> argtuple -> tracefunc -> func -> argtuple (LOAD_CONST, op_arg), # func -> argtuple -> tracefunc -> func -> argtuple -> argspec (CALL_FUNCTION, 0x0003), # func -> argtuple -> retval (POP_TOP, None), # func -> argtuple (UNPACK_SEQUENCE, n_stack_args), # func -> arg(n-1) -> arg(n-2) -> ... -> arg(0) (BUILD_TUPLE, n_stack_args), # func -> reversedargtuple (UNPACK_SEQUENCE, n_stack_args), # func -> arg(0) -> arg(1) -> ... -> arg(n-1) ] inserts[idx] = code elif op == BINARY_SUBSCR: code = [ # obj -> idx (DUP_TOPX, 2), # obj -> idx -> obj -> idx (LOAD_FAST, '_[tracer]'), # obj -> idx -> obj -> idx -> tracer (LOAD_ATTR, 'binary_subscr'), # obj -> idx -> obj -> idx -> tracefunc (ROT_THREE, None), # obj -> idx -> tracefunc -> obj -> idx (CALL_FUNCTION, 0x0002), # obj -> idx -> retval (POP_TOP, None), # obj -> idx ] inserts[idx] = code elif op == GET_ITER: code = [ # obj (DUP_TOP, None), # obj -> obj (LOAD_FAST, '_[tracer]'), # obj -> obj -> tracer (LOAD_ATTR, 'get_iter'), # obj -> obj -> tracefunc (ROT_TWO, None), # obj -> tracefunc -> obj (CALL_FUNCTION, 0x0001), # obj -> retval (POP_TOP, None), # obj ] inserts[idx] = code # Create a new code list which interleaves the generated code with # the original code at the appropriate location. new_code = [] for idx, code_op in enumerate(codelist): if idx in inserts: new_code.extend(inserts[idx]) new_code.append(code_op) return new_code
[docs]def inject_inversion(codelist): """ Inject inversion code into the given code list. This will inject the bytecode operations required to invert the execution of the code using a `CodeInverter` object. The generated opcodes expect the fast local '_[inverter]' and '_[value]' to be available when the code is executed. Parameters ---------- codelist : list The list of byteplay code ops to modify. Returns ------- result : list A *new* list of code ops which implement the desired behavior. Raises ------ ValueError The given code is not suitable for inversion. """ opcode, oparg = codelist[-2] new_code = codelist[:-2] if opcode == LOAD_NAME and len(codelist) == 3: new_code.extend([ #: (LOAD_FAST, '_[inverter]'), #: inverter (LOAD_ATTR, 'load_name'), #: invertfunc (LOAD_CONST, oparg), #: invertfunc -> name (LOAD_FAST, '_[value]'), #: invertfunc -> name - > value (CALL_FUNCTION, 0x0002), #: retval (RETURN_VALUE, None), #: ]) elif opcode == LOAD_ATTR: new_code.extend([ #: obj (LOAD_FAST, '_[inverter]'), #: obj -> inverter (LOAD_ATTR, 'load_attr'), #: obj -> invertfunc (ROT_TWO, None), #: invertfunc -> obj (LOAD_CONST, oparg), #: invertfunc -> obj -> attr (LOAD_FAST, '_[value]'), #: invertfunc -> obj -> attr -> value (CALL_FUNCTION, 0x0003), #: retval (RETURN_VALUE, None), #: ]) elif opcode == CALL_FUNCTION: n_stack_args = (oparg & 0xFF) + 2 * ((oparg >> 8) & 0xFF) new_code.extend([ #: func -> arg(0) -> arg(1) -> ... -> arg(n-1) (BUILD_TUPLE, n_stack_args), #: func -> argtuple (LOAD_FAST, '_[inverter]'), #: func -> argtuple -> inverter (LOAD_ATTR, 'call_function'), #: func -> argtuple -> invertfunc (ROT_THREE, None), #: invertfunc -> func -> argtuple (LOAD_CONST, oparg), #: invertfunc -> func -> argtuple -> argspec (LOAD_FAST, '_[value]'), #: invertfunc -> func -> argtuple -> argspec -> value (CALL_FUNCTION, 0x0004), #: retval (RETURN_VALUE, None), #: ]) elif opcode == BINARY_SUBSCR: new_code.extend([ #: obj -> index (LOAD_FAST, '_[inverter]'), #: obj -> index -> inverter (LOAD_ATTR, 'binary_subscr'), #: obj -> index -> invertfunc (ROT_THREE, None), #: invertfunc -> obj -> index (LOAD_FAST, '_[value]'), #: invertfunc -> obj -> index -> value (CALL_FUNCTION, 0x0003), #: retval (RETURN_VALUE, None), #: ]) else: raise ValueError("can't invert code") return new_code