""" This is optional support code for backends: it finds which cycles in a graph are likely to correspond to source-level 'inner loops'. """ from pypy.tool.algo import graphlib from pypy.objspace.flow.model import Variable from pypy.translator.backendopt.ssa import DataFlowFamilyBuilder class Loop: def __init__(self, headblock, links): self.headblock = headblock self.links = links # list of Links making the cycle, # starting from one of the exits of headblock def find_inner_loops(graph, check_exitswitch_type=None): """Enumerate what look like the innermost loops of the graph. Returns a list of non-overlapping Loop() instances. """ # Heuristic (thanks Stakkars for the idea): # to find the "best" cycle to pick, # # * look for variables that don't change over the whole cycle # # * cycles with _more_ of them are _inside_ cycles with less of them, # because any variable that doesn't change over an outer loop will # not change over an inner loop either, and on the other hand the # outer loop is likely to use and modify variables that remain # constant over the inner loop. # # * if the numbers are the same, fall back to a more arbitrary # measure: loops involving less blocks may be more important # to optimize # # * in a cycle, which block is the head of the loop? Somewhat # arbitrarily we pick the first Bool-switching block that has # two exits. The "first" means the one closest to the # startblock of the graph. # startdistance = {} # {block: distance-from-startblock} pending = [graph.startblock] edge_list = [] dist = 0 while pending: newblocks = [] for block in pending: if block not in startdistance: startdistance[block] = dist for link in block.exits: newblocks.append(link.target) edge = graphlib.Edge(block, link.target) edge.link = link edge_list.append(edge) dist += 1 pending = newblocks vertices = startdistance edges = graphlib.make_edge_dict(edge_list) cycles = graphlib.all_cycles(graph.startblock, vertices, edges) loops = [] variable_families = None for cycle in cycles: # find the headblock candidates = [] for i in range(len(cycle)): block = cycle[i].source v = block.exitswitch if isinstance(v, Variable) and len(block.exits) == 2: if getattr(v, 'concretetype', None) is check_exitswitch_type: dist = startdistance[block] candidates.append((dist, i)) if not candidates: continue _, i = min(candidates) links = [edge.link for edge in cycle[i:] + cycle[:i]] loop = Loop(cycle[i].source, links) # count the variables that remain constant across the cycle, # detected as having its SSA family present across all blocks. if variable_families is None: dffb = DataFlowFamilyBuilder(graph) variable_families = dffb.get_variable_families() num_loop_constants = 0 for v in loop.headblock.inputargs: v = variable_families.find_rep(v) for link in loop.links: block1 = link.target for v1 in block1.inputargs: v1 = variable_families.find_rep(v1) if v1 is v: break # ok, found in this block else: break # not found in this block, fail else: # found in all blocks, this variable is a loop constant num_loop_constants += 1 # smaller keys are "better" key = (-num_loop_constants, # maximize num_loop_constants len(cycle)) # minimize len(cycle) loops.append((key, loop)) loops.sort() # returns 'loops' without overlapping blocks result = [] blocks_seen = {} for key, loop in loops: for link in loop.links: if link.target in blocks_seen: break # overlapping else: # non-overlapping result.append(loop) for link in loop.links: blocks_seen[link.target] = True return result