def find_cycles(system): from rpython.tool.algo import graphlib vertices = dict.fromkeys(system.modules) edges = {} for m in system.modules: edges[m] = [] for n in system.modules[m]._imports: edges[m].append(graphlib.Edge(m, n)) cycles = [] for component in graphlib.strong_components(vertices, edges): random_vertex = component.iterkeys().next() cycles.extend(graphlib.all_cycles(random_vertex, component, edges)) ncycles = [] for cycle in cycles: packs = {} for edge in cycle: package = edge.source.rsplit('.', 1)[0] packs[package] = True if len(packs) > 1: ncycles.append(cycle) cycles = ncycles for cycle in cycles: l = len(cycle[0].source) print cycle[0].source, '->', cycle[0].target for edge in cycle[1:]: print ' ' * l, '->', edge.target print len(cycles), 'inter-package cycles'
def definestr_finalizer_order(cls): import random from rpython.tool.algo import graphlib cls.finalizer_order_examples = examples = [] if cls.large_tests_ok: letters = 'abcdefghijklmnopqrstuvwxyz' COUNT = 20 else: letters = 'abcdefghijklm' COUNT = 2 for i in range(COUNT): input = [] edges = {} for c in letters: edges[c] = [] # make up a random graph for c in letters: for j in range(random.randrange(0, 4)): d = random.choice(letters) edges[c].append(graphlib.Edge(c, d)) input.append((c, d)) # find the expected order in which destructors should be called components = list(graphlib.strong_components(edges, edges)) head = {} for component in components: c = component.keys()[0] for d in component: assert d not in head head[d] = c assert len(head) == len(letters) strict = [] for c, d in input: if head[c] != head[d]: strict.append((c, d)) examples.append((input, components, strict)) class State: pass state = State() def age_of(c): return state.age[ord(c) - ord('a')] def set_age_of(c, newvalue): # NB. this used to be a dictionary, but setting into a dict # consumes memory. This has the effect that this test's # finalizer_trigger method can consume more memory and potentially # cause another collection. This would result in objects # being unexpectedly destroyed at the same 'state.time'. state.age[ord(c) - ord('a')] = newvalue class A: def __init__(self, key): self.key = key self.refs = [] fq.register_finalizer(self) class FQ(rgc.FinalizerQueue): Class = A def finalizer_trigger(self): from rpython.rlib.debug import debug_print while True: a = self.next_dead() if a is None: break debug_print("DEL:", a.key) assert age_of(a.key) == -1 set_age_of(a.key, state.time) state.progress = True fq = FQ() def build_example(input): state.time = 0 state.age = [-1] * len(letters) vertices = {} for c in letters: vertices[c] = A(c) for c, d in input: vertices[c].refs.append(vertices[d]) def f(_): i = 0 while i < len(examples): input, components, strict = examples[i] build_example(input) while state.time < len(letters): from rpython.rlib.debug import debug_print debug_print("STATE.TIME:", state.time) state.progress = False llop.gc__collect(lltype.Void) if not state.progress: break state.time += 1 # summarize the finalization order lst = [] for c in letters: lst.append('%s:%d' % (c, age_of(c))) summary = ', '.join(lst) # check that all instances have been finalized if -1 in state.age: return error(i, summary, "not all instances finalized") # check that if a -> b and a and b are not in the same # strong component, then a is finalized strictly before b for c, d in strict: if age_of(c) >= age_of(d): return error( i, summary, "%s should be finalized before %s" % (c, d)) # check that two instances in the same strong component # are never finalized during the same collection for component in components: seen = {} for c in component: age = age_of(c) if age in seen: d = seen[age] return error( i, summary, "%s and %s should not be finalized" " at the same time" % (c, d)) seen[age] = c i += 1 return "ok" def error(i, summary, msg): return '%d\n%s\n%s' % (i, summary, msg) return f
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