def insert_ll_stackcheck(translator): from rpython.translator.backendopt.support import find_calls_from from rpython.rlib.rstack import stack_check from rpython.tool.algo.graphlib import Edge, make_edge_dict, break_cycles_v rtyper = translator.rtyper graph = rtyper.annotate_helper(stack_check, []) rtyper.specialize_more_blocks() stack_check_ptr = rtyper.getcallable(graph) stack_check_ptr_const = Constant(stack_check_ptr, lltype.typeOf(stack_check_ptr)) edges = set() insert_in = set() block2graph = {} for caller in translator.graphs: pyobj = getattr(caller, 'func', None) if pyobj is not None: if getattr(pyobj, '_dont_insert_stackcheck_', False): continue for block, callee in find_calls_from(translator, caller): if getattr(getattr(callee, 'func', None), 'insert_stack_check_here', False): insert_in.add(callee.startblock) block2graph[callee.startblock] = callee continue if block is not caller.startblock: edges.add((caller.startblock, block)) block2graph[caller.startblock] = caller edges.add((block, callee.startblock)) block2graph[block] = caller edgelist = [Edge(block1, block2) for (block1, block2) in edges] edgedict = make_edge_dict(edgelist) for block in break_cycles_v(edgedict, edgedict): insert_in.add(block) for block in insert_in: v = Variable() v.concretetype = lltype.Void unwind_op = SpaceOperation('direct_call', [stack_check_ptr_const], v) block.operations.insert(0, unwind_op) # prevents cycles of tail calls from occurring -- such cycles would # not consume any stack, so would turn into potentially infinite loops graph = block2graph[block] graph.inhibit_tail_call = True return len(insert_in)
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
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