diff --git a/llpython/byte_control.py b/llpython/byte_control.py index e21252a..efd54aa 100644 --- a/llpython/byte_control.py +++ b/llpython/byte_control.py @@ -1,11 +1,14 @@ #! /usr/bin/env python # ______________________________________________________________________ +# Module imports from __future__ import absolute_import -import opcode -from . import opcode_util -import pprint +import opcode +import pprint +import types + +from . import opcode_util from .bytecode_visitor import BasicBlockVisitor, BenignBytecodeVisitorMixin from .control_flow import ControlFlowGraph @@ -251,24 +254,39 @@ class ControlFlowBuilder (BenignBytecodeVisitorMixin, BasicBlockVisitor): 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 type(obj) == types.FunctionType: + cfg = build_cfg(obj) + else: + cfg = ControlFlowBuilder.build_cfg_from_co(obj) + cfg.pprint() + return opcode_util.visit_code_args(_visit, *args, **kws) # ______________________________________________________________________ diff --git a/llpython/byte_flow.py b/llpython/byte_flow.py index b37d39d..452a7f7 100644 --- a/llpython/byte_flow.py +++ b/llpython/byte_flow.py @@ -4,6 +4,8 @@ from __future__ import absolute_import import dis import opcode +import pprint +import types from .bytecode_visitor import BasicBlockVisitor from . import opcode_util @@ -293,7 +295,16 @@ class BytecodeFlowBuilder (BasicBlockVisitor): op_UNARY_NOT = _op op_UNARY_POSITIVE = _op #op_UNPACK_EX = _not_implemented - #op_UNPACK_SEQUENCE = _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 = i, op, opname, arg, [seq] + self.stack.append(ret_val) + return ret_val + #op_WITH_CLEANUP = _not_implemented op_YIELD_VALUE = _op @@ -308,9 +319,7 @@ class BytecodeFlowBuilder (BasicBlockVisitor): def build_flow_from_co(cls, code_obj): '''Given a Python code object, return a flow representation of that code object.''' - bbs = opcode_util.build_basic_blocks(code_obj) - cfg = byte_control.ControlFlowBuilder().visit(bbs, - code_obj.co_argcount) + cfg = byte_control.ControlFlowBuilder.build_cfg_from_co(code_obj) return cls().visit_cfg(cfg) @classmethod @@ -333,23 +342,15 @@ def build_flow(func): def demo_flow_builder(builder_cls, *args): import pprint - try: - from .tests import llfuncs - except ImportError: - llfuncs = object() - if not args: - args = ('pymod',) - for arg in args: - if arg.endswith('.py'): - with open(arg) as in_file: - in_source = in_file.read() - in_codeobj = compile(in_source, arg, 'exec') - for codeobj in opcode_util.itercodeobjs(in_codeobj): - print("_" * 70) - print(codeobj) - pprint.pprint(builder_cls.build_flow_from_co(codeobj)) + def _visit(obj): + print("_" * 70) + print(obj) + if type(obj) == types.FunctionType: + flow = builder_cls.build_flow(obj) else: - pprint.pprint(builder_cls.build_flow(getattr(llfuncs, arg))) + flow = builder_cls.build_flow_from_co(obj) + pprint.pprint(flow) + return opcode_util.visit_code_args(_visit, *args) # ______________________________________________________________________ # Main (self-test) routine diff --git a/llpython/opcode_util.py b/llpython/opcode_util.py index 657c851..e79257e 100644 --- a/llpython/opcode_util.py +++ b/llpython/opcode_util.py @@ -214,6 +214,34 @@ def itercodeobjs(codeobj): # ______________________________________________________________________ +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.""" + 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 arg.endswith('.py'): + with open(arg) as in_file: + in_source = in_file.read() + in_codeobj = compile(in_source, arg, 'exec') + for codeobj in itercodeobjs(in_codeobj): + visitor(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.