Compare commits

...
Sign in to create a new pull request.

38 commits

Author SHA1 Message Date
Jon Riehl
aacfce25bf Manual merge from origin/master after applying PR#83. 2013-09-09 16:14:32 -05:00
Jon Riehl
ca17b03180 Merge branch 'master' of github.com:llvmpy/llvmpy into llpython-dev 2013-09-09 16:04:55 -05:00
Jon Riehl
4f815224f9 Added types for more of the Python C API. 2013-07-25 15:56:01 -05:00
Jon Riehl
f332fbf9de Added a few additional Python C API function declarations, used for comparing objects. 2013-07-23 16:42:59 -05:00
Jon Riehl
1adf3d871a Added support for UNARY_INVERT bytecode. 2013-07-23 16:42:46 -05:00
Jon Riehl
9fe9e8eb39 Incremental extensions to llpython to better support using the Python C-API. 2013-07-12 19:38:50 -05:00
Jon Riehl
e4f4d56fe6 Added support for POP_TOP and SETUP_LOOP to llpython.af_to_api. 2013-06-18 11:52:30 -05:00
Jon Riehl
a6cf8c4f71 Made some improvements to llpython.af_to_api, specifically attempting to normalize variable access so reference counting becomes the target API's responsibility. 2013-06-17 18:08:36 -05:00
Jon Riehl
d8297cf50b Added support for rudimentary control flow, and completely incorrect refcounting in llpython.af_to_api. 2013-06-14 18:18:30 -05:00
Jon Riehl
a8747d2125 Initial proof of concept for generic API calling code generator in llpython.af_to_api. 2013-06-14 16:00:42 -05:00
Jon Riehl
fcf5420803 Added argument naming to llpython.byte_translator. 2013-06-14 15:59:51 -05:00
Jon Riehl
ecf88f93e2 Merge branch 'master' of github.com:llvmpy/llvmpy into llpython-dev 2013-06-14 11:35:48 -05:00
Jon Riehl
fd41a93554 Added llpython.af_to_api module. Started work on a code generator that accepts address flows, and outputs an LLVM function that implements the given code object using (undefined) API calls for bytecode instructions. 2013-06-13 15:25:28 -05:00
Jon Riehl
5f81cab357 Moved most of bytecode_visitor.BytecodeFlowVisitor into a new parent class GenericFlowVisitor, which is a useful starting point for address-based flow visitors. 2013-06-11 17:11:38 -05:00
Jon Riehl
68030d51d1 Modified BytecodeFlowBuilder and AddressFlowBuilder to use a named tuple for representing bytecode instructions. 2013-06-11 16:44:15 -05:00
Jon Riehl
6c7d493441 Fixed llpython.type_flow's main routine. Was using addr_flow.build_addr_flow() which was removed. 2013-06-07 19:18:34 -05:00
Jon Riehl
0b24adb80a Modified various passes to use the inspect module instead of using type comparisons or isinstance. 2013-06-07 15:30:45 -05:00
Jon Riehl
ee557c55cc Added llpython.tests.test_all module for unit testing all llpython. 2013-06-06 14:13:39 -05:00
Jon Riehl
722453502c Continued generalization of byte_flow.demo_flow_builder(), moving traversal logic into visitor function opcode_util.visit_code_args(). Modified byte_control.main() to use visit_code_args(). 2013-06-06 14:08:09 -05:00
Jon Riehl
c55198095f Modified FlowTestMixin.build_and_test_flow() to return generated flow for further testing. 2013-06-06 14:04:48 -05:00
Jon Riehl
bba305774a Moved most of llpython.tests.test_addr_flow into llpython.tests.test_byte_flow, made some modifications to various modules to facilitate proper handling of try-finally. 2013-06-05 18:52:27 -05:00
Jon Riehl
4837bf194e Moved some utility functions into class methods of the BytecodeFlowBuilder class, and eliminated redundant code in AddressFlowBuilder. 2013-06-05 15:28:17 -05:00
Jon Riehl
102d6eac66 Adding basic unit test for llpython.addr_flow. 2013-06-05 13:49:02 -05:00
Jon Riehl
31119a48d4 Modified main routine in llpython.addr_flow to make it easier to determine which code object caused an exception in the translator. 2013-06-05 13:48:30 -05:00
Jon Riehl
b5a15a5019 Added function to iterate through nested code objects, llpython.opcode_util.itercodeobjs(), and used it in llpython.addr_flow. 2013-06-03 19:12:19 -05:00
Jon Riehl
c890cbdd92 Attempting to improve readability of opcode value stack action map using namedtuple in llpython.opcode_util. 2013-06-03 18:00:06 -05:00
Jon Riehl
420d0ed88d Merge branch 'master' of github.com:llvmpy/llvmpy into llpython-dev 2013-06-03 16:01:03 -05:00
Jon Riehl
643f0706e9 Checking in non-functional attempt at type dependency simplification pass in llpython.type_flow. 2013-06-03 16:00:46 -05:00
Jon Riehl
abbf972768 Added address flow builder, and type constraint generator. 2013-05-21 11:58:26 -05:00
Jon Riehl
329b10dbf0 Merge branch 'master' of github.com:llvmpy/llvmpy into llpython-dev 2013-05-20 20:32:40 -05:00
Jon Riehl
8b075317cd Merge branch 'master' of github.com:llvmpy/llvmpy into llpython-dev 2013-05-16 15:38:55 -05:00
Jon Riehl
c06f811473 Reintroduced dead code elimination to CFA pass to avoid dataflow problems. Updated tests. 2013-05-16 15:38:12 -05:00
Jon Riehl
c5d95e01e1 More work on CFA for try-finally in the presence of loops. 2013-05-15 18:40:44 -05:00
Jon Riehl
8ae1b70206 Initial attempt to support return statements in try-finally block in llpython.byte_control. Added incomplete unit tests. 2013-05-14 17:34:54 -05:00
Jon Riehl
d3b252531d Updates to llpython.opcode_util to better support loop and exception control flow.
* Inlcuded non-argument branching opcodes in the hasjump opcode list.
* Added all the SETUP_* opcodes to the hascbranch list.
* Modified itercode() to not output the EXTENDED_ARG bytecode.
* Cleaned up extendlabels(), and made it use the hasjump opcode list uniformly.
2013-05-14 14:52:11 -05:00
Jon Riehl
6f532f164a Working on full opcode coverage and support for exception control flow. 2013-05-14 14:52:11 -05:00
Jon Riehl
5ef00671e7 Add support for more bytecodes. 2013-05-14 14:52:11 -05:00
Jon Riehl
8787af154e Change idom() method to get_a_dom(), update uses, and documentation. 2013-05-14 14:52:11 -05:00
14 changed files with 1930 additions and 251 deletions

88
llpython/addr_flow.py Normal file
View 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
View 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

View file

@ -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)
# ______________________________________________________________________

View file

@ -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)
# ______________________________________________________________________

View file

@ -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:

View file

@ -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)]

View file

@ -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

View file

@ -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

View file

@ -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

View 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

View 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

View 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

View 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
View 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