Compare commits
38 commits
master
...
llpython-d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aacfce25bf | ||
|
|
ca17b03180 | ||
|
|
4f815224f9 | ||
|
|
f332fbf9de | ||
|
|
1adf3d871a | ||
|
|
9fe9e8eb39 | ||
|
|
e4f4d56fe6 | ||
|
|
a6cf8c4f71 | ||
|
|
d8297cf50b | ||
|
|
a8747d2125 | ||
|
|
fcf5420803 | ||
|
|
ecf88f93e2 | ||
|
|
fd41a93554 | ||
|
|
5f81cab357 | ||
|
|
68030d51d1 | ||
|
|
6c7d493441 | ||
|
|
0b24adb80a | ||
|
|
ee557c55cc | ||
|
|
722453502c | ||
|
|
c55198095f | ||
|
|
bba305774a | ||
|
|
4837bf194e | ||
|
|
102d6eac66 | ||
|
|
31119a48d4 | ||
|
|
b5a15a5019 | ||
|
|
c890cbdd92 | ||
|
|
420d0ed88d | ||
|
|
643f0706e9 | ||
|
|
abbf972768 | ||
|
|
329b10dbf0 | ||
|
|
8b075317cd | ||
|
|
c06f811473 | ||
|
|
c5d95e01e1 | ||
|
|
8ae1b70206 | ||
|
|
d3b252531d | ||
|
|
6f532f164a | ||
|
|
5ef00671e7 | ||
|
|
8787af154e |
14 changed files with 1930 additions and 251 deletions
88
llpython/addr_flow.py
Normal file
88
llpython/addr_flow.py
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
#! /usr/bin/env python
|
||||
# ______________________________________________________________________
|
||||
# Module imports
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
from .byte_flow import Instr, BytecodeFlowBuilder, demo_flow_builder
|
||||
from .opcode_util import build_basic_blocks, itercodeobjs
|
||||
|
||||
# ______________________________________________________________________
|
||||
# Class definition(s)
|
||||
|
||||
class AddressFlowBuilder(BytecodeFlowBuilder):
|
||||
'''
|
||||
Builds on top of the BytecodeFlowBuilder with two important differences:
|
||||
|
||||
* Child nodes are represented by bytecode indices.
|
||||
|
||||
* All operations (other than purely stack manipulation operations)
|
||||
are retained in the block list (as opposed to being nested).
|
||||
|
||||
The resulting data structure describes a directed acyclic graph
|
||||
(DAG) in a similar fashion to BytecodeFlowBuilder:
|
||||
|
||||
* `flow_dag` ``:=`` ``{`` `blocks` ``*`` ``}``
|
||||
* `blocks` ``:=`` `block_index` ``:`` ``[`` `bytecode_tuple` ``*`` ``]``
|
||||
* `bytecode_tuple` ``:=`` ``(`` `opcode_index` ``,`` `opcode` ``,``
|
||||
`opname` ``,`` `arg` ``,`` ``[`` `opcode_index` ``*`` ``]`` ``)``
|
||||
'''
|
||||
def _visit_op(self, i, op, arg, opname, pops, pushes, appends):
|
||||
assert pops is not None, ('%s not well defined in opcode_util.'
|
||||
'OPCODE_MAP' % opname)
|
||||
if pops:
|
||||
if pops < 0:
|
||||
pops = arg - pops - 1
|
||||
assert pops <= len(self.stack), ("Stack underflow at instruction "
|
||||
"%d (%s)!" % (i, opname))
|
||||
stk_args = [stk_arg[0] for stk_arg in self.stack[-pops:]]
|
||||
del self.stack[-pops:]
|
||||
else:
|
||||
stk_args = []
|
||||
ret_val = Instr(i, op, opname, arg, stk_args)
|
||||
if pushes:
|
||||
self.stack.append(ret_val)
|
||||
self.block.append(ret_val)
|
||||
return ret_val
|
||||
|
||||
def op_IMPORT_FROM (self, i, op, arg):
|
||||
# References top of stack without popping, so we can't use the
|
||||
# generic machinery.
|
||||
opname = self.opmap[op][0]
|
||||
ret_val = Instr(i, op, opname, arg, [self.stack[-1][0]])
|
||||
self.stack.append(ret_val)
|
||||
self.block.append(ret_val)
|
||||
return ret_val
|
||||
|
||||
def op_JUMP_IF_FALSE (self, i, op, arg):
|
||||
ret_val = Instr(i, op, self.opnames[op], arg, [self.stack[-1][0]])
|
||||
self.block.append(ret_val)
|
||||
return ret_val
|
||||
|
||||
op_JUMP_IF_TRUE = op_JUMP_IF_FALSE
|
||||
|
||||
def op_LIST_APPEND (self, i, op, arg):
|
||||
'''This method is used for both LIST_APPEND, and SET_ADD
|
||||
opcodes.'''
|
||||
elem = self.stack.pop()
|
||||
container = self.stack[-arg]
|
||||
ret_val = Instr(i, op, self.opnames[op], arg, [container[0], elem[0]])
|
||||
self.block.append(ret_val)
|
||||
return ret_val
|
||||
|
||||
op_SET_ADD = op_LIST_APPEND
|
||||
|
||||
# ______________________________________________________________________
|
||||
# Main (self-test) routine
|
||||
|
||||
def main(*args):
|
||||
return demo_flow_builder(AddressFlowBuilder, *args)
|
||||
|
||||
# ______________________________________________________________________
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
main(*sys.argv[1:])
|
||||
|
||||
# ______________________________________________________________________
|
||||
# End of addr_flow.py
|
||||
437
llpython/af_to_api.py
Normal file
437
llpython/af_to_api.py
Normal file
|
|
@ -0,0 +1,437 @@
|
|||
#! /usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
# ______________________________________________________________________
|
||||
# Module imports
|
||||
|
||||
from __future__ import print_function, division, absolute_import
|
||||
|
||||
import inspect
|
||||
import opcode
|
||||
|
||||
import llvm.core as lc
|
||||
|
||||
from . import byte_control
|
||||
from . import addr_flow
|
||||
from . import opcode_util
|
||||
from .bytetype import lvoid, li1, lc_int, lc_long, l_pyobj_p
|
||||
from .bytecode_visitor import GenericFlowVisitor
|
||||
from .nobitey import get_string_constant
|
||||
|
||||
# ______________________________________________________________________
|
||||
# Class definitions
|
||||
|
||||
class AddressFlowToLLVMPyAPICalls(GenericFlowVisitor):
|
||||
'''
|
||||
Code generator for translating from a Python code object and its
|
||||
address flow (output by addr_flow.AddressFlowBuilder) into an LLVM
|
||||
function. The resulting LLVM function calls into a user-provided
|
||||
(or undefined) API function for each interpreter byte code.
|
||||
|
||||
Target API function names are based on a programmable name
|
||||
mangling scheme:
|
||||
|
||||
<PREFIX>OPCODE_NAME<POSTFIX>
|
||||
|
||||
The <PREFIX> and <POSTFIX> strings are determined by the prefix()
|
||||
and postfix() methods. These methods may either be overloaded, or
|
||||
specialized at construction time using _prefix and _postfix
|
||||
arguments. The OPCODE_NAME is the opcode name as determined by
|
||||
the map in opcode.opname.
|
||||
'''
|
||||
def __init__(self, _prefix=None, _postfix=None, **kwds):
|
||||
if _prefix is not None:
|
||||
if inspect.isfunction(_prefix):
|
||||
__prefix = _prefix
|
||||
else:
|
||||
def __prefix(opname, *opargs):
|
||||
return _prefix
|
||||
self.prefix = __prefix
|
||||
if _postfix is not None:
|
||||
if inspect.isfunction(_postfix):
|
||||
__postfix = _postfix
|
||||
else:
|
||||
def __postfix(opname, *opargs):
|
||||
return _postfix
|
||||
self.postfix = __postfix
|
||||
|
||||
def prefix(self, opname, *opargs):
|
||||
return ''
|
||||
|
||||
def postfix(self, opname, *opargs):
|
||||
return ''
|
||||
|
||||
def get_op_function(self, opname, *opargs, **kwds):
|
||||
print(opname, opargs, kwds)
|
||||
target_fn_ty = lc.Type.function(kwds.get('return_type', self.obj_type),
|
||||
[arg.type for arg in opargs])
|
||||
target_fn_name = ''.join((self.prefix(opname, *opargs), opname,
|
||||
self.postfix(opname, *opargs)))
|
||||
return self.llvm_module.get_or_insert_function(target_fn_ty,
|
||||
target_fn_name)
|
||||
|
||||
def call_op_function(self, index, opname, *opargs, **kwds):
|
||||
target_fn = self.get_op_function(opname, *opargs, **kwds)
|
||||
if target_fn.type.pointee.return_type != lvoid:
|
||||
name = kwds.get('name', 'op_%d' % index)
|
||||
result = self.builder.call(target_fn, opargs, name)
|
||||
else:
|
||||
result = self.builder.call(target_fn, opargs)
|
||||
return result
|
||||
|
||||
def translate_cfg(self, code_obj, cfg, llvm_module=None,
|
||||
monotype=l_pyobj_p, **kwds):
|
||||
'''
|
||||
Generate LLVM code for the given code object and it's control
|
||||
flow graph.
|
||||
|
||||
If no LLVM module is given as an argument, translate_cfg()
|
||||
creates a new module.
|
||||
|
||||
Returns the resulting LLVM module.
|
||||
'''
|
||||
assert inspect.iscode(code_obj)
|
||||
self.obj_type = monotype
|
||||
self.null = lc.Constant.null(self.obj_type)
|
||||
self.code_obj = code_obj
|
||||
self.cfg = cfg
|
||||
self.target_function_name = kwds.get(
|
||||
'target_function_name', 'co_%s_%x' % (code_obj.co_name,
|
||||
id(code_obj)))
|
||||
if llvm_module is None:
|
||||
llvm_module = lc.Module.new('lmod_' + self.target_function_name)
|
||||
self.llvm_module = llvm_module
|
||||
self.visit(cfg.blocks)
|
||||
del self.llvm_module
|
||||
del self.cfg
|
||||
del self.code_obj
|
||||
return llvm_module
|
||||
|
||||
def enter_flow_object(self, flow):
|
||||
'''
|
||||
Set up any state for dealing a new dictionary of basic blocks.
|
||||
'''
|
||||
super(AddressFlowToLLVMPyAPICalls, self).enter_flow_object(flow)
|
||||
self.nargs = opcode_util.get_nargs(self.code_obj)
|
||||
lltype = lc.Type.function(
|
||||
self.obj_type, tuple(self.obj_type for _ in range(self.nargs)))
|
||||
self.llvm_function = self.llvm_module.add_function(
|
||||
lltype, self.target_function_name)
|
||||
self.llvm_blocks = {}
|
||||
for block in self.block_list:
|
||||
if 0 in self.cfg.blocks_reaching[block]:
|
||||
bb = self.llvm_function.append_basic_block(
|
||||
'BLOCK_%d' % (block,))
|
||||
self.llvm_blocks[block] = bb
|
||||
self.symtab = {}
|
||||
self.values = {}
|
||||
|
||||
def exit_flow_object(self, flow):
|
||||
'''
|
||||
Clean up any state created while visiting the given dictionary
|
||||
of basic blocks.
|
||||
'''
|
||||
super(AddressFlowToLLVMPyAPICalls, self).exit_flow_object(flow)
|
||||
del self.symtab
|
||||
del self.llvm_blocks
|
||||
del self.llvm_function
|
||||
|
||||
def generate_co_init(self):
|
||||
'''
|
||||
Initialize the code object's local variables on the stack.
|
||||
'''
|
||||
for name in self.code_obj.co_varnames:
|
||||
ptr = self.builder.alloca(self.obj_type, name + '_p')
|
||||
self.symtab[name] = ptr
|
||||
self.builder.store(self.null, ptr)
|
||||
for arg_index, arg in zip(range(self.nargs), self.llvm_function.args):
|
||||
if arg_index == 0:
|
||||
arg.name = '_globals_%x' % id(self.code_obj)
|
||||
self.globals = arg
|
||||
else:
|
||||
local_index = arg_index - 1
|
||||
name = self.code_obj.co_varnames[local_index]
|
||||
arg.name = name
|
||||
self.call_op_function(
|
||||
None, 'STORE_FAST', arg, self.symtab[name],
|
||||
return_type=lvoid)
|
||||
|
||||
def generate_co_deinit(self, index):
|
||||
for value in self.symtab.values():
|
||||
self.call_op_function(index, 'DELETE_FAST',
|
||||
lc.Constant.int(li1, 0), value,
|
||||
return_type=lvoid)
|
||||
|
||||
def enter_block(self, block):
|
||||
'''
|
||||
Set up state for generating code in a new basic block. If
|
||||
this is the first basic block, initialize the local variables
|
||||
using generate_co_init().
|
||||
'''
|
||||
ret_val = False
|
||||
if block in self.llvm_blocks:
|
||||
self.llvm_block = self.llvm_blocks[block]
|
||||
self.builder = lc.Builder.new(self.llvm_block)
|
||||
if block == 0:
|
||||
self.generate_co_init()
|
||||
ret_val = True
|
||||
return ret_val
|
||||
|
||||
def exit_block(self, block):
|
||||
'''
|
||||
Tear down any state created for code generation in the current
|
||||
basic block. If the basic block isn't already terminated by a
|
||||
control flow statement, assume it branches to the next basic
|
||||
block.
|
||||
'''
|
||||
# XXX Isn't this really a bug in GenericFlowVisitor.visit()?
|
||||
if block in self.llvm_blocks:
|
||||
bb_instrs = self.llvm_block.instructions
|
||||
if ((len(bb_instrs) == 0) or
|
||||
(not bb_instrs[-1].is_terminator)):
|
||||
out_blocks = list(self.cfg.blocks_out[block])
|
||||
assert len(out_blocks) == 1, [str(i) for i in bb_instrs]
|
||||
self.builder.branch(self.llvm_blocks[out_blocks[0]])
|
||||
del self.builder
|
||||
del self.llvm_block
|
||||
|
||||
def _op(self, i, op, arg, *args, **kwds):
|
||||
args = [self.values[stkarg] for stkarg in args]
|
||||
if arg is not None:
|
||||
args.insert(0, lc.Constant.int(lc_int, arg))
|
||||
# XXX Modify the visitor! Should be passing Instr named
|
||||
# tuples here, and not looking up the operation name.
|
||||
result = self.call_op_function(i, self.opnames[op], *args, **kwds)
|
||||
self.values[i] = result
|
||||
return [result]
|
||||
|
||||
def _not_implemented(self, i, op, arg, *args, **kwds):
|
||||
raise NotImplementedError(self.opnames[op])
|
||||
|
||||
op_BINARY_ADD = _op
|
||||
op_BINARY_AND = _op
|
||||
op_BINARY_DIVIDE = _op
|
||||
op_BINARY_FLOOR_DIVIDE = _op
|
||||
op_BINARY_LSHIFT = _op
|
||||
op_BINARY_MODULO = _op
|
||||
op_BINARY_MULTIPLY = _op
|
||||
op_BINARY_OR = _op
|
||||
op_BINARY_POWER = _op
|
||||
op_BINARY_RSHIFT = _op
|
||||
op_BINARY_SUBSCR = _op
|
||||
op_BINARY_SUBTRACT = _op
|
||||
op_BINARY_TRUE_DIVIDE = _op
|
||||
op_BINARY_XOR = _op
|
||||
op_BREAK_LOOP = _op
|
||||
op_BUILD_CLASS = _op
|
||||
op_BUILD_LIST = _op
|
||||
op_BUILD_MAP = _op
|
||||
op_BUILD_SET = _op
|
||||
op_BUILD_SLICE = _op
|
||||
op_BUILD_TUPLE = _op
|
||||
op_CALL_FUNCTION = _op
|
||||
op_CALL_FUNCTION_KW = _op
|
||||
op_CALL_FUNCTION_VAR = _op
|
||||
op_CALL_FUNCTION_VAR_KW = _op
|
||||
op_COMPARE_OP = _op
|
||||
op_CONTINUE_LOOP = _op
|
||||
op_DELETE_ATTR = _op
|
||||
op_DELETE_DEREF = _op
|
||||
|
||||
def op_DELETE_FAST(self, i, op, arg, *args, **kwds):
|
||||
varname = self.code_obj.co_varnames[arg]
|
||||
result = self.call_op_function(i, 'DELETE_FAST',
|
||||
lc.Constant.int(li1, 1),
|
||||
self.symtab[varname], return_type=lvoid)
|
||||
return [result]
|
||||
|
||||
def op_DELETE_GLOBAL(self, i, op, arg, *args, **kwds):
|
||||
varname = get_string_constant(self.llvm_module,
|
||||
self.code_obj.co_names[arg])
|
||||
result = self.call_op_function(i, 'DELETE_GLOBAL', self.globals,
|
||||
varname, return_type=lvoid)
|
||||
self.values[i] = result
|
||||
return [result]
|
||||
|
||||
op_DELETE_NAME = _op
|
||||
op_DELETE_SLICE = _op
|
||||
op_DELETE_SUBSCR = _op
|
||||
op_DUP_TOP = _not_implemented
|
||||
op_DUP_TOPX = _not_implemented
|
||||
op_DUP_TOP_TWO = _not_implemented
|
||||
op_END_FINALLY = _op
|
||||
op_EXEC_STMT = _op
|
||||
op_EXTENDED_ARG = _op
|
||||
op_FOR_ITER = _op
|
||||
op_GET_ITER = _op
|
||||
op_IMPORT_FROM = _op
|
||||
op_IMPORT_NAME = _op
|
||||
op_IMPORT_STAR = _op
|
||||
op_INPLACE_ADD = _op
|
||||
op_INPLACE_AND = _op
|
||||
op_INPLACE_DIVIDE = _op
|
||||
op_INPLACE_FLOOR_DIVIDE = _op
|
||||
op_INPLACE_LSHIFT = _op
|
||||
op_INPLACE_MODULO = _op
|
||||
op_INPLACE_MULTIPLY = _op
|
||||
op_INPLACE_OR = _op
|
||||
op_INPLACE_POWER = _op
|
||||
op_INPLACE_RSHIFT = _op
|
||||
op_INPLACE_SUBTRACT = _op
|
||||
op_INPLACE_TRUE_DIVIDE = _op
|
||||
op_INPLACE_XOR = _op
|
||||
|
||||
def op_JUMP_ABSOLUTE(self, i, op, arg, *args, **kwds):
|
||||
return [self.builder.branch(self.llvm_blocks[arg])]
|
||||
|
||||
def op_JUMP_FORWARD(self, i, op, arg, *args, **kwds):
|
||||
return [self.builder.branch(self.llvm_blocks[i + arg + 3])]
|
||||
|
||||
op_JUMP_IF_FALSE = _not_implemented
|
||||
op_JUMP_IF_FALSE_OR_POP = _not_implemented
|
||||
op_JUMP_IF_TRUE = _not_implemented
|
||||
op_JUMP_IF_TRUE_OR_POP = _not_implemented
|
||||
op_LIST_APPEND = _op
|
||||
op_LOAD_ATTR = _op
|
||||
op_LOAD_BUILD_CLASS = _op
|
||||
op_LOAD_CLOSURE = _op
|
||||
|
||||
def op_LOAD_CONST(self, i, op, arg, *args, **kwds):
|
||||
py_val = self.code_obj.co_consts[arg]
|
||||
if isinstance(py_val, int):
|
||||
# XXX Add bounds check on integer values; use big int
|
||||
# (from string?) constructor if necessary.
|
||||
result = self.call_op_function(i, 'LOAD_CONST_INT',
|
||||
lc.Constant.int(lc_long, py_val))
|
||||
elif isinstance(py_val, float):
|
||||
result = self.call_op_function(i, 'LOAD_CONST_FLOAT',
|
||||
lc.Constant.double(py_val))
|
||||
elif py_val is None:
|
||||
result = self.call_op_function(i, 'LOAD_CONST_NONE')
|
||||
else:
|
||||
raise NotImplementedError('Constant conversion for %r' % (py_val,))
|
||||
self.values[i] = result
|
||||
return [result]
|
||||
|
||||
op_LOAD_DEREF = _op
|
||||
|
||||
def op_LOAD_FAST(self, i, op, arg, *args, **kwds):
|
||||
varname = self.code_obj.co_varnames[arg]
|
||||
args = self.symtab[varname],
|
||||
result = self.call_op_function(i, 'LOAD_FAST', *args)
|
||||
self.values[i] = result
|
||||
return [result]
|
||||
|
||||
def op_LOAD_GLOBAL(self, i, op, arg, *args, **kwds):
|
||||
varname = get_string_constant(self.llvm_module,
|
||||
self.code_obj.co_names[arg])
|
||||
result = self.call_op_function(i, 'LOAD_GLOBAL', self.globals, varname)
|
||||
self.values[i] = result
|
||||
return [result]
|
||||
|
||||
op_LOAD_LOCALS = _op
|
||||
op_LOAD_NAME = _op
|
||||
op_MAKE_CLOSURE = _op
|
||||
op_MAKE_FUNCTION = _op
|
||||
op_MAP_ADD = _op
|
||||
op_NOP = _op
|
||||
op_POP_BLOCK = _op
|
||||
op_POP_EXCEPT = _op
|
||||
|
||||
def _op_cbranch(self, i, op, arg, *args, **kwds):
|
||||
if op in opcode.hasjabs:
|
||||
branch_taken = self.llvm_blocks[arg]
|
||||
else:
|
||||
branch_taken = self.llvm_blocks[i + arg + 3]
|
||||
branch_not_taken = self.llvm_blocks[i + 3]
|
||||
_kwds = kwds.copy()
|
||||
_kwds.update(return_type=li1)
|
||||
test = self._op(i, op, None, *args, **_kwds)[0]
|
||||
return [test, self.builder.cbranch(test, branch_taken,
|
||||
branch_not_taken)]
|
||||
|
||||
op_POP_JUMP_IF_FALSE = _op_cbranch
|
||||
op_POP_JUMP_IF_TRUE = _op_cbranch
|
||||
|
||||
def op_POP_TOP(self, i, op, arg, *args):
|
||||
return [self.call_op_function(i, 'POP_TOP', self.values[args[0]],
|
||||
return_type=lvoid)]
|
||||
|
||||
op_PRINT_EXPR = _op
|
||||
op_PRINT_ITEM = _op
|
||||
op_PRINT_ITEM_TO = _op
|
||||
op_PRINT_NEWLINE = _op
|
||||
op_PRINT_NEWLINE_TO = _op
|
||||
op_RAISE_VARARGS = _op
|
||||
|
||||
def op_RETURN_VALUE(self, i, op, arg, *args):
|
||||
self.generate_co_deinit(i)
|
||||
return [self.builder.ret(self.values[args[0]])]
|
||||
|
||||
op_ROT_FOUR = _op
|
||||
op_ROT_THREE = _op
|
||||
op_ROT_TWO = _op
|
||||
op_SETUP_EXCEPT = _op
|
||||
op_SETUP_FINALLY = _op
|
||||
op_SETUP_LOOP = _op_cbranch
|
||||
op_SETUP_WITH = _op
|
||||
op_SET_ADD = _op
|
||||
op_SLICE = _op
|
||||
op_STOP_CODE = _op
|
||||
op_STORE_ATTR = _op
|
||||
op_STORE_DEREF = _op
|
||||
|
||||
def op_STORE_FAST(self, i, op, arg, *args):
|
||||
src_index, = args
|
||||
src = self.values[src_index]
|
||||
varname = self.code_obj.co_varnames[arg]
|
||||
dest = self.symtab[varname]
|
||||
result = self.call_op_function(i, 'STORE_FAST', src, dest,
|
||||
return_type=lvoid)
|
||||
return [result]
|
||||
|
||||
def op_STORE_GLOBAL(self, i, op, arg, *args):
|
||||
varname = get_string_constant(self.llvm_module,
|
||||
self.code_obj.co_names[arg])
|
||||
result = self.call_op_function(
|
||||
i, 'STORE_GLOBAL', self.values[args[0]], self.globals, varname,
|
||||
return_type=lvoid)
|
||||
return [result]
|
||||
|
||||
op_STORE_LOCALS = _not_implemented
|
||||
op_STORE_MAP = _op
|
||||
op_STORE_NAME = _op
|
||||
op_STORE_SLICE = _op
|
||||
op_STORE_SUBSCR = _op
|
||||
op_UNARY_CONVERT = _op
|
||||
op_UNARY_INVERT = _op
|
||||
op_UNARY_NEGATIVE = _op
|
||||
op_UNARY_NOT = _op
|
||||
op_UNARY_POSITIVE = _op
|
||||
op_UNPACK_EX = _op
|
||||
op_UNPACK_SEQUENCE = _op
|
||||
op_WITH_CLEANUP = _op
|
||||
op_YIELD_VALUE = _op
|
||||
|
||||
# ______________________________________________________________________
|
||||
# Function definition(s)
|
||||
|
||||
def demo_translator(*args, **kwds):
|
||||
def _visit(obj):
|
||||
if inspect.isfunction(obj):
|
||||
obj = opcode_util.get_code_object(obj)
|
||||
print('\n; %s\n; %r' % ('_' * 70, obj))
|
||||
cfg = byte_control.ControlFlowBuilder.build_cfg_from_co(obj)
|
||||
cfg.blocks = addr_flow.AddressFlowBuilder().visit_cfg(cfg)
|
||||
print(AddressFlowToLLVMPyAPICalls(**kwds).translate_cfg(obj, cfg,
|
||||
**kwds))
|
||||
return opcode_util.visit_code_args(_visit, *args)
|
||||
|
||||
# ______________________________________________________________________
|
||||
# Main (self-test) routine
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
demo_translator(*sys.argv[1:], _prefix = '_')
|
||||
|
||||
# ______________________________________________________________________
|
||||
# End of af_to_api.py
|
||||
|
|
@ -1,13 +1,45 @@
|
|||
# ______________________________________________________________________
|
||||
from __future__ import absolute_import
|
||||
import opcode
|
||||
from . import opcode_util
|
||||
import pprint
|
||||
# Module imports
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import opcode
|
||||
import pprint
|
||||
import inspect
|
||||
|
||||
from . import opcode_util
|
||||
from .bytecode_visitor import BasicBlockVisitor, BenignBytecodeVisitorMixin
|
||||
from .control_flow import ControlFlowGraph
|
||||
|
||||
# ______________________________________________________________________
|
||||
# Module data
|
||||
|
||||
# The following opcodes branch based on the control (a.k.a. frame)
|
||||
# stack:
|
||||
RETURN_VALUE, CONTINUE_LOOP, BREAK_LOOP, END_FINALLY, RAISE_VARARGS = (
|
||||
opcode.opmap[opname] for opname in (
|
||||
'RETURN_VALUE', 'CONTINUE_LOOP', 'BREAK_LOOP', 'END_FINALLY',
|
||||
'RAISE_VARARGS'))
|
||||
|
||||
# The following opcodes push a new frame on the control stack:
|
||||
SETUP_EXCEPT, SETUP_FINALLY, SETUP_LOOP, SETUP_WITH = (
|
||||
opcode.opmap.get(opname, None) for opname in (
|
||||
'SETUP_EXCEPT', 'SETUP_FINALLY', 'SETUP_LOOP', 'SETUP_WITH'))
|
||||
|
||||
WHY_NOT = 1
|
||||
WHY_EXCEPTION = WHY_NOT << 1
|
||||
WHY_RERAISE = WHY_EXCEPTION << 1 # We don't worry about this code
|
||||
# during CFA, since its primary use
|
||||
# is to log traceback information;
|
||||
# WHY_RERAISE's bytecode control flow
|
||||
# is the same as WHY_EXCEPTION.
|
||||
WHY_RETURN = WHY_RERAISE << 1
|
||||
WHY_BREAK = WHY_RETURN << 1
|
||||
WHY_CONTINUE = WHY_BREAK << 1
|
||||
WHY_YIELD = WHY_CONTINUE << 1
|
||||
|
||||
# ______________________________________________________________________
|
||||
# Class definition(s)
|
||||
|
||||
class ControlFlowBuilder (BenignBytecodeVisitorMixin, BasicBlockVisitor):
|
||||
'''Visitor responsible for traversing a bytecode basic block map and
|
||||
|
|
@ -17,10 +49,10 @@ class ControlFlowBuilder (BenignBytecodeVisitorMixin, BasicBlockVisitor):
|
|||
which is used by later transformers for dataflow analysis.
|
||||
'''
|
||||
def visit (self, flow, nargs = 0, *args, **kws):
|
||||
'''Given a bytecode flow, and an optional number of arguments,
|
||||
return a :py:class:`llpython.control_flow.ControlFlowGraph`
|
||||
instance describing the full control flow of the bytecode
|
||||
flow.'''
|
||||
'''Given a map of bytecode basic blocks, and an optional
|
||||
number of arguments, return a
|
||||
:py:class:`llpython.control_flow.ControlFlowGraph` instance
|
||||
describing the full control flow of the bytecode flow.'''
|
||||
self.nargs = nargs
|
||||
ret_val = super(ControlFlowBuilder, self).visit(flow, *args, **kws)
|
||||
del self.nargs
|
||||
|
|
@ -32,17 +64,26 @@ class ControlFlowBuilder (BenignBytecodeVisitorMixin, BasicBlockVisitor):
|
|||
self.block_list = list(blocks.keys())
|
||||
self.block_list.sort()
|
||||
self.cfg = ControlFlowGraph()
|
||||
self.loop_stack = []
|
||||
self.control_stack = []
|
||||
self.continue_targets = {} # Map from SETUP_LOOP addresses to
|
||||
# start of loop addresses, based on
|
||||
# observed CONTINUE_LOOP opcodes.
|
||||
self.break_targets = set() # Set of SETUP_LOOP address that
|
||||
# have at least one observed
|
||||
# BREAK_LOOP opcode corresponding
|
||||
# to them.
|
||||
for block in self.block_list:
|
||||
self.cfg.add_block(block, blocks[block])
|
||||
|
||||
def exit_blocks (self, blocks):
|
||||
super(ControlFlowBuilder, self).exit_blocks(blocks)
|
||||
assert self.blocks == blocks
|
||||
self.cfg.unlink_unreachables()
|
||||
self.cfg.compute_dataflow()
|
||||
self.cfg.update_for_ssa()
|
||||
ret_val = self.cfg
|
||||
del self.loop_stack
|
||||
del self.continue_targets
|
||||
del self.control_stack
|
||||
del self.cfg
|
||||
del self.block_list
|
||||
del self.blocks
|
||||
|
|
@ -59,23 +100,91 @@ class ControlFlowBuilder (BenignBytecodeVisitorMixin, BasicBlockVisitor):
|
|||
def _get_next_block (self, block):
|
||||
return self.block_list[self.block_list.index(block) + 1]
|
||||
|
||||
def _generate_handler_edge (self, block, i, op, arg, why):
|
||||
"""Given a reason (corresponding to the why code in
|
||||
Python/ceval.c), interrupt control flow, possibly using the
|
||||
control flow (a.k.a. frame) stack to calculate the next
|
||||
target.
|
||||
|
||||
Returns True if an edge was added to the CFG, False otherwise.
|
||||
Based on the opcode the return result may mean different
|
||||
things (for example: if why == WHY_RETURN, then a False return
|
||||
result means the function returned, and no edge was
|
||||
generated)."""
|
||||
ret_val = False
|
||||
if len(self.control_stack) > 0:
|
||||
handlers = set((SETUP_FINALLY, SETUP_WITH))
|
||||
if why == WHY_EXCEPTION:
|
||||
handlers.add(SETUP_EXCEPT)
|
||||
reversed_stack = reversed(self.control_stack)
|
||||
target = None
|
||||
for handler_i, handler_op, handler_arg in reversed_stack:
|
||||
if handler_op in handlers:
|
||||
target = handler_i + handler_arg + 3
|
||||
elif handler_op == SETUP_LOOP:
|
||||
if why == WHY_CONTINUE:
|
||||
# Only generate a WHY_CONTINUE edge if a continue
|
||||
# statement has been observed for this loop.
|
||||
if handler_i not in self.continue_targets:
|
||||
break
|
||||
elif op == CONTINUE_LOOP:
|
||||
target = arg
|
||||
assert target == self.continue_targets[handler_i]
|
||||
else:
|
||||
target = self.continue_targets[handler_i]
|
||||
elif why == WHY_BREAK:
|
||||
# Only generate a WHY_BREAK edge if a break
|
||||
# statement has been observed for this loop.
|
||||
if handler_i not in self.break_targets:
|
||||
break
|
||||
else:
|
||||
target = handler_i + handler_arg + 3
|
||||
if target is not None:
|
||||
self.cfg.add_edge(block, target)
|
||||
ret_val = True
|
||||
break
|
||||
return ret_val
|
||||
|
||||
def exit_block (self, block):
|
||||
assert block == self.block
|
||||
del self.block
|
||||
i, op, arg = self.blocks[block][-1]
|
||||
opname = opcode.opname[op]
|
||||
if op in opcode.hasjabs:
|
||||
goto_next = False
|
||||
if op == RETURN_VALUE:
|
||||
self._generate_handler_edge(block, i, op, arg, WHY_RETURN)
|
||||
elif op == CONTINUE_LOOP:
|
||||
branched = self._generate_handler_edge(block, i, op, arg,
|
||||
WHY_CONTINUE)
|
||||
assert branched, ("Attempted to continue outside of loop %r" %
|
||||
(self.blocks[block][-1],))
|
||||
elif op == BREAK_LOOP:
|
||||
branched = self._generate_handler_edge(block, i, op, arg,
|
||||
WHY_BREAK)
|
||||
assert branched, ("Attempted to break outside of loop %r" %
|
||||
(self.blocks[block][-1],))
|
||||
elif op == RAISE_VARARGS:
|
||||
self._generate_handler_edge(block, i, op, arg, WHY_EXCEPTION)
|
||||
elif op == END_FINALLY:
|
||||
# The following does a lot of redundant traversal of the
|
||||
# simulated frame stack, but it works, and keeps a lot of
|
||||
# special case logic out of _generate_handler_edge().
|
||||
self._generate_handler_edge(block, i, op, arg, WHY_EXCEPTION)
|
||||
self._generate_handler_edge(block, i, op, arg, WHY_RETURN)
|
||||
self._generate_handler_edge(block, i, op, arg, WHY_BREAK)
|
||||
self._generate_handler_edge(block, i, op, arg, WHY_CONTINUE)
|
||||
goto_next = True # why == WHY_NOT
|
||||
elif op in opcode.hasjabs:
|
||||
self.cfg.add_edge(block, arg)
|
||||
elif op in opcode.hasjrel:
|
||||
self.cfg.add_edge(block, i + arg + 3)
|
||||
elif opname == 'BREAK_LOOP':
|
||||
loop_i, _, loop_arg = self.loop_stack[-1]
|
||||
self.cfg.add_edge(block, loop_i + loop_arg + 3)
|
||||
elif opname != 'RETURN_VALUE':
|
||||
self.cfg.add_edge(block, self._get_next_block(block))
|
||||
if op in opcode_util.hascbranch:
|
||||
else:
|
||||
goto_next = True
|
||||
if op in opcode_util.hascbranch or goto_next:
|
||||
self.cfg.add_edge(block, self._get_next_block(block))
|
||||
|
||||
# ____________________________________________________________
|
||||
# LOAD/STORE_FAST
|
||||
|
||||
def op_LOAD_FAST (self, i, op, arg, *args, **kws):
|
||||
self.cfg.blocks_reads[self.block].add(arg)
|
||||
return super(ControlFlowBuilder, self).op_LOAD_FAST(i, op, arg, *args,
|
||||
|
|
@ -86,34 +195,97 @@ class ControlFlowBuilder (BenignBytecodeVisitorMixin, BasicBlockVisitor):
|
|||
return super(ControlFlowBuilder, self).op_STORE_FAST(i, op, arg, *args,
|
||||
**kws)
|
||||
|
||||
# ____________________________________________________________
|
||||
# *_LOOP: Special loop control flow.
|
||||
|
||||
def _get_current_loop (self):
|
||||
for handler in reversed(self.control_stack):
|
||||
if handler[1] == SETUP_LOOP:
|
||||
return handler
|
||||
return None, None, None
|
||||
|
||||
def op_BREAK_LOOP (self, i, op, arg, *args, **kws):
|
||||
handler_i, _, _ = self._get_current_loop()
|
||||
assert handler_i is not None
|
||||
self.break_targets.add(handler_i)
|
||||
|
||||
def op_CONTINUE_LOOP (self, i, op, arg, *args, **kws):
|
||||
"""
|
||||
CONTINUE_LOOP has to be handled differently than BREAK_LOOP,
|
||||
since its argument specifies where the start of the loop is
|
||||
(in the case of for-loops, FOR_ITER defines the true start of
|
||||
the loop, instead of SETUP_LOOP.)
|
||||
"""
|
||||
handler_i, _, _ = self._get_current_loop()
|
||||
assert handler_i is not None
|
||||
if handler_i in self.continue_targets:
|
||||
assert arg == self.continue_targets[handler_i]
|
||||
else:
|
||||
self.continue_targets[handler_i] = arg
|
||||
|
||||
# ____________________________________________________________
|
||||
# POP_BLOCK
|
||||
|
||||
def op_POP_BLOCK (self, i, op, arg, *args, **kws):
|
||||
self.control_stack.pop()
|
||||
return super(ControlFlowBuilder, self).op_POP_BLOCK(i, op, arg, *args,
|
||||
**kws)
|
||||
# ____________________________________________________________
|
||||
# SETUP_*
|
||||
|
||||
def op_SETUP_EXCEPT (self, i, op, arg, *args, **kws):
|
||||
self.control_stack.append((i, op, arg))
|
||||
return super(ControlFlowBuilder, self).op_SETUP_EXCEPT(i, op, arg,
|
||||
*args, **kws)
|
||||
|
||||
def op_SETUP_FINALLY (self, i, op, arg, *args, **kws):
|
||||
self.control_stack.append((i, op, arg))
|
||||
return super(ControlFlowBuilder, self).op_SETUP_FINALLY(i, op, arg,
|
||||
*args, **kws)
|
||||
|
||||
def op_SETUP_LOOP (self, i, op, arg, *args, **kws):
|
||||
self.loop_stack.append((i, op, arg))
|
||||
self.control_stack.append((i, op, arg))
|
||||
return super(ControlFlowBuilder, self).op_SETUP_LOOP(i, op, arg, *args,
|
||||
**kws)
|
||||
|
||||
def op_POP_BLOCK (self, i, op, arg, *args, **kws):
|
||||
self.loop_stack.pop()
|
||||
return super(ControlFlowBuilder, self).op_POP_BLOCK(i, op, arg, *args,
|
||||
**kws)
|
||||
def op_SETUP_WITH (self, i, op, arg, *args, **kws):
|
||||
self.control_stack.append((i, op, arg))
|
||||
return super(ControlFlowBuilder, self).op_SETUP_WITH(i, op, arg, *args,
|
||||
**kws)
|
||||
|
||||
# ____________________________________________________________
|
||||
# Class convenience methods
|
||||
|
||||
@classmethod
|
||||
def build_cfg_from_co(cls, co_obj):
|
||||
return cls().visit(opcode_util.build_basic_blocks(co_obj),
|
||||
co_obj.co_argcount)
|
||||
|
||||
@classmethod
|
||||
def build_cfg(cls, func):
|
||||
co_obj = opcode_util.get_code_object(func)
|
||||
return cls.build_cfg_from_co(co_obj)
|
||||
|
||||
# ______________________________________________________________________
|
||||
|
||||
def build_cfg (func):
|
||||
'''Given a Python function, create a bytecode flow, visit the flow
|
||||
object, and return a control flow graph.'''
|
||||
co_obj = opcode_util.get_code_object(func)
|
||||
return ControlFlowBuilder().visit(opcode_util.build_basic_blocks(co_obj),
|
||||
co_obj.co_argcount)
|
||||
return ControlFlowBuilder.build_cfg(func)
|
||||
|
||||
# ______________________________________________________________________
|
||||
# Main (self-test) routine
|
||||
|
||||
def main (*args, **kws):
|
||||
from tests import llfuncs
|
||||
if not args:
|
||||
args = ('doslice',)
|
||||
for arg in args:
|
||||
build_cfg(getattr(llfuncs, arg)).pprint()
|
||||
def _visit(obj):
|
||||
print("_" * 70)
|
||||
print(obj)
|
||||
if inspect.isfunction(obj):
|
||||
cfg = build_cfg(obj)
|
||||
else:
|
||||
cfg = ControlFlowBuilder.build_cfg_from_co(obj)
|
||||
cfg.pprint()
|
||||
return opcode_util.visit_code_args(_visit, *args, **kws)
|
||||
|
||||
# ______________________________________________________________________
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,21 @@
|
|||
# ______________________________________________________________________
|
||||
|
||||
from __future__ import absolute_import
|
||||
import dis
|
||||
import opcode
|
||||
import pprint
|
||||
import inspect
|
||||
from collections import namedtuple
|
||||
|
||||
from .bytecode_visitor import BasicBlockVisitor
|
||||
from . import opcode_util
|
||||
from . import byte_control
|
||||
|
||||
# ______________________________________________________________________
|
||||
# Class definition(s)
|
||||
|
||||
Instr = namedtuple('Instr', ('address', 'opcode', 'opname', 'oparg',
|
||||
'stackargs'))
|
||||
|
||||
# ______________________________________________________________________
|
||||
|
||||
|
|
@ -41,7 +52,7 @@ class BytecodeFlowBuilder (BasicBlockVisitor):
|
|||
del self.stack[-pops:]
|
||||
else:
|
||||
stk_args = []
|
||||
ret_val = (i, op, opname, arg, stk_args)
|
||||
ret_val = Instr(i, op, opname, arg, stk_args)
|
||||
if pushes:
|
||||
self.stack.append(ret_val)
|
||||
if appends:
|
||||
|
|
@ -63,13 +74,13 @@ class BytecodeFlowBuilder (BasicBlockVisitor):
|
|||
labels.sort()
|
||||
self.blocks = dict((index, [])
|
||||
for index in labels)
|
||||
self.loop_stack = []
|
||||
self.control_stack = []
|
||||
self.stacks = {}
|
||||
|
||||
def exit_blocks (self, blocks):
|
||||
ret_val = self.blocks
|
||||
del self.stacks
|
||||
del self.loop_stack
|
||||
del self.control_stack
|
||||
del self.blocks
|
||||
return ret_val
|
||||
|
||||
|
|
@ -113,11 +124,19 @@ class BytecodeFlowBuilder (BasicBlockVisitor):
|
|||
op_BINARY_XOR = _op
|
||||
|
||||
def op_BREAK_LOOP (self, i, op, arg):
|
||||
loop_i, _, loop_arg = self.loop_stack[-1]
|
||||
assert arg is None
|
||||
return self._op(i, op, loop_i + loop_arg + 3)
|
||||
if self.opnames[op] == 'BREAK_LOOP':
|
||||
# Break target was already computed in control flow analysis;
|
||||
# reuse that, replacing the opcode argument.
|
||||
blocks_out = tuple(self.cfg.blocks_out[self.block_no])
|
||||
assert len(blocks_out) == 1
|
||||
assert arg is None
|
||||
arg = blocks_out[0]
|
||||
# else: Continue target is already in the argument. Note that
|
||||
# the argument might not be the same as CFG destination block,
|
||||
# since we might have a finally block to visit first.
|
||||
return self._op(i, op, arg)
|
||||
|
||||
#op_BUILD_CLASS = _op
|
||||
op_BUILD_CLASS = _op
|
||||
op_BUILD_LIST = _op
|
||||
op_BUILD_MAP = _op
|
||||
op_BUILD_SLICE = _op
|
||||
|
|
@ -127,7 +146,7 @@ class BytecodeFlowBuilder (BasicBlockVisitor):
|
|||
op_CALL_FUNCTION_VAR = _op
|
||||
op_CALL_FUNCTION_VAR_KW = _op
|
||||
op_COMPARE_OP = _op
|
||||
#op_CONTINUE_LOOP = _op
|
||||
op_CONTINUE_LOOP = op_BREAK_LOOP
|
||||
op_DELETE_ATTR = _op
|
||||
op_DELETE_FAST = _op
|
||||
op_DELETE_GLOBAL = _op
|
||||
|
|
@ -141,12 +160,29 @@ class BytecodeFlowBuilder (BasicBlockVisitor):
|
|||
def op_DUP_TOPX (self, i, op, arg):
|
||||
self.stack += self.stack[-arg:]
|
||||
|
||||
#op_END_FINALLY = _op
|
||||
#op_DUP_TOP_TWO = _not_implemented
|
||||
|
||||
# See the note regarding END_FINALLY in the definition of
|
||||
# opcope_util.OPCODE_MAP.
|
||||
op_END_FINALLY = _op
|
||||
|
||||
op_EXEC_STMT = _op
|
||||
#op_EXTENDED_ARG = _op
|
||||
|
||||
def op_EXTENDED_ARG (self, i, op, arg):
|
||||
raise ValueError("Unexpected EXTENDED_ARG opcode at index %d (should "
|
||||
"be removed by itercode)." % i)
|
||||
|
||||
op_FOR_ITER = _op
|
||||
op_GET_ITER = _op
|
||||
op_IMPORT_FROM = _op
|
||||
|
||||
def op_IMPORT_FROM (self, i, op, arg):
|
||||
# References top of stack without popping, so we can't use the
|
||||
# generic machinery.
|
||||
opname = self.opmap[op][0]
|
||||
ret_val = Instr(i, op, opname, arg, [self.stack[-1]])
|
||||
self.stack.append(ret_val)
|
||||
return ret_val
|
||||
|
||||
op_IMPORT_NAME = _op
|
||||
op_IMPORT_STAR = _op
|
||||
op_INPLACE_ADD = _op
|
||||
|
|
@ -166,15 +202,26 @@ class BytecodeFlowBuilder (BasicBlockVisitor):
|
|||
op_JUMP_FORWARD = _op
|
||||
|
||||
def op_JUMP_IF_FALSE (self, i, op, arg):
|
||||
opname, _, _, _ = self.opmap[op]
|
||||
ret_val = (i, op, opname, arg, [self.stack[-1]])
|
||||
ret_val = Instr(i, op, self.opnames[op], arg, [self.stack[-1]])
|
||||
self.block.append(ret_val)
|
||||
return ret_val
|
||||
|
||||
#op_JUMP_IF_FALSE_OR_POP = _not_implemented
|
||||
op_JUMP_IF_TRUE = op_JUMP_IF_FALSE
|
||||
op_LIST_APPEND = _op
|
||||
#op_JUMP_IF_TRUE_OR_POP = op_JUMP_IF_FALSE_OR_POP
|
||||
|
||||
def op_LIST_APPEND (self, i, op, arg):
|
||||
'''This method is used for both LIST_APPEND, and SET_ADD
|
||||
opcodes.'''
|
||||
elem = self.stack.pop()
|
||||
container = self.stack[-arg]
|
||||
ret_val = Instr(i, op, self.opnames[op], arg, [container, elem])
|
||||
self.block.append(ret_val)
|
||||
return ret_val
|
||||
|
||||
op_LOAD_ATTR = _op
|
||||
op_LOAD_CLOSURE = _op
|
||||
#op_LOAD_BUILD_CLASS = _not_implemented
|
||||
#op_LOAD_CLOSURE = _not_implemented
|
||||
op_LOAD_CONST = _op
|
||||
op_LOAD_DEREF = _op
|
||||
op_LOAD_FAST = _op
|
||||
|
|
@ -186,8 +233,9 @@ class BytecodeFlowBuilder (BasicBlockVisitor):
|
|||
op_NOP = _op
|
||||
|
||||
def op_POP_BLOCK (self, i, op, arg):
|
||||
self.loop_stack.pop()
|
||||
return self._op(i, op, arg)
|
||||
_, _, _, target_stack_size = self.control_stack.pop()
|
||||
pops = len(self.stack) - target_stack_size
|
||||
return self._visit_op(i, op, arg, self.opnames[op], pops, 0, 1)
|
||||
|
||||
op_POP_JUMP_IF_FALSE = _op
|
||||
op_POP_JUMP_IF_TRUE = _op
|
||||
|
|
@ -210,19 +258,38 @@ class BytecodeFlowBuilder (BasicBlockVisitor):
|
|||
def op_ROT_TWO (self, i, op, arg):
|
||||
self.stack[-2:] = (self.stack[-1], self.stack[-2])
|
||||
|
||||
#op_SETUP_EXCEPT = _op
|
||||
#op_SETUP_FINALLY = _op
|
||||
def _op_SETUP (self, i, op, arg):
|
||||
self.control_stack.append((i, op, arg, len(self.stack)))
|
||||
ret_val = Instr(i, op, self.opnames[op], arg, [])
|
||||
self.block.append(ret_val)
|
||||
return ret_val
|
||||
|
||||
def op_SETUP_LOOP (self, i, op, arg):
|
||||
self.loop_stack.append((i, op, arg))
|
||||
self.block.append((i, op, self.opnames[op], arg, []))
|
||||
op_SETUP_EXCEPT = _op_SETUP
|
||||
op_SETUP_FINALLY = _op_SETUP
|
||||
op_SETUP_LOOP = _op_SETUP
|
||||
|
||||
def op_SETUP_WITH (self, i, op, arg):
|
||||
assert arg is not None
|
||||
# Care has to be taken here. SETUP_WITH pushes two things on
|
||||
# the value stack (the exit ), and once on the handler frame.
|
||||
ctx = self.stack.pop()
|
||||
# We signal that the value is an exit handler by setting arg to None
|
||||
exit_handler = Instr(i, op, self.opnames[op], None, [ctx])
|
||||
self.stack.append(exit_handler)
|
||||
ret_val = Instr(i, op, self.opnames[op], arg, [ctx])
|
||||
self.control_stack.append((i, op, arg, len(self.stack)))
|
||||
self.stack.append(ret_val)
|
||||
self.block.append(ret_val)
|
||||
return ret_val
|
||||
|
||||
op_SET_ADD = op_LIST_APPEND
|
||||
op_SLICE = _op
|
||||
#op_STOP_CODE = _op
|
||||
#op_STOP_CODE = _not_implemented
|
||||
op_STORE_ATTR = _op
|
||||
op_STORE_DEREF = _op
|
||||
op_STORE_FAST = _op
|
||||
op_STORE_GLOBAL = _op
|
||||
#op_STORE_LOCALS = _not_implemented
|
||||
op_STORE_MAP = _op
|
||||
op_STORE_NAME = _op
|
||||
op_STORE_SLICE = _op
|
||||
|
|
@ -232,29 +299,68 @@ class BytecodeFlowBuilder (BasicBlockVisitor):
|
|||
op_UNARY_NEGATIVE = _op
|
||||
op_UNARY_NOT = _op
|
||||
op_UNARY_POSITIVE = _op
|
||||
op_UNPACK_SEQUENCE = _op
|
||||
#op_WITH_CLEANUP = _op
|
||||
#op_UNPACK_EX = _not_implemented
|
||||
|
||||
def op_UNPACK_SEQUENCE (self, i, op, arg):
|
||||
seq = self.stack.pop()
|
||||
opname = self.opnames[op]
|
||||
while arg > 0:
|
||||
arg -= 1
|
||||
ret_val = Instr(i, op, opname, arg, [seq])
|
||||
self.stack.append(ret_val)
|
||||
return ret_val
|
||||
|
||||
#op_WITH_CLEANUP = _not_implemented
|
||||
op_YIELD_VALUE = _op
|
||||
|
||||
@classmethod
|
||||
def build_flow(cls, func):
|
||||
'''Given a Python function, return a flow representation of that
|
||||
function.'''
|
||||
cfg = byte_control.build_cfg(func)
|
||||
return cls().visit_cfg(cfg)
|
||||
|
||||
@classmethod
|
||||
def build_flow_from_co(cls, code_obj):
|
||||
'''Given a Python code object, return a flow representation of
|
||||
that code object.'''
|
||||
cfg = byte_control.ControlFlowBuilder.build_cfg_from_co(code_obj)
|
||||
return cls().visit_cfg(cfg)
|
||||
|
||||
@classmethod
|
||||
def build_flows_from_co(cls, root_code_obj):
|
||||
'''Given a Python code object, return a map from that code
|
||||
object and any nested code objects to flow representations of
|
||||
those code objects.'''
|
||||
return dict((co, cls.build_flow_from_co(co))
|
||||
for co in opcode_util.itercodeobjs(root_code_obj))
|
||||
|
||||
# ______________________________________________________________________
|
||||
# Function definition(s)
|
||||
|
||||
def build_flow(func):
|
||||
'''Kept for backwards compatibility in downstream modules. Use
|
||||
BytecodeFlowBuilder.build_flow() instead.'''
|
||||
return BytecodeFlowBuilder.build_flow(func)
|
||||
|
||||
# ______________________________________________________________________
|
||||
|
||||
def build_flow (func):
|
||||
'''Given a Python function, return a bytecode flow tree for that
|
||||
function.'''
|
||||
import byte_control
|
||||
cfg = byte_control.build_cfg(func)
|
||||
return BytecodeFlowBuilder().visit_cfg(cfg)
|
||||
def demo_flow_builder(builder_cls, *args):
|
||||
def _visit(obj):
|
||||
print("_" * 70)
|
||||
print(obj)
|
||||
if inspect.isfunction(obj):
|
||||
flow = builder_cls.build_flow(obj)
|
||||
else:
|
||||
flow = builder_cls.build_flow_from_co(obj)
|
||||
pprint.pprint(flow)
|
||||
return opcode_util.visit_code_args(_visit, *args)
|
||||
|
||||
# ______________________________________________________________________
|
||||
# Main (self-test) routine
|
||||
|
||||
def main (*args):
|
||||
import pprint
|
||||
from tests import llfuncs
|
||||
if not args:
|
||||
args = ('doslice',)
|
||||
for arg in args:
|
||||
pprint.pprint(build_flow(getattr(llfuncs, arg)))
|
||||
def main(*args):
|
||||
return demo_flow_builder(BytecodeFlowBuilder, *args)
|
||||
|
||||
# ______________________________________________________________________
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# ______________________________________________________________________
|
||||
'''Defines a bytecode based LLVM translator for llpython code.
|
||||
'''
|
||||
|
|
@ -8,6 +9,7 @@ import opcode
|
|||
import types
|
||||
import logging
|
||||
|
||||
from llvm import LLVMException
|
||||
import llvm.core as lc
|
||||
|
||||
from . import opcode_util
|
||||
|
|
@ -38,6 +40,13 @@ _compare_mapping_sint = {'>':lc.ICMP_SGT,
|
|||
'<=':lc.ICMP_SLE,
|
||||
'!=':lc.ICMP_NE}
|
||||
|
||||
_compare_mapping_ptr = {'>':lc.ICMP_UGT,
|
||||
'<':lc.ICMP_ULT,
|
||||
'==':lc.ICMP_EQ,
|
||||
'>=':lc.ICMP_UGE,
|
||||
'<=':lc.ICMP_ULE,
|
||||
'!=':lc.ICMP_NE}
|
||||
|
||||
# XXX Stolen from numba.llvm_types:
|
||||
|
||||
class LLVMCaster (object):
|
||||
|
|
@ -118,6 +127,37 @@ class LLVMCaster (object):
|
|||
raise NotImplementedError(lkind1, lkind2)
|
||||
return ret_val
|
||||
|
||||
# ______________________________________________________________________
|
||||
|
||||
def _convert_const(py_val):
|
||||
'''Convert a constant Python value into a comparable LLVM
|
||||
constant. Preserves Python values and data structures such as
|
||||
lists, tuples, and None.
|
||||
'''
|
||||
if isinstance(py_val, list):
|
||||
ret_val = [_convert_const(child) for child in py_val]
|
||||
elif isinstance(py_val, tuple):
|
||||
ret_val = tuple(_convert_const(child) for child in py_val)
|
||||
elif isinstance(py_val, int):
|
||||
ret_val = lc.Constant.int(bytetype.lc_int, py_val)
|
||||
elif isinstance(py_val, float):
|
||||
ret_val = lc.Constant.double(py_val)
|
||||
elif py_val == None:
|
||||
ret_val = py_val
|
||||
else:
|
||||
raise NotImplementedError('Constant conversion for %r' % (py_val,))
|
||||
return ret_val
|
||||
|
||||
# ______________________________________________________________________
|
||||
|
||||
def get_or_insert_global_variable(llvm_module, variable_ty, variable_name):
|
||||
try:
|
||||
ret_val = llvm_module.get_global_variable_named(variable_name)
|
||||
# XXX Check LLVM value is of correct type?!
|
||||
except LLVMException:
|
||||
ret_val = llvm_module.add_global_variable(variable_ty, variable_name)
|
||||
return ret_val
|
||||
|
||||
# ______________________________________________________________________
|
||||
# Class definitions
|
||||
|
||||
|
|
@ -196,6 +236,10 @@ class LLVMTranslator (BytecodeFlowVisitor):
|
|||
if self.llvm_function is None:
|
||||
self.llvm_function = self.llvm_module.add_function(
|
||||
self.llvm_type, self.target_function_name)
|
||||
if self.llvm_function.args and not self.llvm_function.args[0].name:
|
||||
for index in range(len(self.llvm_function.args)):
|
||||
argname = self.code_obj.co_varnames[index]
|
||||
self.llvm_function.args[index].name = argname
|
||||
self.llvm_blocks = {}
|
||||
self.llvm_definitions = {}
|
||||
self.pending_phis = {}
|
||||
|
|
@ -371,11 +415,12 @@ class LLVMTranslator (BytecodeFlowVisitor):
|
|||
raise NotImplementedError("LLVMTranslator.op_BUILD_SLICE")
|
||||
|
||||
def op_BUILD_TUPLE (self, i, op, arg, *args, **kws):
|
||||
return args
|
||||
return [args]
|
||||
|
||||
def op_CALL_FUNCTION (self, i, op, arg, *args, **kws):
|
||||
fn = args[0]
|
||||
args = args[1:]
|
||||
argcount = len(args)
|
||||
fn_name = getattr(fn, '__name__', None)
|
||||
if isinstance(fn, (types.FunctionType, types.MethodType)):
|
||||
ret_val = [fn(self.builder, *args)]
|
||||
|
|
@ -415,6 +460,9 @@ class LLVMTranslator (BytecodeFlowVisitor):
|
|||
elif arg1.type.kind in (lc.TYPE_FLOAT, lc.TYPE_DOUBLE):
|
||||
ret_val = [self.builder.fcmp(_compare_mapping_float[cmp_kind],
|
||||
arg1, arg2)]
|
||||
elif isinstance(arg1.type, lc.PointerType):
|
||||
ret_val = [self.builder.icmp(_compare_mapping_ptr[cmp_kind],
|
||||
arg1, arg2)]
|
||||
else:
|
||||
raise NotImplementedError('Comparison of type %r' % (arg1.type,))
|
||||
return ret_val
|
||||
|
|
@ -474,17 +522,7 @@ class LLVMTranslator (BytecodeFlowVisitor):
|
|||
raise NotImplementedError("LLVMTranslator.op_LOAD_ATTR")
|
||||
|
||||
def op_LOAD_CONST (self, i, op, arg, *args, **kws):
|
||||
py_val = self.code_obj.co_consts[arg]
|
||||
if isinstance(py_val, int):
|
||||
ret_val = [lc.Constant.int(bytetype.lc_int, py_val)]
|
||||
elif isinstance(py_val, float):
|
||||
ret_val = [lc.Constant.double(py_val)]
|
||||
elif py_val == None:
|
||||
ret_val = [None]
|
||||
else:
|
||||
raise NotImplementedError('Constant converstion for %r' %
|
||||
(py_val,))
|
||||
return ret_val
|
||||
return [_convert_const(self.code_obj.co_consts[arg])]
|
||||
|
||||
def op_LOAD_DEREF (self, i, op, arg, *args, **kws):
|
||||
name = self.code_obj.co_freevars[arg]
|
||||
|
|
@ -540,10 +578,23 @@ class LLVMTranslator (BytecodeFlowVisitor):
|
|||
return [self.builder.store(store_val, dest_addr)]
|
||||
|
||||
def op_UNARY_CONVERT (self, i, op, arg, *args, **kws):
|
||||
raise NotImplementedError("LLVMTranslator.op_UNARY_CONVERT")
|
||||
var_ty = args[0]
|
||||
if isinstance(var_ty, lc.Type):
|
||||
var_name = var_ty.__name__
|
||||
ret_val = [get_or_insert_global_variable(self.llvm_module, var_ty,
|
||||
var_name)]
|
||||
else:
|
||||
raise NotImplementedError("LLVMTranslator.op_UNARY_CONVERT: %r" %
|
||||
(var_ty,))
|
||||
return ret_val
|
||||
|
||||
def op_UNARY_INVERT (self, i, op, arg, *args, **kws):
|
||||
raise NotImplementedError("LLVMTranslator.op_UNARY_INVERT")
|
||||
arg1, = args
|
||||
if isinstance(arg1.type, lc.IntegerType):
|
||||
ret_val = [self.builder.xor(arg1, lc.Constant.int(arg1.type, -1))]
|
||||
else:
|
||||
raise NotImplementedError('Invert for type %r' % (arg1.type,))
|
||||
return ret_val
|
||||
|
||||
def op_UNARY_NEGATIVE (self, i, op, arg, *args, **kws):
|
||||
raise NotImplementedError("LLVMTranslator.op_UNARY_NEGATIVE")
|
||||
|
|
@ -590,7 +641,7 @@ def llpython_into (llvm_function, **kws):
|
|||
# Main (self-test) routine
|
||||
|
||||
def main (*args):
|
||||
from tests import llfuncs, llfunctys
|
||||
from .tests import llfuncs, llfunctys
|
||||
if not args:
|
||||
args = ('doslice',)
|
||||
elif 'all' in args:
|
||||
|
|
|
|||
|
|
@ -170,8 +170,8 @@ class BasicBlockVisitor (BytecodeVisitor):
|
|||
block_indices.sort()
|
||||
for block_index in block_indices:
|
||||
self.enter_block(block_index)
|
||||
for i, op, arg in blocks[block_index]:
|
||||
self.visit_op(i, op, arg)
|
||||
for op_tuple in blocks[block_index]:
|
||||
self.visit_op(*op_tuple)
|
||||
self.exit_block(block_index)
|
||||
return self.exit_blocks(blocks)
|
||||
|
||||
|
|
@ -189,7 +189,7 @@ class BasicBlockVisitor (BytecodeVisitor):
|
|||
|
||||
# ______________________________________________________________________
|
||||
|
||||
class BytecodeFlowVisitor (BytecodeVisitor):
|
||||
class GenericFlowVisitor (BytecodeVisitor):
|
||||
def visit (self, flow):
|
||||
self.block_list = list(flow.keys())
|
||||
self.block_list.sort()
|
||||
|
|
@ -208,15 +208,6 @@ class BytecodeFlowVisitor (BytecodeVisitor):
|
|||
del self.block_list
|
||||
return self.exit_flow_object(flow)
|
||||
|
||||
def visit_op (self, i, op, arg, *args, **kws):
|
||||
new_args = []
|
||||
for child_i, child_op, _, child_arg, child_args in args:
|
||||
new_args.extend(self.visit_op(child_i, child_op, child_arg,
|
||||
*child_args))
|
||||
ret_val = super(BytecodeFlowVisitor, self).visit_op(i, op, arg,
|
||||
*new_args)
|
||||
return ret_val
|
||||
|
||||
def enter_flow_object (self, flow):
|
||||
self.new_flow = {}
|
||||
|
||||
|
|
@ -233,6 +224,18 @@ class BytecodeFlowVisitor (BytecodeVisitor):
|
|||
|
||||
# ______________________________________________________________________
|
||||
|
||||
class BytecodeFlowVisitor (GenericFlowVisitor):
|
||||
def visit_op (self, i, op, arg, *args, **kws):
|
||||
new_args = []
|
||||
for child_i, child_op, _, child_arg, child_args in args:
|
||||
new_args.extend(self.visit_op(child_i, child_op, child_arg,
|
||||
*child_args))
|
||||
ret_val = super(BytecodeFlowVisitor, self).visit_op(i, op, arg,
|
||||
*new_args)
|
||||
return ret_val
|
||||
|
||||
# ______________________________________________________________________
|
||||
|
||||
class BenignBytecodeVisitorMixin (object):
|
||||
def _do_nothing (self, i, op, arg, *args, **kws):
|
||||
return [(i, op, self.opnames[op], arg, args)]
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# ______________________________________________________________________
|
||||
|
||||
import ctypes
|
||||
|
|
@ -22,9 +23,10 @@ li8_ptr = lc.Type.pointer(li8)
|
|||
lc_int = lc.Type.int(ctypes.sizeof(ctypes.c_int) * 8)
|
||||
lc_long = lc.Type.int(ctypes.sizeof(ctypes.c_long) * 8)
|
||||
|
||||
l_pyobject_head = [lc_size_t, lc.Type.pointer(li32)]
|
||||
l_pyobject_head = [lc_size_t, li8_ptr]
|
||||
l_pyobject_head_struct = lc.Type.struct(l_pyobject_head)
|
||||
l_pyobj_p = l_pyobject_head_struct_p = lc.Type.pointer(l_pyobject_head_struct)
|
||||
l_pyobj_pp = lc.Type.pointer(l_pyobj_p)
|
||||
l_pyfunc = lc.Type.function(l_pyobj_p, (l_pyobj_p, l_pyobj_p))
|
||||
|
||||
strlen = lc.Type.function(lc_size_t, (li8_ptr,))
|
||||
|
|
@ -33,10 +35,38 @@ strndup = lc.Type.function(li8_ptr, (li8_ptr, lc_size_t))
|
|||
malloc = lc.Type.function(li8_ptr, (lc_size_t,))
|
||||
free = lc.Type.function(lvoid, (li8_ptr,))
|
||||
|
||||
Py_BuildValue = lc.Type.function(l_pyobj_p, [li8_ptr], True)
|
||||
PyArg_ParseTuple = lc.Type.function(lc_int, [l_pyobj_p, li8_ptr], True)
|
||||
PyBool_FromLong = lc.Type.function(l_pyobj_p, [lc_long])
|
||||
PyErr_GivenExceptionMatches = lc.Type.function(lc_int, (l_pyobj_p, l_pyobj_p))
|
||||
PyEval_SaveThread = lc.Type.function(li8_ptr, [])
|
||||
PyEval_RestoreThread = lc.Type.function(lc.Type.void(), [li8_ptr])
|
||||
PyEval_RestoreThread = lc.Type.function(lvoid, [li8_ptr])
|
||||
PyInt_AsLong = lc.Type.function(lc_long, [l_pyobj_p])
|
||||
PyInt_FromLong = lc.Type.function(l_pyobj_p, [lc_long])
|
||||
PyNumber_Add = lc.Type.function(l_pyobj_p, (l_pyobj_p, l_pyobj_p))
|
||||
PyNumber_Divide = lc.Type.function(l_pyobj_p, (l_pyobj_p, l_pyobj_p))
|
||||
PyNumber_Multiply = lc.Type.function(l_pyobj_p, (l_pyobj_p, l_pyobj_p))
|
||||
PyNumber_Remainder = lc.Type.function(l_pyobj_p, (l_pyobj_p, l_pyobj_p))
|
||||
PyNumber_Subtract = lc.Type.function(l_pyobj_p, (l_pyobj_p, l_pyobj_p))
|
||||
PyNumber_TrueDivide = lc.Type.function(l_pyobj_p, (l_pyobj_p, l_pyobj_p))
|
||||
PyNumber_InPlaceAdd = lc.Type.function(l_pyobj_p, (l_pyobj_p, l_pyobj_p))
|
||||
PyNumber_InPlaceDivide = lc.Type.function(l_pyobj_p, (l_pyobj_p, l_pyobj_p))
|
||||
PyNumber_InPlaceMultiply = lc.Type.function(l_pyobj_p, (l_pyobj_p, l_pyobj_p))
|
||||
PyNumber_InPlaceRemainder = lc.Type.function(l_pyobj_p, (l_pyobj_p, l_pyobj_p))
|
||||
PyNumber_InPlaceSubtract = lc.Type.function(l_pyobj_p, (l_pyobj_p, l_pyobj_p))
|
||||
PyNumber_InPlaceTrueDivide = lc.Type.function(l_pyobj_p, (l_pyobj_p,
|
||||
l_pyobj_p))
|
||||
PyObject_IsTrue = lc.Type.function(lc_int, [l_pyobj_p])
|
||||
PyObject_RichCompare = lc.Type.function(l_pyobj_p, (l_pyobj_p, l_pyobj_p,
|
||||
lc_int))
|
||||
PySequence_Contains = lc.Type.function(lc_int, (l_pyobj_p, l_pyobj_p))
|
||||
PyString_Check = lc.Type.function(lc_int, [l_pyobj_p])
|
||||
PyString_CheckExact = lc.Type.function(lc_int, [l_pyobj_p])
|
||||
PyString_Format = lc.Type.function(l_pyobj_p, (l_pyobj_p, l_pyobj_p))
|
||||
Py_BuildValue = lc.Type.function(l_pyobj_p, [li8_ptr], True)
|
||||
Py_DecRef = lc.Type.function(lvoid, [l_pyobj_p])
|
||||
Py_IncRef = lc.Type.function(lvoid, [l_pyobj_p])
|
||||
|
||||
PyInt_Type = li8
|
||||
|
||||
# ______________________________________________________________________
|
||||
# End of bytetype.py
|
||||
|
|
|
|||
|
|
@ -30,12 +30,12 @@ class ControlFlowGraph (object):
|
|||
|
||||
def unlink_unreachables (self):
|
||||
changed = True
|
||||
next_blocks = self.blocks.keys()
|
||||
next_blocks = set(self.blocks.keys())
|
||||
next_blocks.remove(0)
|
||||
while changed:
|
||||
changed = False
|
||||
blocks = next_blocks
|
||||
next_blocks = blocks[:]
|
||||
next_blocks = blocks.copy()
|
||||
for block in blocks:
|
||||
if len(self.blocks_in[block]) == 0:
|
||||
blocks_out = self.blocks_out[block]
|
||||
|
|
@ -100,15 +100,25 @@ class ControlFlowGraph (object):
|
|||
if hasattr(self, 'reaching_definitions'):
|
||||
del self.reaching_definitions
|
||||
|
||||
def idom (self, block):
|
||||
'''Compute the immediate dominator (idom) of the given block
|
||||
key. Returns None if the block has no in edges.
|
||||
def get_a_dom (self, block):
|
||||
'''Find an immediate predecessor of the given block such that
|
||||
the predecessor is either the only entry point, or the
|
||||
precessor is not in its own dominance set (a non-loop
|
||||
predecessor). Returns None if the given block has no
|
||||
predecessor.
|
||||
|
||||
Note that in the case where there are multiple immediate
|
||||
dominators (a join after a non-loop branch), this returns one
|
||||
of the predecessors, but is not guaranteed to reliably select
|
||||
one over the others (depends on the ordering of the set type
|
||||
iterator).'''
|
||||
Note that in the case where there are multiple dominators (a
|
||||
join after a non-loop branch), this returns one of the
|
||||
predecessors, but is not guaranteed to reliably select one
|
||||
over the others (depends on the ordering of the set type
|
||||
iterator).
|
||||
|
||||
Note: Previously, this method's documentation erroneously
|
||||
identified the return value as being the immediate dominator
|
||||
of the input block. Instead, it attempts to find a "nearby"
|
||||
dominator. Normally, the immediate dominator of a join is the
|
||||
least upperbound of the closed immediate dominance
|
||||
relationship over its two entrants.'''
|
||||
preds = self.blocks_in[block]
|
||||
npreds = len(preds)
|
||||
if npreds == 0:
|
||||
|
|
@ -142,7 +152,7 @@ class ControlFlowGraph (object):
|
|||
ret_val = {}
|
||||
for pred in preds:
|
||||
ret_val[pred] = self.block_writes_to_writer_map(pred)
|
||||
crnt = self.idom(pred)
|
||||
crnt = self.get_a_dom(pred)
|
||||
while crnt != None:
|
||||
crnt_writer_map = self.block_writes_to_writer_map(crnt)
|
||||
# This order of update favors the first definitions
|
||||
|
|
@ -150,7 +160,7 @@ class ControlFlowGraph (object):
|
|||
# visits blocks in reverse execution order.
|
||||
crnt_writer_map.update(ret_val[pred])
|
||||
ret_val[pred] = crnt_writer_map
|
||||
crnt = self.idom(crnt)
|
||||
crnt = self.get_a_dom(crnt)
|
||||
if not has_memoized:
|
||||
self.reaching_definitions = {}
|
||||
self.reaching_definitions[block] = ret_val
|
||||
|
|
|
|||
|
|
@ -1,147 +1,173 @@
|
|||
# ______________________________________________________________________
|
||||
|
||||
from collections import namedtuple
|
||||
import dis
|
||||
import opcode
|
||||
import inspect
|
||||
|
||||
# ______________________________________________________________________
|
||||
# Module data
|
||||
|
||||
hasjump = opcode.hasjrel + opcode.hasjabs
|
||||
hascbranch = [op for op in hasjump
|
||||
if 'IF' in opcode.opname[op]
|
||||
or opcode.opname[op] in ('FOR_ITER', 'SETUP_LOOP')]
|
||||
# Note that opcode.hasjrel and opcode.hasjabs applies only to opcodes
|
||||
# that calculate a jump point based on the argument. This ignores
|
||||
# jumps that use the frame stack to calculate their targets, and
|
||||
# exceptions.
|
||||
|
||||
NON_ARG_JUMP_NAMES = ('BREAK_LOOP', 'RETURN_VALUE', 'END_FINALLY',
|
||||
'RAISE_VARARGS')
|
||||
NON_ARG_JUMPS = [opcode.opmap[opname]
|
||||
for opname in NON_ARG_JUMP_NAMES
|
||||
if opname in opcode.opmap]
|
||||
HAS_CBRANCH_NAMES = 'FOR_ITER',
|
||||
hasjump = opcode.hasjrel + opcode.hasjabs + NON_ARG_JUMPS
|
||||
hascbranch = [op for op, opname in ((op, opcode.opname[op])
|
||||
for op in hasjump)
|
||||
if 'IF' in opname
|
||||
or 'SETUP' in opname
|
||||
or opname in HAS_CBRANCH_NAMES]
|
||||
|
||||
OpcodeData = namedtuple('OpcodeData', ('pops', 'pushes', 'is_stmt'))
|
||||
NO_OPCODE_DATA = OpcodeData(None, None, None)
|
||||
|
||||
# Since the actual opcode value may change, manage opcode abstraction
|
||||
# data by opcode name.
|
||||
|
||||
OPCODE_MAP = {
|
||||
'BINARY_ADD': (2, 1, None),
|
||||
'BINARY_AND': (2, 1, None),
|
||||
'BINARY_DIVIDE': (2, 1, None),
|
||||
'BINARY_FLOOR_DIVIDE': (2, 1, None),
|
||||
'BINARY_LSHIFT': (2, 1, None),
|
||||
'BINARY_MODULO': (2, 1, None),
|
||||
'BINARY_MULTIPLY': (2, 1, None),
|
||||
'BINARY_OR': (2, 1, None),
|
||||
'BINARY_POWER': (2, 1, None),
|
||||
'BINARY_RSHIFT': (2, 1, None),
|
||||
'BINARY_SUBSCR': (2, 1, None),
|
||||
'BINARY_SUBTRACT': (2, 1, None),
|
||||
'BINARY_TRUE_DIVIDE': (2, 1, None),
|
||||
'BINARY_XOR': (2, 1, None),
|
||||
'BREAK_LOOP': (0, None, 1),
|
||||
'BUILD_CLASS': (None, None, None),
|
||||
'BUILD_LIST': (-1, 1, None),
|
||||
'BUILD_MAP': (None, None, None),
|
||||
'BUILD_SET': (None, None, None),
|
||||
'BUILD_SLICE': (None, None, None),
|
||||
'BUILD_TUPLE': (-1, 1, None),
|
||||
'CALL_FUNCTION': (-2, 1, None),
|
||||
'CALL_FUNCTION_KW': (-3, 1, None),
|
||||
'CALL_FUNCTION_VAR': (-3, 1, None),
|
||||
'CALL_FUNCTION_VAR_KW': (-4, 1, None),
|
||||
'COMPARE_OP': (2, 1, None),
|
||||
'CONTINUE_LOOP': (None, None, None),
|
||||
'DELETE_ATTR': (1, None, 1),
|
||||
'DELETE_DEREF': (None, None, None),
|
||||
'DELETE_FAST': (0, None, 1),
|
||||
'DELETE_GLOBAL': (0, None, 1),
|
||||
'DELETE_NAME': (0, None, 1),
|
||||
'DELETE_SLICE+0': (1, None, 1),
|
||||
'DELETE_SLICE+1': (2, None, 1),
|
||||
'DELETE_SLICE+2': (2, None, 1),
|
||||
'DELETE_SLICE+3': (3, None, 1),
|
||||
'DELETE_SUBSCR': (2, None, 1),
|
||||
'DUP_TOP': (None, None, None),
|
||||
'DUP_TOPX': (None, None, None),
|
||||
'DUP_TOP_TWO': (None, None, None),
|
||||
'END_FINALLY': (None, None, None),
|
||||
'EXEC_STMT': (None, None, None),
|
||||
'EXTENDED_ARG': (None, None, None),
|
||||
'FOR_ITER': (1, 1, 1),
|
||||
'GET_ITER': (1, 1, None),
|
||||
'IMPORT_FROM': (None, None, None),
|
||||
'IMPORT_NAME': (None, None, None),
|
||||
'IMPORT_STAR': (1, None, 1),
|
||||
'INPLACE_ADD': (2, 1, None),
|
||||
'INPLACE_AND': (2, 1, None),
|
||||
'INPLACE_DIVIDE': (2, 1, None),
|
||||
'INPLACE_FLOOR_DIVIDE': (2, 1, None),
|
||||
'INPLACE_LSHIFT': (2, 1, None),
|
||||
'INPLACE_MODULO': (2, 1, None),
|
||||
'INPLACE_MULTIPLY': (2, 1, None),
|
||||
'INPLACE_OR': (2, 1, None),
|
||||
'INPLACE_POWER': (2, 1, None),
|
||||
'INPLACE_RSHIFT': (2, 1, None),
|
||||
'INPLACE_SUBTRACT': (2, 1, None),
|
||||
'INPLACE_TRUE_DIVIDE': (2, 1, None),
|
||||
'INPLACE_XOR': (2, 1, None),
|
||||
'JUMP_ABSOLUTE': (0, None, 1),
|
||||
'JUMP_FORWARD': (0, None, 1),
|
||||
'JUMP_IF_FALSE': (1, 1, 1),
|
||||
'JUMP_IF_FALSE_OR_POP': (None, None, None),
|
||||
'JUMP_IF_TRUE': (1, 1, 1),
|
||||
'JUMP_IF_TRUE_OR_POP': (None, None, None),
|
||||
'LIST_APPEND': (2, 0, 1),
|
||||
'LOAD_ATTR': (1, 1, None),
|
||||
'LOAD_BUILD_CLASS': (None, None, None),
|
||||
'LOAD_CLOSURE': (None, None, None),
|
||||
'LOAD_CONST': (0, 1, None),
|
||||
'LOAD_DEREF': (0, 1, None),
|
||||
'LOAD_FAST': (0, 1, None),
|
||||
'LOAD_GLOBAL': (0, 1, None),
|
||||
'LOAD_LOCALS': (None, None, None),
|
||||
'LOAD_NAME': (0, 1, None),
|
||||
'MAKE_CLOSURE': (None, None, None),
|
||||
'MAKE_FUNCTION': (-2, 1, None),
|
||||
'MAP_ADD': (None, None, None),
|
||||
'NOP': (0, None, None),
|
||||
'POP_BLOCK': (0, None, 1),
|
||||
'POP_EXCEPT': (None, None, None),
|
||||
'POP_JUMP_IF_FALSE': (1, None, 1),
|
||||
'POP_JUMP_IF_TRUE': (1, None, 1),
|
||||
'POP_TOP': (1, None, 1),
|
||||
'PRINT_EXPR': (1, None, 1),
|
||||
'PRINT_ITEM': (1, None, 1),
|
||||
'PRINT_ITEM_TO': (2, None, 1),
|
||||
'PRINT_NEWLINE': (0, None, 1),
|
||||
'PRINT_NEWLINE_TO': (1, None, 1),
|
||||
'RAISE_VARARGS': (None, None, None),
|
||||
'RETURN_VALUE': (1, None, 1),
|
||||
'ROT_FOUR': (None, None, None),
|
||||
'ROT_THREE': (None, None, None),
|
||||
'ROT_TWO': (None, None, None),
|
||||
'SETUP_EXCEPT': (None, None, None),
|
||||
'SETUP_FINALLY': (None, None, None),
|
||||
'SETUP_LOOP': (None, None, None),
|
||||
'SETUP_WITH': (None, None, None),
|
||||
'SET_ADD': (None, None, None),
|
||||
'SLICE+0': (1, 1, None),
|
||||
'SLICE+1': (2, 1, None),
|
||||
'SLICE+2': (2, 1, None),
|
||||
'SLICE+3': (3, 1, None),
|
||||
'STOP_CODE': (None, None, None),
|
||||
'STORE_ATTR': (2, None, 1),
|
||||
'STORE_DEREF': (1, 0, 1),
|
||||
'STORE_FAST': (1, None, 1),
|
||||
'STORE_GLOBAL': (1, None, 1),
|
||||
'STORE_LOCALS': (None, None, None),
|
||||
'STORE_MAP': (1, None, 1),
|
||||
'STORE_NAME': (1, None, 1),
|
||||
'STORE_SLICE+0': (1, None, 1),
|
||||
'STORE_SLICE+1': (2, None, 1),
|
||||
'STORE_SLICE+2': (2, None, 1),
|
||||
'STORE_SLICE+3': (3, None, 1),
|
||||
'STORE_SUBSCR': (3, None, 1),
|
||||
'UNARY_CONVERT': (1, 1, None),
|
||||
'UNARY_INVERT': (1, 1, None),
|
||||
'UNARY_NEGATIVE': (1, 1, None),
|
||||
'UNARY_NOT': (1, 1, None),
|
||||
'UNARY_POSITIVE': (1, 1, None),
|
||||
'UNPACK_EX': (None, None, None),
|
||||
'UNPACK_SEQUENCE': (None, None, None),
|
||||
'WITH_CLEANUP': (None, None, None),
|
||||
'YIELD_VALUE': (1, None, 1),
|
||||
'BINARY_ADD': OpcodeData(2, 1, None),
|
||||
'BINARY_AND': OpcodeData(2, 1, None),
|
||||
'BINARY_DIVIDE': OpcodeData(2, 1, None),
|
||||
'BINARY_FLOOR_DIVIDE': OpcodeData(2, 1, None),
|
||||
'BINARY_LSHIFT': OpcodeData(2, 1, None),
|
||||
'BINARY_MODULO': OpcodeData(2, 1, None),
|
||||
'BINARY_MULTIPLY': OpcodeData(2, 1, None),
|
||||
'BINARY_OR': OpcodeData(2, 1, None),
|
||||
'BINARY_POWER': OpcodeData(2, 1, None),
|
||||
'BINARY_RSHIFT': OpcodeData(2, 1, None),
|
||||
'BINARY_SUBSCR': OpcodeData(2, 1, None),
|
||||
'BINARY_SUBTRACT': OpcodeData(2, 1, None),
|
||||
'BINARY_TRUE_DIVIDE': OpcodeData(2, 1, None),
|
||||
'BINARY_XOR': OpcodeData(2, 1, None),
|
||||
'BREAK_LOOP': OpcodeData(0, None, 1),
|
||||
'BUILD_CLASS': OpcodeData(3, 1, None),
|
||||
'BUILD_LIST': OpcodeData(-1, 1, None),
|
||||
'BUILD_MAP': OpcodeData(-1, 1, None),
|
||||
'BUILD_SET': OpcodeData(-1, 1, None),
|
||||
'BUILD_SLICE': OpcodeData(-1, 1, None), # oparg should only be 2 or 3
|
||||
'BUILD_TUPLE': OpcodeData(-1, 1, None),
|
||||
'CALL_FUNCTION': OpcodeData(-2, 1, None),
|
||||
'CALL_FUNCTION_KW': OpcodeData(-3, 1, None),
|
||||
'CALL_FUNCTION_VAR': OpcodeData(-3, 1, None),
|
||||
'CALL_FUNCTION_VAR_KW': OpcodeData(-4, 1, None),
|
||||
'COMPARE_OP': OpcodeData(2, 1, None),
|
||||
'CONTINUE_LOOP': OpcodeData(0, None, 1),
|
||||
'DELETE_ATTR': OpcodeData(1, None, 1),
|
||||
'DELETE_DEREF': NO_OPCODE_DATA,
|
||||
'DELETE_FAST': OpcodeData(0, None, 1),
|
||||
'DELETE_GLOBAL': OpcodeData(0, None, 1),
|
||||
'DELETE_NAME': OpcodeData(0, None, 1),
|
||||
'DELETE_SLICE+0': OpcodeData(1, None, 1),
|
||||
'DELETE_SLICE+1': OpcodeData(2, None, 1),
|
||||
'DELETE_SLICE+2': OpcodeData(2, None, 1),
|
||||
'DELETE_SLICE+3': OpcodeData(3, None, 1),
|
||||
'DELETE_SUBSCR': OpcodeData(2, None, 1),
|
||||
'DUP_TOP': NO_OPCODE_DATA,
|
||||
'DUP_TOPX': NO_OPCODE_DATA,
|
||||
'DUP_TOP_TWO': NO_OPCODE_DATA,
|
||||
|
||||
# The data for END_FINALLY is a total fabrication; END_FINALLY may
|
||||
# pop 1 or 3 values off the value stack, based on the type of the
|
||||
# top of the value stack. If, however, a value stack simulator
|
||||
# ignores the part of the CPython evaluator loop that pushes the
|
||||
# why code on the value stack for WHY_RETURN and WHY_CONTINUE (as
|
||||
# this table does), this should work out fine.
|
||||
'END_FINALLY': OpcodeData(0, 0, 1),
|
||||
|
||||
'EXEC_STMT': OpcodeData(3, 0, 1),
|
||||
'EXTENDED_ARG': NO_OPCODE_DATA,
|
||||
'FOR_ITER': OpcodeData(1, 1, 1),
|
||||
'GET_ITER': OpcodeData(1, 1, None),
|
||||
'IMPORT_FROM': NO_OPCODE_DATA,
|
||||
'IMPORT_NAME': OpcodeData(2, 1, None),
|
||||
'IMPORT_STAR': OpcodeData(1, None, 1),
|
||||
'INPLACE_ADD': OpcodeData(2, 1, None),
|
||||
'INPLACE_AND': OpcodeData(2, 1, None),
|
||||
'INPLACE_DIVIDE': OpcodeData(2, 1, None),
|
||||
'INPLACE_FLOOR_DIVIDE': OpcodeData(2, 1, None),
|
||||
'INPLACE_LSHIFT': OpcodeData(2, 1, None),
|
||||
'INPLACE_MODULO': OpcodeData(2, 1, None),
|
||||
'INPLACE_MULTIPLY': OpcodeData(2, 1, None),
|
||||
'INPLACE_OR': OpcodeData(2, 1, None),
|
||||
'INPLACE_POWER': OpcodeData(2, 1, None),
|
||||
'INPLACE_RSHIFT': OpcodeData(2, 1, None),
|
||||
'INPLACE_SUBTRACT': OpcodeData(2, 1, None),
|
||||
'INPLACE_TRUE_DIVIDE': OpcodeData(2, 1, None),
|
||||
'INPLACE_XOR': OpcodeData(2, 1, None),
|
||||
'JUMP_ABSOLUTE': OpcodeData(0, None, 1),
|
||||
'JUMP_FORWARD': OpcodeData(0, None, 1),
|
||||
'JUMP_IF_FALSE': OpcodeData(1, 1, 1),
|
||||
'JUMP_IF_FALSE_OR_POP': NO_OPCODE_DATA,
|
||||
'JUMP_IF_TRUE': OpcodeData(1, 1, 1),
|
||||
'JUMP_IF_TRUE_OR_POP': NO_OPCODE_DATA,
|
||||
'LIST_APPEND': NO_OPCODE_DATA,
|
||||
'LOAD_ATTR': OpcodeData(1, 1, None),
|
||||
'LOAD_BUILD_CLASS': NO_OPCODE_DATA,
|
||||
'LOAD_CLOSURE': NO_OPCODE_DATA,
|
||||
'LOAD_CONST': OpcodeData(0, 1, None),
|
||||
'LOAD_DEREF': OpcodeData(0, 1, None),
|
||||
'LOAD_FAST': OpcodeData(0, 1, None),
|
||||
'LOAD_GLOBAL': OpcodeData(0, 1, None),
|
||||
'LOAD_LOCALS': OpcodeData(0, 1, None),
|
||||
'LOAD_NAME': OpcodeData(0, 1, None),
|
||||
'MAKE_CLOSURE': NO_OPCODE_DATA,
|
||||
'MAKE_FUNCTION': OpcodeData(-2, 1, None),
|
||||
'MAP_ADD': NO_OPCODE_DATA,
|
||||
'NOP': OpcodeData(0, None, None),
|
||||
'POP_BLOCK': OpcodeData(0, None, 1),
|
||||
'POP_EXCEPT': NO_OPCODE_DATA,
|
||||
'POP_JUMP_IF_FALSE': OpcodeData(1, None, 1),
|
||||
'POP_JUMP_IF_TRUE': OpcodeData(1, None, 1),
|
||||
'POP_TOP': OpcodeData(1, None, 1),
|
||||
'PRINT_EXPR': OpcodeData(1, None, 1),
|
||||
'PRINT_ITEM': OpcodeData(1, None, 1),
|
||||
'PRINT_ITEM_TO': OpcodeData(2, None, 1),
|
||||
'PRINT_NEWLINE': OpcodeData(0, None, 1),
|
||||
'PRINT_NEWLINE_TO': OpcodeData(1, None, 1),
|
||||
'RAISE_VARARGS': OpcodeData(-1, None, 1),
|
||||
'RETURN_VALUE': OpcodeData(1, None, 1),
|
||||
'ROT_FOUR': NO_OPCODE_DATA,
|
||||
'ROT_THREE': NO_OPCODE_DATA,
|
||||
'ROT_TWO': NO_OPCODE_DATA,
|
||||
'SETUP_EXCEPT': NO_OPCODE_DATA,
|
||||
'SETUP_FINALLY': NO_OPCODE_DATA,
|
||||
'SETUP_LOOP': NO_OPCODE_DATA,
|
||||
'SETUP_WITH': NO_OPCODE_DATA,
|
||||
'SET_ADD': NO_OPCODE_DATA,
|
||||
'SLICE+0': OpcodeData(1, 1, None),
|
||||
'SLICE+1': OpcodeData(2, 1, None),
|
||||
'SLICE+2': OpcodeData(2, 1, None),
|
||||
'SLICE+3': OpcodeData(3, 1, None),
|
||||
'STOP_CODE': NO_OPCODE_DATA,
|
||||
'STORE_ATTR': OpcodeData(2, None, 1),
|
||||
'STORE_DEREF': OpcodeData(1, 0, 1),
|
||||
'STORE_FAST': OpcodeData(1, None, 1),
|
||||
'STORE_GLOBAL': OpcodeData(1, None, 1),
|
||||
'STORE_LOCALS': NO_OPCODE_DATA,
|
||||
'STORE_MAP': OpcodeData(1, None, 1),
|
||||
'STORE_NAME': OpcodeData(1, None, 1),
|
||||
'STORE_SLICE+0': OpcodeData(1, None, 1),
|
||||
'STORE_SLICE+1': OpcodeData(2, None, 1),
|
||||
'STORE_SLICE+2': OpcodeData(2, None, 1),
|
||||
'STORE_SLICE+3': OpcodeData(3, None, 1),
|
||||
'STORE_SUBSCR': OpcodeData(3, None, 1),
|
||||
'UNARY_CONVERT': OpcodeData(1, 1, None),
|
||||
'UNARY_INVERT': OpcodeData(1, 1, None),
|
||||
'UNARY_NEGATIVE': OpcodeData(1, 1, None),
|
||||
'UNARY_NOT': OpcodeData(1, 1, None),
|
||||
'UNARY_POSITIVE': OpcodeData(1, 1, None),
|
||||
'UNPACK_EX': NO_OPCODE_DATA,
|
||||
'UNPACK_SEQUENCE': NO_OPCODE_DATA,
|
||||
'WITH_CLEANUP': NO_OPCODE_DATA,
|
||||
'YIELD_VALUE': OpcodeData(1, None, 1),
|
||||
}
|
||||
|
||||
# ______________________________________________________________________
|
||||
|
|
@ -167,6 +193,7 @@ def itercode(code, start = 0):
|
|||
i = i + 2
|
||||
if op == opcode.EXTENDED_ARG:
|
||||
extended_arg = oparg * 65536
|
||||
continue
|
||||
|
||||
delta = yield num, op, oparg
|
||||
if delta is not None:
|
||||
|
|
@ -176,6 +203,50 @@ def itercode(code, start = 0):
|
|||
|
||||
# ______________________________________________________________________
|
||||
|
||||
def itercodeobjs(codeobj):
|
||||
"Iterator that traverses code objects via the co_consts member."
|
||||
yield codeobj
|
||||
for const in codeobj.co_consts:
|
||||
if inspect.iscode(const):
|
||||
for childobj in itercodeobjs(const):
|
||||
yield childobj
|
||||
|
||||
# ______________________________________________________________________
|
||||
|
||||
def visit_code_args(visitor, *args, **kws):
|
||||
"""Utility function for testing or demonstrating various code
|
||||
analysis passes in llpython.
|
||||
|
||||
Takes a visitor function and a sequence of command line arguments.
|
||||
The visitor function should be able to handle either function
|
||||
objects or code objects."""
|
||||
def _visit_code_objs(root_obj):
|
||||
for code_obj in itercodeobjs(root_obj):
|
||||
visitor(code_obj)
|
||||
try:
|
||||
from .tests import llfuncs
|
||||
except ImportError:
|
||||
llfuncs = object()
|
||||
if not args:
|
||||
if 'default_args' in kws:
|
||||
args = kws['default_args']
|
||||
else:
|
||||
args = ('pymod',)
|
||||
for arg in args:
|
||||
if inspect.iscode(arg):
|
||||
_visit_code_objs(arg)
|
||||
elif inspect.isfunction(arg):
|
||||
_visit_code_objs(get_code_object(arg))
|
||||
elif arg.endswith('.py'):
|
||||
with open(arg) as in_file:
|
||||
in_source = in_file.read()
|
||||
in_codeobj = compile(in_source, arg, 'exec')
|
||||
_visit_code_objs(in_codeobj)
|
||||
else:
|
||||
visitor(getattr(llfuncs, arg))
|
||||
|
||||
# ______________________________________________________________________
|
||||
|
||||
def extendlabels(code, labels = None):
|
||||
"""Extend the set of jump target labels to account for the
|
||||
passthrough targets of conditional branches.
|
||||
|
|
@ -194,15 +265,8 @@ def extendlabels(code, labels = None):
|
|||
i += 1
|
||||
if op >= dis.HAVE_ARGUMENT:
|
||||
i += 2
|
||||
label = -1
|
||||
if op in hasjump:
|
||||
label = i
|
||||
if label >= 0:
|
||||
if label not in labels:
|
||||
labels.append(label)
|
||||
elif op == opcode.opmap['BREAK_LOOP']:
|
||||
if i not in labels:
|
||||
labels.append(i)
|
||||
if op in hasjump and i < n and i not in labels:
|
||||
labels.append(i)
|
||||
return labels
|
||||
|
||||
# ______________________________________________________________________
|
||||
|
|
@ -221,5 +285,12 @@ def build_basic_blocks (co_obj):
|
|||
labels + [len(co_code)]))
|
||||
return blocks
|
||||
|
||||
# ______________________________________________________________________
|
||||
|
||||
def get_nargs(co_obj):
|
||||
flags = co_obj.co_flags
|
||||
return (1 + co_obj.co_argcount + (1 if flags & 4 else 0) +
|
||||
(1 if flags & 8 else 0))
|
||||
|
||||
# ______________________________________________________________________
|
||||
# End of opcode_util.py
|
||||
|
|
|
|||
31
llpython/tests/test_addr_flow.py
Normal file
31
llpython/tests/test_addr_flow.py
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
#! /usr/bin/env python
|
||||
# ______________________________________________________________________
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import unittest
|
||||
|
||||
from llpython import addr_flow
|
||||
|
||||
from . import test_byte_flow
|
||||
|
||||
# ______________________________________________________________________
|
||||
# Class (test case) definition(s)
|
||||
|
||||
class TestAddressFlowBuilder(unittest.TestCase, test_byte_flow.FlowTestMixin):
|
||||
BUILDER_CLS = addr_flow.AddressFlowBuilder
|
||||
|
||||
def fail_unless_valid_instruction(self, instr):
|
||||
super(TestAddressFlowBuilder, self).fail_unless_valid_instruction(
|
||||
instr)
|
||||
for arg_addr in instr[-1]:
|
||||
self.fail_unless_valid_address(arg_addr)
|
||||
|
||||
# ______________________________________________________________________
|
||||
# Main (unit test) routine
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
# ______________________________________________________________________
|
||||
# End of test_addr_flow.py
|
||||
16
llpython/tests/test_all.py
Normal file
16
llpython/tests/test_all.py
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
#! /usr/bin/env python
|
||||
# ______________________________________________________________________
|
||||
|
||||
import unittest
|
||||
|
||||
from .test_byte_control import TestByteControl
|
||||
from .test_byte_flow import TestBytecodeFlowBuilder
|
||||
from .test_addr_flow import TestAddressFlowBuilder
|
||||
|
||||
# ______________________________________________________________________
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
# ______________________________________________________________________
|
||||
# End of llpython/tests/test_all.py
|
||||
279
llpython/tests/test_byte_control.py
Normal file
279
llpython/tests/test_byte_control.py
Normal file
|
|
@ -0,0 +1,279 @@
|
|||
#! /usr/bin/env python
|
||||
# ______________________________________________________________________
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
from llpython import byte_control
|
||||
|
||||
# ______________________________________________________________________
|
||||
# Global data
|
||||
|
||||
# Technically we could also compute if we need special logic for the
|
||||
# old bytecode compiler by scanning for JUMP_IF_TRUE and JUMP_IF_FALSE
|
||||
# opcodes. These opcodes require additional POP_TOP's be inserted.
|
||||
OLD_BYTECODE_COMPILER = sys.version_info < (2, 7)
|
||||
|
||||
got_done = 0
|
||||
|
||||
# ______________________________________________________________________
|
||||
# Utility function definitions
|
||||
|
||||
def do_something():
|
||||
global got_done
|
||||
got_done += 1
|
||||
print("Something good got done.")
|
||||
return got_done
|
||||
|
||||
# ____________________________________________________________
|
||||
|
||||
def do_something_else():
|
||||
raise Exception("Something bad got done, and I don't like it")
|
||||
|
||||
# ______________________________________________________________________
|
||||
# Test function definitions
|
||||
|
||||
def try_finally_0(m, n): # why == WHY_RETURN
|
||||
try:
|
||||
return n - m
|
||||
finally:
|
||||
do_something()
|
||||
return do_something_else()
|
||||
|
||||
# ____________________________________________________________
|
||||
|
||||
def try_finally_1(m, n): # why == WHY_BREAK
|
||||
i = -1
|
||||
for i in range(m, n):
|
||||
try:
|
||||
if i == 101:
|
||||
break
|
||||
finally:
|
||||
do_something()
|
||||
return i
|
||||
|
||||
# ____________________________________________________________
|
||||
|
||||
def try_finally_2(m, n): # why == WHY_CONTINUE
|
||||
i = m
|
||||
while i < n:
|
||||
try:
|
||||
if i == 101:
|
||||
i += 200
|
||||
continue
|
||||
finally:
|
||||
do_something()
|
||||
i += 1
|
||||
return i
|
||||
|
||||
# ____________________________________________________________
|
||||
|
||||
def try_finally_3(m, n): # why == WHY_EXCEPTION (or WHY_RETURN)
|
||||
d = {}
|
||||
try:
|
||||
return d[n] - d[m]
|
||||
finally:
|
||||
do_something()
|
||||
return do_something_else()
|
||||
|
||||
# ____________________________________________________________
|
||||
|
||||
def try_finally_4(m, n): # why == WHY_NOT
|
||||
try:
|
||||
rv = n - m
|
||||
finally:
|
||||
do_something()
|
||||
return rv
|
||||
|
||||
# ____________________________________________________________
|
||||
|
||||
def try_finally_5(m, n):
|
||||
for i in range(m, n):
|
||||
try:
|
||||
if i == 99:
|
||||
break
|
||||
elif i == 121:
|
||||
continue
|
||||
elif i == 86:
|
||||
return
|
||||
elif i < -102:
|
||||
raise ValueError(i)
|
||||
else:
|
||||
try:
|
||||
if i < 0:
|
||||
raise ValueError(i)
|
||||
finally:
|
||||
do_something()
|
||||
finally:
|
||||
do_something()
|
||||
return do_something()
|
||||
|
||||
# ______________________________________________________________________
|
||||
# Class (test case) definition(s)
|
||||
|
||||
class TestByteControl(unittest.TestCase):
|
||||
def fail_unless_cfg_match(self, test_cfg, block_count, edges):
|
||||
assert len(test_cfg.blocks) == block_count
|
||||
block_keys = list(test_cfg.blocks.keys())
|
||||
block_keys.sort()
|
||||
expected_blocks_in = dict((block_key, set())
|
||||
for block_key in block_keys)
|
||||
expected_blocks_out = dict((block_key, set())
|
||||
for block_key in block_keys)
|
||||
for from_block_ofs, to_block_ofs in edges:
|
||||
from_block = block_keys[from_block_ofs]
|
||||
to_block = block_keys[to_block_ofs]
|
||||
expected_blocks_in[to_block].add(from_block)
|
||||
expected_blocks_out[from_block].add(to_block)
|
||||
for block_key in block_keys:
|
||||
expected_in = expected_blocks_in[block_key]
|
||||
test_in = test_cfg.blocks_in[block_key]
|
||||
self.assertEqual(
|
||||
expected_in, test_in, '%r != %r for blocks_in[%d]' % (
|
||||
test_in, expected_in, block_key))
|
||||
expected_out = expected_blocks_out[block_key]
|
||||
test_out = test_cfg.blocks_out[block_key]
|
||||
self.assertEqual(
|
||||
expected_out, test_out, '%r != %r for set blocks_out[%d]' % (
|
||||
test_out, expected_out, block_key))
|
||||
|
||||
def test_raise(self):
|
||||
cfg = byte_control.build_cfg(do_something_else)
|
||||
self.fail_unless_cfg_match(cfg, 2, ())
|
||||
|
||||
def test_try_finally_0(self):
|
||||
"""
|
||||
Expected CFG (Python 2.7+):
|
||||
digraph CFG_try_finally_0 {
|
||||
BLOCK_0 -> BLOCK_3; // 0 -> 1
|
||||
BLOCK_0 -> BLOCK_15; // 0 -> 3
|
||||
BLOCK_3 -> BLOCK_15; // 1 -> 3
|
||||
// DEAD: BLOCK_11 -> BLOCK_15; // 2 -> 3
|
||||
BLOCK_15 -> BLOCK_23; // 3 -> 4, why == WHY_NOT
|
||||
BLOCK_23; // 4
|
||||
}
|
||||
(Possibly terminal blocks: 15, 23.)
|
||||
"""
|
||||
cfg = byte_control.build_cfg(try_finally_0)
|
||||
self.fail_unless_cfg_match(cfg, 5, ((0, 1), (0, 3), (1, 3),
|
||||
(3, 4)))
|
||||
|
||||
def test_try_finally_1(self):
|
||||
"""
|
||||
Expected CFG (Python 2.7+):
|
||||
digraph CFG_try_finally_1 {
|
||||
BLOCK_0 -> BLOCK_9; // 0 -> 1
|
||||
BLOCK_0 -> BLOCK_63; // 0 -> 11
|
||||
BLOCK_9 -> BLOCK_22; // 1 -> 2
|
||||
BLOCK_22 -> BLOCK_25; // 2 -> 3
|
||||
BLOCK_22 -> BLOCK_62; // 2 -> 10
|
||||
BLOCK_25 -> BLOCK_31; // 3 -> 4
|
||||
BLOCK_25 -> BLOCK_51; // 3 -> 8
|
||||
BLOCK_31 -> BLOCK_43; // 4 -> 5
|
||||
BLOCK_31 -> BLOCK_47; // 4 -> 7
|
||||
BLOCK_43 -> BLOCK_51; // 5 -> 8
|
||||
// DEAD: BLOCK_44 -> BLOCK_47; // 6 -> 7
|
||||
BLOCK_47 -> BLOCK_51; // 7 -> 8
|
||||
BLOCK_51 -> BLOCK_59; // 8 -> 9, why == WHY_NOT
|
||||
BLOCK_51 -> BLOCK_63; // 8 -> 11, why == WHY_BREAK, WHY_RETURN, ...
|
||||
BLOCK_59 -> BLOCK_22; // 9 -> 2
|
||||
BLOCK_62 -> BLOCK_63; // 10 -> 11
|
||||
BLOCK_63; // 11
|
||||
}
|
||||
(Possibly terminal blocks: 51, 63.)
|
||||
"""
|
||||
cfg = byte_control.build_cfg(try_finally_1)
|
||||
if not OLD_BYTECODE_COMPILER:
|
||||
self.fail_unless_cfg_match(
|
||||
cfg, 12, ((0, 1), (0, 11), (1, 2), (2, 3), (2, 10), (3, 4),
|
||||
(3, 8), (4, 5), (4, 7), (5, 8), (7, 8),
|
||||
(8, 9), (8, 11), (9, 2), (10, 11)))
|
||||
else:
|
||||
self.fail_unless_cfg_match(
|
||||
cfg, 13, ((0, 1), (0, 12), (1, 2), (2, 3), (2, 11), (3, 4),
|
||||
(3, 9), (4, 5), (4, 7), (5, 9), (7, 8),
|
||||
(8, 9), (9, 10), (9, 12), (10, 2), (11, 12)))
|
||||
|
||||
def test_try_finally_2(self):
|
||||
"""
|
||||
Expected CFG (Python 2.7+):
|
||||
digraph CFG_try_finally_2 {
|
||||
BLOCK_0 -> BLOCK_9; // 0 -> 1
|
||||
BLOCK_0 -> BLOCK_78; // 0 -> 10
|
||||
BLOCK_9 -> BLOCK_21; // 1 -> 2
|
||||
BLOCK_9 -> BLOCK_77; // 1 -> 9
|
||||
BLOCK_21 -> BLOCK_24; // 2 -> 3
|
||||
BLOCK_21 -> BLOCK_56; // 2 -> 7
|
||||
BLOCK_24 -> BLOCK_36; // 3 -> 4
|
||||
BLOCK_24 -> BLOCK_52; // 3 -> 6
|
||||
BLOCK_36 -> BLOCK_56; // 4 -> 7
|
||||
// DEAD: BLOCK_49 -> BLOCK_52; // 5 -> 6
|
||||
BLOCK_52 -> BLOCK_56; // 6 -> 7
|
||||
BLOCK_56 -> BLOCK_9; // 7 -> 1, why == WHY_CONTINUE
|
||||
BLOCK_56 -> BLOCK_64; // 7 -> 8, why == WHY_NOT
|
||||
BLOCK_64 -> BLOCK_9; // 8 -> 1
|
||||
BLOCK_77 -> BLOCK_78; // 9 -> 10
|
||||
BLOCK_78; // 10
|
||||
}
|
||||
(Possibly terminal blocks: 56, 78.)
|
||||
"""
|
||||
cfg = byte_control.build_cfg(try_finally_2)
|
||||
if not OLD_BYTECODE_COMPILER:
|
||||
self.fail_unless_cfg_match(
|
||||
cfg, 11, ((0, 1), (0, 10), (1, 2), (1, 9), (2, 3), (2, 7),
|
||||
(3, 4), (3, 6), (4, 7), (6, 7), (7, 1),
|
||||
(7, 8), (8, 1), (9, 10)))
|
||||
|
||||
def test_try_finally_3(self):
|
||||
"""
|
||||
Expected (Python 2.7+):
|
||||
digraph CFG_foo3 {
|
||||
BLOCK_0 -> BLOCK_9; // 0 -> 1
|
||||
BLOCK_0 -> BLOCK_29; // 0 -> 3
|
||||
BLOCK_9 -> BLOCK_29; // 1 -> 3
|
||||
// DEAD: BLOCK_25 -> BLOCK_29; // 2 -> 3
|
||||
BLOCK_29 -> BLOCK_37; // 3 -> 4, why == WHY_NOT
|
||||
BLOCK_37; // 4
|
||||
}
|
||||
(Possibly terminal blocks: 29, 37.)
|
||||
"""
|
||||
cfg = byte_control.build_cfg(try_finally_3)
|
||||
self.fail_unless_cfg_match(cfg, 5, ((0, 1), (0, 3), (1, 3),
|
||||
(3, 4)))
|
||||
|
||||
def test_try_finally_4(self):
|
||||
"""
|
||||
Expected:
|
||||
digraph CFG_foo4 {
|
||||
BLOCK_0 -> BLOCK_3; // 0 -> 1
|
||||
BLOCK_0 -> BLOCK_17; // 0 -> 2
|
||||
BLOCK_3 -> BLOCK_17; // 1 -> 2
|
||||
BLOCK_17 -> BLOCK_25; // 2 -> 3, why == WHY_NOT
|
||||
BLOCK_25; // 3
|
||||
}
|
||||
(Possibly terminal blocks: 17, 25.)
|
||||
"""
|
||||
cfg = byte_control.build_cfg(try_finally_4)
|
||||
self.fail_unless_cfg_match(cfg, 4, ((0, 1), (0, 2), (1, 2), (2, 3)))
|
||||
|
||||
def test_try_finally_5(self):
|
||||
cfg = byte_control.build_cfg(try_finally_5)
|
||||
if not OLD_BYTECODE_COMPILER:
|
||||
self.fail_unless_cfg_match(
|
||||
cfg, 26, ((0, 1), (0, 25), (1, 2), (2, 24), (2, 3), (3, 4),
|
||||
(3, 22), (4, 5), (4, 7), (5, 22), (7, 8),
|
||||
(7, 10), (8, 22), (10, 11), (10, 12),
|
||||
(11, 22), (12, 13), (12, 15), (13, 22),
|
||||
(15, 16), (15, 20), (16, 17), (16, 19), (17, 20),
|
||||
(19, 20), (20, 21), (20, 22), (21, 22),
|
||||
(22, 25), (22, 2), (22, 23), (23, 2), (24, 25)))
|
||||
|
||||
# ______________________________________________________________________
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
# ______________________________________________________________________
|
||||
# End of test_byte_control.py
|
||||
101
llpython/tests/test_byte_flow.py
Normal file
101
llpython/tests/test_byte_flow.py
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
#! /usr/bin/env python
|
||||
# ______________________________________________________________________
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import unittest
|
||||
|
||||
from llpython import byte_flow
|
||||
from llpython import opcode_util
|
||||
|
||||
from . import test_byte_control as tbc
|
||||
from . import llfuncs
|
||||
|
||||
# ______________________________________________________________________
|
||||
# Class (test case) definition(s)
|
||||
|
||||
class FlowTestMixin(object):
|
||||
|
||||
def fail_unless_valid_address(self, address):
|
||||
self.failUnless(address >= 0)
|
||||
self.failUnless(address < self.max_addr)
|
||||
self.failUnless(address in self.valid_addrs)
|
||||
|
||||
def fail_unless_valid_instruction(self, instr):
|
||||
address = instr[0]
|
||||
self.visited.add(address)
|
||||
self.fail_unless_valid_address(instr[0])
|
||||
|
||||
def fail_unless_valid_flow(self, flow, func):
|
||||
self.failUnless(len(flow) > 0)
|
||||
func_code = opcode_util.get_code_object(func).co_code
|
||||
self.valid_addrs = set(addr for addr, _, _ in
|
||||
opcode_util.itercode(func_code))
|
||||
self.visited = set()
|
||||
self.max_addr = len(func_code)
|
||||
for block_index, block_instrs in flow.items():
|
||||
self.failUnless(block_index < self.max_addr)
|
||||
for instr in block_instrs:
|
||||
self.fail_unless_valid_instruction(instr)
|
||||
del self.max_addr
|
||||
# Make sure that all instructions identified by itercode were
|
||||
# checked at least once; they should be represented in the
|
||||
# resulting flow, even if their basic block is unreachable.
|
||||
self.failUnless(self.valid_addrs == self.visited,
|
||||
'Failed to visit following addresses: %r' %
|
||||
(self.valid_addrs - self.visited))
|
||||
del self.visited
|
||||
del self.valid_addrs
|
||||
return flow
|
||||
|
||||
def build_and_test_flow(self, func):
|
||||
return self.fail_unless_valid_flow(
|
||||
self.BUILDER_CLS.build_flow(func), func)
|
||||
|
||||
def test_doslice(self):
|
||||
self.build_and_test_flow(llfuncs.doslice)
|
||||
|
||||
def test_ipow(self):
|
||||
self.build_and_test_flow(llfuncs.ipow)
|
||||
|
||||
def test_pymod(self):
|
||||
self.build_and_test_flow(llfuncs.pymod)
|
||||
|
||||
def test_try_finally_0(self):
|
||||
self.build_and_test_flow(tbc.try_finally_0)
|
||||
|
||||
def test_try_finally_1(self):
|
||||
self.build_and_test_flow(tbc.try_finally_1)
|
||||
|
||||
def test_try_finally_2(self):
|
||||
self.build_and_test_flow(tbc.try_finally_2)
|
||||
|
||||
def test_try_finally_3(self):
|
||||
self.build_and_test_flow(tbc.try_finally_3)
|
||||
|
||||
def test_try_finally_4(self):
|
||||
self.build_and_test_flow(tbc.try_finally_4)
|
||||
|
||||
def test_try_finally_5(self):
|
||||
self.build_and_test_flow(tbc.try_finally_5)
|
||||
|
||||
# ______________________________________________________________________
|
||||
|
||||
class TestBytecodeFlowBuilder(unittest.TestCase, FlowTestMixin):
|
||||
|
||||
BUILDER_CLS = byte_flow.BytecodeFlowBuilder
|
||||
|
||||
def fail_unless_valid_instruction(self, instr):
|
||||
super(TestBytecodeFlowBuilder, self).fail_unless_valid_instruction(
|
||||
instr)
|
||||
for child_instr in instr[-1]:
|
||||
self.fail_unless_valid_instruction(child_instr)
|
||||
|
||||
# ______________________________________________________________________
|
||||
# Main (unit test) routine
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
# ______________________________________________________________________
|
||||
# End of test_byte_flow.py
|
||||
284
llpython/type_flow.py
Normal file
284
llpython/type_flow.py
Normal file
|
|
@ -0,0 +1,284 @@
|
|||
#! /usr/bin/env python
|
||||
# ______________________________________________________________________
|
||||
# Module imports
|
||||
|
||||
from .bytecode_visitor import BasicBlockVisitor, BenignBytecodeVisitorMixin
|
||||
|
||||
DEBUG_SIMPLIFY = False
|
||||
|
||||
if DEBUG_SIMPLIFY:
|
||||
from pprint import pprint as pp
|
||||
|
||||
# ______________________________________________________________________
|
||||
# Class definition(s)
|
||||
|
||||
class TypeFlowBuilder(BenignBytecodeVisitorMixin, BasicBlockVisitor):
|
||||
def __init__(self, co_obj, *args, **kws):
|
||||
super(TypeFlowBuilder, self).__init__(*args, **kws)
|
||||
self.co_obj = co_obj
|
||||
self.locals = {}
|
||||
self.globals = {}
|
||||
self.refs = {}
|
||||
self.type_flow = {}
|
||||
self.requirements = {}
|
||||
|
||||
def get_type_eqns(self):
|
||||
return self.type_flow, self.requirements, self.locals, self.globals
|
||||
|
||||
def simplify(self):
|
||||
"""
|
||||
This method isn't working as intended. It should simplify
|
||||
strongly connected components s.t. instead of outputing
|
||||
several types like the following:
|
||||
|
||||
{0: set(['in0']),
|
||||
3: set(['in1']),
|
||||
...
|
||||
10: set([0, 3, 34, 37, 62, 65, 'in0', 'in1']),
|
||||
...
|
||||
34: set([0, 3, 34, 37, 62, 65, 'in0', 'in1']),
|
||||
37: set(['in1']),
|
||||
...
|
||||
62: set([0, 3, 34, 37, 62, 65, 'in0', 'in1']),
|
||||
65: set(['in1']),
|
||||
...
|
||||
75: set([0, 3, 34, 37, 62, 65, 'in0', 'in1']),
|
||||
...}
|
||||
|
||||
It outputs the following:
|
||||
|
||||
{0: set(['in0']),
|
||||
3: set(['in1']),
|
||||
...
|
||||
10: set(['in0', 'in1']),
|
||||
...
|
||||
34: set(['in0', 'in1']),
|
||||
37: set(['in1']),
|
||||
...
|
||||
62: set(['in0', 'in1']),
|
||||
65: set(['in1']),
|
||||
...
|
||||
75: set(['in0', 'in1']),
|
||||
...}
|
||||
"""
|
||||
if not DEBUG_SIMPLIFY:
|
||||
raise NotImplementedError("See docstring.")
|
||||
type_flow = self.type_flow
|
||||
changed = True
|
||||
while changed:
|
||||
changed = False
|
||||
next_flow = type_flow.copy()
|
||||
for index, types in type_flow.items():
|
||||
if isinstance(types, set):
|
||||
next_types = set.union(
|
||||
*(type_flow.get(child_index,
|
||||
set([child_index]))
|
||||
for child_index in types))
|
||||
else:
|
||||
next_types = set([types])
|
||||
if next_types != types:
|
||||
next_flow[index] = next_types
|
||||
changed = True
|
||||
pp(next_flow)
|
||||
print()
|
||||
type_flow = next_flow
|
||||
return type_flow
|
||||
|
||||
def _op(self, i, op, opname, arg, args, *extras, **kws):
|
||||
self.type_flow[i] = set(args)
|
||||
|
||||
op_BINARY_ADD = _op
|
||||
op_BINARY_AND = _op
|
||||
op_BINARY_DIVIDE = _op
|
||||
op_BINARY_FLOOR_DIVIDE = _op
|
||||
op_BINARY_LSHIFT = _op
|
||||
op_BINARY_MODULO = _op
|
||||
op_BINARY_MULTIPLY = _op
|
||||
op_BINARY_OR = _op
|
||||
op_BINARY_POWER = _op
|
||||
op_BINARY_RSHIFT = _op
|
||||
op_BINARY_SUBSCR = _op
|
||||
op_BINARY_SUBTRACT = _op
|
||||
op_BINARY_TRUE_DIVIDE = _op
|
||||
op_BINARY_XOR = _op
|
||||
|
||||
#op_BUILD_CLASS = _do_nothing
|
||||
#op_BUILD_LIST = _do_nothing
|
||||
#op_BUILD_MAP = _do_nothing
|
||||
#op_BUILD_SET = _do_nothing
|
||||
#op_BUILD_SLICE = _do_nothing
|
||||
#op_BUILD_TUPLE = _do_nothing
|
||||
|
||||
#op_CALL_FUNCTION = _do_nothing
|
||||
#op_CALL_FUNCTION_KW = _do_nothing
|
||||
#op_CALL_FUNCTION_VAR = _do_nothing
|
||||
#op_CALL_FUNCTION_VAR_KW = _do_nothing
|
||||
|
||||
def op_COMPARE_OP(self, i, op, opname, arg, args, *extras, **kws):
|
||||
self.requirements[i] = set(args)
|
||||
self.type_flow[i] = bool
|
||||
|
||||
#op_CONTINUE_LOOP = _do_nothing
|
||||
#op_DELETE_ATTR = _do_nothing
|
||||
#op_DELETE_DEREF = _do_nothing
|
||||
#op_DELETE_FAST = _do_nothing
|
||||
#op_DELETE_GLOBAL = _do_nothing
|
||||
#op_DELETE_NAME = _do_nothing
|
||||
#op_DELETE_SLICE = _do_nothing
|
||||
#op_DELETE_SUBSCR = _do_nothing
|
||||
#op_END_FINALLY = _do_nothing
|
||||
#op_EXEC_STMT = _do_nothing
|
||||
#op_EXTENDED_ARG = _do_nothing
|
||||
#op_FOR_ITER = _do_nothing
|
||||
#op_GET_ITER = _do_nothing
|
||||
#op_IMPORT_FROM = _do_nothing
|
||||
#op_IMPORT_NAME = _do_nothing
|
||||
#op_IMPORT_STAR = _do_nothing
|
||||
|
||||
op_INPLACE_ADD = _op
|
||||
op_INPLACE_AND = _op
|
||||
op_INPLACE_DIVIDE = _op
|
||||
op_INPLACE_FLOOR_DIVIDE = _op
|
||||
op_INPLACE_LSHIFT = _op
|
||||
op_INPLACE_MODULO = _op
|
||||
op_INPLACE_MULTIPLY = _op
|
||||
op_INPLACE_OR = _op
|
||||
op_INPLACE_POWER = _op
|
||||
op_INPLACE_RSHIFT = _op
|
||||
op_INPLACE_SUBTRACT = _op
|
||||
op_INPLACE_TRUE_DIVIDE = _op
|
||||
op_INPLACE_XOR = _op
|
||||
|
||||
#op_JUMP_ABSOLUTE = _do_nothing
|
||||
#op_JUMP_FORWARD = _do_nothing
|
||||
#op_JUMP_IF_FALSE = _do_nothing
|
||||
#op_JUMP_IF_FALSE_OR_POP = _do_nothing
|
||||
#op_JUMP_IF_TRUE = _do_nothing
|
||||
#op_JUMP_IF_TRUE_OR_POP = _do_nothing
|
||||
#op_LIST_APPEND = _do_nothing
|
||||
#op_LOAD_ATTR = _do_nothing
|
||||
#op_LOAD_BUILD_CLASS = _do_nothing
|
||||
#op_LOAD_CLOSURE = _do_nothing
|
||||
|
||||
def op_LOAD_CONST(self, i, op, opname, arg, args, *extras, **kws):
|
||||
self.type_flow[i] = type(self.co_obj.co_consts[arg])
|
||||
|
||||
def op_LOAD_DEREF(self, i, op, opname, arg, args, *extras, **kws):
|
||||
if arg not in self.refs:
|
||||
result = set(('inref%d' % arg,))
|
||||
self.refs[arg] = result
|
||||
else:
|
||||
result = self.refs[arg]
|
||||
self.type_flow[i] = result
|
||||
|
||||
def op_LOAD_FAST(self, i, op, opname, arg, args, *extras, **kws):
|
||||
if arg not in self.locals:
|
||||
if arg < self.co_obj.co_argcount:
|
||||
result = set(('in%d' % arg,))
|
||||
else:
|
||||
result = set()
|
||||
self.locals[arg] = result
|
||||
else:
|
||||
result = self.locals[arg]
|
||||
self.type_flow[i] = result
|
||||
|
||||
def op_LOAD_GLOBAL(self, i, op, opname, arg, args, *extras, **kws):
|
||||
if arg not in self.globals:
|
||||
result = set(('in%d' % arg,))
|
||||
self.globals[arg] = result
|
||||
else:
|
||||
result = self.globals[arg]
|
||||
self.type_flow[i] = result
|
||||
|
||||
#op_LOAD_LOCALS = _do_nothing
|
||||
#op_LOAD_NAME = _do_nothing
|
||||
#op_MAKE_CLOSURE = _do_nothing
|
||||
#op_MAKE_FUNCTION = _do_nothing
|
||||
#op_MAP_ADD = _do_nothing
|
||||
#op_NOP = _do_nothing
|
||||
#op_POP_BLOCK = _do_nothing
|
||||
#op_POP_EXCEPT = _do_nothing
|
||||
#op_POP_JUMP_IF_FALSE = _do_nothing
|
||||
#op_POP_JUMP_IF_TRUE = _do_nothing
|
||||
#op_POP_TOP = _do_nothing
|
||||
#op_PRINT_EXPR = _do_nothing
|
||||
#op_PRINT_ITEM = _do_nothing
|
||||
#op_PRINT_ITEM_TO = _do_nothing
|
||||
#op_PRINT_NEWLINE = _do_nothing
|
||||
#op_PRINT_NEWLINE_TO = _do_nothing
|
||||
#op_RAISE_VARARGS = _do_nothing
|
||||
|
||||
op_RETURN_VALUE = _op
|
||||
|
||||
#op_SETUP_EXCEPT = _do_nothing
|
||||
#op_SETUP_FINALLY = _do_nothing
|
||||
#op_SETUP_LOOP = _do_nothing
|
||||
#op_SETUP_WITH = _do_nothing
|
||||
#op_SET_ADD = _do_nothing
|
||||
|
||||
#op_SLICE = _do_nothing
|
||||
|
||||
#op_STOP_CODE = _do_nothing
|
||||
#op_STORE_ATTR = _do_nothing
|
||||
#op_STORE_DEREF = _do_nothing
|
||||
|
||||
def op_STORE_FAST(self, i, op, opname, arg, args, *extras, **kws):
|
||||
if arg not in self.locals:
|
||||
if arg < self.co_obj.co_argcount:
|
||||
result = set(('in%d' % arg,))
|
||||
else:
|
||||
result = set()
|
||||
self.locals[arg] = result
|
||||
else:
|
||||
result = self.locals[arg]
|
||||
assert len(args) == 1
|
||||
result.add(args[0])
|
||||
|
||||
#op_STORE_GLOBAL = _do_nothing
|
||||
#op_STORE_LOCALS = _do_nothing
|
||||
#op_STORE_MAP = _do_nothing
|
||||
#op_STORE_NAME = _do_nothing
|
||||
#op_STORE_SLICE = _do_nothing
|
||||
#op_STORE_SUBSCR = _do_nothing
|
||||
|
||||
op_UNARY_CONVERT = _op
|
||||
op_UNARY_INVERT = _op
|
||||
op_UNARY_NEGATIVE = _op
|
||||
op_UNARY_NOT = _op
|
||||
op_UNARY_POSITIVE = _op
|
||||
|
||||
#op_UNPACK_EX = _do_nothing
|
||||
#op_UNPACK_SEQUENCE = _do_nothing
|
||||
#op_WITH_CLEANUP = _do_nothing
|
||||
#op_YIELD_VALUE = _do_nothing
|
||||
|
||||
# ______________________________________________________________________
|
||||
# Function definition(s)
|
||||
|
||||
def build_type_flow(func):
|
||||
from .opcode_util import get_code_object
|
||||
from .addr_flow import AddressFlowBuilder
|
||||
blocks = AddressFlowBuilder.build_flow(func)
|
||||
ty_builder = TypeFlowBuilder(get_code_object(func))
|
||||
ty_builder.visit(blocks)
|
||||
return ty_builder.get_type_eqns()
|
||||
|
||||
# ______________________________________________________________________
|
||||
# Main (self-test) routine
|
||||
|
||||
def main(*args):
|
||||
import pprint
|
||||
from .tests import llfuncs
|
||||
if not args:
|
||||
args = ('pymod',)
|
||||
for arg in args:
|
||||
pprint.pprint(build_type_flow(getattr(llfuncs, arg)))
|
||||
|
||||
# ______________________________________________________________________
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
main(*sys.argv[1:])
|
||||
|
||||
# ______________________________________________________________________
|
||||
# End of type_flow.py
|
||||
Loading…
Add table
Add a link
Reference in a new issue