def find_natural_loops(func, cfg=None): """Return a loop nesting forest for the given function ([Loop])""" cfg = cfg or cfa.cfg(func) dominators = cfa.compute_dominators(func, cfg) loops = [] loop_stack = [] for block in func.blocks: ### Look for incoming back-edge for pred in cfg.predecessors(block): if block in dominators[pred]: # We dominate an incoming block, this means there is a # back-edge (pred, block) loop_stack.append(Loop([block])) ### Populate Loop if loop_stack: loop = loop_stack[-1] head = loop.blocks[0] if head in dominators[block] and head != block: # Dominated by loop header, add loop.blocks.append(block) if head in cfg[block]: loop_stack.pop() if loop_stack: # update outer loop loop_stack[-1].blocks.extend(loop.blocks) loop_stack[-1].children.append(loop) else: # outermost loop, add to forest loops.append(loop) assert not loop_stack return loops
def sccp(func, constantfolder=None): """ Perform Sparse conditional constant propagation. The idea is to have two queues, one for blocks and one for SSA variables (Ops). Blocks must be processed whole, which is a dense operation. Subsequent changes to live code are then sparse operations using the SSA info. When reaching the end of a block started by a CFG edge, we add new edges iff we have an unconditional branch. After all, we are executing optimistically, which means we cannot tell whether the destination blocks are live. Note that we can perform no modifications until after the algorithm terminates, since the cells are only correct after termination. Returns ======= constmap: A dict mapping Ops to Consts deadblock: A set of dead basic blocks """ # Object that folds operations with constant inputs constantfolder = constantfolder or ConstantFolder() # Mapping of CFG edges (block1, block2) to indicate whether there exists # some runtime path from block1 to block2. Blocks without any incoming # runtime path are not explored. executable = defaultdict(bool) # Control flow graph (networkx.DiGraph) cfg = cfa.cfg(func) cfedges = deque([(None, func.startblock)]) # remaining cfg edges ssavars = deque() # SSA edges: (Op, Op) cells = defaultdict(cell) # Cells holding lattice values processed = set() # Set of processed blocks # Initialize all constants in cells vmap(partial(initialize, cells), func) while cfedges or ssavars: if cfedges: src, dst = cfedges.popleft() if not executable[src, dst]: # Process each block only once for each predecessor executable[src, dst] = True # process_phis(dst, constantfolder, cells, ssavars) if dst not in processed: # This is the first time we visit this block, process body processed.add(dst) process_body(constantfolder, dst, cfg, cells, ssavars, cfedges) else: defop, op = ssavars.popleft() if executable[defop.block, op.block]: # Only handle live code ! process_expr(constantfolder, op, cells, ssavars) deadblocks = set(func.blocks) - processed return deadblocks, cells, cfg
def sccp(func, constantfolder=None): """ Perform Sparse conditional constant propagation. The idea is to have two queues, one for blocks and one for SSA variables (Ops). Blocks must be processed whole, which is a dense operation. Subsequent changes to live code are then sparse operations using the SSA info. When reaching the end of a block started by a CFG edge, we add new edges iff we have an unconditional branch. After all, we are executing optimistically, which means we cannot tell whether the destination blocks are live. Note that we can perform no modifications until after the algorithm terminates, since the cells are only correct after termination. Returns ======= constmap: A dict mapping Ops to Consts deadblock: A set of dead basic blocks """ # Mapping of CFG edges (block1, block2) to indicate whether there exists # some runtime path from block1 to block2. Blocks without any incoming # runtime path are not explored. executable = defaultdict(bool) # Object that folds operations with constant inputs constantfolder = constantfolder or SCCPFolder(executable) # Control flow graph (networkx.DiGraph) cfg = cfa.cfg(func) cfedges = deque([(None, func.startblock)]) # remaining cfg edges ssavars = deque() # SSA edges: (Op, Op) cells = defaultdict(cell) # Cells holding lattice values processed = set() # Set of processed blocks # Initialize all constants in cells vmap(partial(initialize, cells), func) while cfedges or ssavars: if cfedges: src, dst = cfedges.popleft() if not executable[src, dst]: # Process each block only once for each predecessor executable[src, dst] = True # process_phis(dst, constantfolder, cells, ssavars) if dst not in processed: # This is the first time we visit this block, process body processed.add(dst) process_body(constantfolder, dst, cfg, cells, ssavars, cfedges) else: defop, op = ssavars.popleft() if executable[defop.block, op.block]: # Only handle live code ! process_expr(constantfolder, op, cells, ssavars) deadblocks = set(func.blocks) - processed return deadblocks, cells, cfg
def test_cfg(self): mod = from_c(source) f = mod.get_function('func_simple') verify(f) flow = cfa.cfg(f) cond_block = findop(f, 'cbranch').block self.assertEqual(len(flow[cond_block]), 2)
def verify_block_order(func): """Verify block order according to dominator tree""" from pykit.analysis import cfa flow = cfa.cfg(func) dominators = cfa.compute_dominators(func, flow) visited = set() for block in func.blocks: visited.add(block.name) for dominator in dominators[block.name]: if dominator not in visited: raise VerifyError("Dominator %s does not precede block %s" % ( dominator, block.name))
def update_outdated_incoming_blocks(func, candidates): """ Update phi nodes in blocks previously containing 'exc_catch'. 'exc_setup' may span many blocks, and none, or only a subset of those blocks may be actual predecessors. """ cfg = cfa.cfg(func) for block in candidates: preds = cfg.predecessors(block) for op in block.leaders: if op.opcode == 'phi': blocks, values = op.args newblocks = [block for block in blocks if block in preds] newvalues = [val for block, val in zip(blocks, values) if block in preds] assert len(newblocks) == len(preds), (op.block, newblocks, preds, blocks) op.set_args([newblocks, newvalues])
def update_outdated_incoming_blocks(func, candidates): """ Update phi nodes in blocks previously containing 'exc_catch'. 'exc_setup' may span many blocks, and none, or only a subset of those blocks may be actual predecessors. """ cfg = cfa.cfg(func) for block in candidates: preds = cfg.predecessors(block) for op in block.leaders: if op.opcode == 'phi': blocks, values = op.args newblocks = [block for block in blocks if block in preds] newvalues = [ val for block, val in zip(blocks, values) if block in preds ] assert len(newblocks) == len(preds), (op.block, newblocks, preds, blocks) op.set_args([newblocks, newvalues])
def dataflow(func, env, sync_context=True): """ Move all allocas to the start block and then use pykit's SSA pass. We can move all allocas since all our objects are immutable """ allocas = [op for op in func.ops if op.opcode == 'alloca'] cfa.move_allocas(func, allocas) CFG = cfa.cfg(func) phis = cfa.ssa(func, CFG) if sync_context: context = env['flypy.typing.context'] for phi, alloc in phis.iteritems(): type = context[alloc] context[phi] = type for arg in phi.args[1]: if isinstance(arg, Undef): context[arg] = type
def test_ssa(self): mod = from_c(source) f = mod.get_function('func_simple') verify(f) self.assertEqual(opcodes(f.startblock), ['alloca', 'store', 'load', 'gt', 'cbranch']) # SSA CFG = cfa.cfg(f) cfa.ssa(f, CFG) assert len(f.blocks) == 4 blocks = list(f.blocks) self.assertEqual(opcodes(blocks[0]), ['gt', 'cbranch']) self.assertEqual(opcodes(blocks[1]), ['jump']) self.assertEqual(opcodes(blocks[2]), ['jump']) self.assertEqual(opcodes(blocks[3]), ['phi', 'ret']) phi = findop(f, 'phi') iblocks, ivals = phi.args self.assertEqual(sorted(iblocks), sorted([blocks[1], blocks[2]])) self.assertEqual(len(ivals), 2)
def test_ssa(self): mod = from_c(source) f = mod.get_function('func_simple') verify(f) self.assertEqual(opcodes(f.startblock), ['alloca', 'store', 'load', 'gt', 'cbranch']) # SSA CFG = cfa.cfg(f) cfa.ssa(f, CFG) assert len(f.blocks) == 4 blocks = list(f.blocks) self.assertEqual(opcodes(blocks[0]), ['gt', 'cbranch']) self.assertEqual(opcodes(blocks[1]), ['jump']) self.assertEqual(opcodes(blocks[2]), ['jump']) self.assertEqual(opcodes(blocks[3]), ['phi', 'convert', 'ret']) phi = findop(f, 'phi') iblocks, ivals = phi.args self.assertEqual(sorted(iblocks), sorted([blocks[1], blocks[2]])) self.assertEqual(len(ivals), 2)
def run(func, env): cfg = cfa.cfg(func) deadblocks = cfa.find_dead_blocks(func, cfg) cfa.delete_blocks(func, cfg, deadblocks)
def dump_cfg(func, env, fancy): CFG = cfa.cfg(func) viz.dump(CFG.nx, os.path.expanduser("~/cfg.dot"))
def reg2mem(func, env=None): cfg = cfa.cfg(func, exceptions=False) # ignore exc_setup split_critical_edges(func, cfg, find_phis(func)) vars, loads = generate_copies(func, find_phis(func)) return vars, loads