More work on CFA for try-finally in the presence of loops.
This commit is contained in:
parent
8ae1b70206
commit
c5d95e01e1
3 changed files with 288 additions and 35 deletions
|
|
@ -1,5 +1,6 @@
|
|||
#! /usr/bin/env python
|
||||
# ______________________________________________________________________
|
||||
|
||||
from __future__ import absolute_import
|
||||
import opcode
|
||||
from . import opcode_util
|
||||
|
|
@ -13,9 +14,10 @@ from .control_flow import ControlFlowGraph
|
|||
|
||||
# The following opcodes branch based on the control (a.k.a. frame)
|
||||
# stack:
|
||||
RETURN_VALUE, CONTINUE_LOOP, BREAK_LOOP, END_FINALLY = (
|
||||
RETURN_VALUE, CONTINUE_LOOP, BREAK_LOOP, END_FINALLY, RAISE_VARARGS = (
|
||||
opcode.opmap[opname] for opname in (
|
||||
'RETURN_VALUE', 'CONTINUE_LOOP', 'BREAK_LOOP', 'END_FINALLY'))
|
||||
'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 = (
|
||||
|
|
@ -24,7 +26,11 @@ SETUP_EXCEPT, SETUP_FINALLY, SETUP_LOOP, SETUP_WITH = (
|
|||
|
||||
WHY_NOT = 1
|
||||
WHY_EXCEPTION = WHY_NOT << 1
|
||||
WHY_RERAISE = WHY_EXCEPTION << 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
|
||||
|
|
@ -41,10 +47,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
|
||||
|
|
@ -57,6 +63,13 @@ class ControlFlowBuilder (BenignBytecodeVisitorMixin, BasicBlockVisitor):
|
|||
self.block_list.sort()
|
||||
self.cfg = ControlFlowGraph()
|
||||
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])
|
||||
|
||||
|
|
@ -66,6 +79,7 @@ class ControlFlowBuilder (BenignBytecodeVisitorMixin, BasicBlockVisitor):
|
|||
self.cfg.compute_dataflow()
|
||||
self.cfg.update_for_ssa()
|
||||
ret_val = self.cfg
|
||||
del self.continue_targets
|
||||
del self.control_stack
|
||||
del self.cfg
|
||||
del self.block_list
|
||||
|
|
@ -91,29 +105,37 @@ class ControlFlowBuilder (BenignBytecodeVisitorMixin, BasicBlockVisitor):
|
|||
|
||||
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 the function
|
||||
returns)."""
|
||||
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)
|
||||
reverse_stack = self.control_stack[::-1]
|
||||
reversed_stack = reversed(self.control_stack)
|
||||
target = None
|
||||
for handler_i, handler_op, handler_arg in reverse_stack:
|
||||
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:
|
||||
if op == CONTINUE_LOOP:
|
||||
target = i + arg + 3
|
||||
# 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:
|
||||
# XXX This isn't going to be correct for
|
||||
# for-loops, or really long while-loops
|
||||
# (which use EXTENDED_ARG):
|
||||
target = handler_i + 3
|
||||
target = self.continue_targets[handler_i]
|
||||
elif why == WHY_BREAK:
|
||||
target = handler_i + handler_arg + 3
|
||||
# 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
|
||||
|
|
@ -137,11 +159,12 @@ class ControlFlowBuilder (BenignBytecodeVisitorMixin, BasicBlockVisitor):
|
|||
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:
|
||||
# XXX Should we detect cases where return, continue, and
|
||||
# break appear inside the try-block? This would create
|
||||
# more accurate control flow graphs by eliding edges we
|
||||
# know won't be taken.
|
||||
# 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)
|
||||
|
|
@ -156,6 +179,9 @@ class ControlFlowBuilder (BenignBytecodeVisitorMixin, BasicBlockVisitor):
|
|||
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,
|
||||
|
|
@ -166,6 +192,44 @@ 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,
|
||||
|
|
@ -186,11 +250,6 @@ class ControlFlowBuilder (BenignBytecodeVisitorMixin, BasicBlockVisitor):
|
|||
return super(ControlFlowBuilder, self).op_SETUP_WITH(i, op, arg, *args,
|
||||
**kws)
|
||||
|
||||
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)
|
||||
|
||||
# ______________________________________________________________________
|
||||
|
||||
def build_cfg (func):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue