"""A flow graph representation for Python bytecode"""
from compiler import misc
import CO_OPTIMIZED, CO_NEWLOCALS, CO_VARARGS, CO_VARKEYWORDS
self.current = self.entry = Block()
self.exit = Block("exit")
self.blocks.add(self.entry)
self.blocks.add(self.exit)
def startBlock(self, block):
print "end", repr(self.current)
print " next", self.current.next
print " prev", self.current.prev
print " ", self.current.get_children()
def nextBlock(self, block=None):
# XXX think we need to specify when there is implicit transfer
# from one block to the next. might be better to represent this
# with explicit JUMP_ABSOLUTE instructions that are optimized
# out when they are unnecessary.
# I think this strategy works: each block has a child
# designated as "next" which is returned as the last of the
# children. because the nodes in a graph are emitted in
# reverse post order, the "next" block will always be emitted
# immediately after its parent.
# Worry: maintaining this invariant could be tricky
# Note: If the current block ends with an unconditional control
# transfer, then it is techically incorrect to add an implicit
# transfer to the block graph. Doing so results in code generation
# for unreachable blocks. That doesn't appear to be very common
# with Python code and since the built-in compiler doesn't optimize
# it out we don't either.
self.current.addNext(block)
def startExitBlock(self):
self.startBlock(self.exit)
def _disable_debug(self):
if len(inst) == 2 and isinstance(inst[1], Block):
self.current.addOutEdge(inst[1])
def getBlocksInOrder(self):
"""Return the blocks in reverse postorder
i.e. each node appears before all of its successors
order = order_blocks(self.entry, self.exit)
return self.blocks.elements()
"""Return nodes appropriate for use with dominator"""
def getContainedGraphs(self):
for b in self.getBlocks():
l.extend(b.getContainedGraphs())
def order_blocks(start_block, exit_block):
"""Order blocks so that they are emitted in the right order"""
# - when a block has a next block, the next block must be emitted just after
# - when a block has followers (relative jumps), it must be emitted before
# - all reachable blocks must be emitted
# Find all the blocks to be emitted.
for c in b.get_children():
# A block is dominated by another block if that block must be emitted
assert b is b.next[0].prev[0], (b, b.next)
# Make sure every block appears in dominators, even if no
# other block must precede it.
dominators.setdefault(b, set())
# preceding blocks dominate following blocks
for c in b.get_followers():
dominators.setdefault(c, set()).add(b)
# Any block that has a next pointer leading to c is also
# dominated because the whole chain will be emitted at once.
# Walk backwards and add them all.
if c.prev and c.prev[0] is not b:
# Find a block that can be emitted next.
break # can't emit yet, dominated by a remaining block
assert 0, 'circular dependency, cannot find next block'
elif b is not exit_block and not b.has_unconditional_transfer():
def __init__(self, label=''):
Block._count = Block._count + 1
return "<block %s id=%d>" % (self.label, self.bid)
return "<block id=%d>" % (self.bid)
insts = map(str, self.insts)
return "<block %s %d:\n%s>" % (self.label, self.bid,
def getInstructions(self):
def addOutEdge(self, block):
def addNext(self, block):
assert len(self.next) == 1, map(str, self.next)
assert len(block.prev) == 1, map(str, block.prev)
_uncond_transfer = ('RETURN_VALUE', 'RAISE_VARARGS',
'JUMP_ABSOLUTE', 'JUMP_FORWARD', 'CONTINUE_LOOP',
def has_unconditional_transfer(self):
"""Returns True if there is an unconditional transfer to an other block
at the end of this block. This means there is no risk for the bytecode
executer to go past this block's bytecode."""
except (IndexError, ValueError):
return op in self._uncond_transfer
return list(self.outEdges) + self.next
"""Get the whole list of followers, including the next block."""
followers = set(self.next)
# Blocks that must be emitted *after* this one, because of
# bytecode offsets (e.g. relative jumps) pointing to them.
if inst[0] in PyFlowGraph.hasjrel:
def getContainedGraphs(self):
"""Return all graphs contained within this block.
For example, a MAKE_FUNCTION block will contain a reference to
the graph for the function body.
contained.append(op.graph)
# the FlowGraph is transformed in place; it exists in one of these states
class PyFlowGraph(FlowGraph):
super_init = FlowGraph.__init__
def __init__(self, name, filename, args=(), optimized=0, klass=None):
self.argcount = getArgCount(args)
self.flags = CO_OPTIMIZED | CO_NEWLOCALS
# Free variables found by the symbol table scan, including
# variables used only in nested scopes, are included here.
# The closure list is used to track the order of cell
# variables and free variables in the resulting code object.
# The offsets used by LOAD_CLOSURE/LOAD_DEREF refer to both
self.varnames = list(args) or []
for i in range(len(self.varnames)):
if isinstance(var, TupleArg):
self.varnames[i] = var.getName()
def setDocstring(self, doc):
self.flags = self.flags | flag
self.argcount = self.argcount - 1
def checkFlag(self, flag):
def setFreeVars(self, names):
self.freevars = list(names)
def setCellVars(self, names):
"""Get a Python code object"""
assert self.stage == FLAT
assert self.stage == CONV
assert self.stage == DONE
return self.newCodeObject()
if opname == "SET_LINENO":
print "\t", "%3d" % pc, opname
print "\t", "%3d" % pc, opname, t[1]
def computeStackDepth(self):
"""Compute the max stack depth.
Approach is to compute the stack effect of each basic block.
Then find the path through the code with the largest total
for b in self.getBlocks():
depth[b] = findDepth(b.getInstructions())
children = b.get_children()
return max([max_depth(c, d) for c in children])
if not b.label == "exit":
return max_depth(self.exit, d)
self.stacksize = max_depth(self.entry, 0)
"""Arrange the blocks in order and resolve jumps"""
for b in self.getBlocksInOrder():
for inst in b.getInstructions():
elif inst[0] != "SET_LINENO":
for i in range(len(insts)):
elif inst[0] != "SET_LINENO":
if opname in self.hasjrel:
offset = begin[oparg] - pc
insts[i] = opname, offset
elif opname in self.hasjabs:
insts[i] = opname, begin[inst[1]]
hasjrel.add(dis.opname[i])
hasjabs.add(dis.opname[i])
"""Convert arguments from symbolic to concrete form"""
assert self.stage == FLAT
self.consts.insert(0, self.docstring)
for i in range(len(self.insts)):
conv = self._converters.get(opname, None)
self.insts[i] = opname, conv(self, oparg)
"""Sort cellvars in the order of varnames and prune from freevars.
for name in self.cellvars:
self.cellvars = [name for name in self.varnames
for name in self.cellvars:
self.cellvars = self.cellvars + cells.keys()
self.closure = self.cellvars + self.freevars
def _lookupName(self, name, list):
"""Return index of name in list, appending if necessary
This routine uses a list instead of a dictionary, because a
dictionary can't store two different keys if the keys have the
same value but different types, e.g. 2 and 2L. The compiler
must treat these two separately, so it does an explicit type
comparison before comparing the values.
for i in range(len(list)):
if t == type(list[i]) and list[i] == name:
def _convert_LOAD_CONST(self, arg):
if hasattr(arg, 'getCode'):
return self._lookupName(arg, self.consts)
def _convert_LOAD_FAST(self, arg):
self._lookupName(arg, self.names)
return self._lookupName(arg, self.varnames)
_convert_STORE_FAST = _convert_LOAD_FAST
_convert_DELETE_FAST = _convert_LOAD_FAST
def _convert_LOAD_NAME(self, arg):
self._lookupName(arg, self.varnames)
return self._lookupName(arg, self.names)
def _convert_NAME(self, arg):
self._lookupName(arg, self.varnames)
return self._lookupName(arg, self.names)
_convert_STORE_NAME = _convert_NAME
_convert_DELETE_NAME = _convert_NAME
_convert_IMPORT_NAME = _convert_NAME
_convert_IMPORT_FROM = _convert_NAME
_convert_STORE_ATTR = _convert_NAME
_convert_LOAD_ATTR = _convert_NAME
_convert_DELETE_ATTR = _convert_NAME
_convert_LOAD_GLOBAL = _convert_NAME
_convert_STORE_GLOBAL = _convert_NAME
_convert_DELETE_GLOBAL = _convert_NAME
def _convert_DEREF(self, arg):
self._lookupName(arg, self.names)
self._lookupName(arg, self.varnames)
return self._lookupName(arg, self.closure)
_convert_LOAD_DEREF = _convert_DEREF
_convert_STORE_DEREF = _convert_DEREF
def _convert_LOAD_CLOSURE(self, arg):
self._lookupName(arg, self.varnames)
return self._lookupName(arg, self.closure)
def _convert_COMPARE_OP(self, arg):
return self._cmp.index(arg)
# similarly for other opcodes...
for name, obj in locals().items():
if name[:9] == "_convert_":