[pypy-commit] pypy default: Use a UnionFind to do the "right" thing algorithmically.

arigo noreply at buildbot.pypy.org
Sun Dec 16 12:13:54 CET 2012


Author: Armin Rigo <arigo at tunes.org>
Branch: 
Changeset: r59456:9e2ad713f881
Date: 2012-12-16 12:13 +0100
http://bitbucket.org/pypy/pypy/changeset/9e2ad713f881/

Log:	Use a UnionFind to do the "right" thing algorithmically.

diff --git a/pypy/translator/backendopt/graphanalyze.py b/pypy/translator/backendopt/graphanalyze.py
--- a/pypy/translator/backendopt/graphanalyze.py
+++ b/pypy/translator/backendopt/graphanalyze.py
@@ -1,13 +1,14 @@
 from pypy.translator.simplify import get_graph, get_funcobj
 from pypy.rpython.lltypesystem.lloperation import llop, LL_OPERATIONS
 from pypy.rpython.lltypesystem import lltype
+from pypy.tool.algo.unionfind import UnionFind
 
 class GraphAnalyzer(object):
     verbose = False
 
     def __init__(self, translator):
         self.translator = translator
-        self._analyzed_calls = {}
+        self._analyzed_calls = UnionFind(lambda graph: Dependency(self))
 
     # method overridden by subclasses
 
@@ -146,75 +147,64 @@
                 self.analyze(op)
 
 
+class Dependency(object):
+    def __init__(self, analyzer):
+        self._analyzer = analyzer
+        self._result = analyzer.bottom_result()
+
+    def merge_with_result(self, result):
+        self._result = self._analyzer.join_two_results(self._result, result)
+
+    def absorb(self, other):
+        self.merge_with_result(other._result)
+
+
 class DependencyTracker(object):
     """This tracks the analysis of cyclic call graphs."""
 
-    # The point is that one analyzer works fine if the question we ask
-    # it is about a single graph, but in the case of recursion, it will
-    # fail if we ask it about multiple graphs.  The purpose of this
+    # The point is that GraphAnalyzer works fine if the question we ask
+    # is about a single graph; but in the case of recursion, it will
+    # fail if we ask about multiple graphs.  The purpose of this
     # class is to fix the cache in GraphAnalyzer._analyzed_calls after
     # each round, whenever a new set of graphs have been added to it.
-    # It works by assuming that we can simply use 'join_two_results'
-    # in order to do so.
+    # It works by assuming that the following is correct: for any set of
+    # graphs that can all (indirectly) call each other, all these graphs
+    # will get the same answer that is the 'join_two_results' of all of
+    # them.
 
     def __init__(self, analyzer):
         self.analyzer = analyzer
-        # mapping {graph: result} (shared with GraphAnalyzer._analyzed_calls)
+        # the UnionFind object, which works like a mapping {graph: Dependency}
+        # (shared with GraphAnalyzer._analyzed_calls)
         self.graph_results = analyzer._analyzed_calls
-        # mapping {graph: set_of_graphs_that_depend_on_it}
-        self.backward_dependencies = {}
         # the current stack of graphs being analyzed
         self.current_stack = []
-        # the set of graphs at which recursion occurs
-        self.recursion_points = set()
+        self.current_stack_set = set()
 
     def enter(self, graph):
-        if self.current_stack:
-            caller_graph = self.current_stack[-1]
-            # record a dependency between the old graph and the new one,
-            # i.e. going backward: FROM the new graph...
-            deps = self.backward_dependencies.setdefault(graph, set())
-            deps.add(caller_graph)                  # ... TO the caller one.
-        #
         if graph not in self.graph_results:
             self.current_stack.append(graph)
-            self.graph_results[graph] = Ellipsis
+            self.current_stack_set.add(graph)
+            self.graph_results.find(graph)
             return True
         else:
-            self.recursion_points.add(graph)
+            if graph in self.current_stack_set:
+                # found a cycle; merge all graphs in that cycle
+                i = len(self.current_stack) - 1
+                while self.current_stack[i] is not graph:
+                    self.graph_results.union(self.current_stack[i], graph)
+                    i -= 1
             return False
 
     def leave_with(self, result):
         graph = self.current_stack.pop()
-        assert self.graph_results[graph] is Ellipsis
-        self.graph_results[graph] = result
-        #
-        if not self.current_stack:
-            self._propagate_backward_recursion()
+        self.current_stack_set.remove(graph)
+        dep = self.graph_results[graph]
+        dep.merge_with_result(result)
 
     def get_cached_result(self, graph):
-        result = self.graph_results[graph]
-        if result is Ellipsis:
-            return self.analyzer.bottom_result()
-        return result
-
-    def _propagate_backward_recursion(self):
-        # called at the end of the analysis.  We need to back-propagate
-        # the results to all graphs, starting from the graphs in
-        # 'recursion_points', if any.
-        recpts = self.recursion_points
-        bwdeps = self.backward_dependencies
-        grpres = self.graph_results
-        join_two_res = self.analyzer.join_two_results
-        while recpts:
-            callee_graph = recpts.pop()
-            result = grpres[callee_graph]
-            for caller_graph in bwdeps.get(callee_graph, ()):
-                oldvalue1 = grpres[caller_graph]
-                result1 = join_two_res(result, oldvalue1)
-                if result1 != oldvalue1:
-                    grpres[caller_graph] = result1
-                    recpts.add(caller_graph)
+        dep = self.graph_results[graph]
+        return dep._result
 
 
 class BoolGraphAnalyzer(GraphAnalyzer):


More information about the pypy-commit mailing list